Prática recomendada para usar $? na festança?

10

Quando li esta resposta sobre $? outra pergunta vem à mente.

Existe alguma prática recomendada de como usar $? na festança?


Vamos dar um exemplo:

Temos um script linear e gostaríamos de saber que todo o comando foi executado ok. Você acha que é bom chamar uma função pequena (vamos chamá-la de "did_it_work"), para verificar o código de erro e interromper se não estiver.

#!/bin/bash 

function did_it_work {
    code=$1
    if [ "$code" -ne "0" ]
    then
        echo "Error failure: code $code "
        exit 1
    fi
}

dir=some/path

mkdir -p $dir
did_it_work $? 

cd $dir
did_it_work $? 

run_some_command
did_it_work $? 

É claro que essa abordagem significa que eu tenho que resolver manualmente o problema, se houver algum, e executar novamente o script.

Você acha que essa é uma boa idéia ou há outras práticas recomendadas para fazer isso?

/Obrigado

Johan
fonte

Respostas:

15

Uma maneira comum é:

die() {
    IFS=' ' # make sure "$*" is joined with spaces

    # output the arguments if any on stderr:
    [ "$#" -eq 0 ] || printf '%s\n' "$*" 1>&2
    exit 1
}

então você o usa assim:

mkdir -p some/path || die "mkdir failed with status $?"

Ou, se você quiser incluir o status de saída, poderá alterá-lo para:

die() {
    last_exit_status=$?
    IFS=' '
    printf '%s\n' "FATAL ERROR: $* (status $last_exit_status)" 1>&2
    exit 1
}

e depois usá-lo é um pouco mais fácil:

mkdir -p some/path || die "mkdir failed"

Quando falha, mkdirprovavelmente já emitiu uma mensagem de erro, para que a segunda seja vista como redundante, e você pode simplesmente:

mkdir -p some/path || exit   # with the same (failing) exit status as mkdir's
mkdir -p some/path || exit 1 # with exit status 1 always

(ou use a primeira variante dieacima sem argumento)

Caso você não tenha visto command1 || command2antes, ele será executado command1e, se command1falhar, será executado command2.

Então você pode ler como "criar o diretório ou morrer".

Seu exemplo seria semelhante a:

mkdir -p some/path || die "mkdir failed"
cd some/path || die "cd failed"
some_command || die "some_command failed"

Ou você pode alinhar diesmais à direita, para que o código principal seja mais óbvio.

mkdir -p some/path         || die "mkdir failed"
cd some/path               || die "cd failed"
some_command               || die "some_command failed"

Ou na linha a seguir, quando as linhas de comando forem longas:

mkdir -p some/path ||
  die "mkdir failed"

cd some/path ||
  die "cd failed"

some_command ||
  die "some_command failed"

Além disso, se você usar o nome some/pathvárias vezes, armazene-o em uma variável para não precisar digitá-lo e alterá-lo facilmente, se necessário. E ao passar argumentos variáveis ​​para comandos, certifique-se de usar o --delimitador de opções para que o argumento não seja considerado uma opção se começar com -.

dir=some/path
mkdir -p -- "$dir"         || die "Cannot make $dir"
cd -P -- "$dir"            || die "Cannot cd to $dir"
some_command               || die "Cannot run some_command"
Mikel
fonte
9

Você pode reescrever seu código assim:

#!/bin/bash
function try {
    "$@"
    code=$?
    if [ $code -ne 0 ]
    then
        echo "$1 did not work: exit status $code"
        exit 1
    fi
}

try mkdir -p some/path
try cd some/path
try run_some_command

Se você realmente não precisa registrar o código de erro, mas apenas se o comando foi bem-sucedido ou não, você pode diminuir try()ainda mais:

function try {
    if ! "$@"
    then
        echo "$1 did not work"
        exit 1
    fi
}
Warren Young
fonte
Você também pode usar o código neste formato. função <pre> try {
BillThor
Se você usar essa funcionalidade em linha e não quiser retornar do lado verdadeiro, poderá substituí return $?-lo pelo :interno.
BillThor
8

Se você realmente deseja exitum erro e está usando o Bash, também deve considerar set -e. De help set:

-e Sai imediatamente se um comando sair com um status diferente de zero.

Obviamente, isso não oferece a flexibilidade de uma função did_it_work (), mas é uma maneira fácil de garantir que seu script bash pare com um erro sem adicionar muitas chamadas à sua nova função.

Steven D
fonte
set -eé útil. Existem alguns comandos que retornam diferente de zero em circunstâncias normais (por exemplo, diff). Quando estou usando set -e em um script em que espero um retorno diferente de zero, eu faço command || true.
Shawn J. Goff
2
Além disso, mesmo se você usar set -e, poderá definir um "manipulador de exceções" para detectar todos os erros trap did_it_work EXIT.
Gilles 'SO- stop be evil' em
1
Isso faz parte do POSIX, não um recurso específico do bash. Usá-lo, mas estar ciente de algumas armadilhas mywiki.wooledge.org/BashFAQ/105
kmkaplan
@ ShawnJ.Goff eu prefiro fazer command && true. Assim, o valor de retorno não é alterado.
kmkaplan
@kmkaplan Seu último comentário não faz nenhum sentido para mim. O objetivo de command || trueé impedir a set -esaída do script se commandretornar um código de saída diferente de zero. Altera o código de saída porque precisamos. A única coisa que command && truefaz é executar true(retornar um código de saída zero) se o comando `for bem-sucedido (retornou um código de saída zero) - é um no-op completo.
tripleee