Obaj mają swoje dziwactwa, niestety.
Oba są wymagane przez POSIX, więc różnica między nimi nie dotyczy przenośności¹.
Prostym sposobem korzystania z narzędzi jest
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Zwróć uwagę na podwójne cudzysłowy wokół podstawień zmiennych, jak zawsze, a także --po poleceniu, w przypadku gdy nazwa pliku zaczyna się od myślnika (w przeciwnym razie polecenia interpretowałyby nazwę pliku jako opcję). Nadal nie udaje się to w przypadku jednego brzegu, co jest rzadkie, ale może być wymuszone przez złośliwego użytkownika²: podstawienie polecenia usuwa końcowe znaki nowej linii. Więc jeśli plik jest zwany foo/barnastępnie basezostanie ustawiony barzamiast bar. Obejściem tego problemu jest dodanie znaku nie będącego znakiem nowej linii i usunięcie go po podstawieniu polecenia:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
Dzięki podstawianiu parametrów nie napotykasz przypadków krawędzi związanych z ekspansją dziwnych znaków, ale istnieje wiele trudności ze znakiem ukośnika. Jedną rzeczą, która wcale nie jest przypadkiem na krawędzi, jest to, że obliczenie części katalogu wymaga innego kodu dla przypadku, w którym nie ma /.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
Przypadek na krawędzi występuje, gdy występuje ukośnik (w tym wielkość katalogu głównego, który jest wszystkimi ukośnikami). Te basenamei dirnamepolecenia zdejmować końcowe ukośniki przed robią swoje. Nie ma sposobu na usunięcie końcowych ukośników za jednym razem, jeśli trzymasz się konstrukcji POSIX, ale możesz to zrobić w dwóch krokach. Musisz zająć się przypadkiem, gdy wejście składa się wyłącznie z ukośników.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
Jeśli zdarzy ci się wiedzieć, że nie jesteś w przypadku krawędzi (np. findWynik inny niż punkt początkowy zawsze zawiera część katalogu i nie ma końca/ ), wówczas manipulowanie łańcuchem ekspansji parametrów jest proste. Jeśli musisz poradzić sobie ze wszystkimi przypadkami na krawędziach, narzędzia są łatwiejsze w użyciu (ale wolniej).
Czasami możesz chcieć traktować foo/jak, foo/.a nie jak foo. Jeśli działasz na podstawie wpisu w katalogu, foo/powinno to być równoważne foo/., a nie foo; robi to różnicę, gdy foojest dowiązaniem symbolicznym do katalogu: foooznacza dowiązanie symboliczne, foo/oznacza katalog docelowy. W takim przypadku basename ścieżki z końcowym ukośnikiem jest korzystnie ., a ścieżka może być własnym katalogiem.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
Szybką i niezawodną metodą jest użycie zsh z jego modyfikatorami historii (to pierwsze usuwa końcowe ukośniki, takie jak narzędzia):
dir=$filename:h base=$filename:t
¹ O ile nie korzystasz z powłok wcześniejszych niż POSIX, takich jak Solaris 10 i starsze /bin/sh(którym brakowało funkcji manipulowania łańcuchem ekspansji parametrów na maszynach wciąż będących w produkcji - ale w instalacji zawsze jest wywoływana powłoka POSIX sh, ale to /usr/xpg4/bin/shnie jest /bin/sh).
² Na przykład: prześlij plik wywołany foodo usługi przesyłania plików, która nie chroni przed tym, a następnie usuń go i spowoduj foousunięcie
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}? Czytałem uważnie i nie zauważyłem, że wspominacie o jakichkolwiek wadach.