O que há de errado com "echo $ (stuff)" ou "echo` stuff` "?


31

Eu usei um dos seguintes

echo $(stuff)
echo `stuff`

(onde stuffé, por exemplo, pwdou dateou algo mais complicado).

Foi-me dito que esta sintaxe está errada, é uma prática ruim, não elegante, excessiva, redundante, excessivamente complicada, uma programação de culto de carga, noobish, ingênua etc.

Mas o comando não trabalho, então o que exatamente está errado com ele?


11
É redundante e, portanto, não deve ser usado. Compare com o uso inútil do gato: porkmail.org/era/unix/award.html
dr01

7
echo $(echo $(echo $(echo$(echo $(stuff)))))também faz o trabalho, e ainda você provavelmente não iria utilizá-lo, certo? ;-)
Peter - Reinstala Monica

1
O que você está tentando fazer exatamente com essa expansão?
precisa

@curiousguy Estou tentando ajudar outros usuários, daí a minha resposta abaixo. Recentemente, vi pelo menos três perguntas que usam essa sintaxe. Seus autores estavam tentando fazer ... vários stuff. : D
Kamil Maciorowski

2
Além disso, não concordamos todos que não é prudente limitar-se a uma câmara de eco? ;-)
Peter - Restabelece Monica

Respostas:


63

tl; dr

A única stuffseria provavelmente trabalhar para você.



Resposta completa

O que acontece

Quando você executa foo $(stuff), é isso que acontece:

  1. stuff corre;
  2. sua saída (stdout), em vez de ser impressa, substitui $(stuff)na chamada de foo;
  3. depois fooé executado, seus argumentos de linha de comando obviamente dependem do que stuffretornou.

Esse $(…)mecanismo é chamado "substituição de comando". No seu caso, o comando principal é o echoque basicamente imprime seus argumentos de linha de comando no stdout. Portanto, tudo o que stufftenta imprimir no stdout é capturado, passado echoe impresso no stdout echo.

Se você deseja que a saída stuffseja impressa em stdout, basta executar a sola stuff.

A `…`sintaxe serve ao mesmo propósito que $(…)(sob o mesmo nome: "substituição de comando"), porém existem poucas diferenças, portanto você não pode trocá-las cegamente. Veja este FAQ e esta pergunta .


Devo evitar, echo $(stuff)não importa o quê?

Há uma razão que você pode querer usar echo $(stuff)se souber o que está fazendo. Pela mesma razão, você deve evitar echo $(stuff)se realmente não sabe o que está fazendo.

O ponto é stuffe echo $(stuff)não é exatamente equivalente. O último significa chamar o operador split + glob na saída de stuffcom o valor padrão de $IFS. Citar duas vezes a substituição de comando evita isso. A citação única da substituição de comando faz com que não seja mais uma substituição de comando.

Para observar isso quando se trata de divisão, execute estes comandos:

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 para globbing:

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

Como você pode ver, echo "$(stuff)"é equivalente (-ish *) a stuff. Você poderia usá-lo, mas qual é o objetivo de complicar as coisas dessa maneira?

Por outro lado, se você quer a saída de stuffse submeter a divisão + englobamento, então você pode encontrar echo $(stuff)útil. Tem que ser sua decisão consciente.

Existem comandos que geram resultados que devem ser avaliados (incluindo divisão, globbing e mais) e executados pelo shell, portanto, eval "$(stuff)"é uma possibilidade (consulte esta resposta ). Eu nunca vi um comando que precise que sua saída sofra divisão + globbing adicionais antes de ser impressa . O uso deliberado echo $(stuff)parece muito incomum.


Que tal var=$(stuff); echo "$var"?

Bom ponto. Este trecho:

var=$(stuff)
echo "$var"

deve ser equivalente a echo "$(stuff)"(-ish *) a stuff. Se for o código inteiro, basta executar stuff.

Se, no entanto, você precisar usar a saída de stuffmais de uma vez, essa abordagem

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

geralmente é melhor que

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

Mesmo que fooseja echoe você entra echo "$var"em seu código, pode ser melhor mantê-lo dessa maneira. Coisas a considerar:

  1. Com var=$(stuff) stuffcorre uma vez; mesmo que o comando seja rápido, evitar a computação da mesma saída duas vezes é a coisa certa. Ou talvez stufftenha outros efeitos além de gravar no stdout (por exemplo, criar um arquivo temporário, iniciar um serviço, iniciar uma máquina virtual, notificar um servidor remoto), para que você não queira executá-lo várias vezes.
  2. Se stuffgerar uma saída dependente do tempo ou algo aleatória, você poderá obter resultados inconsistentes de foo "$(stuff)"e bar "$(stuff)". Após var=$(stuff)o valor de $varser corrigido, você pode ter certeza foo "$var"e bar "$var"obter um argumento de linha de comando idêntico.

