Como agrupar comandos depois de '&&' ou '||' Ramo e Não Invocar Restrição de Variável de Subshell? [duplicado]

1

Este script apenas tenta copiar o arquivo foo para bar/ e, se não der certo, tenta novamente mais 2 vezes.

EDITAR: como se vê, o motivo pelo qual o segundo exemplo funciona e o primeiro não, é porque o uso de (...) para executar um grupo de comandos após o && ou || branch é considerado um subshell e não afeta a variável externa.

Então, pergunta revisada: como posso agrupar alguns comandos como o primeiro exemplo, mas não usar um subshell para fazer isso?

EDIT 2: a resposta é substituir ( ... ) por { ...; } !

#!/usr/bin/env bash
#

# Be safe
set -euo pipefail

# Init variables
__file="foo"
output_dir="bar"
logfile="test.log"
errorlevel=8
count=0

# Try three times to copy a file
until (( errorlevel == 0 || count == 3 )); do

  cp -Lv --no-preserve=mode \
    "$__file" \
    "$output_dir/" \
    | tee -a "$logfile" \
    && (errorlevel=$?; echo "zero return, $errorlevel") \
    || (errorlevel=$?; echo "non-zero return, $errorlevel")

  echo "errorlevel $errorlevel"

  (( ++count ))

  echo "count $count"

done

unset errorlevel
unset count

O problema:

Quando a cópia falha ou é bem-sucedida, a ramificação || ou && é capturada corretamente e define a variável errorlevel a 1 ou 0 , respectivamente, conforme evidenciado com o echo $errorlevel contido na ramo.

Mas o próximo comando, também um eco, retorna o valor inicial da variável 8 ! Eu não entendo porque isso está acontecendo.

Algumas notas:

  1. Estou usando && e || explicitamente porque tenho set -e para tornar esse script o mais seguro possível (essa é, na verdade, uma pequena rotina em um script de 1500 linhas). Se eu não usar || para capturar um retorno diferente de zero, o script será encerrado. Alternativamente, eu poderia set +e , fazer a rotina de cópia sem ter que usar && e || (capturando o errorlevel uma vez) e, em seguida, set -e novamente, mas prefiro não porque não corresponde ao estilo do resto do meu script.
  2. Estou usando set -o pipefail para passar um nível de erro diferente de zero pelo pipeline, para que meu tee -a "$logfile" nem sempre substitua minha saída errorlevel .
  3. Estou usando ++count em vez de count++ porque o primeiro retorna um nível de erro zero e o segundo retorna um nível de erro diferente de zero (e eu teria que fazer um(( count++ )) || true feio por causa do set -e explicado na nota # 1).

Eu tenho trabalhado em torno disso por agora, definindo a variável errorlevel explicitamente para 1 ou 0 , já que eu realmente não me importo com o que é zeroerrorlevel, mas eu ainda gostaria para saber por que o acima não está funcionando como eu esperava.

Veja a solução alternativa:

#!/usr/bin/env bash
#

set -euo pipefail

__file="foo"
output_dir="bar"
logfile="test.log"

errorlevel=1
count=0
until (( errorlevel == 0 || count == 5 )); do
  cp -Lv --no-preserve=mode \
    "$__file" \
    "$output_dir/" \
    | tee -a "$logfile" \
    && errorlevel=0 \
    || errorlevel=1
  (( ++count ))
  sleep 1
done

unset errorlevel
unset count

Qualquer ideia é muito apreciada!

    
por yottabit 12.10.2017 / 02:25

1 resposta

1

Eu prefiro usar um bloco if :

errorlevel=0
if cp -Lv --no-preserve=mode \
    "$__file" \
    "$output_dir/" \
    | tee -a "$logfile"
then
    echo "zero return, $errorlevel"
else
    errorlevel=$?
    echo "non-zero return, $errorlevel"
fi

O if protege um status de saída diferente de zero de acionar a ação -e exit-on-error.

(O problema original, é claro, é que ( ) cria um subshell, então a atribuição da variável nunca afeta o shell pai.)

    
por 12.10.2017 / 03:52