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.