Le assegnazioni di variabili influiscono sulla shell corrente in esecuzione


8

Durante la scrittura del codice ho scoperto che questa riga:

$ TZ="America/Los_Angeles"       date; echo "$TZ"
Thu Dec 24 14:39:15 PST 2015

Indica correttamente l'ora effettiva in "Los Angeles" e che il valore della variabile TZnon viene mantenuto. Tutto come dovrebbe essere previsto.

Tuttavia, con questa linea, che ho usato per espandere alcuni formati fino ad oggi, e che essenzialmente esegue la stessa cosa, mantiene il valore di TZ:

TZ="America/Los_Angeles" eval  date; echo "$TZ"
Thu Dec 24 14:41:34 PST 2015
America/Los_Angeles

Dopo diversi altri test, ho scoperto che ciò accade solo in alcune shell. Succede in trattino, ksh ma non in bash o zsh.

Q

Le domande sono:

  • Perché il valore di TZ viene mantenuto nella presente shell?
  • Come potrebbe essere evitato / controllato (se possibile)?

Ulteriori.

Ho eseguito test in diverse shell con queste due righe:

myTZ="America/Los_Angeles"
unset TZ; { TZ="$myTZ"      date; } >/dev/null; echo -n "  direct $TZ"
unset TZ; { TZ="$myTZ" eval date; } >/dev/null; echo    "  evaled $TZ"

E questo risulta:

/bin/ash        :   direct   evaled America/Los_Angeles
/bin/dash       :   direct   evaled America/Los_Angeles
/bin/sh         :   direct   evaled America/Los_Angeles
/bin/bash       :   direct   evaled
/bin/ksh93      :   direct   evaled America/Los_Angeles
/bin/lksh       :   direct   evaled America/Los_Angeles
/bin/mksh       :   direct   evaled America/Los_Angeles
/bin/zsh        :   direct   evaled
/bin/zsh4       :   direct   evaled 

Il valore TZ influenza la shell in esecuzione in tutte le shell tranne bash e zsh.

Risposte:


6

Come hai scoperto, questo è un comportamento specifico. Ma ha anche senso.

Il valore viene mantenuto nell'ambiente della shell per lo stesso motivo per cui il valore di altre variabili di ambiente viene mantenuto da altri comandi quando si antepongono le definizioni alle loro linee di comando: si stanno impostando le variabili nel loro ambiente.

Gli builtin speciali sono generalmente la varietà più intrinseca di qualsiasi shell - evalè essenzialmente un nome accessibile per il parser della shell, settiene traccia e configura le opzioni della shell e i parametri della shell, return/ break/ continueattiva il flusso di controllo del loop, trapgestisce i segnali, execapre / chiude i file. Queste sono tutte utilità fondamentali - e in genere sono implementate con involucri a malapena sulla carne e patate del guscio.

L'esecuzione della maggior parte dei comandi comporta un ambiente a più livelli - un ambiente subshell (che non deve necessariamente essere un processo separato) - che non si ottiene quando si chiamano i builtin speciali. Quindi, quando si imposta l'ambiente per uno di questi comandi, si imposta l'ambiente per la shell. Perché in pratica rappresentano la tua shell.

Ma non sono gli unici comandi che mantengono l'ambiente in quel modo - anche le funzioni fanno lo stesso. E gli errori si comportano diversamente per i built-in speciali: prova cat <doesntexiste poi prova exec <doesntexisto anche solo : <doesntexiste mentre il catcomando si lamenterà, execo :ucciderà una shell POSIX. Lo stesso vale per gli errori di espansione sulla loro riga di comando. Sono il ciclo principale , in sostanza.

Questi comandi non devono conservare l'ambiente: alcune shell avvolgono i loro interni più strettamente di altre, espongono meno delle funzionalità principali e aggiungono più buffer tra il programmatore e l'interfaccia. Questi stessi gusci potrebbero anche essere un po 'più lenti di altri. Sicuramente richiedono molte regolazioni non standard per renderle conformi alle specifiche. E comunque, non è che questa sia una brutta cosa:

fn(){ bad_command || return=$some_value return; }

Quella roba è facile . In quale altro modo conserveresti il ​​ritorno di bad_commandcosì semplicemente senza dover impostare un mucchio di ambiente extra e tuttavia fare i compiti in modo condizionale?

arg=$1 shift; x=$y unset y

Anche quel genere di cose funziona. Gli swap sul posto sono più semplici.

IFS=+  set -- "$IFS" x y z
x="$*" IFS=$1 shift
echo "${x#"$IFS"}" "$*"

+x+y+z x y z

...o...

expand(){
    PS4="$*" set -x "" "$PS4" 
    { $1; }  2>&1
    PS4=$2   set +x
}   2>/dev/null

x='echo kill my computer; $y'
y='haha! just kidding!' expand "${x##*[\`\(]*}"

... è un altro che mi piace usare ...

echo kill my computer; haha! just kidding!

@BinaryZebra - ma il punto è che non funzionano in modo diverso - quando si impostano le variabili per qualche altro comando, persistono nell'ambiente di quell'altro eseguibile. quando si impostano le variabili nell'ambiente della shell, persistono anche.
mikeserv,

3

Si scopre che esiste un motivo molto specifico per questo comportamento.
La descrizione di ciò che accade è un po 'più lunga.

Solo incarichi.

Una riga di comando fatta (solo) di assegnazioni imposterà le variabili per questa shell.

$ unset a b c d
$ a=b c=d
$ echo "<$a::$c>"
<b::d>

Il valore delle variabili assegnate verrà mantenuto.

Comando esterno.

Le assegnazioni prima di un comando esterno impostano le variabili solo per quella shell:

$ unset a b c d
$ a=b c=d bash -c 'echo "one:|$c|"'; echo "two:<$c>"
one:|d|
two:<>

E intendo "esterno" come qualsiasi comando che deve essere cercato in PERCORSO.

Questo vale anche per i normali built-in (come ad esempio cd):

$ unset a b c d; a=b c=d cd . ; echo "<$a::$c>"
<::>

Fino a qui tutto è come normalmente previsto.

Built-in speciali.

Ma per i built-in speciali, POSIX richiede che i valori siano impostati per questa shell .

  1. Le assegnazioni di variabili specificate con speciali utilità integrate rimangono in vigore al termine del completamento.
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>

Sto usando una chiamata per shpresumere che shsia una shell conforme a POSIX.

Questo non è qualcosa che viene solitamente utilizzato.

Ciò significa che gli incarichi posti davanti a qualsiasi di questo elenco di incorporamenti speciali devono conservare i valori assegnati nell'attuale shell corrente:

break : continue . eval exec exit export 
readonly return set shift times trap unset

Questo accadrà se una shell funziona secondo le specifiche POSIX.

Conclusione:

È possibile impostare variabili per un solo comando, qualsiasi comando, assicurandosi che il comando non sia un built-in speciale. Il comando commandè un normale built-in. Dice solo alla shell di usare un comando, non una funzione. Questa linea funziona in tutte le shell (tranne ksh93):

$ unset a b c d; a=b c=d command eval 'f=g'; echo "<$a::$c::$f>"
<::::g>

In tal caso, le variabili aeb sono impostate per l'ambiente del comando comando e successivamente scartate.

Invece, questo manterrà i valori assegnati (tranne bash e zsh):

$ unset a b c d; a=b c=d eval 'f=g'; echo "<$a::$c::$f>"
<b::d::g>

Si noti che l'assegnazione dopo eval è quotata singolarmente per proteggerla da espansioni indesiderate.

Quindi: per posizionare le variabili nell'ambiente di comando utilizzare command eval:

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.