È un bug in bash? `return` non esce dalla funzione se chiamato da una pipe


16

Ultimamente ho avuto strani problemi con bash. Durante il tentativo di semplificare il mio script, ho trovato questo piccolo pezzo di codice:

$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1

returnavrebbe dovuto uscire dalla funzione senza stampare $?, no? Bene, quindi ho verificato se posso tornare da una pipa da solo:

$ echo | while read -r; do return 1; done
bash: return: can only `return' from a function or sourced script

Lo stesso succede senza whileloop:

$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.

C'è qualcosa che mi manca qui? Una ricerca su Google non ha apportato nulla al riguardo! La mia versione bash è 4.2.37 (1)-release su Debian Wheezy.


Qualcosa non va nelle impostazioni che ho suggerito nella mia risposta che consentono al tuo script di comportarsi nel modo intuitivo che ti aspettavi che facesse?
jlliagre,

@jlliagre È uno script piuttosto complesso su migliaia di righe. Con la preoccupazione di rompere qualcos'altro, preferisco semplicemente evitare di eseguire una pipe all'interno della funzione, quindi l'ho sostituita con una sostituzione del processo. Grazie!
Teresa e Junior,

Perché non rimuovere i primi due esempi, se whilenon sono necessari per la riproduzione? Distrae dal punto.
Lightness Races with Monica

@LightnessRacesinOrbit Un whileloop è un utilizzo molto comune per una pipe con return. Il secondo esempio è più diretto al punto, ma è qualcosa che non credo che qualcuno avrebbe mai usato ...
Teresa e Junior,

1
Purtroppo la mia risposta corretta è stata cancellata ... Sei in una zona grigia mentre fai qualcosa che non è specificato. Il comportamento dipende da come la shell interpreta le pipe e questo è persino diverso tra Bourne Shell e Korn Shell anche se ksh è stato derivato da sorgenti sh. In Bourne Shell, il ciclo while è in una subshell quindi vedi l'eco come con bash, In ksh il ciclo while è il processo in primo piano e quindi ksh non chiama echo con il tuo esempio.
schily,

Risposte:


10

Correlato: /programming//a/7804208/4937930

Non è un bug che non è possibile uscire da uno script o tornare da una funzione di exito returnin subshells. Vengono eseguiti in un altro processo e non influiscono sul processo principale.

Inoltre, suppongo che tu stia vedendo comportamenti non documentati di bash su specifiche (probabilmente) indefinite. In una funzione, non viene affermato alcun errore per il returnlivello superiore dei comandi subshell e si comporta come exit.

IMHO è un bug bash per il comportamento incoerente di returndipendere dal fatto che l'istruzione principale sia o meno in una funzione.

#!/bin/bash

o() {
    # Runtime error, but no errors are asserted,
    # each $? is set to the return code.
    echo | return 10
    echo $?
    (return 11)
    echo $?

    # Valid, each $? is set to the exit code.
    echo | exit 12
    echo $?
    (exit 13)
    echo $?
}
o

# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?

# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?

Produzione:

$ bash script.sh 
10
11
12
13
script.sh: line 20: return: can only `return' from a function or sourced script
1
script.sh: line 22: return: can only `return' from a function or sourced script
1
22
23

La mancanza di verbosità dell'errore potrebbe non essere documentata. Ma il fatto che returnnon funzioni da una sequenza di comandi di livello superiore in una subshell, e in particolare non esca dalla subshell, è ciò che i documenti esistenti mi hanno già fatto aspettare. L'OP potrebbe utilizzare exit 1 || return 1dove stanno cercando di utilizzare returne quindi ottenere il comportamento previsto. EDIT: la risposta di @ herbert indica che il livello superiore returnin subshell funziona come exit(ma solo dalla subshell).
dubiousjim,

1
@dubiousjim Aggiornato il mio script. Voglio dire returnin una semplice sequenza subshell dovrebbe essere asserito come errore di runtime in ogni caso , ma in realtà non lo è quando si verifica in una fucntion. Questo problema è stato discusso anche in gnu.bash.bug , ma non ci sono conclusioni.
Yaegashi,

1
La tua risposta non è corretta in quanto non è specificato se il ciclo while si trova in una subshell o è il processo in primo piano. Indipendentemente dall'implementazione della shell reale, la returndichiarazione è in una funzione e quindi legale. Il comportamento risultante tuttavia non è specificato.
schily,

Non dovresti scrivere che si tratta di un comportamento non documentato mentre i componenti della pipe del fatto in una sottostruttura sono documentati nella pagina del manuale di bash. Non dovresti scrivere il comportamento è probabilmente basato su specifiche non definite mentre POSIX specifica i comportamenti consentiti. Non dovresti sospettare un bug bash mentre bash sta seguendo lo standard POSIX consentendo il ritorno in una funzione ma non all'esterno.
jlliagre,

17

Non è un bug bashma il suo comportamento documentato :

Ogni comando in una pipeline viene eseguito nella propria subshell

L' returnistruzione è valida all'interno di una definizione di funzione ma anche in una sottostruttura, non influisce sulla sua shell madre, quindi l'istruzione successiva echoviene eseguita indipendentemente. Si tratta tuttavia di una costruzione shell non portatile poiché lo standard POSIX consente di eseguire i comandi che compongono una pipeline in una subshell (impostazione predefinita) o in quella superiore (un'estensione consentita).

Inoltre, ogni comando di una pipeline multi-comando si trova in un ambiente subshell; come estensione, tuttavia, qualsiasi o tutti i comandi in una pipeline possono essere eseguiti nell'ambiente corrente. Tutti gli altri comandi devono essere eseguiti nell'attuale ambiente shell.

Eventualmente, puoi dire bashdi comportarti come ti aspetti con un paio di opzioni:

$ set +m # disable job control
$ shopt -s lastpipe # do not run the last command of a pipeline a subshell 
$ o(){ echo | while read -r; do return 0; done; echo $?;}
$ o
$          <- nothing is printed here

1
Dal momento returnche non abbandonerà la funzione, non avrebbe più senso se la shell avesse appena stampato bash: return: can only `return' from a function or sourced script, invece di dare all'utente un falso senso che la funzione sarebbe potuta tornare?
Teresa e Junior,

