Cosa c'è di sbagliato in "echo $ (stuff)" o "echo` stuff` "?


31

Ho usato uno dei seguenti

echo $(stuff)
echo `stuff`

(dove stuffè ad esempio pwdo dateo qualcosa di più complicato).

Poi mi è stato detto che questa sintassi è sbagliata, una cattiva pratica, non elegante, eccessiva, ridondante, eccessivamente complicata, una programmazione di culto del carico, noobish, ingenua ecc.

Ma il comando fa il lavoro, in modo da che cosa è esattamente che non va?


11
È ridondante e pertanto non deve essere utilizzato. Paragonare all'uso inutile di Cat: porkmail.org/era/unix/award.html
dr01

7
echo $(echo $(echo $(echo$(echo $(stuff)))))funziona anche e probabilmente non lo useresti, giusto? ;-)
Peter - Ripristina Monica il

1
Cosa stai cercando di fare con quell'espansione, esattamente?
curiousguy,

@curiousguy Sto cercando di aiutare altri utenti, quindi la mia risposta di seguito. Di recente, ho visto almeno tre domande che utilizzano questa sintassi. I loro autori stavano provando a fare ... vari stuff. : D
Kamil Maciorowski,

2
Inoltre, non siamo tutti d'accordo sul fatto che non è saggio limitarsi a una camera di eco ?? ;-)
Peter - Ripristina Monica il

Risposte:


63

tl; dr

Una suola stuffavrebbe molto probabilmente lavorare per voi.



Risposta completa

Che succede

Quando corri foo $(stuff), ecco cosa succede:

  1. stuff piste;
  2. il suo output (stdout), invece di essere stampato, sostituisce $(stuff)nell'invocazione di foo;
  3. quindi fooviene eseguito, i suoi argomenti della riga di comando ovviamente dipendono da ciò che è stato stuffrestituito.

Questo $(…)meccanismo si chiama "sostituzione comando". Nel tuo caso il comando principale è echoche fondamentalmente stampa i suoi argomenti della riga di comando su stdout. Quindi qualunque cosa stufftenti di stampare su stdout viene catturato, passato echoe stampato su stdout da echo.

Se vuoi che l'output di stuffsia stampato su stdout, esegui la suola stuff.

La `…`sintassi ha lo stesso scopo di $(…)(con lo stesso nome: "sostituzione comando"), tuttavia ci sono alcune differenze, quindi non è possibile scambiarle ciecamente. Vedi queste FAQ e questa domanda .


Dovrei evitare echo $(stuff)qualunque cosa?

C'è un motivo che potresti voler usare echo $(stuff)se sai cosa stai facendo. Per lo stesso motivo dovresti evitare echo $(stuff)se non sai davvero cosa stai facendo.

Il punto è stuffe echo $(stuff)non sono esattamente equivalenti. Quest'ultimo significa chiamare l'operatore split + glob sull'output di stuffcon il valore predefinito di $IFS. La doppia citazione della sostituzione del comando impedisce questo. La virgoletta singola per la sostituzione del comando non la rende più una sostituzione di comando.

Per osservare questo quando si tratta di dividere eseguire questi comandi:

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

E per globbing:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

Come puoi vedere echo "$(stuff)"è equivalente (-ish *) a stuff. Potresti usarlo ma che senso ha complicare le cose in questo modo?

D'altra parte, se si desidera che l'output di stuffsubisca la suddivisione + globbing, potrebbe essere echo $(stuff)utile. Tuttavia, deve essere una tua decisione consapevole.

Ci sono comandi che generano output che dovrebbero essere valutati (che include split, globbing e altro) ed eseguiti dalla shell, quindi eval "$(stuff)"è una possibilità (vedi questa risposta ). Non ho mai visto un comando che necessita del suo output per subire ulteriori scissioni + globbing prima di essere stampato . L'uso deliberato echo $(stuff)sembra molto raro.


Che dire var=$(stuff); echo "$var"?

Buon punto. Questo frammento:

var=$(stuff)
echo "$var"

dovrebbe essere equivalente a echo "$(stuff)"equivalente (-ish *) a stuff. Se è l'intero codice, esegui stuffinvece.

Se, tuttavia, è necessario utilizzare l'output di stuffpiù di una volta, questo approccio

var=$(stuff)
foo "$var"
bar "$var"

di solito è meglio di

foo "$(stuff)"
bar "$(stuff)"

Anche se lo fooè echoe ottieni il echo "$var"tuo codice, potrebbe essere meglio mantenerlo in questo modo. Cose da considerare:

  1. Con var=$(stuff) stuffcorse una volta; anche se il comando è veloce, evitare di calcolare due volte lo stesso output è la cosa giusta. O forse stuffha effetti diversi dalla scrittura su stdout (ad esempio la creazione di un file temporaneo, l'avvio di un servizio, l'avvio di una macchina virtuale, la notifica a un server remoto), quindi non si desidera eseguirlo più volte.
  2. Se stuffgenera output dipendente dal tempo o in qualche modo casuale, è possibile ottenere risultati incoerenti da foo "$(stuff)"e bar "$(stuff)". Dopo che var=$(stuff)il valore di $varè stato risolto, puoi essere sicuro foo "$var"e bar "$var"ottenere un argomento della riga di comando identico.

