У обоих есть свои причуды, к сожалению.
POSIX требует и того, и другого, поэтому разница между ними не является проблемой переносимостиtability.
Простой способ использовать утилиты
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Обратите внимание на двойные кавычки вокруг подстановок переменных, как всегда, а также команду --
after, если имя файла начинается с тире (в противном случае команды интерпретируют имя файла как опцию). Это по-прежнему не удается в одном крайнем случае, что редко, но может быть вызвано злонамеренным пользователем2: подстановка команд удаляет завершающие символы новой строки. Так что если имя файла вызывается, foo/bar
тогда base
будет установлено bar
вместо bar
. Обходной путь - добавить не символ новой строки и удалить его после подстановки команды:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
При подстановке параметров вы не сталкиваетесь с крайними случаями, связанными с раскрытием странных символов, но с косой чертой есть ряд трудностей. Одна вещь, которая вообще не является крайним случаем, состоит в том, что для вычисления части каталога требуется другой код для случая, когда его нет /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
Крайний случай - когда есть завершающий слеш (включая случай корневого каталога, который все слэши). Эта basename
и dirname
команда сдирать заднюю косые черты , прежде чем они делают свою работу. Если вы придерживаетесь конструкций POSIX, нет способа зачистить завершающие косые черты за один раз, но вы можете сделать это в два этапа. Вы должны позаботиться о случае, когда ввод состоит только из косых черт.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
Если вам случается знать, что вы не находитесь в крайнем случае (например, find
результат, отличный от начальной точки, всегда содержит часть каталога и не имеет конечного значения /
), тогда манипулирование строкой расширения параметра является простым. Если вам нужно справиться со всеми крайними случаями, утилиты проще в использовании (но медленнее).
Иногда вы можете хотеть относиться к foo/
как, foo/.
а не как foo
. Если вы действуете в записи каталога, то foo/
предполагается, что она эквивалентна foo/.
, а не foo
; это имеет значение, когда foo
есть символическая ссылка на каталог: foo
означает символическую ссылку, foo/
означает целевой каталог. В этом случае базовое имя пути с косой чертой имеет преимущество .
, и путь может быть его собственным dirname.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
Быстрый и надежный метод - использовать zsh с его модификаторами истории (это сначала удаляет завершающие косые черты, как утилиты):
dir=$filename:h base=$filename:t
¹ Если вы не используете оболочки до POSIX, такие как Solaris 10 и более ранние /bin/sh
(в которых не было функций манипуляции со строками расширения параметров на машинах, которые все еще находятся в производстве - но всегда есть оболочка POSIX, вызываемая sh
при установке, только она /usr/xpg4/bin/sh
, а не /bin/sh
).
² Например: отправьте файл, вызванный foo
в службу загрузки файлов, которая не защищает от этого, затем удалите его и foo
вместо этого удалите
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
? Я внимательно читал и не заметил, что вы упомянули какие-либо недостатки.