
Estou tendo problemas com um caso que considero um bug no funcionamento do shell ou bash, no qual a execução de um comando não interativo fornecido por um heredoc (talvez de um arquivo também, não testei) e um read
(ou semelhante) no heredoc, faz com que o código após um read
comando sejaignorado, e a execução terminasem erros, executando os comandosdepoiso comando que executa o heredoc.
Mais especificamente, estou executando o código em um contêiner docker que atua como uma caixa de ferramentas para vários utilitários do Linux, e esse problema faz com que o código não seja realmente seguro, no sentido de que não posso confiar que isso acontecerá como espero.
Uma amostra do problema pode ser vista abaixo:
#!/bin/bash
set -eou pipefail
echo "before outside" >&2
docker run --rm -i ubuntu:20.04 /bin/bash <<-SHELL
set -eou pipefail
echo "before inside" >&2
read 'var'
echo "after inside" >&2
SHELL
echo "after outside" >&2
Eu esperava que o código acima executasse todos os ecos e terminasse com sucesso ou gerasse um erro no read
comando e terminasse com um erro.
Infelizmente, a saída do código acima é:
before outside
before inside
after outside
Basicamente ignorou o que estava depois do read
comando, talvez porque tenha uma read
instrução em um comando que não aloca um pseudo-tty ( docker run -it
aloca um pseudo-tty, mas não pode executar o código acima, por causa do heredoc que é canalizado para o comando, em vez disso ele fornece o erro the input device is not a TTY
:).
Se eu remover o 2 set -eou pipefail
tenho o mesmo problema, mesmo usando read 'var' ||:
, então não acho que tenha relação com (um dosPegadinhasde) set -e
.
Um exemplo de resultado esperado é o seguinte:
#!/bin/bash
set -eou pipefail
echo "before outside" >&2
docker run --rm -i ubuntu:20.04 /bin/bash <<-SHELL
set -eou pipefail
echo "before inside" >&2
unknow_command error ||:
echo "after inside" >&2
SHELL
echo "after outside" >&2
Que termina com sucesso e imprime:
before outside
before inside
/bin/bash: line 3: unknow_command: command not found
after inside
after outside
Ou:
#!/bin/bash
set -eou pipefail
echo "before outside" >&2
docker run --rm -i ubuntu:20.04 /bin/bash <<-SHELL
set -eou pipefail
echo "before inside" >&2
unknow_command error
echo "after inside" >&2
SHELL
echo "after outside" >&2
Que termina com um erro e imprime:
before outside
before inside
/bin/bash: line 3: unknow_command: command not found
Para mim estaria tudo bem se o read
comando fosse simplesmente ignorado (com um valor vazio como entrada, por exemplo) e todos os comandos posteriores fossem executados, mesmo os do heredoc, ou que desse um erro e parasse a execução imediatamente (se Não ignoro o erro no comando docker).
O acima foi apenas um exemplo, em um caso real pode ser muito pior porque o read
comando (ou similar) pode não ser chamado diretamente, mas dentro de um comando, e apenas sob algumas condições. Por exemplo:
#!/bin/bash
set -eou pipefail
docker run --rm -i my_mysql /bin/bash <<-SHELL
set -eou pipefail
some_important_command_1
mysql -u "$user" -p "$pass" -e "some sql command"
some_important_command_2
SHELL
some_important_command_after_2
O código acima pode parecer bom, mas se a senha estiver vazia, ele tentará ler do stdin, causando o problema no primeiro exemplo, pulando some_important_command_2
, mas executando some_important_command_after_2
isso deve ser executado somente depois some_important_command_2
.
O exemplo do mysql acima também foi apenas um exemplo, posso verificar neste caso se a senha está vazia e tratar dela.O verdadeiro problema é que não consigo determinar se esse problema pode acontecer dentro de um código e não vejo nenhuma maneira segura de evitá-lo, além de interromper o uso do contêiner docker toolbox e instalar todos os utilitários e outras coisas dentro de todos os hosts e mantê-los atualizados (em vez de apenas manter a imagem do contêiner atualizada). Também não funcionará para comandos executados especificamente em serviços dentro de contêineres (como o exemplo do mysql acima).
Alguém tem uma solução para o problema acima?Uma solução quenão é específicoaos exemplos que dei, mas uma abordagem geral que pode resolveresse tipo de bug(seja concluindo com sucesso, executando todos os comandos ou dando um erro e interrompendo todos os comandos subsequentes).
Atualizar:
Adicionando saídas declare -p var
posteriores echo "after inside"
:
before outside
before inside
declare -- var="echo \"after inside\" >&2"
after outside
Acho que o comando read acaba lendo o heredoc como @muru apontou nos comentários. Também posso ver se adicionar echo "var=\$var"
after echo "after inside"
, que imprime: var=echo "after inside" >&2
. Então agora preciso encontrar uma maneira de pular essas leituras do próprio heredoc.