Intereting Posts
«Нет такого файла или каталога», когда я отчетливо вижу файл с правильными разрешениями Подключитесь к экземпляру KVM, используя virsh, когда изображение запущено через Eucalyptus? Как просматривать по proxyfied openVPN Копирование адреса Active Directory из окна Nautilus Grep несколько файлов с датами в FileName для определенного диапазона дат mplayer (через консоль) не всегда показывает продолжительность аудио файла – как изменить поведение? Как я могу узнать, подключен ли кто-то к моей машине удаленно? Как заблокировать устройства USB на основе класса устройств в Linux Как заставить пользователя нажать Enter для выхода в терминал, чтобы терминал не закрывался автоматически? Как удалить список недавно полученных документов в Fedora? Несоответствие между общей памятью из / proc / meminfo и ipcs proc / sys / vm / flush_mmap_pages отсутствует в ядре 3.9, как его получить? Как использовать ag для поиска текста в файлах с определенными расширениями? Есть ли замена «ls», которая может обрабатывать подстановочные знаки? Ошибка авторизации SSL с несколькими серверами Apache

dirname и basename против расширения параметров

Есть ли объективная причина предпочесть одну форму другому? Производительность, надежность, мобильность?

filename=/some/long/path/to/a_file parentdir_v1="${filename%/*}" parentdir_v2="$(dirname "$filename")" basename_v1="${filename##*/}" basename_v2="$(basename "$filename")" echo "$parentdir_v1" echo "$parentdir_v2" echo "$basename_v1" echo "$basename_v2" 

Производит:

 /some/long/path/to /some/long/path/to a_file a_file 

(v1 использует расширение параметра оболочки, v2 использует внешние двоичные файлы.)

К сожалению, у них есть свои причуды.

Оба они требуются POSIX, поэтому разница между ними не связана с переносимостью¹.

Обычный способ использования утилит – это

 base=$(basename -- "$filename") dir=$(dirname -- "$filename") 

Обратите внимание на двойные кавычки вокруг переменных подстановок, как всегда, а также -- после команды, в случае, если имя файла начинается с тире (иначе команды будут интерпретировать имя файла как опцию). Это все еще не работает в одном случае с краем, что редко, но может быть вызвано вредоносным пользователем²: замена команды удаляет завершающие символы новой строки. Поэтому, если имя файла называется 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/ like foo/. а не как foo . Если вы действуете на записи в каталоге, то foo/ должен быть эквивалентен foo/. , а не foo ; это имеет значение, когда foo является символической ссылкой на каталог: foo означает символическую ссылку, foo/ означает целевой каталог. В этом случае преимущественным является базовое имя пути с завершающей косой чертой . , и путь может быть его собственным именем.

 case "$filename" in */) base="."; dir="$filename";; */*) base="${filename##*/}"; dir="${filename%"$base"}";; *) base="$filename"; dir=".";; esac 

Быстрый и надежный метод заключается в использовании zsh с его модификаторами истории (это первый штрих завершает косые черты, как и утилиты):

 dir=$filename:h base=$filename:t 

¹ Если вы не используете оболочки pre-POSIX, такие как Solaris 10 и old /bin/sh (которые не имеют функций манипуляции с расширением параметров на машинах, которые все еще находятся в производстве, но всегда существует оболочка POSIX с именем sh в установке, только это /usr/xpg4/bin/sh , not /bin/sh ).
² Например: отправьте файл с именем foo␤ в службу загрузки файлов, которая не защищает от этого, затем удалите его и foo удалить

Оба находятся в POSIX, поэтому переносимость «должна» не беспокоить. Предполагается, что замена оболочки должна выполняться быстрее.

Однако – это зависит от того, что вы подразумеваете под переносным. Некоторые (не обязательно) старые системы не реализовали эти функции в своих /bin/sh (Solaris 10 и старше приходят на ум), а с другой стороны, некоторое время назад разработчикам было предостережено, что имя dirname не было таким портативным, как basename .

Для справки:

  • basename – возвращает некаталогическую часть имени пути (POSIX)
  • dirname – возвращает часть каталога пути (POSIX)

    Утилита dirname возникла в System III. Он эволюционировал с помощью выпусков System V до версии, которая соответствует требованиям, указанным в этом описании в System V Release 3. 4.3 BSD и более ранние версии не включали имя dir.

  • sh на странице Solaris 10 (Oracle)
    На странице руководства не упоминается ## или %/ .

