Implicazioni sulla sicurezza dell'oblio di citare una variabile nelle shell bash / POSIX


206

Se segui unix.stackexchange.com per un po ', dovresti ora sapere che lasciare una variabile non quotata nel contesto dell'elenco (come in echo $var) nelle shell Bourne / POSIX (zsh essendo l'eccezione) ha un significato molto speciale e non dovrebbe essere fatto a meno che tu non abbia un'ottima ragione per farlo.

E 'discusso a lungo in una serie di domande e risposte qui (esempi: ? Perché il mio guscio starter script su spazi o altri caratteri speciali , quando è doppio citando necessario? , Ampliamento di una variabile di shell e l'effetto di glob e diviso su di esso , Citato vs espansione della stringa non quotata)

Questo è stato il caso dall'uscita iniziale della shell Bourne alla fine degli anni '70 e non è stata cambiata dalla shell Korn (uno dei più grandi rimpianti di David Korn (domanda n. 7) ) o bashche per lo più ha copiato la shell Korn, e questo è come è stato specificato da POSIX / Unix.

Ora, stiamo ancora vedendo un numero di risposte qui e persino occasionalmente codice di shell rilasciato pubblicamente in cui le variabili non sono quotate. Avresti pensato che la gente avrebbe imparato ormai.

Nella mia esperienza, ci sono principalmente 3 tipi di persone che omettono di citare le loro variabili:

  • principianti. Questi possono essere scusati in quanto è certamente una sintassi completamente non intuitiva. Ed è il nostro ruolo su questo sito per educarli.

  • persone smemorate.

  • persone che non sono convinte anche dopo ripetuti martellamenti, che pensano che sicuramente l'autore della shell Bourne non intendesse che citassimo tutte le nostre variabili .

Forse possiamo convincerli se esponiamo il rischio associato a questo tipo di comportamenti.

Qual è la cosa peggiore di quanto possa accadere se ti dimentichi di citare le tue variabili. È davvero così male?

Di che tipo di vulnerabilità stiamo parlando qui?

In quali contesti può essere un problema?


8
BashPitfalls è qualcosa che ti piace, penso.
Pawel7318,

backlink di questo articolo che ho scritto , grazie per il writeup
mirabilos

5
Vorrei suggerire di aggiungere un quarto gruppo: le persone che sono state colpite sopra la testa per eccessive citazioni troppe volte, forse dai membri del terzo gruppo che hanno eliminato la loro frustrazione per gli altri (la vittima diventa il bullo). La cosa triste ovviamente è che quelli del quarto gruppo potrebbero non riuscire a citare le cose quando conta di più.
Ack

Risposte:


201

Preambolo

Innanzitutto, direi che non è il modo giusto di affrontare il problema. È un po 'come dire " non dovresti uccidere le persone perché altrimenti andrai in prigione ".

Allo stesso modo, non citi la tua variabile perché altrimenti stai introducendo vulnerabilità di sicurezza. Citi le tue variabili perché è sbagliato non (ma se la paura della prigione può aiutare, perché no).

Un piccolo riassunto per coloro che sono appena saliti sul treno.

Nella maggior parte delle shell, lasciare un'espansione variabile non quotata (anche se (e il resto di questa risposta) si applica anche alla sostituzione dei comandi ( `...`o $(...)) e all'espansione aritmetica ( $((...))o $[...])) ha un significato molto speciale. Il modo migliore per descriverlo è che è come invocare una sorta di operatore implicito split + glob¹ .

cmd $var

in un'altra lingua verrebbe scritto qualcosa del tipo:

cmd(glob(split($var)))

$varviene prima suddiviso in un elenco di parole in base a regole complesse che coinvolgono il $IFSparametro speciale (la parte divisa ) e quindi ogni parola risultante da tale suddivisione viene considerata come un modello che viene espanso in un elenco di file corrispondenti ( parte glob ) .

