У обоих есть свои причуды, к сожалению.
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%.}
? Я внимательно читал и не заметил, что вы упомянули какие-либо недостатки.foo/
какfoo
, а не какfoo/.
, что не согласуется с POSIX-совместимыми утилитами./
если мне это нужно.find
результат, который всегда содержит часть каталога и не имеет запаздывания/
" Не совсем верно,find ./
будет выводиться./
как первый результат.Оба находятся в POSIX, поэтому переносимость «не должна» беспокоить. Предполагается, что замены оболочки выполняются быстрее.
Однако - это зависит от того, что вы подразумеваете под портативным. Некоторые (не обязательно) старые системы не реализовали эти функции в своих
/bin/sh
(Solaris 10 и более ранних версиях), в то время как с другой стороны, разработчики предупреждали, что ониdirname
не так переносимы, какbasename
.Для справки:
dirname - возвращает часть каталога с именем пути (POSIX)
Страница справочника sh на Solaris 10 (Oracle)
На странице справочника не упоминается
##
или%/
.При рассмотрении переносимости я должен был бы учитывать все системы, в которых я поддерживаю программы. Не все POSIX, поэтому есть компромиссы. Ваши компромиссы могут отличаться.
источник
А также есть:
Такие странные вещи случаются, потому что существует много интерпретаций и синтаксического анализа, а остальное должно произойти, когда говорят два процесса. Подстановки команд удаляют завершающие символы новой строки. И NUL (хотя это, очевидно, здесь не актуально) .
basename
иdirname
в любом случае также уберет завершающие символы новой строки, потому что, как еще вы разговариваете с ними? Я знаю, что переводы строки в имени файла в любом случае являются своего рода анафемой, но вы никогда не знаете. И не имеет смысла идти по неправильному пути, когда вы могли бы поступить иначе.Тем не менее ...
${pathname##*/} != basename
и аналогично${pathname%/*} != dirname
. Эти команды предназначены для выполнения в основном четко определенной последовательности шагов для достижения указанных результатов.Спецификация ниже, но сначала вот более краткая версия:
Это полностью POSIX-совместимый
basename
в простомsh
. Это не сложно сделать. Я слил пару веток, которые я использую ниже, потому что я мог, не влияя на результаты.Вот спецификация:
... возможно, комментарии отвлекают ....
источник
[!/]
, это как[^/]
? Но ваш комментарий вместе с этим, кажется, не соответствует ...basename
представляет собой набор инструкций о том, как сделать это с вашей оболочкой. Но[!charclass]
переносимый способ сделать это с помощью globs[^class]
- для регулярных выражений - а оболочки не предназначены для регулярных выражений. О соответствии комментария ...case
фильтры, так что если я соответствую строке , которая содержит слэш/
и в!/
то , если следующий рисунок случая ниже спичек любых хвостовых/
Slashes на все они могут быть только все косыми. И тот, который ниже, не может иметь никакого трейлинга /Вы можете получить импульс от работы
basename
иdirname
(я не понимаю, почему они не являются встроенными - если это не кандидаты, я не знаю, что это), но реализация должна обрабатывать такие вещи, как:^ Из базового имени (3)
и другие крайние случаи.
Я использовал:
(Моя последняя реализация GNU
basename
иdirname
добавляет некоторые необычные переключатели командной строки для таких вещей, как обработка нескольких аргументов или удаление суффиксов, но это очень легко добавить в оболочку.)Это не так сложно превратить их во
bash
встроенные (используя основную реализацию системы), но вышеприведенную функцию не нужно компилировать, и они также обеспечивают некоторую поддержку.источник
x//
правильно, но я исправил для вас, прежде чем ответить. Я надеюсь, что это так.dirname a///b//c//d////e
доходностьa///b//c//d///
.