Algumas pessoas têm essa noção errônea que read
é o comando para ler uma linha. Não é.
read
lê palavras de uma linha (possivelmente continuada por barra invertida), na qual as palavras são $IFS
delimitadas e a barra invertida pode ser usada para escapar dos delimitadores (ou continuar linhas).
A sintaxe genérica é:
read word1 word2... remaining_words
read
lê stdin um byte de cada vez até encontrar um caractere de nova linha unescaped (ou fim-de-entrada), divide que de acordo com regras complexas e armazena o resultado dessa divisão em $word1
, $word2
... $remaining_words
.
Por exemplo, em uma entrada como:
<tab> foo bar\ baz bl\ah blah\
whatever whatever
e com o valor padrão de $IFS
, read a b c
atribuiria:
$a
⇐ foo
$b
⇐ bar baz
$c
⇐ blah blahwhatever whatever
Agora, se passado apenas um argumento, isso não se torna read line
. Ainda está read remaining_words
. O processamento de barra invertida ainda está concluído, os caracteres de espaço em branco do IFS ainda são removidos do início e do fim.
A -r
opção remove o processamento da barra invertida. Portanto, o mesmo comando acima com -r
atribuiria
$a
⇐ foo
$b
⇐ bar\
$c
⇐ baz bl\ah blah\
Agora, para a parte de divisão, é importante perceber que existem duas classes de caracteres para $IFS
: os caracteres de espaço em branco do IFS (ou seja, espaço e tab (e nova linha, embora aqui isso não importe, a menos que você use -d), o que também acontece estar no valor padrão de $IFS
) e os outros. O tratamento para essas duas classes de personagens é diferente.
Com IFS=:
( :
não sendo um espaço em branco IFS), uma entrada como :foo::bar::
seria dividido em ""
, "foo"
, ""
, bar
e ""
(e um extra ""
com algumas implementações embora isso não importa, exceto read -a
). Enquanto se substituirmos isso :
por espaço, a divisão será feita em somente foo
e bar
. Os principais e os finais são ignorados e as sequências são tratadas como uma. Existem regras adicionais quando caracteres em branco e não em branco são combinados $IFS
. Algumas implementações podem adicionar / remover o tratamento especial dobrando os caracteres no IFS ( IFS=::
ou IFS=' '
).
Portanto, aqui, se não queremos que os caracteres de espaço em branco à esquerda e à esquerda sejam removidos, precisamos remover esses caracteres de espaço em branco do IFS do IFS.
Mesmo com caracteres IFS que não sejam espaços em branco, se a linha de entrada contiver um (e apenas um) desses caracteres e for o último caractere na linha (como IFS=: read -r word
em uma entrada como foo:
) com shells POSIX (não, zsh
nem em algumas pdksh
versões), essa entrada é considerado como uma foo
palavra, porque nessas conchas, os caracteres $IFS
são considerados terminadores ; portanto word
, conterão foo
, não foo:
.
Portanto, a maneira canônica de ler uma linha de entrada com o read
builtin é:
IFS= read -r line
(observe que, na maioria das read
implementações, isso funciona apenas para linhas de texto, pois o caractere NUL não é suportado, exceto em zsh
).
O uso da var=value cmd
sintaxe garante que IFS
somente seja definido de forma diferente pela duração desse cmd
comando.
Nota do histórico
O read
builtin foi introduzido pelo shell Bourne e já devia ler palavras , não linhas. Existem algumas diferenças importantes com os shells POSIX modernos.
O shell Bourne read
não suportava uma -r
opção (que foi introduzida pelo shell Korn), então não há como desativar o processamento de barra invertida além de pré-processar a entrada com algo parecido sed 's/\\/&&/g'
.
O shell Bourne não tinha a noção de duas classes de caracteres (que novamente foram introduzidas pelo ksh). No shell Bourne, todos os caracteres passam pelo mesmo tratamento que os caracteres de espaço em branco do IFS no ksh, ou seja, IFS=: read a b c
em uma entrada que foo::bar
seria atribuída bar
a $b
, e não na sequência vazia.
No shell Bourne, com:
var=value cmd
Se cmd
for um built-in (como read
é), var
permanece definido como value
após a cmd
conclusão. Isso é particularmente crítico $IFS
porque, no shell Bourne, $IFS
é usado para dividir tudo, não apenas as expansões. Além disso, se você remover o caractere de espaço do $IFS
shell Bourne, "$@"
não funcionará mais.
No shell Bourne, o redirecionamento de um comando composto faz com que ele seja executado em um subshell (nas versões anteriores, até coisas como read var < file
ou exec 3< file; read var <&3
não funcionavam), portanto, era raro no shell Bourne usar read
para qualquer coisa, exceto a entrada do usuário no terminal (onde esse tratamento de continuação de linha fazia sentido)
Alguns Unices (como HP / UX, também há um util-linux
) ainda têm um line
comando para ler uma linha de entrada (que costumava ser um comando UNIX padrão até a Especificação Única do UNIX versão 2 ).
É basicamente o mesmo, head -n 1
exceto que ele lê um byte de cada vez para garantir que não leia mais de uma linha. Nesses sistemas, você pode fazer:
line=`line`
Obviamente, isso significa gerar um novo processo, executar um comando e ler sua saída através de um pipe, muito menos eficiente que o ksh IFS= read -r line
, mas ainda muito mais intuitivo.