Doppia e tripla sostituzione in bash e zsh


23

Seguito dato alla parte di fondo di questa domanda .

In bashposso usare ${!FOO}per la doppia sostituzione, in zsh ${(P)FOO}. In entrambi, la vecchia scuola (hack-y) eval \$$FOOfunziona.

Quindi, la cosa più intelligente e più logica per me sarebbe la ${${FOO}}, ${${${FOO}}}…sostituzione doppia / tripla / n. Perché questo non funziona come previsto?

Secondo: cosa fa \la evaldichiarazione? Credo che sia una via di fuga, rendendo qualcosa di eval \$$$FOOimpossibile. Come fare una sostituzione tripla / n con quella che funziona in ogni shell?

Risposte:


15

Il \deve essere utilizzato per impedire l'espansione $$(corrente ID processo). Per la tripla sostituzione, è necessario doppio eval, quindi anche più fughe per evitare le espansioni indesiderate in ogni eval:

#! /bin/bash
l0=value
l1=l0
l2=l1
l3=l2
l4=l3

echo $l0
eval echo \$$l1
eval eval echo \\$\$$l2
eval eval eval echo \\\\$\\$\$$l3
eval eval eval eval echo \\\\\\\\$\\\\$\\$\$$l4

Eppure: l3=l2; eval eval eval echo \\\$\\$\$$l353294quindi non esattamente modulare.
Profpatsch,

2
Perché non posso fare qualcosa del genere eval $(eval echo \$$l2)? Odio bash, non ha assolutamente senso.
Profpatsch,

@Profpatsch: aggiunti altri esempi.
Choroba,

Ah, è n². Non voglio nemmeno provare a capire perché è così.
Profpatsch,

2
@Profpatsch: facile. In ogni passaggio, il numero di barre rovesciate si dimezza (quindi è 2ⁿ, in effetti).
Choroba,


6

Supponendo che il valore di FOOsia un nome di variabile valido (diciamo BAR), eval \$$FOOdivide il valore di BARin parole separate, tratta ogni parola come un modello jolly ed esegue la prima parola del risultato come comando, passando le altre parole come argomenti. La barra rovesciata di fronte al dollaro lo rende trattato letteralmente, quindi l'argomento passato all'integrato evalè la stringa di quattro caratteri $BAR.

${${FOO}}è un errore di sintassi. Non fa una "doppia sostituzione" perché non c'è una tale caratteristica in nessuna delle shell comuni (non con questa sintassi comunque). In zsh, ${${FOO}} è valido ed è una doppia sostituzione, ma si comporta in modo diverso da quello che desideri: esegue due trasformazioni successive sul valore di FOO, entrambe le quali sono la trasformazione dell'identità, quindi è solo un modo elegante di scrivere ${FOO}.

Se vuoi trattare il valore di una variabile come una variabile, fai attenzione a citare le cose correttamente. È molto più semplice se imposti il ​​risultato su una variabile:

eval "value=\${$FOO}"

1
Impostandolo come variabile, in modo che la prima parola non venga utilizzata come comando? Amico, questo tipo di logica è difficile da capire. È il problema generale che sembra avere con bash.
Profpatsch,

4

{ba,z}sh soluzione

Ecco una funzione che funziona in entrambi {ba,z}sh. Credo che sia anche conforme a POSIX.

Senza impazzire con il preventivo, puoi usarlo per molti livelli di indiretta in questo modo:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

O se hai più (!?!) Livelli di indiretta potresti usare un loop.

Avvisa quando viene dato:

  • Input nullo
  • Un nome di variabile che non è impostato

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
var_expand() {
  if [ -z "${1-}" ] || [ $# -ne 1 ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

Una soluzione cross-shell funzionante per me. Si spera che "espansione doppia variabile compatibile POSIX" reindirizzi a questa risposta in futuro.
BlueDrink9

2

Proprio come ${$foo}non funziona al posto di ${(P)foo}in zsh, nemmeno ${${$foo}}. Devi solo specificare ogni livello di riferimento indiretto:

$ foo=bar
$ bar=baz
$ baz=3
$ echo $foo
bar
$ echo ${(P)foo}
baz
$ echo ${(P)${(P)foo}}
3

Naturalmente, ${!${!foo}}non funziona bash, perché bashnon consente sostituzioni nidificate.


Grazie, buono a sapersi. Un vero peccato che questo sia solo zsh, quindi non puoi davvero inserirlo in uno script (tutti hanno bash, ma non è il contrario).
Profpatsch,

Sospetto che zshsia installato molto più spesso di quanto tu possa supporre. Potrebbe non essere collegato shcome bashspesso (ma non sempre), ma potrebbe essere ancora disponibile per gli script di shell. Pensaci come fai Perl o Python.
chepner,

2

Perché dovresti farlo?

Puoi sempre farlo in diversi passaggi come:

eval "l1=\${$var}"
eval "l2=\${$l1}"
...

O usa una funzione come:

deref() {
  if [ "$1" -le 0 ]; then
    eval "$3=\$2"
  else
    eval "deref $(($1 - 1)) \"\${$2}\" \"\$3\""
  fi
}

Poi:

$ a=b b=c c=d d=e e=blah
$ deref 3 a res; echo "$res"
d
$ deref 5 a res; echo "$res"
blah

FWIW, in zsh:

$ echo ${(P)${(P)${(P)${(P)a}}}}
blah

Huh, usando diversi passaggi non mi è venuto in mente fino ad ora ... strano.
Profpatsch,

È ovvio, dal mio punto di vista, perché @Profpatsch vuole farlo . Perché è il modo più intuitivo per farlo. Essere difficili da implementare sostituzioni multiple in bash è un'altra cosa.
Nikos Alexandris,

2

Soluzione POSIX

Senza impazzire con la quotazione, puoi usare questa funzione per molti livelli di riferimento indiretto in questo modo:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

O se hai più (!?!) Livelli di indiretta potresti usare un loop.

Avvisa quando viene dato:

  • Input nullo
  • Più di un argomento
  • Un nome di variabile che non è impostato

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
function var_expand {
  if [ "$#" -ne 1 ] || [ -z "${1-}" ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

C'è un motivo per cui non hai combinato questa risposta con la tua precedente? A prima vista non vedo la differenza tra loro.
BlueDrink9
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.