Czy to błąd w bash? `return` nie wychodzi z funkcji, jeśli jest wywoływany z potoku


16

Ostatnio mam dziwne problemy z bash. Próbując uprościć mój skrypt, wymyśliłem ten mały fragment kodu:

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

returnpowinien był wyjść z funkcji bez drukowania $?, prawda? Cóż, wtedy sprawdziłem, czy mogę sam wrócić z rury:

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

To samo dzieje się bez whilepętli:

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

Czy czegoś tu brakuje? Wyszukiwarka Google nic nie przyniosła! Moja wersja bash to 4.2.37 (1) - wydanie na Debian Wheezy.


Coś nie tak z ustawieniami, które zasugerowałem w odpowiedzi, które pozwalają twojemu skryptowi zachowywać się intuicyjnie, tak jak się spodziewałeś?
jlliagre,

@jlliagre Jest to dość złożony skrypt na tysiące linii. W trosce o złamanie czegoś innego wolę po prostu unikać uruchamiania potoku w ramach funkcji, więc zastąpiłem go podstawieniem procesu. Dzięki!
Teresa e Junior

Dlaczego nie usunąć pierwszych dwóch przykładów, jeśli whilenie są one potrzebne do reprodukcji? Odwraca uwagę od rzeczy.
Wyścigi lekkości z Monicą

@LightnessRacesinOrbit whilePętla jest bardzo powszechnym zastosowaniem dla potoku z return. Drugi przykład jest bardziej bezpośredni do rzeczy, ale nie sądzę, aby ktokolwiek kiedykolwiek użyłby ...
Teresa e Junior

1
Niestety moja poprawna odpowiedź została usunięta ... Jesteś w szarej strefie, ponieważ robisz coś, co nie jest określone. Zachowanie zależy od tego, jak powłoka interpretuje rury, a to nawet różni się między powłoką Bourne'a i powłoką Korna, mimo że ksh pochodzi ze źródeł sh. W powłoce Bourne'a pętla while znajduje się w podpowłoce, dlatego echo jest widoczne jak w przypadku bash, w ksh pętla while jest procesem pierwszego planu i dlatego ksh nie wywołuje echa w twoim przykładzie.
schily

Odpowiedzi:


10

Powiązane: /programming//a/7804208/4937930

To nie jest błąd, że nie można wyjść ze skryptu lub powrócić z funkcji przez exitlub returnw podpowłoce. Są one wykonywane w innym procesie i nie wpływają na proces główny.

Poza tym, przypuszczam, że widzisz nieudokumentowane zachowania bash na (prawdopodobnie) nieokreślonej specyfikacji. W funkcji nie są zgłaszane żadne błędy returnna najwyższym poziomie poleceń podpowłoki i po prostu zachowuje się jak exit.

IMHO jest błędem bashowym za niespójne zachowanie polegające returnna tym, czy główna instrukcja jest w funkcji, czy nie.

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

Wynik:

$ 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

Brak szczegółowości błędów może być nieudokumentowany. Ale fakt, że returnnie działa z sekwencji poleceń najwyższego poziomu w podpowłoce, a w szczególności nie wychodzi z podpowłoki, jest tym, czego już oczekiwałem od istniejących dokumentów. OP może użyć exit 1 || return 1tam, gdzie próbują użyć return, a następnie powinien uzyskać oczekiwane zachowanie. EDYCJA: Odpowiedź @ herbert wskazuje, że najwyższy poziom returnw podpowłoce działa jako exit(ale tylko z podpowłoki).
dubiousjim

1
@dubiousjim Zaktualizowałem mój skrypt. Mam na myśli, że returnw prostej sekwencji podpowłoki należy w każdym przypadku stwierdzić , że jest to błąd czasu wykonywania , ale tak naprawdę nie jest to przypadek , w którym występuje on. Problem ten został również omówiony w gnu.bash.bug , ale nie ma żadnych wniosków.
yaegashi,

1
Twoja odpowiedź jest nieprawidłowa, ponieważ nie jest określone, czy pętla while znajduje się w podpowłoce, czy jest procesem pierwszego planu. Niezależnie od implementacji rzeczywistej powłoki, returninstrukcja działa, a zatem jest legalna. Wynikowe zachowanie jest jednak nieokreślone.
schily

