Leia a entrada em bash dentro de um loop while

97

Estou tendo um script bash que é algo como seguir,

cat filename | while read line
do
    read input;
    echo $input;
done

mas isso claramente não está me dando a saída correta, já que quando eu leio no loop while, ele tenta ler o nome do arquivo por causa do possível redirecionamento de E / S.

Alguma outra maneira de fazer o mesmo?

w2lame
fonte
A mesma coisa acontece quando você troca de usuário em bash e executa o comando read sob troca de usuário no script
krupal

Respostas:

106

Leia a partir do dispositivo terminal de controle:

read input </dev/tty

mais informações: http://compgroups.net/comp.unix.shell/Fixing-stdin-inside-a-redirected-loop

úmido
fonte
12
-1, pois isso contornará qualquer outro redirecionamento. Por exemplo, bash yourscript < /foo/barirá aguardar a entrada do usuário, isso é aceitável apenas ao ler senhas. A resposta de @GordonDavisson é preferível para todos os outros usos.
tiwo
55

Você pode redirecionar o stdin regular através da unidade 3 para mantê-lo dentro do pipeline:

{ cat notify-finished | while read line; do
    read -u 3 input
    echo "$input"
done; } 3<&0

BTW, se você realmente está usando catdessa forma, substitua-o por um redirecionamento e as coisas se tornam ainda mais fáceis:

while read line; do
    read -u 3 input
    echo "$input"
done 3<&0 <notify-finished

Ou você pode trocar stdin e unidade 3 nessa versão - leia o arquivo com a unidade 3 e apenas deixe stdin sozinho:

while read line <&3; do
    # read & use stdin normally inside the loop
    read input
    echo "$input"
done 3<notify-finished
Gordon Davisson
fonte
por que seu segundo script está pendurado?
Luca Borrione
2
@LucaBorrione: Como você está usando? Ele está esperando que você forneça a entrada (observe que read lineestá lendo a partir de notificação concluída, mas se você apenas executá-lo conforme read -u 3 inputestá escrito, ele está lendo no console)?
Gordon Davisson
4

Tente mudar o loop assim:

for line in $(cat filename); do
    read input
    echo $input;
done

Teste de unidade:

for line in $(cat /etc/passwd); do
    read input
    echo $input;
    echo "[$line]"
done
dimba
fonte
@ w2lame Testado novamente, altere o loop "while" para loop "for" - está funcionando para mim. Tente "sex -x" para ver de onde vem o erro
dimba
4
não use gato, veja a resposta de Hai Vu
Fredrik Pihl
+1. Isso foi muito mais fácil de implementar para minhas necessidades específicas do que outras sugestões.
Nathan Wallace
1
Consulte Não leia linhas com para na wiki do Wooledge. Além disso, shellcheck.net aviso SC2013
Charles Duffy
3

Parece que você leu duas vezes, a leitura dentro do loop while não é necessária. Além disso, você não precisa invocar o comando cat:

while read input
do
    echo $input
done < filename
Hai Vu
fonte
4
O objetivo do OP é que a leitura dentro do loop venha do usuário, enquanto a externa é ler a partir do arquivo. Assim, eles desejam legitimamente duas leituras diferentes, de duas fontes diferentes. Isso fica claro tanto no texto da pergunta (que descreve o readcomportamento do interno como "incorreto [porque] ele tenta ler o arquivo filename") e na resposta aceita.
Charles Duffy
1

Encontrei este parâmetro -u com read.

"-u 1" significa "ler de stdin"

while read -r newline; do
    ((i++))
    read -u 1 -p "Doing $i""th file, called $newline. Write your answer and press Enter!"
    echo "Processing $newline with $REPLY" # united input from two different read commands.
done <<< $(ls)
xerostomus
fonte
-6
echo "Enter the Programs you want to run:"
> ${PROGRAM_LIST}
while read PROGRAM_ENTRY
do
   if [ ! -s ${PROGRAM_ENTRY} ]
   then
      echo ${PROGRAM_ENTRY} >> ${PROGRAM_LIST}
   else
      break
   fi
done
Munchk1n
fonte