Bash: извлечение последних двух каналов для имени пути
Кажется, я провалился в чем-то довольно просто, в bash. У меня есть строковая переменная, которая содержит полный путь к каталогу. Я хотел бы назначить последние два каталога в другой строке. Например, если у меня есть:
DIRNAME = /a/b/c/d/e
Мне бы хотелось:
DIRNAME2 = d/e
Я уверен, что есть простая команда bash или sed
, которая сделает это, но она ускользает от меня. Я бы вроде обобщенной версии basename
или dirname
, которая не просто возвращает крайние части имени.
Спасибо!
Dave
Ответы
Ответ 1
DIRNAME="/a/b/c/d/e"
D2=$(dirname "$DIRNAME")
DIRNAME2=$(basename "$D2")/$(basename "$DIRNAME")
Или в одну строку (но будьте осторожны со всеми двойными кавычками - легче разделить):
DIRNAME2=$(basename "$(dirname "$DIRNAME")")/$(basename "$DIRNAME")
Не пытайтесь использовать эту игру с обратными кавычками, если вы не сильно увлечены мазохизмом. И если в путях могут быть пробелы, используйте двойные кавычки вокруг имен переменных.
Это будет работать практически с любой оболочкой Korn Shell, а также с Bash. В bash
доступны и другие механизмы - другие ответы иллюстрируют некоторые из многих опций, хотя expr
также является решением старой школы (он также присутствовал в Unix 7-го издания). Этот код, использующий обратные кавычки, работает и в оболочке Bash и Korn, но не в оболочке Heirloom (которая аналогична оболочке Unix System V Release 2/3/4, IIRC).
DIRNAME2='basename "\'dirname \\"$DIRNAME\\"\'"'/'basename "$DIRNAME"'
(Два уровня вложенности не так уж ужасны, но это довольно плохо; три уровня становятся действительно сложными!)
тестирование
При тестировании манипуляции с именем пути, которая должна выдерживать пробелы в имени пути, стоит проверить с именем, содержащим двойные пробелы (а не одиночные пробелы или их использование). Например:
DIRNAME="/a b/ c d / ee ff / gg hh "
echo "DIRNAME=[[$DIRNAME]]"
echo "basename1=[[$(basename "$DIRNAME")]]"
echo "basename2=[['basename \"$DIRNAME\"']]"
echo
D2=$(dirname "$DIRNAME")
echo "D2=[[$D2]]"
DIRNAME2=$(basename "$D2")/$(basename "$DIRNAME")
echo "DIRNAME2=[[$DIRNAME2]]"
echo
DIRNAME3=$(basename "$(dirname "$DIRNAME")")/$(basename "$DIRNAME")
echo "DIRNAME3=[[$DIRNAME3]]"
DIRNAME4='basename "\'dirname \\"$DIRNAME\\"\'"'/'basename "$DIRNAME"'
echo "DIRNAME4=[[$DIRNAME2]]"
Выход из этого:
DIRNAME=[[/a b/ c d / ee ff / gg hh ]]
basename1=[[ gg hh ]]
basename2=[[ gg hh ]]
D2=[[/a b/ c d / ee ff ]]
DIRNAME2=[[ ee ff / gg hh ]]
DIRNAME3=[[ ee ff / gg hh ]]
DIRNAME4=[[ ee ff / gg hh ]]
Ответ 2
Я предпочитаю использовать встроенные функции как можно больше, чтобы избежать создания ненужных процессов. Поскольку ваш script может запускаться под Cygwin или другой ОС, создание которых очень дорого.
Я думаю, что это не так много, если вы просто хотите извлечь два диска:
base1="${DIRNAME##*/}"
dir1="${DIRNAME%/*}"
DIRNAME2="${dir1##*/}/$base1"
Это также позволяет избежать особых char проблем, связанных с выполнением других команд.
Ответ 3
Я не знаю метода, специально для обрезки путей, но вы можете это сделать с помощью bash соответствия регулярных выражений:
DIRNAME=/a/b/c/d/e
if [[ "$DIRNAME" =~ ([^/]+/+[^/]+)/*$ ]]; then
echo "Last two: ${BASH_REMATCH[1]}"
else
echo "No match"
fi
Примечание. Я сделал шаблон здесь немного более сложным, чем вы могли ожидать, чтобы обрабатывать некоторые допустимые, но не общие вещи в пути: он обрезает конечные косые черты и допускает множественные (избыточные) косые черты между двумя последними именами. Например, запуск его на "/a/b/c//d//" будет соответствовать "c//d".
Ответ 4
Я думаю, что более короткий способ использования глобусов, но:
$ DIRNAME='a/b/c/d/e'
$ LAST_TWO=$(expr "$DIRNAME" : '.*/\([^/]*/[^/]*\)$')
$ echo $LAST_TWO
d/e
Ответ 5
DIRNAME="a/b/c/d/e"
DIRNAME2=`echo $DIRNAME | awk -F/ '{print $(NF-1)"_"$(NF)}'`
DIRNAME2 then has the value d_e
Измените подчеркивание на все, что пожелаете.
Ответ 6
dirname=/a/b/c/d/e
IFS=/ read -a dirs <<< "$dirname"
printf "%s/%s\n" "${dirs[-2]}" "${dirs[-1]}"
Ответ 7
function getDir() {
echo $1 | awk -F/ '
{
n=NF-'$2'+1;
if(n<1)exit;
for (i=n;i<=NF;i++) {
printf("/%s",$i);
}
}'
}
dir="/a/b/c/d/e"
dir2=`getDir $dir 1`
echo $dir2
Вы можете получить любое количество каталогов от последней из этой функции.
Для вашего случая запустите его как,
dir2=`getDir $dir 2`;
Выход:/d/e