Это ошибка в Bash? `return` не завершает функцию, если вызывается из канала

16

У меня были некоторые странные проблемы с Bash в последнее время. Пытаясь упростить свой сценарий, я разработал небольшой фрагмент кода:

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

returnдолжен был выйти из функции без печати $?, не так ли? Ну, тогда я проверил, могу ли я вернуться из трубы в одиночку:

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

То же самое происходит без whileцикла:

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

Есть что-то, чего я здесь не хватает? Поиск Google не принес ничего об этом! Моя версия bash - 4.2.37 (1) -релиз на Debian Wheezy.

Тереза ​​и Джуниор
источник
Что-то не так с настройками, которые я предложил в своем ответе, позволяющими вашему сценарию вести себя интуитивно, как вы ожидали?
Jlliagre
@jlliagre Это довольно сложный скрипт на тысячи строк. В связи с тем, что нужно что-то сломать, я предпочитаю просто избегать запуска канала внутри функции, поэтому я заменил его заменой процесса. Благодарность!
Тереза ​​и Джуниор
Почему бы не удалить первые два примера, если они whileне нужны для воспроизведения? Это отвлекает от сути.
Легкость гонки с Моникой
@LightnessRacesinOrbit whileЦикл - это очень распространенное использование для канала с return. Второй пример более прямолинеен, но я не верю, что кто-то когда-либо будет его использовать ...
Teresa e Junior
1
К сожалению, мой правильный ответ был удален ... Вы находитесь в серой зоне, поскольку вы делаете что-то неуказанное. Поведение зависит от того, как оболочка интерпретирует каналы, и это даже отличается между оболочкой Борна и оболочкой Корна, хотя ksh был получен из sh источников. В Bourne Shell цикл while находится в подоболочке, поэтому вы видите эхо как в bash. В ksh цикл while является процессом переднего плана, и поэтому ksh не вызывает echo в вашем примере.
Щил

Ответы:

10

Связанный: /programming//a/7804208/4937930

Это не ошибка, что вы не можете выйти из скрипта или вернуться из функции в exitили returnв подоболочки. Они выполняются в другом процессе и не влияют на основной процесс.

Кроме того, я полагаю, вы видите недокументированное поведение bash в (возможно) неопределенной спецификации. В функции не допускаются ошибки returnна верхнем уровне команд подоболочки, и она просто ведет себя как exit.

ИМХО, это ошибка bash для противоречивого поведения в returnзависимости от того, находится главный оператор в функции или нет.

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

Выход:

$ 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
yaegashi
источник
Отсутствие многословия ошибок может быть недокументировано. Но тот факт, что returnон не работает из последовательности команд верхнего уровня в подоболочке и, в частности, не выходит из подоболочки, это то, что уже заставило меня ожидать существующие документы. ОП может использовать exit 1 || return 1там, где они пытаются использовать return, и затем должен получить ожидаемое поведение. РЕДАКТИРОВАТЬ: @ ответ Герберта указывает, что верхний уровень returnв подоболочке функционирует как exit(но только из подоболочки).
dubiousjim
1
@dubiousjim Обновил мой скрипт. Я имею returnв виду, что в простой последовательности подоболочек следует утверждать, что в любом случае это ошибка времени выполнения , но на самом деле это не так, когда это происходит в функции. Эта проблема также обсуждалась в gnu.bash.bug , но пока нет никаких выводов.
Яэгаши
1
Ваш ответ неверен, так как не указано, находится ли цикл while в подоболочке или является процессом переднего плана. Независимо от реализации фактической оболочки, returnоператор находится в функции и, следовательно, является законным. Получающееся поведение однако не определено.
Щил
Вы не должны писать, что это недокументированное поведение, в то время как компоненты факта находятся в подоболочке, документировано на странице руководства bash. Вы не должны писать, что поведение, вероятно, основано на неопределенных спецификациях, в то время как POSIX определяет допустимые поведения. Вы не должны подозревать ошибку bash, когда bash следует стандарту POSIX, разрешив возврат в функцию, но не наружу.
июля
17

Это не ошибка, bashа ее документированное поведение :

Каждая команда в конвейере выполняется в своей собственной оболочке

returnИнструкция действительно существо внутри определения функции , но будучи в субоболочке , а также, это не влияет на его родительскую оболочку так , следующую инструкцию, echo, выполняются независимо. Тем не менее, это непереносимая конструкция оболочки, поскольку стандарт POSIX позволяет командам, составляющим конвейер, выполняться либо в подоболочке (по умолчанию), либо в верхней (разрешенное расширение).

Кроме того, каждая команда многокомпонентного конвейера находится в среде подоболочек; однако в качестве расширения любая или все команды в конвейере могут выполняться в текущей среде. Все остальные команды должны выполняться в текущей среде оболочки.

Надеюсь, вы можете сказать, bashчтобы вести себя так, как вы ожидаете, с помощью нескольких вариантов:

