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/bar
następnie base
zostanie ustawiony bar
zamiast 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 basename
i dirname
polecenia 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. find
Wynik 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 foo
jest dowiązaniem symbolicznym do katalogu: foo
oznacza 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/sh
nie jest /bin/sh
).
² Na przykład: prześlij plik wywołany foo
do usługi przesyłania plików, która nie chroni przed tym, a następnie usuń go i spowoduj foo
usunię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.