Bash: Como ler uma linha de cada vez a partir da saída de um comando?

49

Eu estou tentando ler a saída de um comando no bash usando a while loop.

while read -r line
do
    echo "$line"
done <<< $(find . -type f)

A saída que eu recebi

ranveer@ranveer:~/tmp$ bash test.sh
./test.py ./test1.py ./out1 ./test.sh ./out ./out2 ./hello
ranveer@ranveer:~/tmp$ 

Depois disso eu tentei

$(find . -type f) | 
while read -r line
do
    echo "$line"
done 

mas gerou um erro test.sh: line 5: ./test.py: Permission denied.

Então, como eu leio linha por linha, porque acho que atualmente ela está consumindo toda a linha de uma só vez.

Saída necessária:

./test.py
./test1.py
./out1
./test.sh
./out
./out2
./hello
RanRag
fonte
3
Sugiro ler o Bash FAQ 01 - muitas informações e conselhos úteis sobre armadilhas a serem evitadas.
jw013
Para a while readparte, consulte Noções básicas sobre o IFS e as perguntas vinculadas lá.
Gilles 'SO- stop be evil'
Para usar find, consulte Como posso usar dois comandos bash no comando -exec of find? ou Executando a função definida pelo usuário em uma chamada find -exec (da qual essa pergunta é uma duplicata).
Gilles 'SO- stop be evil'

Respostas:

54

Há um erro, você < <(command)não precisa<<<$(command)

< <( )é uma substituição de processo , $()é uma substituição de comando e <<<é uma string aqui .

Gilles Quenot
fonte
2
@RanRag Pare de tentar $( )contornar tudo! Essa é a sintaxe da substituição de comandos , que é apenas uma maneira de usar a saída de comandos. Tubos, substituição de processo e strings aqui são outros, e todos eles têm sintaxe diferente, naturalmente. Você não deve analisar os nomes dos arquivos, a menos que saiba realmente o que está fazendo.
jw013
Obrigado, funcionou vai ler mais sobre Process Substitution.
21812 RanRag
@ jw013: Sou iniciante no bash. No futuro, manterá sua sugestão em minha mente.
RanRag
13

Observe que não há nada que impeça que os nomes de arquivos contenham caracteres de nova linha. A maneira canônica de executar um comando para cada arquivo encontrado por find é.

find . -type f -exec cmd {} \;

E se você quiser que as coisas sejam feitas no bash:

find . -type f -exec bash -c '
  for file do
    something with "$file"
  done' bash {} +

Além disso, a maneira canônica de chamar o comando "read" nos scripts (se você não deseja que ele faça um processamento extra na entrada) é:

IFS= read -r var

-ré parar readde tratar caracteres de barra invertida especialmente (como um caractere de escape para separadores e nova linha), E IFS = para definir a lista de separadores para a cadeia vazia para read(caso contrário, se houver espaço em branco estava nessa lista, eles seriam retirados do início e fim da entrada).

Usar loops em shells geralmente é uma má ideia (não como as coisas são feitas nos shells, nas quais você faz várias ferramentas trabalharem coletivamente e simultaneamente a uma tarefa, em vez de executar uma ou mais ferramentas centenas de vezes em sequência).

Stéphane Chazelas
fonte