No bash 5.0, desejo capturar o ${PIPESTATUS[@]}
comando canalizado que é executado via eval
. No entanto, eval
parece mascarar ${PIPESTATUS[@]}
, mas não mascara, $?
o que é equivalente a ${PIPESTATUS[-1]}
. Existe uma maneira de extrair ${PIPESTATUS[@]}
dos resultados de eval
? Acrescentar algo à string de comando abaixo parece && array=( ${PIPESTATUS[@]} ) && export array
não funcionar.
Estou correto ao presumir que isso $?
não é simplesmente ${PIPESTATUS[-1]}
?
Meu código de exemplo (executado como root):
#!/usr/bin/env bash
#without eval, ${PIPESTATUS[@]} has two entries as it should.
apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log
commandsPipestatus=( ${PIPESTATUS[@]} )
for status in ${commandsPipestatus[@]}; do
echo $status
done
echo ""
echo ""
#with eval, ${PIPESTATUS[@]} has one entry and is equal to $?
commandstring="apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log"
eval "$commandstring"
commandsPipestatus=( ${PIPESTATUS[@]} )
for status in ${commandsPipestatus[@]}; do
echo $status
done
EDIT: Corrigida uma pequena correção técnica em minha declaração original sobre PIPESTATUS. Além disso, com base nas respostas abaixo, aqui estão alguns esclarecimentos:
- Estou usando
eval
porque estou construindo strings de comando programaticamente e algumas delas podem conterbash -c
... que executam o comando como usuários diferentes. - Analisei a configuração
pipefail
e isso pode funcionar às vezes, embora nem sempre, porque às vezes preciso saber o status de várias etapas no tubo. Se eu pudesse definiset -o pipefail
-lo e desativá-lo depois, isso poderia funcionar, mas não estou vendo como desativá-lopipefail
e não posso simplesmente sair do subshell e continuar em um novo ondepipefail
não foi definido. Como desmarco uma opção de shell comopipefail
? - Meu entendimento acima sobre como exportar um array que contém está
PIPESTATUS
incorreto? Como posso simplesmente exportarPIPESTATUS
doeval
subshell?
EDIT 2: Graças à excelente resposta marcada abaixo, minha decisão final foi para o caso em que estou montando dinamicamente strings de comando que incluem redirecionamento (é por isso que preciso de eval), eu usaria e depois da execução set -o pipefail
do set +o pipefail
.
Também estou pesquisando novamente as práticas recomendadas para executar comandos criados dinamicamente, pois tenho vários anos a mais de experiência em bash desde a última vez que o fiz. Meus casos de uso eval
são:
- Os comandos podem conter
bash -c
, então não posso usá-bash -c
los para executá-los - Pode conter redirecionamento, como
>> log
- Os comandos dinâmicos também podem ter argumentos adicionados a eles para fins de depuração
Pelo que sei, posso fazer as coisas de maneira diferente para 1 e, para 3, deveria usar coisas como set -x [command]
e trap
. Ainda não encontrei uma solução para 2.
$?
contém o status do componente mais à direita do último comando que foi executado (ou do componente com falha mais à direita, sepipefail
estiver definido), embora possivelmente modificado pela!
palavra-chave prefix.Os elementos de
$PIPESTATUS
são os status de saída de cada componente do pipeline anterior, sem!
efeito nos valores.Depois
(exit 1) | (exit 2) | (exit 3)
,$PIPESTATUS
será (1 2 3) e$?
será 3. Depois! (exit 1) | (exit 2) | (exit 3)
,$PIPESTATUS
será igual, mas$?
será 0, igual a$(( ! 3 ))
.false
ou(exit 1)
ainda é um pipeline com um componente, no primeiro caso um comando simples, no segundo um subshell, então você obtémPIPESTATUS=(1)
and$?
= 1.É o mesmo
eval 'any shell code'
que é apenas um comando simples.Depois
eval 'A | B' | C
,$PIPESTATUS
conterá o status de saída deeval
(que será o do último comando executado:)B
e o deC
. A propósito, o mesmo(A | B) | C
.Você poderia fazer:
Para preservar o status dos componentes do pipeline, conforme visto pelo intérprete invocado por
eval
, no seu caso:No entanto, aqui, parece-me que você prefere:
Aquilo é:
pipefail
opção para que essa função retorne falha se umapt-get
outee
falhar. Observe que o corpo da função é um subshell em vez do comando group command mais comum ({ ...; }
), o que significa que essa opção é definida apenas localmente. Com versões recentes dobash
, você também pode usarlocal -; set -o pipefail
a opção para ser definida localmente na função sem a necessidade de um subshell.Observe, entretanto, que tanto a saída normal quanto os erros
apt-get
serão enviados para stdout.Se estiver usando
zsh
, você poderia fazer:Para enviar o stdout do stderr
apt-get
para o log e para seu respectivo destino original. Atee
entrada é feita internamente e o status de saídaapt-get
é preservado.E talvez crie uma função auxiliar para isso, como:
Usar uma função em vez de
eval
é provavelmente a melhor solução.Então você poderia fazer algo assim, permitindo a instalação de vários pacotes, se necessário:
set -o pipefail
é uma maneira de exibir o erro doapt-get
comando sem precisar inspecionar tudoPIPESTATUS
, mas as coisas podem ficar um pouco confusas se, digamos, vocêtee
não conseguir gravar em seu log.EDIT: com base em informações adicionais
Suponho que o problema que você está enfrentando
eval
é porque ele está executando seu comando em um subshell (ou pelo menos se comportando de maneira semelhante). Parece que você receberá apenas um único código de saída (feliz por estar errado nessa suposição).Você pode imprimir o status do pipe dentro do comando, mas será complicado obtê-lo.
Essa monstruosidade é uma maneira de abordá-la, mas é bastante complicada e é necessária alguma personalização em torno do processamento de texto em relação ao seu comando, e ainda assim só é possível obter um código de saída no final
Eu sinto que há algo que poderia ser feito para alimentar a
awk
saída ereadarray
capturar os diferentes status dos tubos, mas vai ser muito confuso (além de já estar confuso).Quando algo fica tão feio, você deve começar a se perguntar se toda a abordagem está errada.
Talvez se você elaborar mais sobre seus requisitos, uma maneira diferente de fazer isso possa surgir.