Beide haben leider ihre Macken.
Beide werden von POSIX benötigt, daher ist der Unterschied zwischen ihnen kein Problem der Portabilität¹.
Der einfache Weg, um die Dienstprogramme zu verwenden, ist
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Beachten Sie die doppelten Anführungszeichen bei Variablensubstitutionen wie immer und auch --
nach dem Befehl, falls der Dateiname mit einem Bindestrich beginnt (andernfalls würden die Befehle den Dateinamen als Option interpretieren). Dies schlägt in einem Randfall immer noch fehl, was selten vorkommt, aber möglicherweise von einem böswilligen Benutzer erzwungen wird²: Durch das Ersetzen von Befehlen werden nachgestellte Zeilenumbrüche entfernt. Also , wenn ein Dateiname genannt wird , foo/bar
dann base
wird gesetzt werden bar
statt bar
. Eine Problemumgehung besteht darin, ein Nicht-Zeilenumbruchzeichen hinzuzufügen und es nach der Befehlsersetzung zu entfernen:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
Mit der Parametersubstitution stoßen Sie nicht auf Randfälle, die mit der Erweiterung von seltsamen Zeichen zusammenhängen, aber es gibt eine Reihe von Schwierigkeiten mit dem Schrägstrich. Eine Sache, die überhaupt kein Randfall ist, besteht darin, dass für die Berechnung des Verzeichnisteils ein anderer Code erforderlich ist als für den Fall, dass es keinen gibt /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
Der Kantenfall liegt vor, wenn ein abschließender Schrägstrich angezeigt wird (einschließlich des Stammverzeichnisses, bei dem es sich ausschließlich um Schrägstriche handelt). Die Befehle basename
und dirname
entfernen abschließende Schrägstriche, bevor sie ihre Arbeit erledigen. Es gibt keine Möglichkeit, die abschließenden Schrägstriche auf einmal zu entfernen, wenn Sie sich an POSIX-Konstrukte halten, aber Sie können dies in zwei Schritten tun. Sie müssen sich um den Fall kümmern, dass die Eingabe nur aus Schrägstrichen besteht.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
Wenn Sie zufällig wissen, dass Sie sich nicht in einer Randbedingung befinden (z. B. enthält ein find
anderes Ergebnis als der Startpunkt immer einen Verzeichnisteil und weist keine nachfolgenden Elemente auf /
), ist die Manipulation der Parametererweiterungszeichenfolge unkompliziert. Wenn Sie alle Randfälle bewältigen müssen, sind die Dienstprogramme einfacher zu verwenden (aber langsamer).
Manchmal können Sie behandeln wollen foo/
wie foo/.
anstatt wie foo
. Wenn Sie auf einem Verzeichniseintrag sind handeln dann foo/
sollte äquivalent sein foo/.
, nicht foo
; Dies macht einen Unterschied, wenn foo
es sich um eine symbolische Verknüpfung zu einem Verzeichnis handelt: foo
bedeutet die symbolische Verknüpfung, foo/
bedeutet das Zielverzeichnis. In diesem Fall ist der Basisname eines Pfads mit einem abschließenden Schrägstrich vorteilhaft .
und der Pfad kann ein eigener Verzeichnisname sein.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
Die schnelle und zuverlässige Methode besteht darin, zsh mit seinen Verlaufsmodifikatoren zu verwenden (in diesem Beispiel werden abschließende Schrägstriche wie in den Dienstprogrammen entfernt):
dir=$filename:h base=$filename:t
¹ Wenn Sie pre-POSIX - Shells wie Solaris verwenden 10 und älter ist /bin/sh
(die Parameter Expansion String - Manipulation fehlte noch in der Produktion von Maschinen bietet - aber es gibt immer eine POSIX genannt shell sh
in der Installation, nur es ist /usr/xpg4/bin/sh
nicht /bin/sh
).
² Zum Beispiel: Übermitteln Sie eine aufgerufene Datei foo
an einen Datei-Upload-Dienst, der nicht davor schützt, und löschen Sie sie dann und veranlassen Sie foo
stattdessen, dass sie gelöscht wird
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
? Ich habe sorgfältig gelesen und habe nicht bemerkt, dass Sie irgendwelche Nachteile erwähnt haben.foo/
wie behandeltfoo
, nicht wiefoo/.
, was mit POSIX-kompatiblen Dienstprogrammen nicht konsistent ist./
wenn ich ihn benötige.find
Ergebnis, das immer einen Verzeichnisteil enthält und kein Trailing hat/
" Nicht ganz wahr,find ./
wird./
als erstes Ergebnis ausgegeben .Beide sind in POSIX enthalten, daher sollte die Portabilität keine Rolle spielen. Es sollte angenommen werden, dass die Shell-Substitutionen schneller ablaufen.
Dies hängt jedoch davon ab, was Sie unter tragbar verstehen. Einige (nicht notwendigerweise) alte Systeme haben diese Funktionen in ihren
/bin/sh
(Solaris 10 und älter ) nicht implementiert , während die Entwickler vor einiger Zeit darauf hingewiesen wurden, dass siedirname
nicht so portabel sind wiebasename
.Als Referenz:
dirname - gibt den Verzeichnisanteil eines Pfadnamens zurück (POSIX)
sh Manualpage unter Solaris 10 (Oracle)
Die Manualpage erwähnt
##
oder nicht%/
.Bei der Prüfung der Portabilität müsste ich alle Systeme berücksichtigen , auf denen ich Programme verwalte. Nicht alle sind POSIX, also gibt es Kompromisse. Ihre Kompromisse können sich unterscheiden.
quelle
Es gibt auch:
Solche seltsamen Dinge passieren, weil es viel Interpretieren und Parsen gibt und der Rest, der passieren muss, wenn zwei Prozesse sprechen. Befehlsersetzungen entfernen nachfolgende Zeilenumbrüche. Und NULs (obwohl das hier offensichtlich nicht relevant ist) .
basename
unddirname
werden auf jeden fall auch nachgestellte newlines entfernen, weil wie redest du sonst mit ihnen? Ich weiß, dass das Nachziehen von Zeilenumbrüchen in einem Dateinamen sowieso ein Anathema ist, aber man weiß es nie. Und es macht keinen Sinn, den möglicherweise fehlerhaften Weg zu gehen, wenn Sie es anders machen könnten.Trotzdem ...
${pathname##*/} != basename
und ebenso${pathname%/*} != dirname
. Diese Befehle sind zum Ausführen eines Großteils angegeben genau definierte Abfolge von Schritten um zu den angegebenen Ergebnissen zu gelangen.Die Spezifikation ist unten, aber zuerst ist hier eine tersere Version:
Das ist eine voll POSIX-konforme
basename
in einfachersh
. Es ist nicht schwer zu tun. Ich habe ein paar Zweige zusammengelegt, die ich unten benutze, weil ich konnte, ohne die Ergebnisse zu beeinflussen.Hier ist die Spezifikation:
... vielleicht lenken die Kommentare ab ...
quelle
[!/]
zuvor gesehen , ist das so[^/]
? Aber Ihr Kommentar daneben scheint nicht zu passen ....basename
eine Reihe von Anweisungen, wie Sie dies mit Ihrer Shell tun können. Aber[!charclass]
ist die tragbare Möglichkeit, dies mit Globs zu tun,[^class]
für Regex - und Shells sind nicht für Regex spezifiziert? Über die Kommentar passende ...case
Filter, so dass , wenn ich eine Zeichenfolge übereinstimmen , die einen Schrägstrich enthält/
und einen!/
dann , wenn der nächste Fall Muster unten alle nachgestellten Matches/
an alle Schrägstriche können sie nur sein alle Schrägstriche. Und eine darunter, die keine Trailing /Sie können einen Schub von In-Process erhalten
basename
unddirname
(ich verstehe nicht, warum dies keine Buildins sind - wenn dies keine Kandidaten sind, weiß ich nicht, was das ist), aber die Implementierung muss Dinge wie Folgendes handhaben:^ Aus dem Basisnamen (3)
und andere Randfälle.
Ich habe verwendet:
(Meine neueste Implementierung von GNU
basename
unddirname
fügt einige spezielle Phantasie Befehlszeilenoptionen für Sachen wie mehrere Argumente oder Suffix Handhabung Strippen, aber das ist super einfach in der Schale hinzuzufügen.)Es ist auch nicht so schwierig, diese in
bash
Builtins umzuwandeln (indem die zugrunde liegende Systemimplementierung verwendet wird), aber die obige Funktion muss nicht kompiliert werden, und sie bietet auch einen gewissen Schub.quelle
x//
richtig gehandhabt, aber ich habe sie für Sie behoben, bevor ich antwortete. Ich hoffe das ist es.dirname a///b//c//d////e
Erträgea///b//c//d///
.