Perché non posso specificare una variabile di ambiente e visualizzarla nella stessa riga di comando?


91

Considera questo frammento:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

Qui ho messo $SOMEVARa AAAsulla prima linea - e quando mi associo sulla seconda linea, mi vengono i AAAcontenuti come previsto.

Ma poi, se provo a specificare la variabile sulla stessa riga di comando di echo:

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

... Non ottengo BBBcome mi aspettavo - ottengo il vecchio valore ( AAA).

È così che dovrebbero essere le cose? Se è così, come LD_PRELOAD=/... program args ...mai puoi specificare variabili come e farlo funzionare? Cosa mi sto perdendo?


2
Funziona quando si rende l'assegnazione un'istruzione separata o quando si richiama uno script con il proprio ambiente, ma non quando si precede un comando nell'ambiente corrente. Interessante!
Todd A. Jacobs

1
La ragione per cui LD_PRELOADfunziona è che la variabile è impostata nel programma ambientale - non sulla sua linea di comando.
In pausa fino a nuovo avviso.

Risposte:


103

Quello che vedi è il comportamento previsto. Il problema è che la shell genitore valuta $SOMEVARsulla riga di comando prima di richiamare il comando con l'ambiente modificato. È necessario ottenere la valutazione del $SOMEVARdifferito fino a quando l'ambiente non è impostato.

Le tue opzioni immediate includono:

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz.
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'.

Entrambi usano virgolette singole per impedire la valutazione della shell genitrice $SOMEVAR; viene valutato solo dopo che è stato impostato nell'ambiente (temporaneamente, per la durata del singolo comando).

Un'altra opzione è usare la notazione sotto-shell (come suggerito anche da Marcus Kuhn nella sua risposta ):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

La variabile è impostata solo nella sottostruttura


Fantastico, @JonathanLeffler - molte grazie per la spiegazione; Saluti!
sdaau

L'aggiunta di @ markus-kuhn è difficile da sopravvalutare.
Alex Che

37

Il problema, rivisitato

Francamente, il manuale è confuso su questo punto. Il manuale GNU Bash dice:

L'ambiente per qualsiasi comando o funzione semplice [nota che questo esclude i comandi incorporati] può essere temporaneamente ampliato aggiungendo un prefisso con assegnazioni di parametri, come descritto in Parametri della shell. Queste istruzioni di assegnazione influenzano solo l'ambiente visto da quel comando.

Se analizzi davvero la frase, ciò che sta dicendo è che l' ambiente per il comando / funzione viene modificato, ma non l'ambiente per il processo genitore. Quindi, questo funzionerà:

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

perché l'ambiente per il comando env è stato modificato prima dell'esecuzione. Tuttavia, questo non funzionerà:

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

a causa di quando l'espansione dei parametri viene eseguita dalla shell.

Fasi dell'interprete

Un'altra parte del problema è che Bash definisce questi passaggi per il suo interprete:

  1. Legge il suo input da un file (vedi Shell Scripts), da una stringa fornita come argomento all'opzione di invocazione -c (vedi Invoking Bash), o dal terminale dell'utente.
  2. Divide l'input in parole e operatori, obbedendo alle regole di citazione descritte in Quoting. Questi token sono separati da metacaratteri. L'espansione degli alias viene eseguita in questo passaggio (vedere Alias).
  3. Analizza i token in comandi semplici e composti (vedere Comandi della shell).
  4. Esegue le varie espansioni della shell (vedi Espansioni della shell), suddividendo i token espansi in elenchi di nomi di file (vedi Espansione del nome di file) e comandi e argomenti.
  5. Esegue tutti i reindirizzamenti necessari (vedere Reindirizzamenti) e rimuove gli operatori di reindirizzamento ei loro operandi dall'elenco degli argomenti.
  6. Esegue il comando (vedere Esecuzione di comandi).
  7. Facoltativamente, attende il completamento del comando e raccoglie il suo stato di uscita (vedere Stato di uscita).