Ad esempio, se $varcontiene *.txt,/var/*.xmle $IFS contiene ,, cmdverrebbe chiamato con un numero di argomenti, il primo è cmde quelli successivi sono i txt file nella directory corrente e i xmlfile in /var.

Se volessi chiamare cmdcon solo i due argomenti letterali cmd e *.txt,/var/*.xml, dovresti scrivere:

cmd "$var"

che sarebbe nella tua altra lingua più familiare:

cmd($var)

Cosa intendiamo per vulnerabilità in una shell ?

Dopotutto, è noto fin dagli albori che gli script di shell non devono essere utilizzati in contesti sensibili alla sicurezza. Sicuramente, OK, lasciare una variabile non quotata è un bug ma non può fare così tanto male, vero?

Bene, nonostante il fatto che qualcuno ti direbbe che gli script di shell non dovrebbero mai essere usati per i CGI web, o che per fortuna la maggior parte dei sistemi non consente al giorno d'oggi script di shell setuid / setgid, una cosa che shellshock (il bug bash sfruttabile da remoto che ha reso il i titoli di settembre 2014) hanno rivelato che le shell sono ancora ampiamente utilizzate dove probabilmente non dovrebbero: nei CGI, negli script hook del client DHCP, nei comandi sudoers, invocati da (se non come ) comandi setuid ...

A volte inconsapevolmente. Ad esempio system('cmd $PATH_INFO') in uno script php/ perl/ pythonCGI invoca una shell per interpretare quella riga di comando (per non parlare del fatto che cmdpuò essere uno script di shell e il suo autore non si sarebbe mai aspettato che fosse chiamato da un CGI).

Hai una vulnerabilità quando esiste un percorso per l'escalation dei privilegi, ovvero quando qualcuno (chiamiamolo l'attaccante ) è in grado di fare qualcosa che non è destinato a.

Invariabilmente ciò significa che l'attaccante fornisce i dati, che i dati vengono elaborati da un utente / processo privilegiato che inavvertitamente fa qualcosa che non dovrebbe fare, nella maggior parte dei casi a causa di un bug.

Fondamentalmente, hai un problema quando il tuo codice buggy elabora i dati sotto il controllo dell'attaccante .

Ora, non è sempre ovvio da dove provengano quei dati ed è spesso difficile stabilire se il tuo codice potrà mai elaborare dati non attendibili.

Per quanto riguarda le variabili, nel caso di uno script CGI, è abbastanza ovvio, i dati sono i parametri CGI GET / POST e cose come i parametri cookie, percorso, host ...

Per uno script setuid (eseguito come utente quando richiamato da un altro), sono gli argomenti o le variabili di ambiente.

Un altro vettore molto comune sono i nomi dei file. Se stai ricevendo un elenco di file da una directory, è possibile che i file siano stati piantati lì dall'aggressore .

A tale proposito, anche al prompt di una shell interattiva, potresti essere vulnerabile (durante l'elaborazione di file in /tmpo ~/tmp per esempio).

Anche a ~/.bashrcpuò essere vulnerabile (ad esempio, bashlo interpreterà quando invocato sshper eseguire un ForcedCommand like nelle gitdistribuzioni del server con alcune variabili sotto il controllo del client).

Ora, uno script non può essere chiamato direttamente per elaborare dati non attendibili, ma può essere chiamato da un altro comando che lo fa. Oppure il tuo codice errato può essere copiato e incollato in script che lo fanno (da te 3 anni o uno dei tuoi colleghi). Un punto in cui è particolarmente critico è nelle risposte nei siti di domande e risposte poiché non saprai mai dove potrebbero finire le copie del tuo codice.

Giù per affari; quanto è cattivo?

Lasciare una variabile (o sostituzione di comando) non quotata è di gran lunga la fonte numero uno delle vulnerabilità di sicurezza associate al codice della shell. In parte perché quei bug si traducono spesso in vulnerabilità, ma anche perché è così comune vedere variabili non quotate.

In realtà, quando si cercano vulnerabilità nel codice della shell, la prima cosa da fare è cercare variabili non quotate. È facile da individuare, spesso un buon candidato, generalmente facile da rintracciare ai dati controllati dagli aggressori.

Esiste un numero infinito di modi in cui una variabile non quotata può trasformarsi in una vulnerabilità. Darò solo alcune tendenze comuni qui.

Rivelazione di un 'informazione

La maggior parte delle persone si imbatterà in bug associati a variabili non quotate a causa della parte divisa (ad esempio, è comune per i file avere spazi nei loro nomi al giorno d'oggi e lo spazio è nel valore predefinito di IFS). Molte persone trascureranno la parte globale . La parte glob è pericolosa almeno quanto la parte divisa .

Globbing fatto su input esterni non autorizzati significa che l'attaccante può farti leggere il contenuto di qualsiasi directory.

Nel:

echo You entered: $unsanitised_external_input

se $unsanitised_external_inputcontiene /*, ciò significa che l'attaccante può vedere il contenuto di /. Nessun grosso problema. Diventa più interessante, però, con il /home/*quale ti dà un elenco di nomi utente sulla macchina /tmp/*, /home/*/.forwardper suggerimenti su altre pratiche pericolose, /etc/rc*/*per servizi abilitati ... Non c'è bisogno di nominarli singolarmente. Un valore di /* /*/* /*/*/*...elencherà solo l'intero file system.