$ 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
jlliagre
источник
1
Поскольку returnне выйдет из функции, не будет ли больше смысла, если оболочка только что напечатана bash: return: can only `return' from a function or sourced script, вместо того, чтобы дать пользователю ложное представление о том, что функция могла вернуться?
Тереза ​​и Джуниор
2
Я не вижу, чтобы в документации говорилось, что возврат в subshell действителен. Могу поспорить, что эта функция была скопирована из ksh, оператор return вне функции или исходный скрипт ведут себя как выход . Я не уверен насчет оригинальной оболочки Bourne.
Cuonglm
1
@jlliagre: Возможно, Тереза ​​не понимает терминологию того, что она просит, но я не понимаю, почему для bash было бы «сложно» выдать диагностику, если вы выполняете returnкоманду из подоболочки. В конце концов, он знает, что находится в подоболочке, о чем свидетельствует $BASH_SUBSHELLпеременная. Самая большая проблема заключается в том, что это может привести к ложным срабатываниям; пользователь, который понимает, как работают подоболочки, мог бы написать сценарии, которые используются returnвместо exitзавершения подоболочки. (И, конечно же, существуют допустимые случаи, когда кто-то может захотеть установить переменные или сделать это cdв подоболочке.)
Скотт
1
@ Скотт, я думаю, я хорошо понимаю ситуацию. Канал создает подоболочку и returnвозвращается из подоболочки вместо сбоя, поскольку он находится внутри фактической функции. Проблема заключается в том, что help returnконкретно говорится: Causes a function or sourced script to exit with the return value specified by N.читая документацию, любой пользователь может ожидать, что она хотя бы потерпит неудачу или напечатает предупреждение, но никогда не будет вести себя как exit.
Teresa e Junior
1
Мне кажется, что любой, кто ожидает, что функция в return подоболочке в функции вернется из функции (в основном процессе оболочки), не очень хорошо понимает подоболочки. И наоборот, я ожидал бы, что читатель, понимающий подоболочки, ожидает, что return в подоболочке функция завершит подоболочку, как и exitраньше.
Скотт
6

Согласно документации POSIX, использование returnвне функции или сценария не определено . Таким образом, это зависит от вашей оболочки для обработки.

Оболочка SystemV будет сообщать об ошибке, в то время как внутри ksh, returnвне функции или исходного сценария ведут себя как exit. Большинство других POSIX-оболочек и osh schily также ведут себя так:

$ 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

kshи zshне выводил, потому что последняя часть конвейера в этих оболочках была выполнена в текущей оболочке вместо subshell. Оператор return повлиял на текущую оболочку, которая вызвала функцию, поэтому функция возвращается немедленно, ничего не печатая.

В интерактивном сеансе bashтолько сообщите об ошибке, но не завершил работу оболочки, schily's oshсообщил об ошибке и завершил работу оболочки:

$ 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

( zshВ интерактивном режиме и выходе терминала не прекращается, bash, yashи schily's oshсообщает об ошибке , но не прекращает оболочку)

cuonglm
источник
1
Можно утверждать, returnчто используется внутри функции здесь.
Jlliagre
1
@jlliagre: Не уверен, что вы имели в виду, returnбыло использование внутри subshell внутри функции , кроме kshи zsh.
Cuonglm
2
Я имею в виду нахождение внутри подоболочки, которая сама находится внутри функции, не обязательно означает, что она находится за пределами этой функции, то есть ничего в компонентах конвейера стандартных состояний не следует считать находящимися вне функции, в которой они находятся. Это заслуживает пояснения со стороны Open Group.
Jlliagre
3
Я думаю нет. Это за пределами функции. Оболочка, которая вызвала функцию, и подоболочка, которая выполнила return , отличаются.
cuonglm
Я понимаю вашу аргументацию, которая правильно объясняет проблему, моя точка зрения соответствует грамматике оболочки, описанной в стандарте POSIX, конвейер является частью составного списка, который является частью составной команды, которая является телом функции. Нигде не указано, что компоненты конвейера должны рассматриваться вне функции. Также как если я нахожусь в машине, и эта машина припаркована в гараже, я могу предположить, что я тоже в этом гараже ;-)
jlliagre
4

Я думаю, что вы получили ожидаемое поведение, в bash, каждая команда в конвейере выполняется в подоболочке. Вы можете убедиться, попытавшись изменить глобальную переменную вашей функции:

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

Кстати, возврат работает, но он возвращается из подоболочки. Опять вы можете проверить это:

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

Будет выводить следующее:

1
This should not be printed.

Так что оператор возврата правильно вышел из подоболочки

,

Герберт
источник
2
Следовательно, чтобы выйти из функции, используйте, foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?и вы получите результат 2. Но для ясности я хотел бы сделать return 1БЭ exit 1.
dubiousjim
Кстати, есть ли обоснование тому, что все элементы конвейера (не все, кроме одного) выполнены в подоболочках?
Incnis Mrsi
@IncnisMrsi: Смотрите ответ jlliagre .
Скотт
1

Более общий ответ заключается в том, что bash и некоторые другие оболочки обычно помещают все элементы конвейера в отдельные процессы. Это разумно, когда командная строка

программа 1 | программа 2 | программа 3

поскольку программы все равно обычно запускаются в отдельных процессах (если вы не говорите ). Но это может стать сюрпризом дляexec program

команда 1 | команда 2 | команда 3

где некоторые или все команды являются встроенными командами. Тривиальные примеры включают в себя:

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

Немного более реалистичный пример

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

где все while... do... doneпетля помещается в подпроцесс, и поэтому ее изменения в tне видны основной оболочку после концов петли. И это именно то, что вы делаете - добавление в whileцикл, запуск цикла как подоболочки, а затем попытка возврата из подоболочки.

Скотт
источник