Le migliori pratiche per usare $? a bash?


10

Quando leggo questa risposta su $? un'altra domanda mi viene in mente.

Esistono delle migliori pratiche su come usare $? a bash?


Facciamo un esempio:

Abbiamo uno script lineare e vorrei sapere che tutto il comando è stato eseguito correttamente. Pensi che sia giusto chiamare una piccola funzione (chiamiamola "did_it_work"), per controllare il codice di errore e rompere se non lo è.

#!/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 $? 

Questo approccio ovviamente significa che devo risolvere manualmente il problema, se presente, e rieseguire lo script.

Pensi che sia una buona idea o ci sono altre buone pratiche per farlo?

/Grazie

Risposte:


15

Un modo comune è:

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

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

quindi lo usi in questo modo:

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

Oppure, se si desidera che includa lo stato di uscita, è possibile modificarlo in:

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

e poi usarlo è un po 'più semplice:

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

Quando fallisce, mkdirprobabilmente avrà già emesso un messaggio di errore, in modo che il secondo possa essere visto come ridondante e potresti semplicemente fare:

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

(o usa la prima variante di cui diesopra senza argomento)

Nel caso in cui non l'avessi mai visto command1 || command2prima, funziona command1e, se command1fallisce, funzionacommand2 .

Quindi puoi leggerlo come "make the directory or die".

Il tuo esempio sarebbe simile a:

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

Oppure puoi allineare l' diesulteriore a destra in modo che il codice principale sia più evidente.

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

O sulla riga seguente quando le righe di comando sono lunghe:

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

cd some/path ||
  die "cd failed"

some_command ||
  die "some_command failed"

Inoltre, se hai intenzione di utilizzare il nome some/pathpiù volte, memorizzalo in una variabile in modo da non dover continuare a digitarlo e, se necessario, puoi cambiarlo facilmente. E quando si passano argomenti variabili ai comandi, assicurarsi di utilizzare il --delimitatore di opzioni in modo che l'argomento non venga preso come opzione se inizia con -.

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"

9

È possibile riscrivere il codice in questo modo:

#!/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 in realtà non è necessario registrare il codice di errore, ma solo se il comando ha avuto esito positivo o meno, è possibile abbreviare try()ulteriormente in questo modo:

function try {
    if ! "$@"
    then
        echo "$1 did not work"
        exit 1
    fi
}

Puoi anche usare il codice in questo formato. <pre> funzione try {
BillThor

Se si utilizza questa funzionalità in linea e non si desidera tornare dal lato reale, è possibile sostituirla return $?con :incorporata.
BillThor

8

Se vuoi davvero fare exitun errore e stai usando Bash, dovresti anche considerare set -e. Da help set:

-e Esce immediatamente se un comando esce con uno stato diverso da zero.

Questo ovviamente non ti dà la flessibilità di una funzione did_it_work (), ma è un modo semplice per assicurarti che il tuo script bash si fermi su un errore senza aggiungere molte chiamate alla tua nuova funzione.


set -eè utile. Esistono alcuni comandi che restituiscono valori diversi da zero in circostanze normali (ad esempio, diff). Quando sto usando set -e in uno script in cui mi aspetto un ritorno diverso da zero, lo faccio command || true.
Shawn J. Goff

2
Inoltre, anche se lo si utilizza set -e, è possibile impostare un "gestore eccezioni" per rilevare tutti gli errori trap did_it_work EXIT.
Gilles 'SO-smetti di essere malvagio' l'

1
Questo fa parte di POSIX, non una funzione specifica di bash. Usalo ma fai
kmkaplan

@ ShawnJ.Goff Preferisco fare command && true. In questo modo il valore restituito non viene modificato.
kmkaplan,

@kmkaplan Il tuo ultimo commento non ha alcun senso per me. Lo scopo di command || trueè impedire l' set -euscita dallo script se commandrestituisce un codice di uscita diverso da zero. Cambia il codice di uscita perché è necessario. L'unica cosa che command && truefa è eseguire true(restituire un codice di uscita zero) se il comando `ha avuto successo (ha restituito un codice di uscita zero) - è un no-op completo.
Tripleee,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.