Vejamos um exemplo, com algum texto de entrada cuidadosamente criado:
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 read(mas usando printf '%s\n' "$text"para imprimir com cuidado, $textsem nenhum risco de expansão). (Abaixo, $ está o prompt do shell.)
$ printf '%s\n' "$text" |
while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]
readcomeu as barras invertidas: a barra invertida-nova faz com que a nova linha seja ignorada e a barra invertida-qualquer coisa ignora a 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]
É melhor, temos duas linhas como esperado. As duas linhas quase contêm o conteúdo desejado: o espaço duplo entre helloe worldfoi retido, porque está dentro da linevariável. Por outro lado, o espaço inicial foi consumido. Isso readocorre porque lê quantas palavras você passar pelas variáveis, exceto que a última variável contém o restante da linha - mas ainda começa com a primeira palavra, ou seja, os espaços iniciais são descartados.
Portanto, para ler cada linha literalmente, precisamos garantir que não haja divisão de palavras . Fazemos isso definindo a IFSvariável 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 configuramos IFS especificamente para a duração do readbuilt-in . Os IFS= read -r lineconjuntos do ambiente variável IFS(para um valor vazio) especificamente para a execução de read. Essa é uma instância da sintaxe geral de comando simples : uma sequência (possivelmente vazia) de atribuições de variáveis seguida por um nome de comando e seus argumentos (também é possível ativar redirecionamentos a qualquer momento). Como readé um built-in, a variável nunca acaba realmente no ambiente de um processo externo; no entanto, o valor de $IFSé o que estamos atribuindo lá enquanto readestiver executando is. Observe que readnão é um built-in especial ; portanto, a atribuição dura apenas por sua duração.
Portanto, tomamos o cuidado de não alterar o valor de IFSoutras instruções que possam depender dele. Esse código funcionará independentemente do que o código circundante tenha definido IFSinicialmente e não causará nenhum problema se o código dentro do loop depender IFS.
Contraste com esse trecho 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 $PATHnão seria dividido $PATHem componentes separados por dois pontos. Se o código fosse IFS=; while read …, seria ainda mais óbvio que IFSnão está definido :no corpo do loop.
Obviamente, seria possível restaurar o valor IFSapós a execução read. Mas isso exigiria conhecer o valor anterior, que é um esforço extra. IFS= readé a maneira mais simples (e, convenientemente, também a mais curta).
¹ E, se readfor interrompido por um sinal interceptado, possivelmente enquanto o interceptador estiver em execução - isso não é especificado pelo POSIX e depende, na prática, do shell.
while IFS=X readnão dividir aX, maswhile IFS=X; readfaz ...