Por que set-o errexit está quebrando essa expressão read / heredoc?

7

Eu tenho usado o padrão abaixo para imprimir mensagens de várias linhas no terminal em um script bash.

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF
echo "$message"

Isso tem funcionado - até poucos dias atrás, o padrão simplesmente parou de funcionar. Ao parar de funcionar, quero dizer quando bash encontrou essas expressões heredoc no script - parece não fazer nada - sem saída.
A única coisa que posso pensar sobre isso mudou nos últimos dias é que o ambiente em que os scripts são executados é um Ubuntu live USB 14.04, versus instalações "completas". Então eu descobri que quando eu movia o heredoc antes da declaração set -o errexit dos scripts ele começou a funcionar novamente. ou seja, isso não funciona

#!/bin/bash

set -o errexit

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF

echo "$message"

resultado: (nada)
Mas isso funciona

#!/bin/bash

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF

echo "$message"

resultado

$ sudo ./script.sh 
this is a 
mulitline
message
  • bash --version - GNU bash, version 4.3.11(1)-release (i686-pc-linux-gnu)
por the_velour_fog 23.02.2016 / 05:00

4 respostas

11

read retorna um status de saída diferente de zero se não encontrar um delimitador, o que é sempre o caso quando o delimitador é uma string vazia.

    
por 23.02.2016 / 05:30
8

O código de saída do comando de leitura é 1 quando o marcador de fim de arquivo (EOF) é atingido. Isso sempre ocorrerá quando o delimitador -d for nulo '' neste caso especial em que o fluxo de origem é um heredoc que não pôde conter um \ 0.

$ read -d '' message <<-_ThisMessageEnds_
>     this is a
>     multi line
>     message
> _ThisMessageEnds_
$ exitval=$?
$ echo "The exit val was $exitval"
The exit val was 1.

Se o valor de saída for um erro (não 0), a saída do script pode ser evitada com uma construção AND / OR:

read -d '' message <<-_ThisMessageEnds_ || echo "$message"
    this is a
    multi line
    message
_ThisMessageEnds_

Isso enviará a mensagem para o console e evitará que saia com errexit .

Mas, como estamos nesse caminho para reduzir, por que não usar isso diretamente:

cat <<-_ThisMessageEnds_
    this is a
    mulitline
    message
_ThisMessageEnds_

Nenhum comando de leitura executado (mais velocidade), nenhuma variável necessária, nenhum erro do código de saída, menos código para manter.

    
por 23.02.2016 / 07:41
7
read -d '' message

lê stdin até o primeiro caractere NUL sem escape (como você não adicionou -r ) ou o final da entrada e armazena os dados após $IFS e processamento de caracteres de barra invertida em $message (sem o delimitador).

Se nenhum delimitador sem escape for encontrado na entrada, o status de saída read será diferente de zero. Apenas retorna 0 (sucesso) se um registro completo e terminado for lido.

É mais útil para lidar com registros delimitados por NUL, como a saída de find -print0 (embora você precise de uma sintaxe IFS= read -rd '' record ).

Aqui, você precisa incluir um delimitador NUL em seu documento aqui para que read retorne com êxito. Isso, no entanto, não é possível com bash , que tira os caracteres NUL dos documentos here (que é pelo menos melhor que yash que retira tudo além do primeiro NUL, ou ksh93, que parece entrar em um loop infinito quando um documento aqui contém um NUL).

zsh é o único shell que pode ter um NUL em seus documentos aqui ou armazená-lo em suas variáveis ou passar caracteres NUL em argumentos para seus builtins / funções. Em zsh , você pode fazer:

NUL=$'
ONE=$''
IFS= read -d "$ONE" -r var << EOF
1
2
3$ONE
EOF
' IFS= read -d $NUL -r var << EOF 1 2 3$NUL EOF

( zsh também entende read -d '' como um delimitador NUL como bash . read -d $'bash' também funciona em read , mas que transmite um argumento vazio para read -d '' , como em bash as $NUL não suporta bytes NUL em sua linha de comando).

(observe que há um caractere extra de nova linha depois disso bash )

Em read -d , você pode usar um caractere diferente:

var=$(cat <<EOF
message
here
EOF
)

Mas você também pode fazer:

read -d '' message

Isso ainda não permitirá caracteres NUL. No entanto, esse é o código padrão, portanto, você não precisa confiar no ksh93 do zsh / bash específico. Observe também que ele remove todos os caracteres de nova linha à direita e, exceto em cat quando o %code% builtin está ativado, isso significa gerar um processo e um comando extra.

    
por 24.02.2016 / 14:07
4

Quando você usa set -o errexit e seu script quebra, significa que há algo errado.

Aqui, é read , que não consegue ler corretamente sua entrada.

Em bash , quando você usa read -d '' , o read builtin usará o caractere nulo read como terminador de linha. Portanto, quando não houver message em sua entrada, 1 lerá toda a entrada na variável read e retornará um status de saída diferente de zero para indicar que houve um erro:

$ while read -d '' line; do echo "$line"; done < <(printf '1')

imprime nada enquanto:

$ while read -d '' line; do echo "$line"; done < <(printf '1
$ while read -d '' line; do echo "$line"; done < <(printf '1')
') 1

fornece read .

while também retornará status diferente de zero quando atingir EOF, mas é usado para indicar que não há mais entrada para ler quando você usa while com um loop %code% , portanto, o loop %code% pode ser terminado. Não é relevante para o seu problema.

    
por 23.02.2016 / 12:01

Tags