SHELL: Loop facilmente em itens separados por quebra de linha

tl; dr

Faça loop em arquivos sem se preocupar com os espaços nos nomes dos arquivos:

while read file
do
echo
"[$file]"
done < <(ls -1)

Resposta completa

Existem duas maneiras de fazer um loop em itens separados por quebra de linha, por exemplo, em uma lista de arquivos.

Suponha que os seguintes comandos sejam executados em um diretório que contenha esses arquivos:

$ ls -l
total
0
-rw-r--r-- 1 debona staff 0 17 oct 19:58 a file with spaces
-rw-r--r-- 1 debona staff 0 17 oct 22:51 common_file

O “for loop”, a maneira dolorosa

for file in `ls -1`
do
echo
"[$file]"
done

Conforme o “loop for” divide a lista em cada um IFS(o que não é a quebra de linha por padrão), ele imprime a seguinte saída:

[a]
[file]
[with]
[spaces]
[common_file]

OK, vamos definir a IFSquebra de linha:

IFS='
'
# Do you really think that IFS="n" could work?
for file in `ls -1`
do
echo
"[$file]"
done

Sim, ele faz um loop em cada arquivo:

[a file with spaces]
[common_file]

Por que isso é doloroso? Normalmente, alterar o IFSestá se tornando difícil quando há loops aninhados. Se você precisar fazer um loop em uma lista de arquivos, terá que alterar IFSnovamente para aninhar um loop em itens separados por espaço dentro.

O “loop while”, a maneira mais fácil

ls -1 | while read file
do
echo
"[$file]"
done

O readshell embutido lê uma linha da entrada padrão. Tenha cuidado, ele pode ler a linha vazia enquanto o loop for ignora os itens vazios.
Como @gkalabin menciona nos comentários, neste caso, o corpo do loop while é executado em um subshell .

As duas armadilhas principais com o subshell:

  • Todo o material dentro de um subshell não estará disponível após sua execução.
  • Se você estiver exitdentro de um subshell, sairá do próprio subshell, não do “script principal”.

whileé uma palavra-chave shell que não se subdivide por si mesma. Em nosso caso, o corpo do loop while é uma subcamada por causa do tubo.

Para evitar canalizar a lssaída na whileentrada, podemos usar um arquivo temporário:

  1. redirecionar a lssaída no arquivo
  2. redirecionar o arquivo na whileentrada
  3. depois de fazer um loop nele, remova o arquivo

Felizmente, seu amado shell fornece uma maneira simples de fazer isso: a substituição do processo

<(ls -1) # this is the process substitution of the `ls - 1` command

echo <(ls -1) # it prints a path to a "special file" which contains the output of the command

# => /dev/fd/63

cat <(ls -1) # it prints the `ls -1` output

# => a file with spaces

# => common_file

Portanto, o loop while pode ser reescrito assim:

while read file
do
echo
"[$file]"
done < <(ls -1)