Como atribuir a saída de um comando a uma variável de shell?

76

Quero atribuir o resultado de uma expressão a uma variável e concatená-la com uma string e, em seguida, repeti-la. Aqui está o que eu tenho:

#!/bin/bash
cd ~/Desktop;
thefile= ls -t -U | grep -m 1 "Screen Shot";
echo "Most recent screenshot is: "$thefile;

Mas isso gera:

Screen Shot 2011-07-03 at 1.55.43 PM.png
Most recent screenshot is: 

Portanto, parece que isso não está sendo atribuído $thefilee está sendo impresso à medida que é executado.

Nathan G.
fonte

Respostas:

108

Uma atribuição de shell é uma única palavra, sem espaço após o sinal de igual. Então, o que você escreveu atribui um valor vazio a thefile; além disso, como a atribuição é agrupada com um comando, ela torna thefileuma variável de ambiente e a atribuição é local para esse comando específico, ou seja, apenas a chamada para lsver o valor atribuído.

Você deseja capturar a saída de um comando, portanto, você precisa usar a substituição de comando :

thefile=$(ls -t -U | grep -m 1 "Screen Shot")

(Alguma literatura mostra uma sintaxe alternativa thefile=`ls …`; a sintaxe de aspas é equivalente à sintaxe entre parênteses em dólar, exceto que, por vezes, as aspas dentro de aspas são estranhas, então use $(…).)

Outras observações sobre o seu script:

  • Combinar -t(classificar por tempo) com -U(não classificar) não faz sentido; apenas use -t.
  • Em vez de usar greppara corresponder às capturas de tela, é mais claro passar um curinga lse usar headpara capturar o primeiro arquivo:

    thefile=$(ls -t *"Screen Shot"* | head -n 1)
  • Geralmente, é uma má ideia analisar a saída dels . Isso pode falhar bastante se você tiver nomes de arquivos com caracteres não imprimíveis. No entanto, é difícil classificar arquivos por data sem ls, portanto, é uma solução aceitável se você souber que não terá caracteres não imprimíveis ou barras invertidas nos nomes dos arquivos.

  • Sempre use aspas duplas nas substituições de variáveis , ou seja, escreva aqui

    echo "Most recent screenshot is: $thefile"

    Sem aspas duplas, o valor da variável é reexpandido, o que causará problemas se contiver espaço em branco ou outros caracteres especiais.

  • Você não precisa de ponto e vírgula no final de uma linha. Eles são redundantes, mas inofensivos.
  • Em um script de shell, geralmente é uma boa ideia incluir set -e. Isso informa ao shell para sair se algum comando falhar (retornando um status diferente de zero).

Se você encontrar o GNU (em particular se estiver executando Linux ou Cygwin não incorporado), existe outra abordagem para encontrar o arquivo mais recente: findlistar os arquivos e suas datas, e usar sorte tailextrair o arquivo mais novo.

thefile=$(find -maxdepth 1 -type f -name "*Screen Shot*" -printf "%T@ %p" |
          sort -k 1n | tail -n 1)

Se você deseja escrever esse script no zsh, em vez do bash, há uma maneira muito mais fácil de capturar o arquivo mais recente, porque o zsh possui qualificadores globais que permitem correspondências curinga não apenas nos nomes, mas também nos metadados do arquivo. A (om[1])parte após o padrão são os qualificadores glob; omclassifica as correspondências aumentando a idade (ou seja, pela hora da modificação, a mais nova primeiro) e [1]extrai apenas a primeira correspondência. A correspondência inteira precisa estar entre parênteses, porque é tecnicamente uma matriz, pois o globbing retorna uma lista de arquivos, mesmo que [1]isso signifique que, nesse caso específico, a lista contenha (no máximo) um arquivo.

#!/bin/zsh
set -e
cd ~/Desktop
thefile=(*"Screen Shot"*(om[1]))
echo "Most recent screenshot is: $thefile"
Gilles
fonte
7
Uau! Mais informações do que eu poderia ter esperado. Muito obrigado pela sua resposta; Eu realmente aprecio todo o esforço que você coloca nisso! Você acabou de me mostrar que eu realmente tenho muito a aprender. Vou comprar um livro no bash: p.
Nathan G.
3
Isso é o que eu chamaria de resposta definitiva ! :)
alex
4

Se você quiser fazer isso com várias linhas / vários comandos / s, poderá fazer o seguinte:

output=$( bash <<EOF
#multiline/multiple command/s
EOF
)

Ou:

output=$(
#multiline/multiple command/s
)

Exemplo:

#!/bin/bash
output="$( bash <<EOF
echo first
echo second
echo third
EOF
)"
echo "$output"

Resultado:

first
second
third
Jahid
fonte
Esta é uma boa resposta. existe uma maneira de especificar uma variável como parte do comando? por exemplooutput=$(echo $someVariable)
Zach Smith