Perché l'impostazione di una variabile prima di un comando è legale in bash?


68

Ho appena incontrato diverse risposte come l' analisi di un file di testo delimitato ... che usano il costrutto:

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

dove la IFSvariabile è impostata prima del readcomando.

Ho letto il riferimento bash ma non riesco a capire perché questo è legale.

Provai

$ x="once upon" y="a time" echo $x $y

dal prompt dei comandi di bash ma non è stato fatto eco. Qualcuno può indicarmi dove è definita quella sintassi nel riferimento che consente di impostare la variabile IFS in quel modo? È un caso speciale o posso fare qualcosa di simile con altre variabili?


Risposte:


69

È sotto Shell Grammar, Simple Commands (enfasi aggiunta):

Un semplice comando è una sequenza di assegnazioni di variabili opzionali seguita da parole e reindirizzamenti separati da spazi vuoti e terminata da un operatore di controllo. La prima parola specifica il comando da eseguire e viene passata come argomento zero. Le parole rimanenti vengono passate come argomenti al comando invocato.

Quindi puoi passare qualsiasi variabile desideri. Il tuo echoesempio non funziona perché le variabili vengono passate al comando, non impostate nella shell. La shell si espande $xe $y prima di invocare il comando. Funziona, ad esempio:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time

Grazie! Ho fatto molte ricerche su Google prima di chiedere, e stavo cercando di capire dove dicesse che nel riferimento, senza fortuna. Sto cercando di migliorare con la scrittura di script bash e la tua risposta aiuta.
Mike Lippert,

1
Hmm penso di aver bisogno di trovare un riferimento migliore, il mio (vedi link sopra) non dice che non ha una sezione Shell Grammar.
Mike Lippert,

1
@MikeLippert Vedi 3.7.4 in quel riferimento ("L'ambiente per qualsiasi comando o funzione semplice può essere temporaneamente aumentato aggiungendolo con assegnazioni di parametri"). Penso che il riferimento provenga da una versione precedente di bash. Ho appena eseguito man bashil mio sistema ...
derobert,

29

Le variabili definite diventano come variabili di ambiente nel processo biforcato.

Se corri

A="b" echo $A

quindi bash si espande prima $Ain ""e poi viene eseguito

A="b" echo

Ecco il modo corretto:

x="once upon" y="a time" bash -c 'echo $x $y'

Nota le virgolette singole bash -c, altrimenti hai lo stesso problema di cui sopra.

Quindi il tuo esempio di loop è legale perché il comando bash incorporato 'read' cercherà IFS nelle sue variabili di ambiente e troverà ,. Perciò,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

stamperà TEST is and I is test

Infine, come per la sintassi, in un ciclo for è prevista una stringa. Pertanto ho dovuto usare i backtick per trasformarlo in un comando. Tuttavia, mentre i loop prevedono una sintassi dei comandi, ad esempio IFS=, read xx yy zz.


1
Grazie, ho imparato un po 'più di quanto ho chiesto dalla tua risposta. Vorrei votare anche la tua risposta, ma non sono ancora autorizzato e ho contrassegnato la risposta accettata sulla prima.
Mike Lippert,

1
Grazie, è quello che speravo. E un voto in su è meno significativo di sentire il tuo apprezzamento!
Mike Fairhurst,

Per chiarire il tuo commento sotto la prima riga di codice: Sì, con la Avariabile non impostata bash si espande $Ain una stringa vuota ma per evitare confusione non userei ""perché il codice non è equivalente a A="b" echo "". Non ci saranno argomenti per echo.
pabouk,

8

man bash

AMBIENTE

[...] L'ambiente per qualsiasi comando o funzione semplice può essere temporaneamente incrementato prefissandolo con assegnazioni di parametri, come descritto sopra in PARAMETRI. Queste istruzioni di assegnazione influiscono solo sull'ambiente visto da quel comando.

Le variabili vengono espanse prima dell'assegnazione delle variabili. Per l'ovvia ragione che var=xavrebbe funzionato anche nell'altro modo, ma var=$othervarnon avrebbe funzionato. Cioè il tuo $xè necessario prima che sia disponibile. Ma questo non è il problema principale. Il problema principale è che la riga di comando può essere modificata solo dall'ambiente shell ma l'assegnazione non diventa parte dell'ambiente shell.

