Perché la shell ignora le virgolette negli argomenti passati attraverso variabili?

Funziona come pubblicizzato:

# example 1 #!/bin/bash grep -ir 'hello world' . 

Questo non:

 # example 2 #!/bin/bash argumentString="-ir 'hello world'" grep $argumentString . 

Nonostante il 'hello world' sia racchiuso tra virgolette nel secondo esempio, grep interpreta 'hello come un argomento e world' come un altro, il che significa che, in questo caso, 'hello sarà il modello di ricerca e il world' sarà il percorso di ricerca .

Ancora una volta, ciò accade solo quando gli argomenti vengono espansi dalla variabile argumentString . grep interpreta correttamente 'hello world' come un singolo argomento nel primo esempio.

Qualcuno può spiegare perché questo è? Esiste un modo corretto per espandere una variabile stringa che conserverà la syntax di ogni carattere in modo tale che venga interpretata correttamente dai comandi della shell?

Perché

Quando la stringa viene espansa, viene divisa in parole, ma non viene rivalutata per trovare caratteri speciali come virgolette o segni di dollaro o … Questo è il modo in cui la shell si è “sempre” comportata, dal momento che la shell Bourne torna nel 1978 o giù di lì.

fissare

In bash , usa una matrice per contenere gli argomenti:

 argumentArray=(-ir 'hello world') grep "${argumentArray[@]}" . 

Oppure, se coraggioso / temerario, usa eval :

 argumentString="-ir 'hello world'" eval "grep $argumentString ." 

D’altra parte, la discrezione è spesso la parte migliore del valore, e lavorare con eval è un luogo in cui la discrezione è migliore del coraggio. Se non hai il pieno controllo della stringa che è eval d (se c’è un input dell’utente nella stringa di comando che non è stata rigorosamente convalidata), allora ti stai aprendo a problemi potenzialmente seri.

Nota che la sequenza di espansioni per Bash è descritta in Shell Expansions nel manuale di GNU Bash. Nota in particolare nelle sezioni 3.5.3 Espansione parametri shell, 3.5.7 Suddivisione in parole e 3.5.9 Rimozione quote.

Quando metti caratteri di citazione in variabili, diventano semplici letterali (vedi http://mywiki.wooledge.org/BashFAQ/050 ; grazie a @ triple per aver indicato questo link)

Invece, prova ad usare una matrice per passare i tuoi argomenti:

 argumentString=(-ir 'hello world') grep "${argumentString[@]}" . 

All’interno delle virgolette doppie, le virgolette singole perdono il loro significato speciale, quindi sono semplici personaggi comuni. Per farlo funzionare, usa qualcosa come eval :

 eval grep $argumentString . 

che dovrebbe riassemblare correttamente la linea di comando dalle stringhe concatenate.

Nell’osservare questa e le domande correlate, sono sorpreso che nessuno abbia fatto uso di una subshell esplicita. Per bash e altre shell moderne, è ansible eseguire esplicitamente una riga di comando. In bash, richiede l’opzione -c.

 argumentString="-ir 'hello world'" bash -c "grep $argumentString ." 

Funziona esattamente come richiesto dall’interrogante originale. Ci sono due restrizioni a questa tecnica:

  1. È ansible utilizzare solo virgolette singole all’interno delle stringhe di comando o argomento.
  2. Solo le variabili di ambiente esportate saranno disponibili per il comando

Inoltre, questa tecnica gestisce il reindirizzamento e le tubazioni e anche altri gonismi funzionano. È inoltre ansible utilizzare i comandi interni di bash e qualsiasi altro comando che funzioni sulla riga di comando, poiché si richiede essenzialmente a bash di subshell di interpretarlo direttamente come riga di comando. Ecco un esempio più complesso, una variante ls -l un po ‘troppo complessa.

 cmd="prefix=`pwd` && ls | xargs -n 1 echo \'In $prefix:\'" bash -c "$cmd" 

Ho sviluppato processori di comando sia in questo modo che con array di parametri. In generale, in questo modo è molto più facile scrivere ed eseguire il debug ed è banale il comando che stai eseguendo. OTOH, gli array di parametri funzionano bene quando si hanno array di parametri astratti, invece di voler solo una semplice variante di comando.