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.foo/
jakfoo
, a nie jakfoo/.
, co nie jest zgodne z narzędziami zgodnymi z POSIX./
gdybym tego potrzebował, mogłem po prostu poprawić (lub „przywrócić”) trailing .find
wynik, który zawsze zawiera część katalogu i nie ma/
końca”. Niezupełnie prawda,find ./
wyświetli się./
jako pierwszy wynik.Oba są w systemie POSIX, więc przenośność „nie powinna” mieć znaczenia. Należy założyć, że podstawienia powłoki działają szybciej.
Jednak zależy to od tego, co rozumiesz przez przenośny. Niektóre (niekoniecznie) stare systemy nie implementowały tych funkcji w swoich
/bin/sh
(Solaris 10 i starsze przychodzą na myśl), podczas gdy z drugiej strony, dawno temu ostrzegano programistów, żedirname
nie jest tak przenośny jakbasename
.Na przykład:
dirname - zwraca część katalogu nazwy ścieżki (POSIX)
sh strona podręcznika na temat Solaris 10 (Oracle)
Strona podręcznika nie wspomina
##
ani%/
.Rozważając przenośność, musiałbym wziąć pod uwagę wszystkie systemy, w których utrzymuję programy. Nie wszystkie są POSIX, więc są kompromisy. Twoje kompromisy mogą się różnić.
źródło
Jest również:
Dziwne rzeczy takie się zdarzają, ponieważ jest dużo interpretacji i analizy, a reszta musi się zdarzyć, gdy rozmawiają dwa procesy. Podstawienia poleceń usuwają końcowe znaki nowej linii. I NULs (choć to oczywiście nie ma tu znaczenia) .
basename
adirname
także usunie końcowe znaki nowej linii, bo jak inaczej z nimi rozmawiasz? Wiem, że końcowe znaki w nazwie pliku i tak są swego rodzaju przekleństwem, ale nigdy nie wiadomo. I nie ma sensu iść w jakikolwiek wadliwy sposób, kiedy można zrobić inaczej.Nadal ...
${pathname##*/} != basename
i podobnie${pathname%/*} != dirname
. Polecenia te zostały określone w celu przeprowadzenia przeważnie dobrze określonej sekwencji kroków w celu uzyskania określonych rezultatów.Specyfikacja jest poniżej, ale najpierw jest wersja terser:
Jest to w pełni zgodny z POSIX
basename
w prosty sposóbsh
. Nie jest to trudne. Połączyłem kilka gałęzi, których używam poniżej, ponieważ mogłem bez wpływu na wyniki.Oto specyfikacja:
... może komentarze odwracają uwagę ...
źródło
[!/]
, czy to tak[^/]
? Ale twój komentarz, który wydaje się nie pasować ...basename
a to zestaw instrukcji, jak to zrobić z powłoką. Ale[!charclass]
czy przenośnym sposobem na zrobienie tego z globs[^class]
jest regex - i powłoki nie są wyspecyfikowane dla regex. O dopasowanie komentarz ...case
filtry, więc jeśli mogę dopasować ciąg, który zawiera ukośnik/
i to!/
wtedy, jeśli następny przypadek wzór poniżej meczów wszelkie końcowe/
ukośniki w ogóle mogą być one tylko wszystkie ukośniki. A jeden poniżej, który nie może mieć żadnego końcowego /Możesz uzyskać impuls z procesu
basename
idirname
(nie rozumiem, dlaczego nie są to wbudowane - jeśli nie są to kandydaci, nie wiem, co to jest), ale implementacja musi obsługiwać takie rzeczy jak:^ Od basename (3)
i inne skrzynki krawędziowe.
Korzystałem z:
(Moja najnowsza implementacja GNU
basename
idirname
dodaje specjalne fantazyjne przełączniki wiersza poleceń do takich rzeczy, jak obsługa wielu argumentów lub usuwanie sufiksów, ale to bardzo łatwe do dodania w powłoce.)Nie jest to również trudne do przekształcenia ich we
bash
wbudowane (dzięki wykorzystaniu podstawowej implementacji systemu), ale powyższa funkcja nie musi być kompilowana, a także zapewniają pewne ulepszenie.źródło
x//
poprawnie, ale naprawiłem dla ciebie przed odpowiedzią. Mam nadzieję, że to tyle.dirname a///b//c//d////e
dajea///b//c//d///
.