2
Non vedo da nessuna parte nella documentazione che il ritorno all'interno della subshell sia valido. Scommetto che questa funzione è stata copiata da ksh, l'istruzione return al di fuori della funzione o lo script di provenienza si comportano come exit . Non sono sicuro della shell Bourne originale.
cuonglm,

1
@jlliagre: Forse Teresa è confusa riguardo alla terminologia di ciò che sta chiedendo, ma non vedo perché sarebbe "complicato" per bash emettere una diagnostica se esegui un returnda una subshell. Dopotutto, sa che si trova in una subshell, come evidenziato dalla $BASH_SUBSHELLvariabile. Il problema più grande è che questo potrebbe portare a falsi positivi; un utente che capisce come funzionano le subshell può aver scritto degli script che usano returnal posto di exitterminare una subshell. (E, naturalmente, ci sono casi validi in cui si potrebbe desiderare di impostare variabili o fare un cdin una subshell.)
Scott

1
@Scott Penso di aver capito bene la situazione. Una pipe crea una subshell e returnsta tornando dalla subshell invece di fallire, poiché si trova all'interno di una funzione effettiva. Il problema è che help returnafferma specificamente: Causes a function or sourced script to exit with the return value specified by N.Dalla lettura della documentazione, qualsiasi utente si aspetterebbe che fallisca o stampi un avviso, ma che non si comporti mai come exit.
Teresa e Junior,

1
Mi sembra che chiunque si aspetti che return in una subshell in una funzione ritorni dalla funzione (nel processo principale della shell) non capisce molto bene le subshell. Al contrario, mi aspetterei che un lettore che capisce le subshell si aspetti da return una subshell in una funzione di terminare la subshell, proprio come exitfarebbe.
Scott,

6

Per la documentazione POSIX, l' utilizzo returnal di fuori della funzione o dello script di provenienza non è specificato . Quindi, dipende dalla shell da gestire.

La shell di SystemV segnalerà un errore, mentre in ksh, returnal di fuori della funzione o lo script di origine si comporta come exit. Anche la maggior parte delle shell POSIX e l'osh di schily si comportano così:

