Qual è il modo giusto per citare $ (comando $ arg)?


17

È giunto il momento di risolvere questo enigma che mi dà fastidio da anni ...

L'ho incontrato di tanto in tanto e ho pensato che fosse la strada da percorrere:

$(comm "$(arg)")

E ho pensato che il mio punto di vista fosse fortemente supportato dall'esperienza. Ma non ne sono più così sicuro. Anche Shellcheck non riesce a decidersi. È entrambi:

"$(dirname $0)"/stop.bash
           ^-- SC2086: Double quote to prevent globbing and word splitting.

E:

$(dirname "$0")/stop.bash
^-- SC2046: Quote this to prevent word splitting.

Qual è la logica dietro?

(È Shellcheck versione 0.4.4, tra l'altro.)


1
Cita tutte le sostituzioni.
Ignacio Vazquez-Abrams,

3
Proprio così "$(dirname "$0")"/stop.bash:? Sembra funzionare ... Qual è la storia?
Tomasz,

1
bash è abbastanza intelligente da sapere quando il contesto è cambiato.
Ignacio Vazquez-Abrams,

1
pensaci in questo modo:: shell_level_1 $(shell_level_2) shell_level_1quando sei dentro $(....)a un "sotto-livello" di shell, MA puoi scriverlo come se fossi al livello primario (cioè, puoi scrivere direttamente "invece di \", ecc.). es: touch "/tmp/a file" ; echo "its size is: $(find "/tmp/a file" -ls | awk '{print $5}) ..." : if you used backticks you'd have to trova \ "/ tmp / a file \" `e print \$5. Con $(...)nessun bisogno: le adatta shell al nuovo livello e si può scrivere direttamente come se l'interprete ora è a quel livello anche.
Olivier Dulac

"Anche Shellcheck non riesce a decidersi." - Le due piste hanno due messaggi diversi e puntano in punti diversi. Vuole entrambi.
Izkata,

Risposte:


45

È necessario utilizzare "$(somecmd "$file")".

Senza le virgolette, un percorso con uno spazio verrà diviso nell'argomento somecmde indirizzerà il file errato. Quindi hai bisogno di citazioni all'interno.

Anche gli spazi nell'output di somecmdcauseranno la divisione, quindi sono necessarie virgolette all'esterno dell'intera sostituzione del comando.

Le virgolette all'interno della sostituzione del comando non hanno alcun effetto sulle virgolette al di fuori di essa. Il manuale di riferimento di Bash non è troppo chiaro su questo, ma BashGuide lo menziona esplicitamente . Anche il testo in POSIX lo richiede, in quanto "qualsiasi script di shell valido" è consentito all'interno $(...):

Con il $(command)modulo, tutti i caratteri che seguono la parentesi aperta alla parentesi di chiusura corrispondente costituiscono il comando. È possibile utilizzare qualsiasi script shell valido per il comando, ad eccezione di uno script costituito esclusivamente da reindirizzamenti che produce risultati non specificati.


Esempio:

$ file="./space here/foo"

un. Nessuna virgoletta, dirnameelabora entrambi ./spacee here/foo:

$ printf "<%s>\n" $(dirname $file)
<.>
<here>

b. Citazioni all'interno, dirnameelabora ./space here/foo, dà ./space here, che è diviso in due:

$ printf "<%s>\n" $(dirname "$file")
<./space>
<here>

c. Citazioni esterne, dirnameelabora entrambi ./spacee here/foo, output su righe separate, ma ora le due righe formano un unico argomento :

$ printf "<%s>\n" "$(dirname $file)"
<.
here>

d. Citazioni sia all'interno che all'esterno, questo dà la risposta corretta:

$ printf "<%s>\n" "$(dirname "$file")"
<./space here>

(sarebbe stato più semplice se fosse stato dirnameelaborato solo il primo argomento, ma ciò non avrebbe mostrato la differenza tra i casi a e c)

Nota che con dirname(e possibilmente altri) devi anche aggiungere --, per evitare che il nome del file venga preso come opzione nel caso in cui inizi con un trattino, quindi usa "$(dirname -- "$file")".


16
Nota: in generale, le virgolette non nidificano in bash; ma in questo caso, $( )crea un nuovo contesto, quindi le virgolette al suo interno sono indipendenti dalle virgolette esterne. E in genere vuoi virgolette in entrambi i contesti.
Gordon Davisson,
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.