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, $text
sem 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]
read
comeu 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 hello
e world
foi retido, porque está dentro da line
variável. Por outro lado, o espaço inicial foi consumido. Isso read
ocorre 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 IFS
variá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 read
built-in . Os IFS= read -r line
conjuntos 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 read
estiver executando is. Observe que read
nã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 IFS
outras instruções que possam depender dele. Esse código funcionará independentemente do que o código circundante tenha definido IFS
inicialmente 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 $PATH
não seria dividido $PATH
em componentes separados por dois pontos. Se o código fosse IFS=; while read …
, seria ainda mais óbvio que IFS
não está definido :
no corpo do loop.
Obviamente, seria possível restaurar o valor IFS
apó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 read
for 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 read
não dividir aX
, maswhile IFS=X; read
faz ...