Vulnerabilità di negazione del servizio.

Portando il caso precedente un po 'troppo lontano e abbiamo un DoS.

In realtà, qualsiasi variabile non quotata nel contesto di un elenco con input non inizializzato è almeno una vulnerabilità DoS.

Persino gli esperti scripter shell comunemente dimenticano di citare cose come:

#! /bin/sh -
: ${QUERYSTRING=$1}

:è il comando no-op. Che cosa potrebbe andare storto?

Questo è destinato ad assegnare $1a $QUERYSTRINGse $QUERYSTRING non fosse impostato. Questo è un modo rapido per rendere richiamabile anche uno script CGI dalla riga di comando.

Questo $QUERYSTRINGè comunque espanso e poiché non è quotato, viene richiamato l'operatore split + glob .

Ora, ci sono alcuni globs che sono particolarmente costosi da espandere. L' /*/*/*/*uno è già abbastanza grave in quanto significa che le directory di quotazione fino a 4 livelli verso il basso. Oltre all'attività del disco e della CPU, ciò significa memorizzare decine di migliaia di percorsi di file (40k qui su una VM server minima, 10k di cui directory).

Ora /*/*/*/*/../../../../*/*/*/*significa 40k x 10k ed /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*è sufficiente per mettere in ginocchio anche la macchina più potente.

Provalo tu stesso (anche se sii pronto per il crash o il blocco della macchina):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

Naturalmente, se il codice è:

echo $QUERYSTRING > /some/file

Quindi puoi riempire il disco.

Basta fare una ricerca su google su shell cgi o bash cgi o ksh cgi e troverai alcune pagine che mostrano come scrivere CGI nelle shell. Nota come la metà di quelli che elaborano i parametri sono vulnerabili.

Anche quello di David Korn è vulnerabile (guarda la gestione dei cookie).

fino a vulnerabilità di esecuzione di codice arbitrario

L'esecuzione di codice arbitrario è il peggior tipo di vulnerabilità, poiché se l'attaccante può eseguire qualsiasi comando, non c'è limite a ciò che può fare.

Questa è generalmente la parte divisa che porta a quelli. Tale suddivisione comporta la trasmissione di numerosi argomenti ai comandi quando è previsto solo uno. Mentre il primo verrà utilizzato nel contesto previsto, gli altri si troveranno in un contesto diverso, quindi potenzialmente interpretato diversamente. Meglio con un esempio:

awk -v foo=$external_input '$2 == foo'

Qui, l'intenzione era di assegnare il contenuto della $external_inputvariabile shell alla foo awkvariabile.

Adesso:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

La seconda parola risultante dalla divisione di $external_input non è assegnata fooma considerata come awkcodice (qui che esegue un comando arbitrario:) uname.

Questo è un problema soprattutto per i comandi che possono eseguire altri comandi ( awk, env, sed(GNU uno), perl, find...) in particolare con le varianti GNU (che accettano opzioni dopo argomenti). A volte, non sospetteresti che i comandi siano in grado di eseguire altri come ksh, basho zsh's [o printf...

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

Se creiamo una directory chiamata x -o yes, il test diventa positivo, perché stiamo valutando un'espressione condizionale completamente diversa.

Peggio ancora, se creiamo un file chiamato x -a a[0$(uname>&2)] -gt 1, con almeno tutte le implementazioni di ksh (che include la sh maggior parte degli Unices commerciali e alcuni BSD), che viene eseguito uname perché tali shell eseguono una valutazione aritmetica sugli operatori di confronto numerico del [comando.

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

Lo stesso vale bashper un nome file come x -a -v a[0$(uname>&2)].

Naturalmente, se non riescono a ottenere un'esecuzione arbitraria, l'attaccante può accontentarsi di un danno minore (che può aiutare a ottenere un'esecuzione arbitraria). Qualsiasi comando in grado di scrivere file o modificare autorizzazioni, proprietà o avere effetti principali o collaterali può essere sfruttato.

Tutti i tipi di cose possono essere fatte con nomi di file.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

E ..finisci per scrivere (ricorsivamente con GNU chmod).

Gli script che eseguono l'elaborazione automatica dei file in aree pubblicamente scrivibili come /tmpdevono essere scritti con molta attenzione.

Che dire [ $# -gt 1 ]

È qualcosa che trovo esasperante. Alcune persone si chiedono se una particolare espansione possa essere problematica per decidere se possono omettere le virgolette.

È come dire. Ehi, sembra che $#non possa essere soggetto all'operatore split + glob, chiediamo alla shell di dividerlo + glob . Oppure Hey, scriviamo un codice errato solo perché è improbabile che il bug venga colpito .

Ora, quanto è improbabile? OK, $#(o $!, $?o qualsiasi sostituzione aritmetica) può contenere solo cifre (o -per alcuni), quindi la parte glob è fuori. Affinché la parte divisa faccia qualcosa, tutto ciò che serve è $IFScontenere cifre (o -).

Con alcune shell, $IFSpuò essere ereditato dall'ambiente, ma se l'ambiente non è sicuro, è comunque un gioco finito.

Ora se scrivi una funzione come:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

Ciò significa che il comportamento della tua funzione dipende dal contesto in cui viene chiamato. O in altre parole, $IFS diventa uno degli input ad esso. A rigor di termini, quando scrivi la documentazione API per la tua funzione, dovrebbe essere qualcosa del tipo:

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

E il codice che chiama la tua funzione deve assicurarsi $IFSche non contenga cifre. Tutto questo perché non ti andava di scrivere quei 2 caratteri a virgoletta doppia.

Ora, affinché quel [ $# -eq 2 ]bug diventi una vulnerabilità, avresti bisogno in qualche modo che il valore di $IFSdiventare sotto il controllo dell'attaccante . Concepibilmente, ciò non accadrebbe normalmente se l'attaccante non fosse riuscito a sfruttare un altro bug.

Questo non è inaudito però. Un caso comune è quando le persone dimenticano di disinfettare i dati prima di usarli nell'espressione aritmetica. Abbiamo già visto in precedenza che può consentire l'esecuzione di codice arbitrario in alcune shell, ma in tutte, consente all'attaccante di assegnare a qualsiasi variabile un valore intero.

Per esempio:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

E con un $1valore with (IFS=-1234567890), quella valutazione aritmetica ha l'effetto collaterale delle impostazioni IFS e il [ comando successivo fallisce, il che significa che il controllo per troppi argomenti viene ignorato.

Che dire quando l' operatore split + glob non viene richiamato?

C'è un altro caso in cui sono necessarie le virgolette intorno alle variabili e ad altre espansioni: quando viene utilizzato come modello.

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

non verificare se $ae $bsono uguali (tranne con zsh) ma se $acorrisponde al modello in $b. E devi citare $bse vuoi confrontare come stringhe (la stessa cosa in "${a#$b}"o "${a%$b}"o "${a##*$b*}"dove $bdovrebbe essere quotata se non deve essere presa come un modello).

Ciò significa che [[ $a = $b ]]può tornare vero nei casi in cui $aè diverso da $b(ad esempio quando $aè anythinge $bè *) o può restituire falso quando sono identici (ad esempio quando entrambi $ae lo $bsono [a]).

Ciò può costituire una vulnerabilità della sicurezza? Sì, come qualsiasi bug. Qui, l'attaccante può modificare il flusso di codice logico del tuo script e / o infrangere le ipotesi che il tuo script sta facendo. Ad esempio, con un codice come:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

L'attaccante può bypassare il controllo passando '[a]' '[a]'.

Ora, se non si applicano né la corrispondenza dei pattern né l' operatore split + glob , qual è il pericolo di lasciare una variabile non quotata?

Devo ammettere che scrivo:

a=$b
case $a in...

Lì, la citazione non fa male ma non è strettamente necessaria.

Tuttavia, un effetto collaterale dell'omissione di virgolette in quei casi (ad esempio nelle risposte a domande e risposte) è che può inviare un messaggio sbagliato ai principianti: che può essere giusto non citare le variabili .

Ad esempio, potrebbero iniziare a pensare che, se a=$bè OK, lo export a=$bsarebbe anche (che non è presente in molte shell in quanto è presente negli argomenti del exportcomando, quindi nel contesto dell'elenco) o env a=$b.

Che dire zsh?

zshrisolto la maggior parte di quei disagi del design. In zsh(almeno quando non si è in modalità di emulazione sh / ksh), se si desidera dividere , globbing o pattern matching , è necessario richiederlo esplicitamente: $=varper dividere e $~varglob o per far sì che il contenuto della variabile sia trattato come uno schema.

Tuttavia, la suddivisione (ma non il globbing) è ancora implicita in caso di sostituzione di comando non quotata (come in echo $(cmd)).

Inoltre, un effetto collaterale a volte indesiderato di non quotare la variabile è la rimozione di vuoti . Il zshcomportamento è simile a quello che puoi ottenere in altre shell disabilitando del tutto il globbing (con set -f) e la divisione (con IFS=''). Ancora dentro:

cmd $var

Non ci sarà split + glob , ma se $varè vuoto, invece di ricevere un argomento vuoto, cmdnon riceverà alcun argomento.

Ciò può causare bug (come l'ovvio [ -n $var ]). Questo può eventualmente infrangere le aspettative e le ipotesi di uno script e causare vulnerabilità, ma non posso trovare un esempio non troppo inverosimile proprio ora).

Che dire quando si fa bisogno della spaccatura + glob operatore?

Sì, in genere è quando si desidera lasciare la variabile non quotata. Ma poi devi assicurarti di sintonizzare correttamente gli operatori split e glob prima di usarlo. Se si desidera solo la parte divisa e non la parte glob (che è il caso per la maggior parte del tempo), è necessario disabilitare il globbing ( set -o noglob/ set -f) e correggere $IFS. Altrimenti causerai anche vulnerabilità (come l'esempio CGI di David Korn menzionato sopra).

Conclusione

In breve, lasciare una variabile (o la sostituzione del comando o l'espansione aritmetica) non quotata nelle shell può essere molto pericolosa, specialmente se eseguita in contesti sbagliati, ed è molto difficile sapere quali siano quei contesti sbagliati.

Questo è uno dei motivi per cui è considerata una cattiva pratica .

Grazie per aver letto finora. Se ti passa per la testa, non preoccuparti. Non ci si può aspettare che tutti comprendano tutte le implicazioni della scrittura del proprio codice nel modo in cui lo scrivono. Ecco perché abbiamo raccomandazioni sulle buone pratiche , quindi possono essere seguite senza necessariamente capire il perché.

(e nel caso non fosse ancora ovvio, si prega di evitare di scrivere codice sensibile alla sicurezza nelle shell).

E per favore cita le tue variabili nelle risposte su questo sito!


¹In ksh93e pdkshe derivati, viene eseguita anche l' espansione del controvento a meno che il globbing sia disabilitato (nel caso di ksh93versioni fino a ksh93u +, anche quando l' braceexpandopzione è disabilitata).


Si noti che, con [[, solo l'RHS dei confronti deve essere citato:if [[ $1 = "$2" ]]; then
mirabilos,

2
@mirabilos, sì, ma la LHS bisogni non non da citare, quindi non c'è alcun motivo valido per non citare lì (se vogliamo prendere la decisione consapevole di citare per default come sembra essere la cosa più sensata da fare ). Si noti inoltre che [[ $* = "$var" ]]non è lo stesso [[ "$*" = "$var" ]]se il primo carattere di $IFSnon è spazio con bash(e anche mkshse $IFSè vuoto anche se in quel caso non sono sicuro di cosa $*sia uguale, dovrei sollevare un bug?)).
Stéphane Chazelas,

1
Sì, puoi citare di default lì. Per favore, non più bug sulla suddivisione dei campi in questo momento, devo ancora correggere quelli che conosco (da te e da altri) prima di poterlo rivalutare.
mirabilos,

2
@Barmar, supponendo che volessi dire foo='bar; rm *', no non lo farà, elencherà comunque il contenuto della directory corrente che può essere considerata una divulgazione di informazioni. print $fooa ksh93(dove printè il sostituto di echoche affronta alcuni dei suoi difetti) ha una vulnerabilità di iniezione di codice anche se (per esempio con foo='-f%.0d z[0$(uname>&2)]') (che effettivamente bisogno print -r -- "$foo". echo "$foo"è ancora sbagliata e non risolvibile (anche se in genere meno dannoso)).
Stéphane Chazelas,

3
Non sono un esperto di bash, ma ci sto scrivendo da oltre un decennio. Ho usato molto le virgolette, ma principalmente per gestire gli spazi bianchi incorporati. Ora li userò molto di più! Sarebbe bello se qualcuno si espandesse su questa risposta per rendere un po 'più facile assorbire tutti i punti fini. Ne ho avuto molto, ma mi mancava anche molto. È già un lungo post, ma so che c'è molto di più qui da imparare. Grazie!
Joe,

34

[Ispirato da questa risposta del cas .]

E se ...

Ma cosa succede se il mio script imposta una variabile su un valore noto prima di usarlo? In particolare, cosa succede se imposta una variabile su uno dei due o più valori possibili (ma la imposta sempre su qualcosa di noto) e nessuno dei valori contiene spazi o caratteri glob? Non è sicuro usarlo senza virgolette in quel caso ?

E se uno dei possibili valori è la stringa vuota, e io dipendo da "svuota rimozione"? Vale a dire, se la variabile contiene la stringa vuota, non voglio ottenere la stringa vuota nel mio comando; Non voglio ottenere niente. Per esempio,

se some_condition
poi
    ignorecase = "- i"
altro
    ignorecase = ""
fi
                                        # Notare che le virgolette nei comandi precedenti non sono strettamente necessarie. 
grep $ ignorecase   other_ grep _args

Non posso dire ; che fallirà se è la stringa vuota.grep "$ignorecase" other_grep_args$ignorecase

Risposta:

Come discusso nell'altra risposta, ciò fallirà comunque se IFScontiene un -o un i. Se ti sei assicurato che IFSnon contiene alcun carattere nella tua variabile (e sei sicuro che la tua variabile non contenga alcun carattere glob), allora questo è probabilmente sicuro.

Ma c'è un modo che è più sicuro (anche se è un po 'brutto e abbastanza non intuitivo): usare ${ignorecase:+"$ignorecase"}. Dalle specifiche del linguaggio dei comandi della shell POSIX , in  2.6.2 Espansione dei parametri ,

${parameter:+[word]}

    Usa valore alternativo.   Se parameternon è impostato o è nullo, è necessario sostituire null; in caso contrario, verrà sostituita l'espansione di word (o una stringa vuota se wordomessa).

Il trucco qui, come è, è che stiamo usando ignorecasecome parameter e "$ignorecase"come word. Quindi ${ignorecase:+"$ignorecase"}significa

Se $ignorecaseè unset o null (cioè, vuoto), deve essere sostituito null (ovvero, niente tra virgolette ); in caso contrario, "$ignorecase"verrà sostituita l'espansione di .

Questo ci porta dove vogliamo andare: se la variabile è impostata su una stringa vuota, verrà “rimossa” (questa intera espressione contorta non valuterà nulla - nemmeno una stringa vuota), e se la variabile ha un non -vuoto, otteniamo quel valore, quotato.


E se ...

Ma cosa succede se ho una variabile che voglio / ho bisogno di essere divisa in parole? (Questo è altrimenti come il primo caso; il mio script ha impostato la variabile e sono sicuro che non contenga alcun carattere glob. Ma potrebbe contenere spazio / i, e lo voglio diviso in argomenti separati nello spazio confini.
PS Voglio ancora la rimozione dei vuoti.)

Per esempio,

se some_condition
poi
    criteri = "- tipo f"
altro
    Criteri = ""
fi
se some_other_condition
poi
    criteri = "$ criteri -mtime +42"
fi
trova "$ directory_inizio" $ criteri   altro_ trova _args

Risposta:

Potresti pensare che questo sia un caso d'uso eval.  No!   Resisti alla tentazione persino di pensare di usare evalqui.

Ancora una volta, se ti sei assicurato che IFSnon contiene alcun carattere nella tua variabile (eccetto per gli spazi, che vuoi essere onorato), e sei sicuro che la tua variabile non contenga alcun carattere glob, allora quanto sopra è probabilmente sicuro.

Ma, se stai usando bash (o ksh, zsh o yash), c'è un modo che è più sicuro: usa un array:

se some_condition
poi
    criterio = (- tipo f) # Potresti dire `criteri = (" - tipo "" f ")`, ma è davvero superfluo.
altro
    criteri = () # Non usare virgolette su questo comando!
fi
se some_other_condition
poi
    criteri + = (- mtime +42) # Nota: non `=`, ma ` + =`, per aggiungere (aggiungere) a un array.
fi
trova "$ directory_inizio" "$ {criteri [@]}"   altro_ trova _args

Da bash (1) ,

È possibile fare riferimento a qualsiasi elemento di un array usando . ... Se è o  , la parola si espande a tutti i membri di . Questi indici differiscono solo quando la parola appare tra virgolette. Se la parola è racchiusa tra virgolette, ... espande ogni elemento in una parola separata.${name[subscript]}subscript@*name${name[@]}name

Quindi si "${criteria[@]}"espande (nell'esempio sopra) lo zero, due o quattro elementi criteriadell'array, ciascuno citato. In particolare, se nessuna delle condizioni  è vera, l' criteriaarray non ha contenuto (come impostato criteria=()dall'istruzione) e non "${criteria[@]}"valuta nulla (nemmeno una scomoda stringa vuota).


Ciò diventa particolarmente interessante e complicato quando hai a che fare con più parole, alcune delle quali sono input dinamici (utente), che non conosci in anticipo e che possono contenere spazi o altri caratteri speciali. Tener conto di:

printf "Inserisci il nome del file da cercare:"
leggi fname
if ["$ fname"! = ""]
poi
    criteri + = (- - nome "$ fname")
fi

Si noti che $fnameviene citato ogni volta che viene utilizzato. Funziona anche se l'utente inserisce qualcosa di simile foo baro foo*"${criteria[@]}"valuta -name "foo bar"o -name "foo*". (Ricorda che ogni elemento dell'array è citato.)

Le matrici non funzionano in tutte le shell POSIX; le matrici sono un ksh / bash / zsh / yash-ism. Tranne ... c'è un array supportato da tutte le shell: la lista degli argomenti, aka "$@". Se hai finito con l'elenco degli argomenti con cui sei stato invocato (ad esempio, hai copiato tutti i "parametri posizionali" (argomenti) in variabili o altrimenti li hai elaborati), puoi utilizzare l'elenco arg come array:

se some_condition
poi
    set - -type f # Potresti dire `set -" -type "" f "`, ma è davvero superfluo.
altro
    impostato --
fi
se some_other_condition
poi
    set - "$ @" -mtime +42
fi
# Allo stesso modo: set - "$ @" -name "$ fname"
trova "$ directory_inizio" "$ @"   altro_ trova _args

Il "$@"costrutto (che, storicamente, è venuto per primo) ha la stessa semantica di - espande ogni argomento (cioè ogni elemento dell'elenco degli argomenti) in una parola separata, come se si fosse digitato ."${name[@]}""$1" "$2" "$3" …

Estratto dalle specifiche del linguaggio dei comandi della shell POSIX , in 2.5.2 Parametri speciali ,

@

    Si espande nei parametri posizionali, a partire da uno, producendo inizialmente un campo per ciascun parametro posizionale impostato. ..., i campi iniziali devono essere mantenuti come campi separati, ... Se non ci sono parametri posizionali, l'espansione di @deve generare zero campi, anche quando @è racchiuso tra virgolette doppie; ...

Il testo completo è in qualche modo enigmatico; il punto chiave è che specifica che "$@"deve generare zero campi quando non ci sono parametri posizionali. Nota storica: quando "$@"fu introdotto per la prima volta nella shell Bourne (predecessore di bash) nel 1979, aveva un bug che "$@"veniva sostituito da una singola stringa vuota quando non c'erano parametri posizionali; vedi Cosa ${1+"$@"}significa in uno script di shell e in cosa differisce "$@"? La tradizionale famiglia Bourne ShellCosa ${1+"$@"}significa ... e dove è necessario? e "$@"contro${1+"$@"} .


Gli array aiutano anche nella prima situazione:

se some_condition
poi
    ignorecase = (- i) # Potresti dire `ignorecase = (" - i ")`, ma è davvero superfluo.
altro
    ignorecase = () # Non usare virgolette su questo comando!
fi
grep "$ {ignorecase [@]}"   other_ grep _args

____________________

PS (csh)

Questo dovrebbe essere ovvio, ma, a beneficio delle persone che sono nuove qui: csh, tcsh, ecc., Non sono shell Bourne / POSIX. Sono una famiglia completamente diversa. Un cavallo di colore diverso. Un altro gioco con la palla. Una razza diversa di gatto. Uccelli di un'altra piuma. E, in particolare, una diversa lattina di vermi.

Parte di quanto detto in questa pagina si applica a csh; come: è una buona idea citare tutte le variabili a meno che tu non abbia una buona ragione per non farlo e sei sicuro di sapere cosa stai facendo. Ma, in csh, ogni variabile è un array - succede che quasi ogni variabile è un array di un solo elemento e agisce in modo abbastanza simile a una normale variabile di shell nelle shell Bourne / POSIX. E la sintassi è terribilmente diversa (e intendo terribilmente ). Quindi non diremo altro sulle shell della famiglia csh qui.


1
Si noti che in csh, si preferisce utilizzare piuttosto $var:qche "$var"quest'ultimo non funziona per le variabili che contengono caratteri di nuova riga (o se si desidera citare gli elementi dell'array singolarmente, anziché unirli con spazi in un singolo argomento).
Stéphane Chazelas,

In bash, bitrate="--bitrate 320"funziona con ${bitrate:+$bitrate}e bitrate=--bitrate 128non funziona ${bitrate:+"$bitrate"}perché interrompe il comando. È sicuro da usare ${variable:+$variable}senza "?
Freedo il

@Freedo: trovo poco chiaro il tuo commento. Non sono particolarmente chiaro cosa vuoi sapere che non ho già detto. Vedi la seconda voce "Ma cosa succede se" - "Ma cosa succede se ho una variabile che voglio / ho bisogno di essere divisa in parole?" Questa è la tua situazione e dovresti seguire i consigli lì. Ma se non puoi (ad es. Perché stai usando una shell diversa da bash, ksh, zsh o yash e stai usando  $@per qualcos'altro), o semplicemente rifiuti di usare un array per altri motivi, mi riferisco a "Come discusso nell'altra risposta". ... (proseguendo)
G-Man il

(Segue) ... Il tuo suggerimento (usando ${variable:+$variable}senza ") fallirà se IFS contiene  -,  0, 2, 3, a, b, e, i, ro  t.
G-Man,

Beh, la mia variabile ha entrambi i caratteri a, e, b, i 2 e 3 e funziona ancora bene con${bitrate:+$bitrate}
Freedo

11

Ero scettico sulla risposta di Stéphane, tuttavia è possibile abusare di $#:

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

o $ ?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

Questi sono esempi inventati, ma il potenziale esiste.

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.