Quello che sta succedendo qui è che i builtin non hanno un proprio ambiente di esecuzione, quindi non vedono mai l'ambiente modificato. Inoltre, i comandi semplici (es / bin / echo) do ottenere un ennvironment modificato (che è il motivo per cui l'esempio env lavorate) ma l'espansione shell si svolge nel corrente ambiente al punto # 4.

In altre parole, non stai passando "aaa $ TESTVAR ccc" a / bin / echo; stai passando la stringa interpolata (come espansa nell'ambiente corrente) a / bin / echo. In questo caso, poiché l'ambiente corrente non ha TESTVAR , stai semplicemente passando "aaa ccc" al comando.

Sommario

La documentazione potrebbe essere molto più chiara. Meno male che c'è Stack Overflow!

Guarda anche

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment


Avevo già votato positivamente, ma sono appena tornato a questa domanda e questo post contiene esattamente i suggerimenti di cui ho bisogno; molte grazie, @CodeGnome!
sdaau

Non so se Bash sia cambiato in quest'area da quando questa risposta è stata pubblicata, ma le assegnazioni di variabili prefissate ora funzionano con i builtin. Ad esempio, FOO=foo eval 'echo $FOO'stampa foocome previsto. Ciò significa che puoi fare cose come IFS="..." read ....
Will Vousden

Penso che quello che sta succedendo sia che Bash in realtà modifica temporaneamente il proprio ambiente e lo ripristina una volta completato il comando, il che può avere strani effetti collaterali.
Will Vousden

22

Per ottenere ciò che desideri, usa

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

Motivo:

  • È necessario separare l'assegnazione con punto e virgola o una nuova riga dal comando successivo, altrimenti non viene eseguita prima che si verifichi l' espansione dei parametri per il comando successivo (echo).

  • È necessario eseguire l'assegnazione all'interno di un ambiente subshell , per assicurarsi che non persista oltre la riga corrente.

Questa soluzione è più breve, più ordinata e più efficiente di alcune delle altre suggerite, in particolare non crea un nuovo processo.


3
Per i futuri googler che finiscono qui: questa è probabilmente la migliore risposta a questa domanda. Per complicarlo ulteriormente, se è necessario che l'assegnazione sia disponibile nell'ambiente del comando, è necessario esportarlo. La subshell impedisce ancora che l'assegnazione persista. (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')")
eaj

@eaj Per esportare una variabile di shell in una singola chiamata di programma esterno, come nel tuo esempio, usa semplicementeSOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"
Markus Kuhn

10

Il motivo è che questo imposta una variabile d'ambiente per una riga. Ma echonon fa l'espansione, lo bashfa. Quindi, la tua variabile viene effettivamente espansa prima che il comando venga eseguito, anche se SOME_VARè BBBnel contesto del comando echo.

Per vedere l'effetto, puoi fare qualcosa come:

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

Qui la variabile non viene espansa finché il processo figlio non viene eseguito, quindi viene visualizzato il valore aggiornato. se controlli di SOME_VARIABLEnuovo nella shell genitore, è ancora AAA, come previsto.


1
+1 per la corretta spiegazione del motivo per cui non funziona come scritto e per una soluzione alternativa possibile.
Jonathan Leffler

1
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

Usare un ; per separare le istruzioni che si trovano sulla stessa riga.


1
Funziona, ma non è proprio questo il punto. L'idea è di impostare l'ambiente per un solo comando, non in modo permanente come fa la tua soluzione.
Jonathan Leffler

Grazie per questo @Kyros; non so come mai mi sia mancato ormai :) Continuo a vagare come LD_PRELOADe cose simili possono funzionare davanti a un eseguibile senza punto e virgola, però ... Molte grazie ancora - ciao!
sdaau

@JonathanLeffler - in effetti, questa era l'idea; Non mi rendevo conto che il punto e virgola rende la modifica permanente, grazie per averlo notato!
sdaau

1

Ecco un'alternativa:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz

Sia che si utilizzino &&o ;per separare i comandi, l'assegnazione persiste, il che non è il comportamento desiderato da OP. Markus Kuhn ha la versione corretta di questa risposta.
eaj
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.