Obviamente, entendo que se pode agregar valor à variável separadora de campo interno. Por exemplo:
$ IFS=blah
$ echo "$IFS"
blah
$
Também entendo que read -r line
os dados serão salvos na stdin
variável denominada line
:
$ read -r line <<< blah
$ echo "$line"
blah
$
No entanto, como um comando pode atribuir valor variável? E ele primeiro armazena dados de stdin
para variável line
e depois atribui valor line
a IFS
?
bash
shell-script
Martin
fonte
fonte
Respostas:
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
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:
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, excetoread -a
). Enquanto se substituirmos isso:
por espaço, a divisão será feita em somentefoo
ebar
. 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=::
ouIFS=' '
).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 comofoo:
) com shells POSIX (não,zsh
nem em algumaspdksh
versões), essa entrada é considerado como umafoo
palavra, porque nessas conchas, os caracteres$IFS
são considerados terminadores ; portantoword
, conterãofoo
, nãofoo:
.Portanto, a maneira canônica de ler uma linha de entrada com o
read
builtin é:(observe que, na maioria das
read
implementações, isso funciona apenas para linhas de texto, pois o caractere NUL não é suportado, exceto emzsh
).O uso da
var=value cmd
sintaxe garante queIFS
somente seja definido de forma diferente pela duração dessecmd
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 parecidosed '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 quefoo::bar
seria atribuídabar
a$b
, e não na sequência vazia.No shell Bourne, com:
Se
cmd
for um built-in (comoread
é),var
permanece definido comovalue
após acmd
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
ouexec 3< file; read var <&3
não funcionavam), portanto, era raro no shell Bourne usarread
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 umline
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: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.fonte
sh
diferenças é útil também para escrever scripts portáteis!)bash-4.4.19
,while read -r; do echo "'$REPLY'"; done
funciona comowhile IFS= read -r line; do echo "'$line'"; done
.read
a leitura de uma linha é errôneo, deve haver algo mais. O que poderia ser essa noção não errônea? Ou essa primeira afirmação é tecnicamente correta, mas, na verdade, a noção não errônea é: "read é o comando para ler palavras de uma linha. Por ser tão poderosa, você pode usá-lo para ler linhas de um arquivo fazendo o seguinte:IFS= read -r line
"A teoria
Existem dois conceitos em jogo aqui:
IFS
é o separador de campos de entrada, o que significa que a sequência de caracteres lida será dividida com base nos caracteres emIFS
. Em uma linha de comando,IFS
normalmente existem caracteres de espaço em branco, é por isso que a linha de comando é dividida em espaços.VAR=value command
significa "modificar o ambiente de comando para queVAR
tenha o valorvalue
". Basicamente, o comandocommand
veráVAR
como tendo o valorvalue
, mas qualquer comando executado depois disso continuaráVAR
com o valor anterior. Em outras palavras, essa variável será modificada apenas para essa instrução.Nesse caso
Portanto, ao fazer
IFS= read -r line
, o que você está fazendo é definirIFS
uma string vazia (nenhum caractere será usado para dividir; portanto, nenhuma divisão ocorrerá), para queread
leia a linha inteira e a veja como uma palavra que será atribuída àline
variável. As alteraçõesIFS
afetam apenas essa instrução, para que os seguintes comandos não sejam afetados pela alteração.Como uma nota rodapé
Embora o comando esteja correto e funcione conforme o planejado, a configuração
IFS
nesse casonão é1, pode não ser necessária. Conforme escrito nabash
página de manual naread
seção incorporada:Como você tem apenas a
line
variável, todas as palavras serão atribuídas a ela de qualquer maneira, portanto, se você não precisar de nenhum dos caracteres de espaço em branco precedente e à direita 1, basta escreverread -r line
e concluir com ela.[1] Apenas como um exemplo de como um valor
unset
ou padrão$IFS
fará comread
que o espaço em branco do IFS seja inicial / final , você pode tentar:Execute-o e você verá que os caracteres anteriores e finais não sobreviverão se
IFS
não estiverem definidos. Além disso, algumas coisas estranhas poderiam acontecer se$IFS
fosse modificado em algum lugar anteriormente no script.fonte
Você deve ler essa declaração em duas partes, a primeira apaga o valor da variável IFS, ou seja, é equivalente à mais legível
IFS=""
, a segunda está lendo aline
variável a partir de stdinread -r line
,.O que é específico nessa sintaxe é que a afetação do IFS é transitória e válida apenas para o
read
comando.A menos que esteja faltando alguma coisa, nesse caso específico, a limpezaIFS
não tem efeito, pois, comoIFS
estiver definido, a linha inteira será lida naline
variável. Haveria uma mudança de comportamento apenas no caso de mais de uma variável ter sido passada como parâmetro para aread
instrução.Editar:
O
-r
existe para permitir que a entrada que termina com\
a não ser processada especialmente, isto é, para a barra invertida para ser incluído naline
variável e não como um carácter de continuação para permitir a entrada multi-linha.A limpeza do IFS tem o efeito colateral de impedir a leitura para aparar possíveis caracteres iniciais ou finais de espaço ou tabulação, por exemplo:
Obrigado a rici por apontar essa diferença.
fonte
read -r line
cortará os espaços em branco à esquerda e à direita antes de atribuir a entrada àline
variável.IFS= read a b <<< 'aa bb' ; echo "-$a-$b-"
mostrará-aa bb--