bash: come propagare errori nella sostituzione del processo?


19

Voglio che i miei script di shell falliscano ogni volta che un comando eseguito con loro fallisce.

In genere lo faccio con:

set -e
set -o pipefail

(in genere aggiungo set -uanche io )

Il fatto è che nessuna delle precedenti funziona con la sostituzione del processo. Questo codice stampa "ok" ed esce con codice di ritorno = 0, mentre vorrei che fallisse:

#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)

Esiste qualcosa di equivalente a "pipefail" se non per la sostituzione del processo? Qualche altro modo per passare a un comando l'output dei comandi come se fossero file, ma generando un errore ogni volta che uno di questi programmi fallisce?

La soluzione di un uomo povero sarebbe rilevare se quei comandi scrivono a stderr (ma alcuni comandi scrivono a stderr in scenari di successo).

Un'altra soluzione più conforme a posix sarebbe l'uso di named pipe, ma ho bisogno di lanciare quei comandi che usano la sostituzione del processo come oneliners costruiti al volo dal codice compilato, e la creazione di named pipe complicherà le cose (comandi extra, errore di intrappolamento per eliminandoli, ecc.)


Non è necessario intercettare errori per eliminare le named pipe. In realtà, non è affatto un buon modo. Prendere in considerazione mkfifo pipe; { rm pipe; cat <file; } >pipe. Questo comando si bloccherà fino a quando un lettore apre pipeperché è il guscio che fa l' open()e così, non appena v'è un lettore su pipeFS collegamento per pipeIS rm'd e quindi catcopie infile fuori al descrittore della shell per quel tubo. E comunque, se vuoi propagare un errore fuori da un processo sub do: <( ! : || kill -2 "$$")
mikeserv

Grazie per il suggerimento sull'eliminazione di pipe denominate. Sfortunatamente, la $$sostituzione non funziona per me, poiché la sostituzione del comando non viene eseguita poiché il comando che utilizza la sostituzione del processo viene eseguito all'interno di una pipeline di comandi generata da un codice "non shell" (python). Probabilmente dovrei creare un sottoprocesso in Python e installarli a livello di codice.
juanleon,

Quindi usa kill -2 0.
mikeserv,

Sfortunatamente "kill -2 0" ucciderebbe (segnale) Python. Scrivere gestori di segnali per eseguire la logica aziendale all'interno di un'applicazione multithread non è qualcosa che non vedo l'ora di :-)
juanleon

Se non vuoi gestire i segnali, perché stai provando a riceverli? Ad ogni modo, mi aspetto che il nome pipe sia la soluzione più semplice alla fine, se non altro perché farlo nel modo giusto richiede di definire il proprio framework e impostare il proprio dispatcher personalizzato. Supera quell'ostacolo e tutto si riunisce.
Mikeserv,

Risposte:


6

Ad esempio, potresti solo aggirare quel problema:

cat <(false || kill $$) <(echo ok)
other_command

La subshell dello script è SIGTERMd prima che il secondo comando possa essere eseguito ( other_command). Il echo okcomando viene eseguito "a volte": il problema è che le sostituzioni di processo sono asincrone. Non è garantito che il kill $$comando sia eseguito prima o dopo il echo okcomando. È una questione di pianificazione dei sistemi operativi.

Considera uno script bash come questo:

#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"

L'output di quello script può essere:

$ ./script
Terminated
$ echo $?
143           # it's 128 + 15 (signal number of SIGTERM)

O:

$ ./script
Terminated
$ pre
post

$ echo $?
143

Puoi provarlo e dopo alcuni tentativi, vedrai i due diversi ordini nell'output. Nel primo lo script è stato terminato prima che gli altri due echocomandi potessero scrivere nel descrittore di file. Nel secondo falseil killcomando o era probabilmente programmato dopo i echocomandi.

O per essere più precisi: la chiamata signal()di sistema killdell'utilità che invia il SIGTERMsegnale al processo di shell è stata programmata (o consegnata) più tardi o prima delle ecoscandagli write()dell'eco.

Tuttavia, lo script si interrompe e il codice di uscita non è 0. Dovrebbe quindi risolvere il problema.

Un'altra soluzione è, ovviamente, utilizzare pipe denominate per questo. Ma dipende dal tuo script quanto sia complesso implementare named pipe o la soluzione alternativa sopra.

Riferimenti:


2

Per la cronaca, e anche se le risposte e i commenti erano buoni e utili, ho finito di implementare qualcosa di leggermente diverso (avevo alcune restrizioni sulla ricezione di segnali nel processo genitore che non avevo menzionato nella domanda)

Fondamentalmente, ho finito per fare qualcosa del genere:

command <(subcomand 2>error_file && rm error_file) <(....) ...

Quindi controllo il file di errore. Se esiste, so quale sottocomando non è riuscito (e il contenuto del file_errore può essere utile). Più prolisso e incisivo che inizialmente desideravo, ma meno ingombrante rispetto alla creazione di pipe con nome in un comando bash da una riga.


Qualunque cosa funzioni per te è di solito il modo migliore. Grazie soprattutto per il ritorno e la risposta: i selfie sono i miei preferiti.
Mikeserv,

2

Questo esempio mostra come usare killinsieme a trap.

#! /bin/bash
failure ()
{
  echo 'sub process failed' >&2
  exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )

Ma killnon è possibile passare un codice di ritorno dal processo secondario al processo principale.


Mi piace questa variazione sulla risposta accettata
sehe

1

In un modo simile a quello che avresti implementato $PIPESTATUS/ $pipestatuscon una shell POSIX che non lo supporta , puoi ottenere lo stato di uscita dei comandi facendoli passare attraverso una pipe:

unset -v false_status echo_status
{ code=$(
    exec 3>&1 >&4 4>&-
    cat 3>&- <(false 3>&-; echo >&3 "false_status=$?") \
             <(echo ok 3>&-; echo >&3 "echo_status=$?")
);} 4>&1
cat_status=$?
eval "$code"
printf '%s_code=%d\n' cat   "$cat_status" \
                      false "$false_status" \
                      echo  "$echo_status"

Che dà:

ok
cat_code=0
false_code=1
echo_code=0

Oppure potresti usare pipefaile implementare la sostituzione dei processi a mano come faresti con le shell che non lo supportano:

set -o pipefail
{
  false <&5 5<&- | {
    echo OK <&5 5<&- | {
      cat /dev/fd/3 /dev/fd/4
    } 4<&0 <&5 5<&-
  } 3<&0
} 5<&0
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.