В обох є свої примхи, на жаль.
Обидва потрібні 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/
так, 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%.}
? Я уважно читав, і не помітив, щоб ти згадував про якісь недоліки.