Si confondono con le funzionalità: si desidera una sostituzione della riga di comando ma inserire la definizione della variabile nell'ambiente dei comandi. Le sostituzioni della riga di comando devono essere effettuate dalla shell. L'ambiente deve essere esplicitamente utilizzato dal comando chiamato. Se e come ciò dipende dal comando.

Il vantaggio di questo utilizzo è che è possibile impostare l'ambiente per un sottoprocesso senza influire sull'ambiente shell.

x="once upon" y="a time" bash -c 'echo $x $y'

funziona come previsto perché in tal caso entrambe le funzionalità sono combinate: la sostituzione della riga di comando non viene eseguita dalla shell chiamante ma dalla shell del sottoprocesso.


1
È un po 'più sottile di così, perché l'esempio funziona anche x="once upon" y="a time" eval 'echo $x $y'quando non sono coinvolti sottoprocessi poiché evalè incorporato. Immagino che la citazione pertinente dalla manpage sia The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments. Considerando l'esempio della domanda, deve essere così poiché readè anche incorporato e funziona con uno stato temporalmente modificato di IFS.
David Ongaro,

4

Il comando fornito è diverso perché $xe $yviene espanso prima dell'esecuzione del echocomando, quindi vengono utilizzati i loro valori nella shell corrente , non i valori che echoverrebbero visualizzati nel suo ambiente se dovesse apparire.


Come si può espandere qualsiasi cosa nella riga di comando dopo l'esecuzione del comando ...?
Hauke ​​Laging,

Le assegnazioni di pre-comando per xe ysono per l'ambiente che echoviene eseguito in, non è l'ambiente in cui gli argomenti echovengono espansi. Per IFS=, read xx yy zz, l'intera stringa viene letta, non divisa, dal readcomando. Poi , che stringa viene divisa in base al valore di IFS, con i corrispondenti pezzi assegnati a xx, yye zz.
Chepner,

Volevo solo sottolineare che l'espressione "espanso prima dell'esecuzione del comando" non ha molto senso perché dopo l'inizio del comando nulla è più espanso. Inoltre: non hai dato una sola occhiata alla mia risposta o credi comunque che ho bisogno di una spiegazione di ciò che sta accadendo ...?
Hauke ​​Laging,

1
Non ho affermato che hai bisogno di una spiegazione né che nulla può essere espanso dopo l'esecuzione del comando. Tuttavia, è vero che bashprima analizza la riga di comando indicata, riconosce che ci sono due assegnazioni variabili da applicare all'ambiente del comando in arrivo, identifica il comando da eseguire ( echo), espande tutti i parametri trovati negli argomenti, quindi esegue il comando echocon gli argomenti espansi.
Chepner,

Nel caso di echonon ero sicuro che potesse "vedere" la variabile, poiché è un comando incorporato e quindi non può essere eseguito in una subshell che potrebbe avere il suo ambiente. Ma l'ho provato con il evalquale è anche incorporato e in effetti lo sa. Ad esempio, prova a=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $ache mostra che aè impostato solo all'interno evalanche se il pid è lo stesso e l'ambiente non viene modificato durante l'eval! (Per accedervi /procdevi eseguirlo sotto Linux.) Sembra che bash faccia un po 'di magia in più qui.
David Ongaro,

2

Vado per il quadro più ampio di " perché è legale"

Risposta: In modo da poter chiamare o invocare un programma e per quella chiamata usare solo una variabile con una particolare variabile.

Ad esempio: hai un parametro per una connessione al database chiamato 'db_connection' e normalmente passi 'test' come nome per la tua connessione al database di prova. In effetti, potresti persino impostarlo come predefinito per cui non è necessario passare esplicitamente. A volte, tuttavia, si desidera lavorare con il database ci. Quindi passate il parametro come 'ci' e quindi il programma chiamato usa quel parametro del database come nome del db da usare per tutte le chiamate al database. Per l'esecuzione successiva, se non si ripete l'approccio e si chiama semplicemente il programma, la variabile tornerà al suo valore predefinito precedente.


0

Puoi anche usare ;. Sarà valutato prima perché è il comando di separazione.

x="once upon" y="a time"; echo $x $y

1
Questo crea due variabili shell e non crea variabili d'ambiente nell'utilità data. Questa è una cosa molto diversa. C'è anche l'effetto collaterale che la shell corrente ora contiene nuove variabili.
Kusalananda
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.