Nie powinieneś pisać, że jest to zachowanie nieudokumentowane, podczas gdy fakt, że elementy potoku znajdują się w podpowłoce, jest udokumentowany na stronie podręcznika bash. Nie powinieneś pisać, że zachowanie jest prawdopodobnie oparte na niezdefiniowanych specyfikacjach, podczas gdy POSIX określa dozwolone zachowania. Nie powinieneś podejrzewać błędu bash, gdy bash jest zgodny ze standardem POSIX, umożliwiając powrót w funkcji, ale nie na zewnątrz.
jlliagre

17

To nie jest błąd, bashale jego udokumentowane zachowanie :

Każde polecenie w potoku jest wykonywane we własnej podpowłoce

returnInstrukcja jest ważna istota wewnątrz definicji funkcji, ale jest w podpowłoce, jak również, że nie ma wpływu na jego powłoki macierzystej więc następnej instrukcji, echojest realizowane niezależnie. Jest to jednak nieprzenośna konstrukcja powłoki, ponieważ standard POSIX pozwala na wykonywanie poleceń tworzących potok w podpowłoce (domyślnie) lub w górnej (dozwolone rozszerzenie).

Ponadto każde polecenie potoku z wieloma poleceniami znajduje się w środowisku podpowłoki; jednak jako rozszerzenie dowolne lub wszystkie polecenia w potoku mogą być wykonywane w bieżącym środowisku. Wszystkie pozostałe polecenia będą wykonywane w bieżącym środowisku powłoki.

Mamy nadzieję, że możesz powiedzieć, bashaby zachowywać się tak, jak oczekujesz, za pomocą kilku opcji:

$ 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
Ponieważ returnnie wyjdzie z funkcji, czy nie ma większego sensu, gdyby powłoka właśnie wydrukowała bash: return: can only `return' from a function or sourced script, zamiast dać użytkownikowi fałszywe poczucie, że funkcja mogła zwrócić?
Teresa e Junior

2
Nigdzie nie widzę w dokumentacji, że zwrot w podpowłoce jest prawidłowy. Założę się, że ta funkcja została skopiowana z ksh, instrukcja return poza funkcją lub skrypt źródłowy zachowują się jak wyjście . Nie jestem pewien co do oryginalnej powłoki Bourne'a.
cuonglm,

1
@jlliagre: Może Teresa jest zdezorientowana terminologią tego, o co prosi, ale nie rozumiem, dlaczego bash wydawałby diagnostykę, jeśli wykonasz a returnz podpowłoki. W końcu wie, że jest w podpowłoce, o czym świadczy $BASH_SUBSHELLzmienna. Największym problemem jest to, że może to prowadzić do fałszywych trafień; użytkownik, który rozumie, jak działa podpowłoki, mógł napisać skrypty, które returnzamiast tego exitkończą podpowłokę. (I oczywiście istnieją uzasadnione przypadki, w których można chcieć ustawić zmienne lub wykonać a cdw podpowłoce.)
Scott,

1
@ Scott Myślę, że dobrze rozumiem sytuację. Potok tworzy podpowłokę i returnwraca z podpowłoki zamiast zawieść, ponieważ znajduje się w rzeczywistej funkcji. Problem polega na tym, że help returnkonkretnie stwierdza: Causes a function or sourced script to exit with the return value specified by N.Po przeczytaniu dokumentacji każdy użytkownik spodziewałby się, że przynajmniej zawiedzie lub wydrukuje ostrzeżenie, ale nigdy się tak nie zachowa exit.
Teresa e Junior,

1
Wydaje mi się, że każdy, kto spodziewa się powrotu return podpowłoki w funkcji z funkcji (w głównym procesie powłoki), nie rozumie zbyt dobrze podpowłoki. I odwrotnie, spodziewałbym się, że czytelnik, który rozumie podpowłoki, oczekuje return w podpowłoce w funkcji, aby zakończyć podpowłokę, tak jak to exitzrobiłoby.
Scott,

6

Zgodnie z dokumentacją POSIX, używanie returnpoza funkcją lub skryptem źródłowym jest nieokreślone . To zależy od twojej powłoki do obsługi.

Powłoka SystemV zgłosi błąd, podczas gdy wewnątrz ksh, returnpoza funkcją lub skryptem źródłowym zachowują się jak exit. Większość innych powłok POSIX i osh Schily'ego zachowuje się tak:

$ 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

kshi zshnie wyprowadzał danych, ponieważ ostatnia część potoku w tych powłokach została wykonana w bieżącej powłoce zamiast podpowłoki. Instrukcja return wpłynęła na bieżące środowisko powłoki, które wywołało funkcję, powoduje natychmiastowe zwrócenie funkcji bez drukowania niczego.

W sesji interaktywnej bashzgłoś tylko błąd, ale nie zakończył powłoki, schily's oshzgłosił błąd i zakończył działanie powłoki:

$ 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

( zshW sesji interaktywnej i wyjście to zrób terminal nie rozwiązana bash, yashi schily's oshzgłosił błąd, ale nie zakończyć powłoki)


1
Można argumentować, że returnjest tutaj używany w funkcji.
jlliagre,

1
@jlliagre: Nie wiem, co masz na myśli, returnbyło użycie wewnątrz podpowłoce wewnątrz funkcji , z wyjątkiem kshi zsh.
cuonglm,

2
Mam na myśli bycie wewnątrz podpowłoki, która sama jest wewnątrz funkcji, niekoniecznie oznacza bycie poza tą funkcją, tj. Nic w standardowych stanach komponentów potoku nie powinno być uważane za znajdujące się poza funkcją, w której się znajdują. To zasługuje na wyjaśnienie przez grupę otwartą.
jlliagre,

3
Myśle że nie. To jest poza funkcją. Powłoka, która wywołała funkcję i podpowłoka, która wykonała return, są różne.
cuonglm,

Rozumiem twoje rozumowanie, które słusznie wyjaśnia problem, mam na myśli gramatykę powłoki opisaną w standardzie POSIX, potok jest częścią listy złożonej, która jest częścią polecenia złożonego, które jest ciałem funkcji. Nigdzie nie stwierdzono, że elementy potoku nie powinny być brane pod uwagę poza funkcją. Podobnie jak w samochodzie i ten samochód jest zaparkowany w garażu, mogę założyć, że ja też jestem w tym garażu ;-)
jlliagre

4

Myślę, że masz oczekiwane zachowanie, w skrócie, każde polecenie w potoku jest wykonywane w podpowłoce. Możesz przekonać się, próbując zmodyfikować zmienną globalną swojej funkcji:

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

Nawiasem mówiąc, powrót działa, ale wraca z podpowłoki. Ponownie możesz sprawdzić, czy:

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

Wyprowadzi następujące:

1
This should not be printed.

Tak więc instrukcja return poprawnie opuściła podpowłokę

.


2
Dlatego, aby wyjść z funkcji, użyj, foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?a otrzymasz wynik 2. Ale dla jasności zrobiłbym return 1to exit 1.
dubiousjim

Nawiasem mówiąc, czy istnieje jakieś uzasadnienie dla faktu, że wszyscy członkowie potoku (nie wszyscy oprócz jednego) są wykonywani w podpowłokach?
Incnis Mrsi,

@IncnisMrsi: Zobacz odpowiedź jlliagre .
Scott,

1

Bardziej ogólną odpowiedzią jest to, że bash i niektóre inne powłoki zwykle umieszczają wszystkie elementy potoku w osobnych procesach. Jest to uzasadnione, gdy wiersz poleceń to

program 1 | program 2 | program 3

ponieważ programy i tak są zwykle uruchamiane w oddzielnych procesach (chyba że mówisz ). Ale może to być zaskoczeniemexec program

polecenie 1 | polecenie 2 | polecenie 3

gdzie niektóre lub wszystkie polecenia są wbudowanymi poleceniami. Trywialne przykłady obejmują:

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

Nieco bardziej realistycznym przykładem jest

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

gdzie cała while... do... donepętla wkłada się podproces, a więc jego zmian tnie są widoczne na głównej powłoce po zakończeniu pętli. I to jest dokładnie to, co robisz - pipowanie w whilepętli, powodowanie, że pętla działa jako podpowłoka, a następnie próba powrotu z podpowłoki.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.