Em alguns casos, em vez de foo "$var"você querer (precisar) usar foo $var, especialmente se stuffgerar vários argumentos para foo(uma variável de matriz pode ser melhor se o seu shell suportar). Mais uma vez, saiba o que está fazendo. Quando se trata echoda diferença entre echo $vare echo "$var"é o mesmo que entre echo $(stuff)e echo "$(stuff)".


* Equivalente ( -ish )?

Eu disse que echo "$(stuff)"é equivalente (-ish) a stuff. Há pelo menos dois problemas que o tornam não exatamente equivalente:

  1. $(stuff)é executado stuffem um subshell, então é melhor dizer que echo "$(stuff)"é equivalente (-ish) a (stuff). Os comandos que afetam o shell em que são executados, se estiverem em um subshell, não afetam o shell principal.

    Neste exemplo stuffé a=1; echo "$a":

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

    Compare com

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

    e com

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

    Outro exemplo, comece stuffsendo cd /; pwd:

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

    e teste stuffe (stuff)versões.

  2. echonão é uma boa ferramenta para exibir dados não controlados . Isso echo "$var"estávamos falando sobre deveria ter sido printf '%s\n' "$var". Mas como a pergunta menciona echoe como a solução mais provável é não usar echoem primeiro lugar, decidi não apresentar printfaté agora.

  3. stuffou (stuff)intercalará a saída stdout e stderr, enquanto echo $(stuff)imprimirá toda a saída stderr de stuff(que roda primeiro) e somente então a saída stdout digerida por echo(que roda por último).

  4. $(…)retira qualquer nova linha à direita e a echoadiciona novamente. Então, echo "$(printf %s 'a')" | xxddá saída diferente de printf %s 'a' | xxd.

  5. Alguns comandos ( lspor exemplo) funcionam de maneira diferente, dependendo se a saída padrão é um console ou não; por isso ls | catnão o mesmo lsfaz. Da mesma forma echo $(ls)irá funcionar de forma diferente do que ls.

    Deixando de lslado, em um caso geral, se você precisar forçar esse outro comportamento, stuff | caté melhor echo $(ls)ou echo "$(ls)"porque não aciona todos os outros problemas mencionados aqui.

  6. Possivelmente status de saída diferente (mencionado para completar esta resposta da wiki; para detalhes, veja outra resposta que merece crédito).


5
Mais uma diferença: o stuffcaminho entrelaçará stdoute stderrproduzirá, enquanto echo `stuff` imprimirá toda a stderrsaída stuffe somente então a stdoutsaída.
Ruslan

3
E este é outro exemplo para: dificilmente pode haver muitas citações nos scripts do bash. echo $(stuff)pode colocá-lo em muito mais problemas, como echo "$(stuff)"se você não quisesse explicações e expansão explicitamente.
Rexkogitans 28/08/19

2
Mais uma pequena diferença: $(…)retira qualquer nova linha à direita e a echoadiciona de volta. Então, echo "$(printf %s 'a')" | xxddá saída diferente de printf %s 'a' | xxd.
derobert

1
Você não deve esquecer que alguns comandos ( lspor exemplo) funcionam de maneira diferente, dependendo se a saída padrão é um console ou não; por isso ls | catnão o mesmo lsfaz. E, claro echo $(ls), funcionará de maneira diferente ls.
Martin Rosenau

@Ruslan A resposta agora é wiki da comunidade. Eu adicionei seu comentário útil ao wiki. Obrigado.
Kamil Maciorowski

5

Outra diferença: o código de saída do sub-shell é perdido e, portanto, o código de saída echoé recuperado.

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

3
Bem-vindo ao Super Usuário! Você pode explicar a diferença que está demonstrando? Graças
bertieb

4
Eu acredito que isso mostra que o código de retorno $?será o código de retorno de echo(para echo $(stuff)) em vez do código returned stuff. A stufffunção return 1(código de saída), mas a echodefine como "0", independentemente do código de retorno de stuff. Ainda deve estar na resposta.
Ismael Miguel

o valor de retorno do subshell foi perdido
phuclv 29/08/18
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.