Vejamos um exemplo com um texto de entrada bem elaborado:
text=' hello world\
foo\bar'
São duas linhas, a primeira começando com um espaço e terminando com uma barra invertida. Primeiro, vamos ver o que acontece sem nenhuma precaução em torno de read (mas usando printf '%s\n' "$text" para imprimir cuidadosamente $text sem qualquer risco de expansão). (Abaixo, $ é o prompt do shell.)
$ printf '%s\n' "$text" |
while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]
read das barras invertidas: a barra invertida-nova linha faz com que a nova linha seja ignorada e a barra invertida-nada ignora essa primeira barra invertida. Para evitar que as barras invertidas sejam tratadas especialmente, usamos read -r .
$ printf '%s\n' "$text" |
while read -r line; do printf '%s\n' "[$line]"; done
[hello world\]
[foo\bar]
Isso é melhor, temos duas linhas conforme o esperado. As duas linhas quase contêm o conteúdo desejado: o espaço duplo entre hello e world foi mantido, porque está dentro da variável line . Por outro lado, o espaço inicial foi comido. Isso porque read lê quantas palavras você passar variáveis, exceto que a última variável contém o resto da linha - mas ainda começa com a primeira palavra, ou seja, os espaços iniciais são descartados.
Então, para ler cada linha literalmente, precisamos ter certeza de que nenhuma divisão de palavras está acontecendo. Fazemos isso definindo a IFS variable como um valor vazio.
$ printf '%s\n' "$text" |
while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello world\]
[foo\bar]
Observe como definimos IFS especificamente para a duração do read interno . O IFS= read -r line define a variável de ambiente IFS (para um valor vazio) especificamente para a execução de read .
Esta é uma instância da sintaxe geral do comando simples : uma sequência (possivelmente vazia) de atribuições de variáveis seguidas por um nome de comando e seus argumentos (também, você pode lançar redirecionamentos em qualquer ponto). Como read é um built-in, a variável nunca acaba no ambiente de um processo externo; No entanto, o valor de $IFS é o que estamos atribuindo lá, desde que read esteja sendo executado¹. Tenha em atenção que read não é incorporado especial , pelo que a tarefa é válida apenas pela sua duração.
Assim, estamos tomando cuidado para não alterar o valor de IFS para outras instruções que possam depender dele. Esse código funcionará independentemente do código que tenha sido definido em IFS , e não causará nenhum problema se o código dentro do loop depender de IFS .
Compare com este snippet de código, que procura arquivos em um caminho separado por dois pontos. A lista de nomes de arquivos é lida de um arquivo, um nome de arquivo por linha.
IFS=":"; set -f
while IFS= read -r name; do
for dir in $PATH; do
## At this point, "$IFS" is still ":"
if [ -e "$dir/name" ]; then echo "$dir/$name"; fi
done
done <filenames.txt
Se o loop fosse while IFS=; read -r name; do … , for dir in $PATH não dividiria $PATH nos componentes separados por dois pontos. Se o código fosse IFS=; while read … , seria ainda mais óbvio que IFS não esteja definido como : no corpo do loop.
Naturalmente, seria possível restaurar o valor de IFS após a execução de read . Mas isso exigiria conhecer o valor anterior, que é um esforço extra. IFS= read é o caminho simples (e, convenientemente, também o caminho mais curto).
¹ E, se read for interrompido por um sinal aprisionado, possivelmente enquanto o trap estiver sendo executado - isso não é especificado por POSIX e depende do shell na prática.