$ for s in /bin/*sh /opt/schily/bin/osh; do
  printf '<%s>\n' $s
  $s -c '
    o(){ echo | while read l; do return 0; done; echo $?;}; o
  '
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0

kshe zshnon l'output perché l'ultima parte della pipe in queste shell è stata eseguita nella shell corrente invece che nella subshell. L'istruzione return ha influito sull'ambiente shell corrente che ha chiamato la funzione, causa immediatamente il ritorno della funzione senza stampare nulla.

Nella sessione interattiva, bashsegnala solo l'errore ma non ha terminato la shell, ha schily's oshsegnalato l'errore e ha terminato la shell:

$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only `return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function

( zshIn sessione e output interattivo è do terminale non chiuso, bash, yashe schily's oshsegnala l'errore, ma non è terminato il guscio)


1
Si può affermare che returnè usato all'interno di una funzione qui.
jlliagre,

1
@jlliagre: non sono sicuro di cosa intendi, è returnstato usare all'interno della funzione subshell all'interno , tranne e . kshzsh
cuonglm,

2
Voglio dire essere all'interno di una subshell che è essa stessa all'interno di una funzione non significa necessariamente essere al di fuori di quella funzione, ovvero nulla nei componenti della pipeline degli stati standard deve essere considerato esterno alla funzione in cui si trovano. Ciò meriterebbe di essere chiarito da Open Group.
jlliagre,

3
Penso che nessuno. Questo è al di fuori della funzione. La shell che ha chiamato la funzione e la subshell che ha eseguito return sono diverse.
cuonglm,

Comprendo il tuo ragionamento che giustamente spiega il problema, il mio punto è secondo la grammatica della shell descritta nello standard POSIX, la pipeline fa parte dell'elenco composto che fa parte del comando composto che è il corpo della funzione. Da nessuna parte si afferma che i componenti della pipeline devono essere considerati al di fuori della funzione. Proprio come se fossi in un'auto e quell'auto è parcheggiata in un garage, posso presumere che io sia anche in quel garage ;-)
jlliagre,

4

Penso che tu abbia il comportamento atteso, in bash, ogni comando in una pipeline viene eseguito in una subshell. Puoi convincerti cercando di modificare una variabile globale della tua funzione:

foo(){ x=42; : | x=3; echo "x==$x";}

A proposito, il ritorno sta funzionando ma ritorna dalla subshell. Ancora una volta puoi controllare che:

foo(){ : | return 1; echo$?; echo "This should not be printed.";}

Produrrà quanto segue:

1
This should not be printed.

Quindi l'istruzione return è uscita correttamente dalla subshell

.


2
Quindi, per uscire dalla funzione, usa foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?e otterrai un risultato di 2. Ma per chiarezza farei l' return 1essere exit 1.
dubiousjim,

A proposito, c'è qualche giustificazione per il fatto che tutti i membri di una pipeline (non tutti tranne uno) sono eseguiti in subshells?
Incnis Mrsi,

@IncnisMrsi: vedi la risposta di jlliagre .
Scott,

1

La risposta più generale è che bash e alcune altre shell normalmente mettono tutti gli elementi di una pipeline in processi separati. Questo è ragionevole quando la riga di comando è

programma 1 | programma 2 | programma 3

poiché i programmi vengono normalmente eseguiti in processi separati comunque (a meno che non lo dica ). Ma può essere una sorpresa perexec program

comando 1 | comando 2 | comando 3

dove alcuni o tutti i comandi sono comandi integrati. Esempi fondamentali includono:

$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/

Un esempio leggermente più realistico è

$ t=0
$ ps | while read pid rest_of_line
> do
>     : $((t+=pid))
> done
$ echo "$t"
0

dove l'intero ciclo while... do... doneviene inserito in un sottoprocesso, e quindi le sue modifiche tnon sono visibili alla shell principale al termine del ciclo. Ed è esattamente quello che stai facendo: eseguire il piping in un whileloop, far sì che il loop venga eseguito come subshell e quindi provare a tornare dalla subshell.

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.