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