In alcuni casi, invece di foo "$var"te, potresti voler (bisogno) usarlo foo $var, specialmente se stuffgenera più argomenti per foo(una variabile di matrice potrebbe essere migliore se la tua shell la supporta). Ancora una volta, sai cosa stai facendo. Quando si tratta echodella differenza tra echo $vare echo "$var"è uguale a tra echo $(stuff)e echo "$(stuff)".


* Equivalente ( -ish )?

Ho detto che echo "$(stuff)"è equivalente (-ish) a stuff. Esistono almeno due problemi che lo rendono non esattamente equivalente:

  1. $(stuff)viene eseguito stuffin una subshell, quindi è meglio dire che echo "$(stuff)"è equivalente (-ish) a (stuff). I comandi che influiscono sulla shell in cui sono in esecuzione, se in una subshell, non influiscono sulla shell principale.

    In questo esempio stuffè a=1; echo "$a":

    a=0
    echo "$(a=1; echo "$a")"      # echo "$(stuff)"
    echo "$a"
    

    Confronta con

    a=0
    a=1; echo "$a"                # stuff
    echo "$a"
    

    e con

    a=0
    (a=1; echo "$a")              # (stuff)
    echo "$a"
    

    Un altro esempio, inizia con stuffessere cd /; pwd:

    cd /bin
    echo "$(cd /; pwd)"           # echo "$(stuff)"
    pwd
    

    e test stuffe (stuff)versioni.

  2. echonon è un buon strumento per visualizzare dati non controllati . Questo di echo "$var"cui stavamo parlando avrebbe dovuto essere printf '%s\n' "$var". Ma poiché la domanda menziona echoe poiché la soluzione più probabile non è quella di utilizzare echoin primo luogo, ho deciso di non presentarmi printffino ad ora.

  3. stuffoppure (stuff)interleave l'output stdout e stderr, mentre echo $(stuff)stampa tutto l'output stderr da stuff(che viene eseguito per primo) e solo successivamente l'output stdout digerito da echo(che viene eseguito per ultimo).

  4. $(…)rimuove qualsiasi nuova riga finale e quindi la echoaggiunge nuovamente. Quindi echo "$(printf %s 'a')" | xxdfornisce un output diverso rispetto a printf %s 'a' | xxd.

  5. Alcuni comandi ( lsad esempio) funzionano in modo diverso a seconda che l'output standard sia una console o meno; quindi ls | catnon fa lo stesso ls. Allo stesso modo echo $(ls)funzionerà diversamente da ls.

    Mettendo lsda parte, in un caso generale se devi forzare questo altro comportamento, allora stuff | catè meglio di echo $(ls)o echo "$(ls)"perché non scatena tutte le altre questioni menzionate qui.

  6. Eventualmente diverso stato di uscita (menzionato per completezza di questa risposta wiki; per i dettagli vedere un'altra risposta che merita credito).


5
Ancora una differenza: il stuffmodo interleave stdoute stderroutput, mentre echo `stuff` stampa tutto l' stderroutput stuffe solo allora l' stdoutoutput.
Ruslan,

3
E questo è un altro esempio di: Difficilmente ci possono essere troppe citazioni negli script bash. echo $(stuff)può metterti in molti più problemi come echo "$(stuff)"se non volessi esplicitamente gorgogliamento ed espansione.
rexkogitans,

2
Un'altra leggera differenza: $(…)elimina qualsiasi nuova riga finale e quindi la echoaggiunge nuovamente. Quindi echo "$(printf %s 'a')" | xxdfornisce un output diverso rispetto a printf %s 'a' | xxd.
derobert,

1
Non dimenticare che alcuni comandi ( lsad esempio) funzionano in modo diverso a seconda che l'output standard sia una console o meno; quindi ls | catnon fa lo stesso ls. E ovviamente echo $(ls)funzionerà diversamente da ls.
Martin Rosenau,

@Ruslan La risposta è ora wiki della community. Ho aggiunto il tuo utile commento al wiki. Grazie.
Kamil Maciorowski il

5

Un'altra differenza: il codice di uscita della sotto-shell viene perso, quindi echoviene recuperato il codice di uscita di .

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0

3
Benvenuto in Super User! Puoi spiegare la differenza che stai dimostrando? Grazie
bertieb il

4
Credo che ciò dimostri che il codice di ritorno $?sarà il codice di ritorno di echo(for echo $(stuff)) anziché quello di returned stuff. La stufffunzione return 1(codice di uscita), ma la echoimposta su "0" indipendentemente dal codice di ritorno di stuff. Dovrebbe essere ancora nella risposta.
Ismael Miguel,

il valore restituito dalla subshell è stato perso
phuclv,
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.