При рассмотрении переносимости я должен был бы учитывать все системы, в которых я поддерживаю программы. Не все POSIX, поэтому есть компромиссы. Ваши компромиссы могут отличаться.

Есть также:

 mkdir ' '; dir=$(basename ./' '); echo "${#dir}" 

 0 

Такие странные вещи случаются, потому что есть много интерпретаций и синтаксического анализа, а остальное это должно произойти, когда говорят два процесса. Замена команд приведет к стиранию новых строк. И NUL (хотя это явно не актуально здесь) . basename и dirname также будут в любом случае отбрасывать завершающие символы новой строки, потому что, как вы еще общаетесь с ними? Я знаю, что конец новых строк в имени файла – это анафема, но вы никогда не знаете. И не имеет смысла идти, возможно, ошибочно, когда вы могли бы сделать иначе.

Все еще … ${pathname##*/} != basename и аналогично ${pathname%/*} != dirname . Эти команды заданы для выполнения в основном четко определенной последовательности шагов для достижения указанных результатов.

Спектр ниже, но вначале это версия с терпением:

 basename() case $1 in (*[!/]*/) basename "${1%"${1##*[!/]}"}" ${2+"$2"} ;; (*/[!/]*) basename "${1##*/}" ${2+"$2"} ;; (${2:+?*}"$2") printf %s%b\\n "${1%"$2"}" "${1:+\n\c}." ;; (*) printf %s%c\\n "${1##///*}" "${1#${1#///}}" ;; esac 

Это полностью совместимое с POSIX basename в простой sh . Это не сложно. Я объединил пару ветвей, которые я использую ниже, потому что я мог не влиять на результаты.

Вот спецификация:

 basename() case $1 in ("") # 1. If string is a null string, it is # unspecified whether the resulting string # is '.' or a null string. In either case, # skip steps 2 through 6. echo . ;; # I feel like I should flip a coin or something. (//) # 2. If string is "//", it is implementation- # defined whether steps 3 to 6 are skipped or # or processed. # Great. What should I do then? echo // ;; # I guess it's *my* implementation after all. (*[!/]*/) # 3. If string consists entirely of <slash> # characters, string shall be set to a sin‐ # gle <slash> character. In this case, skip # steps 4 to 6. # 4. If there are any trailing <slash> characters # in string, they shall be removed. basename "${1%"${1##*[!/]}"}" ${2+"$2"} ;; # Fair enough, I guess. (*/) echo / ;; # For step three. (*/*) # 5. If there are any <slash> characters remaining # in string, the prefix of string up to and # including the last <slash> character in # string shall be removed. basename "${1##*/}" ${2+"$2"} ;; # == ${pathname##*/} ("$2"|\ "${1%"$2"}") # 6. If the suffix operand is present, is not # identical to the characters remaining # in string, and is identical to a suffix of # the characters remaining in string, the # the suffix suffix shall be removed from # string. Otherwise, string is not modi‐ # fied by this step. It shall not be # considered an error if suffix is not # found in string. printf %s\\n "$1" ;; # So far so good for parameter substitution. (*) printf %s\\n "${1%"$2"}" esac # I probably won't do dirname. 

… может быть, комментарии отвлекают ….

Вы можете получить повышение от basename и dirname в процессе (я не понимаю, почему они не являются встроенными), если они не являются кандидатами, я не знаю, что есть), но для реализации необходимо обработать такие вещи, как:

 path dirname basename "/usr/lib" "/usr" "lib" "/usr/" "/" "usr" "usr" "." "usr" "/" "/" "/" "." "." "." ".." "." ".." 

^ Из базового имени (3)

и другие краевые случаи.

Я использовал:

 basename(){ test -n "$1" || return 0 local x="$1"; while :; do case "$x" in */) x="${x%?}";; *) break;; esac; done [ -n "$x" ] || { echo /; return; } printf '%s\n' "${x##*/}"; } dirname(){ test -n "$1" || return 0 local x="$1"; while :; do case "$x" in */) x="${x%?}";; *) break;; esac; done [ -n "$x" ] || { echo /; return; } set -- "$x"; x="${1%/*}" case "$x" in "$1") x=.;; "") x=/;; esac printf '%s\n' "$x" } 

(Моя последняя реализация basename GNU и dirname добавляет некоторые специальные кнопки командной строки для таких вещей, как обработка нескольких аргументов или дескрипция суффикса, но это очень легко добавить в оболочку).

Это не так сложно сделать их встроенными встроенными bash (с использованием базовой реализации системы), но вышеприведенную функцию не нужно компилировать, и они также обеспечивают некоторый импульс.