bash: извлечение двух последних каталогов для пути - PullRequest
15 голосов
/ 22 ноября 2011

Кажется, я потерпел неудачу в чем-то довольно простом в bash. У меня есть строковая переменная, которая содержит полный путь к каталогу. Я бы хотел присвоить последним двум каталогам в нем другую строку. Например, если у меня есть:

DIRNAME = /a/b/c/d/e

Я бы хотел:

DIRNAME2 = d/e

Я уверен, что есть простая конструкция bash или sed команда, которая сделает это, но она ускользает от меня. Я бы вроде обобщенной версии basename или dirname, которая не просто возвращает крайние части имени.

Спасибо! Dave

Ответы [ 7 ]

12 голосов
/ 22 ноября 2011
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  ]]
10 голосов
/ 22 ноября 2011

Я предпочитаю максимально использовать встроенные функции, чтобы избежать создания ненужных процессов.Потому что ваш скрипт может быть запущен под Cygwin или другой ОС, процесс создания которой очень дорогой.

Я думаю, что это не так долго, если вы просто хотите извлечь две папки:

base1="${DIRNAME##*/}"
dir1="${DIRNAME%/*}"
DIRNAME2="${dir1##*/}/$base1"

Это можеттакже избегайте особых проблем с символами, связанных с выполнением других команд.

3 голосов
/ 22 ноября 2011

Я не знаю метода, специально предназначенного для обрезки путей, но вы, безусловно, можете сделать это с помощью соответствия регулярных выражений 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».

2 голосов
/ 22 ноября 2011
DIRNAME="a/b/c/d/e"
DIRNAME2=`echo $DIRNAME | awk -F/ '{print $(NF-1)"_"$(NF)}'`

DIRNAME2 then has the value d_e

Измените подчеркивание на любое другое.

2 голосов
/ 22 ноября 2011

Я думаю, что есть более короткий способ использования глобусов, но:

$ DIRNAME='a/b/c/d/e'
$ LAST_TWO=$(expr "$DIRNAME" : '.*/\([^/]*/[^/]*\)$')
$ echo $LAST_TWO
d/e
1 голос
/ 10 июня 2015
dirname=/a/b/c/d/e
IFS=/ read -a dirs <<< "$dirname"
printf "%s/%s\n" "${dirs[-2]}" "${dirs[-1]}"
0 голосов
/ 22 ноября 2011
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`;

Вывод: / д / е

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...