shellcheck consiglia di non usare basename: perché?


25

Sto provando il shellcheck .

Ho qualcosa del genere

basename "${OPENSSL}" 

e ricevo il seguente suggerimento

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

Dal punto di vista pratico non vedo alcuna differenza

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

Dal momento che basenameè nelle specifiche POSIX , non ho un motivo per cui dovrebbe essere la migliore pratica. Qualche suggerimento?


8
Prevede un nuovo processo quando non è necessario.
Giordania,

@jordanm abbastanza giusto ... Non ho pensato all'efficienza.
Matteo,

3
@jordanm D'altra parte funziona con conchiglie diverse da bash
Matteo

1
@JosephR. è quello che pensavo, ma ho appena scoperto che non funziona csh. Immagino cshnon sia POSIX allora.
terdon

3
@terdon - csh è molto lontano da POSIX.
Giordania,

Risposte:


25

Non si tratta di efficienza, ma di correttezza. basenameusa newline per delimitare i nomi dei file che stampa. Nel solito caso quando si passa solo un nome file, aggiunge una nuova riga finale al proprio output. Poiché i nomi dei file possono contenere nuove righe, ciò rende difficile gestire correttamente questi nomi di file.

E 'ulteriormente complicato dal fatto che la gente di solito uso basenamein questo modo: "$(basename "$file")". Questo rende le cose ancora più difficili, perché $(command)elimina tutte le nuove righe finali command. Considera il caso improbabile che $filetermina con una nuova riga. Poi basenameaggiungerà una nuova riga in più, ma "$(basename "$file")"metterà a nudo sia a capo, lasciando con un nome di file errato.

Un altro problema basenameè che se $fileinizia con un -(trattino aka meno), verrà interpretato come un'opzione. Questo è facile da risolvere:$(basename -- "$file")

Il modo robusto di utilizzare basenameè questo:

# 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%??}"

Un'alternativa è usare ${file##*/}, che è più facile ma ha dei bug propri. In particolare, è sbagliato nei casi in cui $fileè /o foo/.


2
Ottimi punti, +1. Sono contento che l'OP abbia accettato questo invece del mio.
terdon

2
Contrappunto: cosa succede se lo $fileè foo/? E se fosse /?
Gilles 'SO- smetti di essere malvagio'

2
@Gilles: hai ragione. Sto iniziando a pensare che l' basenameapproccio sia migliore dopo tutto, per quanto confuso. Le migliori alternative che posso trovare sono ${${${file}%%/#}##*/}e [[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1], entrambe specifiche per zsh e nessuna delle quali gestisce /correttamente.
Matt,

@terdon Grazie che non l'hai preso sul personale :-). I file con newline non sono comuni ma Matt ha ragione. Ovviamente anche l'uso della sostituzione variabile è più efficiente.
Matteo,

16

Le linee rilevanti shellcheck's codice sorgente sono:

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 ()

Non vi è alcuna spiegazione esplicita, ma basata sul nome della funzione ( checkNeedlessCommands) sembra che @jordanm abbia ragione e suggerisce di evitare di creare un nuovo processo.


7
Possa la fonte essere con te :)
Joseph R.

3

dirname, basename, readlinkEcc (grazie @Marco - questo è corretto) può creare problemi di portabilità quando la sicurezza diventa importante (che richiede la sicurezza del percorso). Molti sistemi (come Fedora Linux) lo posizionano /binmentre altri (come Mac OSX) lo posizionano /usr/bin. Poi c'è Bash su Windows, ad esempio cygwin, msys e altri. È sempre meglio rimanere puro Bash, quando possibile. (per commento di @Marco)

A proposito, grazie per il puntatore a shellcheck, non l'ho mai visto prima.


1
1) Cosa intendi con "sicurezza"? Puoi elaborare? 2) L'OP non menziona dirnameaffatto. 3) Le utility di base dovrebbero trovarsi nel PERCORSO, ovunque siano memorizzate. Non ho ancora visto un sistema in cui basenamenon si trovava nel PERCORSO. 4) Supponendo che bash sia disponibile è un problema di idoneità. È sempre meglio stare lontano da bash e usare una shell POSIX quando è richiesta la portabilità.
Marco,

@Marco Grazie per aver segnalato questi problemi. Sì, hai ragione nel dire che le utility sono sul percorso. Ma dove si desidera fornire maggiore sicurezza a uno script Bash, è buona norma fornire il percorso assoluto. Quindi chiamare '/ bin / basename' funzionerà per i sistemi RedHat, ma produrrà un errore su un Mac. Ciò è meglio spiegato nel Bash Cookbook, dove circa un quarto delle 600 pagine è dedicato a questo argomento. Abbiamo fatto un tentativo approssimativo per risolvere i problemi di sicurezza presenti nella nostra libreria di sicurezza free-source gratuita .
AsymLabs

@Marco Point 4 è un commento valido ma la domanda (OP) inizia con ed è scritta attorno al shellcheck che è progettato per controllare entrambi gli script sh / bash. Pertanto, dobbiamo presumere che non sia strettamente Posix per impostazione predefinita.
AsymLabs

@Marco La sicurezza della variabile d'ambiente path può essere facilmente compromessa. Ad esempio, sono stato molto sorpreso di scoprire alcuni anni fa che Ruby RVM, installato localmente, posizionava di default il suo percorso prima del percorso di sistema.
AsymLabs

L'OP menziona specificamente POSIX e la domanda è taggata posixe non bash. Non riesco a trovare alcun indicatore che l'OP richiede una soluzione bash. La tua affermazione "È sempre meglio rimanere puro Bash" è semplicemente sbagliata, mi dispiace.
Marco,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.