Como canalizar a entrada para um loop Bash while e preservar variáveis ​​após o término do loop

87

O Bash permite usar: cat <(echo "$FILECONTENT")

O Bash também permite usar: while read i; do echo $i; done </etc/passwd

para combinar os dois anteriores, pode ser usado: echo $FILECONTENT | while read i; do echo $i; done

O problema com o último é que ele cria um sub-shell e, depois que o loop while termina, a variável inão pode mais ser acessada.

Minha pergunta é:

Como conseguir algo assim: while read i; do echo $i; done <(echo "$FILECONTENT")ou em outras palavras: Como posso ter certeza de que isobrevive o loop while?

Observe que estou ciente de incluir a instrução while, {}mas isso não resolve o problema (imagine que você deseja usar o loop while na função e na ivariável de retorno )

Wakan Tanka
fonte
Relacionado: stackoverflow.com/questions/37229058/… . Explica todas as opções, incluindo a substituição de processo mencionada abaixo lastpipee seus prós e contras.
ivan_pozdeev

Respostas:

115

A notação correta para Substituição de Processo é:

while read i; do echo $i; done < <(echo "$FILECONTENT")

O último valor de iatribuído no loop fica então disponível quando o loop termina. Uma alternativa é:

echo $FILECONTENT | 
{
while read i; do echo $i; done
...do other things using $i here...
}

As chaves são uma operação de agrupamento de E / S e não criam um subshell. Nesse contexto, eles fazem parte de um pipeline e, portanto, são executados como um subshell, mas é por causa do |, não do { ... }. Você menciona isso na pergunta. AFAIK, você pode fazer um retorno de dentro deles dentro de uma função.


O Bash também fornece o shoptintegrado e uma de suas muitas opções é:

lastpipe

Se definido, e o controle de trabalho não está ativo, o shell executa o último comando de um pipeline não executado em segundo plano no ambiente shell atual.

Assim, usar algo assim em um script torna o modificado sumdisponível após o loop:

FILECONTENT="12 Name
13 Number
14 Information"
shopt -s lastpipe   # Comment this out to see the alternative behaviour
sum=0
echo "$FILECONTENT" |
while read number name; do ((sum+=$number)); done
echo $sum

Fazer isso na linha de comando geralmente resulta na falta de 'controle de trabalho não está ativo' (ou seja, na linha de comando, o controle de trabalho está ativo). O teste sem usar um script falhou.

Além disso, conforme observado por Gareth Rees em sua resposta , às vezes você pode usar uma string here :

while read i; do echo $i; done <<< "$FILECONTENT"

Isso não requer shopt; você pode salvar um processo usando-o.

Jonathan Leffler
fonte
Perdoe minha ignorância. Eu sei que essa é a solução certa e marquei-a como uma resposta, então funcionou para mim. Mas agora, quando eu o executo, while read i; do echo $i; done < <(cat /etc/passwd); echo $iele não retorna a última linha duas vezes. O que estou fazendo errado?
Wakan Tanka
@WakanTanka: Tive que experimentar um pouco ... Acredito que a resposta seja que a leitura com falha é redefinida icomo vazia, então o eco após o loop ecoa uma linha em branco.
Jonathan Leffler
1
Parabéns pela referência de Substituição de Processo. Eu não sabia disso.
Atcold
@Wakan Tanka: obteve o mesmo resultado que o seu, eu uso while read i; do x=$i; done < <(cat /etc/passwd); echo i=$i; echo x=$xfuncionou, // talvez o comportamento do bash daquela época tenha mudado?
yurenchen
Você é um salva-vidas! Há horas que procuro uma solução semelhante. O seu é o único que funcionou.
Pat
0

Esta função duplica $ NUM vezes de arquivos jpg (bash)

function makeDups() {
NUM=$1
echo "Making $1 duplicates for $(ls -1 *.jpg|wc -l) files"
ls -1 *.jpg|sort|while read f
do
  COUNT=0
  while [ "$COUNT" -le "$NUM" ]
  do
    cp $f ${f//sm/${COUNT}sm}
    ((COUNT++))
  done
done
}
Steve
fonte