Shellcheck rät von der Verwendung des Basisnamens ab: Warum?

25

Ich probiere Shellcheck aus .

Ich habe so etwas

basename "${OPENSSL}" 

und ich bekomme folgenden vorschlag

Use parameter expansion instead, such as ${var##*/}.

Aus praktischer Sicht sehe ich keinen Unterschied

$ export OPENSSL=/opt/local/bin/openssl
$ basename ${OPENSSL}
openssl
$ echo ${OPENSSL##*/}
openssl

Da dies basenamein den POSIX-Spezifikationen enthalten ist , gibt es keinen Grund, warum dies die beste Vorgehensweise sein sollte. Irgendein Hinweis?

Matteo
quelle
8
Es gibt einen neuen Prozess vor, wenn dies nicht erforderlich ist.
Jordanien
@jordanm fair genug ... Ich habe nicht über Effizienz nachgedacht.
Matteo
3
@jordanm Auf der anderen Seite funktioniert es mit anderen Shells als bash
Matteo
1
@ JosephR. das habe ich mir gedacht aber gerade rausgefunden, dass es nicht geht csh. Ich denke, cshist dann nicht POSIX.
Terdon
3
@terdon - csh ist sehr weit von POSIX entfernt.
Jordan

Antworten:

25

Es geht nicht um Effizienz, sondern um Korrektheit. basenameBegrenzt die ausgegebenen Dateinamen mit Zeilenumbrüchen. Wenn Sie nur einen Dateinamen übergeben, wird in der Regel eine abschließende neue Zeile in die Ausgabe eingefügt. Da Dateinamen möglicherweise selbst Zeilenumbrüche enthalten, ist es schwierig, diese Dateinamen korrekt zu behandeln.

Es wird weiter durch die Tatsache erschwert , dass die Menschen in der Regel verwenden , basenamewie folgt aus : "$(basename "$file")". Das macht die Sache noch schwieriger, weil $(command)Streifen alle hinteren Zeilenumbrüche aus command. Betrachten Sie den unwahrscheinlichen Fall, $fileder mit einem Zeilenumbruch endet. Dann basenamewird eine zusätzliche Newline hinzufügen, aber "$(basename "$file")"wird der Streifen beiden Zeilenumbrüche, Sie mit einem falschen Dateinamen zu verlassen.

Ein weiteres Problem basenameist, dass wenn $filemit a beginnt- (Bindestrich oder Minus) begonnen wird, dies als Option interpretiert wird. Dieser ist leicht zu beheben:$(basename -- "$file")

Die robuste Art der Verwendung basenameist folgende:

# A file with three trailing newlines.
file=$'/tmp/evil\n\n\n'

# Add an 'x' so we can tell where $file's newlines end and basename's begin.
file_x="$(basename -- "$file"; printf x)"

# Strip off two trailing characters: the 'x' added by us and the newline added by basename. 
base="${file_x%??}"

Eine Alternative ist die Verwendung ${file##*/}, die einfacher ist, aber eigene Fehler aufweist. Insbesondere ist es falsch , in den Fällen , in denen $fileist /oder foo/.

Matt
quelle
2
Sehr gute Punkte, +1. Ich bin froh, dass die OP dies anstelle von mir akzeptiert hat.
Terdon
2
Kontrapunkt : Was ist, wenn $fileist foo/? Was ist, wenn es ist /?
Gilles 'SO- hör auf böse zu sein'
2
@ Gilles: Du hast recht. Ich fange an zu denken, dass der basenameAnsatz immerhin besser ist, so abgedreht er auch ist. Die besten Alternativen, die ich finden kann, sind ${${${file}%%/#}##*/}und [[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1], die beide zsh-spezifisch sind und die beide nicht /richtig handhaben .
Matt
@terdon Danke, dass du es nicht persönlich genommen hast :-). Dateien mit Zeilenumbrüchen sind nicht üblich, aber Matt hat einen Punkt. Natürlich ist die Verwendung der Variablensubstitution auch effizienter.
Matteo
16

Die entsprechenden Zeilen in shellcheck‚s - Quellcode sind:

checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "dirname" =
    style id "Use parameter expansion instead, such as ${var%/*}."
checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "basename" =
    style id "Use parameter expansion instead, such as ${var##*/}."
checkNeedlessCommands _ = return ()

Es gibt keine explizite Erklärung, aber basierend auf dem Namen der function ( checkNeedlessCommands) sieht es so aus, als ob @jordanm ganz richtig ist und Sie vermeiden, einen neuen Prozess zu forken.

terdon
quelle
7
Möge die Quelle mit dir sein :)
Joseph R.
3

dirname, basename, readlinkEtc (dank @Marco - Fehler behoben wird) Portabilität Probleme schaffen kann , wenn die Sicherheit wichtig wird (erfordert Sicherheit des Pfades). Viele Systeme (wie Fedora Linux) platzieren es auf, /binwährend andere (wie Mac OSX) es auf platzieren/usr/bin . Dann gibt es Bash unter Windows, zB cygwin, msys und andere. Es ist immer besser, wenn es geht, pur zu bleiben. (per @Marco Kommentar)

Übrigens, danke für den Hinweis auf Shellcheck, das habe ich noch nie gesehen.

AsymLabs
quelle
1
1) Was meinst du mit "Sicherheit"? Können Sie näher darauf eingehen? 2) Das OP erwähnt überhaupt nicht dirname. 3) Grundlegende Kerndienstprogramme sollten sich im PATH befinden, wo immer sie gespeichert sind. Ich habe noch kein System gesehen, das basenamenicht im PATH enthalten war. 4) Vorausgesetzt, Bash ist verfügbar, ist dies ein Portabilitätsproblem. Es ist immer besser, sich von bash fernzuhalten und eine POSIX-Shell zu verwenden, wenn Portabilität erforderlich ist.
Marco
@Marco Vielen Dank, dass Sie auf diese Probleme hingewiesen haben. Ja, Sie haben Recht, dass sich die Dienstprogramme auf dem Pfad befinden. Wenn man einem Bash-Skript jedoch zusätzliche Sicherheit bieten möchte, empfiehlt es sich, den absoluten Pfad anzugeben. Das Aufrufen von '/ bin / basename' funktioniert für RedHat-Systeme, führt jedoch auf einem Mac zu einem Fehler. Dies wird im Bash Cookbook besser erklärt, in dem etwa ein Viertel der 600 Seiten diesem Thema gewidmet sind. Wir haben grob versucht, die dortigen Sicherheitsprobleme in unserer kostenlosen Quelle secure-lib zu beheben .
AsymLabs
@Marco Punkt 4 ist ein gültiger Kommentar, aber die Frage (OP) beginnt mit und ist um Shellcheck geschrieben, mit dem beide sh / bash-Skripte geprüft werden . Daher müssen wir davon ausgehen, dass es sich nicht ausschließlich um Posix handelt.
AsymLabs
@Marco Die Sicherheit der Pfadumgebungsvariablen kann leicht gefährdet werden. Ich war zum Beispiel vor einigen Jahren sehr überrascht, dass die lokal installierte Ruby-RVM ihren Pfad standardmäßig vor den Systempfad gestellt hat.
AsymLabs
Das OP erwähnt speziell POSIX und die Frage ist markiert posixund nicht bash. Ich finde keinen Indikator dafür, dass das OP eine Bash-Lösung erfordert. Ihre Aussage "Es ist immer besser, rein zu bleiben, Bash" ist einfach falsch, es tut mir leid.
Marco