Capturando saída de várias linhas em uma variável Bash

583

Eu tenho um script 'myscript' que gera o seguinte:

abc
def
ghi

em outro script, eu chamo:

declare RESULT=$(./myscript)

e $RESULTobtém o valor

abc def ghi

Existe uma maneira de armazenar o resultado com as novas linhas ou com o caractere '\ n' para que eu possa imprimi-lo com ' echo -e'?

Parker
fonte
1
Surpreende-me. você não tem $ (cat ./myscipt)? caso contrário eu teria esperado para tentar executar comandos abc, def e ghi
Johannes Schaub - litb
@ litb: sim, suponho que sim; você também pode usar $ (<./ myscript), o que evita a execução de um comando.
Jonathan Leffler
2
(Nota: os dois comentários acima se referem a uma revisão da pergunta iniciada. Eu tenho um script 'myscript' que contém o seguinte , o que levou às perguntas. A revisão atual da pergunta ( eu tenho um script ' myscript ', que exibe o seguinte ) torna os comentários supérfluos. No entanto, a revisão é de 11/11/2011, muito tempo depois que os dois comentários foram feitos.
Jonathan Leffler

Respostas:

1083

Na verdade, o RESULT contém o que você deseja - para demonstrar:

echo "$RESULT"

O que você mostra é o que obtém:

echo $RESULT

Como observado nos comentários, a diferença é que (1) a versão com aspas duplas da variável ( echo "$RESULT") preserva o espaçamento interno do valor exatamente como é representado na variável - novas linhas, guias, vários espaços em branco e tudo - enquanto (2 ) a versão não citada ( echo $RESULT) substitui cada sequência de um ou mais espaços em branco, guias e novas linhas por um único espaço. Assim (1) preserva a forma da variável de entrada, enquanto (2) cria uma linha única de saída potencialmente muito longa com 'palavras' separadas por espaços únicos (onde uma 'palavra' é uma sequência de caracteres que não são espaços em branco; qualquer alfanumérico em qualquer uma das palavras).

Jonathan Leffler
fonte
69
@troelskn: a diferença é que (1) a versão com aspas duplas da variável preserva o espaçamento interno do valor exatamente como está representado na variável, novas linhas, guias, vários espaços em branco e tudo, enquanto (2) a versão sem aspas substitui cada sequência de um ou mais espaços em branco, guias e novas linhas com um único espaço. Assim (1) preserva a forma da variável de entrada, enquanto (2) cria uma linha única de saída potencialmente muito longa com 'palavras' separadas por espaços únicos (onde uma 'palavra' é uma sequência de caracteres que não são espaços em branco; qualquer alfanumérico em qualquer uma das palavras).
precisa
23
Para facilitar a compreensão da resposta: a resposta informa que o eco "$ RESULT" preserva a nova linha, enquanto o eco $ RESULT não.
Yu Shen
Isso não preserva novas linhas e espaços à esquerda em algumas situações.
CommaToast
3
@CommaToast: Você vai elaborar isso? Novas linhas à direita são perdidas; não há uma maneira fácil de contornar isso. Principais espaços em branco - não conheço nenhuma circunstância sob a qual eles estejam perdidos.
Jonathan Leffler
90

Outra armadilha com isso é que o comando substituição - $()- rastreia as novas linhas. Provavelmente nem sempre é importante, mas se você realmente deseja preservar exatamente o que foi produzido, precisará usar outra linha e algumas citações:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

Isso é especialmente importante se você deseja manipular todos os nomes de arquivos possíveis (para evitar comportamentos indefinidos, como operar no arquivo errado).

l0b0
fonte
4
Eu tive que trabalhar por um tempo com uma concha quebrada que não removeu o último de nova linha da substituição de comando (que é não substituição processo ), e quebrou quase tudo. Por exemplo, se você tiver pwd=`pwd`; ls $pwd/$file, você terá uma nova linha antes da /, e colocar o nome entre aspas duplas não ajudará. Foi consertado rapidamente. Isso foi em 1983-5, no ICL Perq PNX; o shell não tinha $PWDcomo variável interna.
precisa
19

Caso você esteja interessado em linhas específicas, use uma matriz de resultados:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"
user2574210
fonte
3
Se houver espaços nas linhas, isso contará campos (conteúdo entre espaços) em vez de linhas.
Liam
2
Você usaria readarraye processo de substituição em vez de substituição de comando: readarray -t RESULT < <(./myscript>.
chepner
15

Além da resposta dada por @ l0b0, eu apenas tive a situação em que eu precisava manter qualquer linha de saída final do script e verificar o código de retorno do script. E o problema com a resposta de l0b0 é que o 'eco x' estava redefinindo $? de volta a zero ... então eu consegui encontrar essa solução muito esperta:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"
Lurchman
fonte
Isso é adorável, na verdade, eu tive o caso de uso em que quero ter uma die ()função que aceite um código de saída arbitrário e, opcionalmente, uma mensagem, e eu queria usar outra usage ()função para fornecer a mensagem, mas as novas linhas continuavam sendo esmagadas, espero que isso me permita contornar isso.
precisa saber é o seguinte
1

Depois de tentar a maioria das soluções aqui, a coisa mais fácil que encontrei foi o óbvio - usando um arquivo temporário. Não sei ao certo o que você deseja fazer com sua saída de várias linhas, mas você pode lidar com isso linha por linha usando a leitura. A única coisa que você realmente não pode fazer é colocá-lo facilmente na mesma variável, mas para os propósitos mais práticos, é muito mais fácil lidar com isso.

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

Corte rápido para fazer a ação solicitada:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

Observe que isso adiciona uma linha extra. Se você trabalha com isso, pode codificar, sou muito preguiçoso.


EDIT: Embora este caso funcione perfeitamente bem, as pessoas que estiverem lendo isso devem estar cientes de que você pode compactar facilmente seu stdin dentro do loop while, fornecendo um script que executará uma linha, limpará stdin e sairá. Como ssh vai fazer o que eu acho? Eu vi recentemente, outros exemplos de código aqui: /unix/24260/reading-lines-from-a-file-with-bash-for-vs- while

Mais uma vez! Desta vez, com um manipulador de arquivo diferente (stdin, stdout, stderr são 0-2, para que possamos usar & 3 ou superior no bash).

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

você também pode usar o mktemp, mas este é apenas um exemplo de código rápido. O uso do mktemp se parece com:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

Em seguida, use $ filenamevar como se fosse o nome real de um arquivo. Provavelmente não precisa ser explicado aqui, mas alguém se queixou nos comentários.

user1279741
fonte
Tentei outras soluções também, com a sua primeira sugestão que eu finalmente tenho meu script de trabalho
Kar.ma
1
Votos negativos: isso é excessivamente complexo e falha em evitar várias basharmadilhas comuns .
tripleee
Ya alguém me falou sobre o problema estranho do arquivo stdin no outro dia e eu fiquei tipo "uau". Deixe-me adicionar algo muito rapidamente.
user1279741
Em Bash, você pode usar a readextensão de comando -u 3para ler do descritor de arquivo 3.
Jonathan Leffler
Por que não enquanto ... do ... feito <<(.
Myscript.sh
0

Que tal isso, ele lerá cada linha em uma variável e poderá ser usada posteriormente! digamos que a saída myscript é redirecionada para um arquivo chamado myscript_output

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'
Rahul Reddy
fonte
4
Bem, isso não é festa, isso é estranho.
vadipp