Це помилка в баші? `return` не припиняє функцію, якщо викликається з труби


16

Останнім часом у мене виникають дивні проблеми з басом. Намагаючись спростити свій сценарій, я придумав цей невеликий фрагмент коду:

$ 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 Це досить складний сценарій у тисячах рядків. Що стосується того, щоб зламати щось інше, я вважаю за краще просто уникати запуску труби в межах функції, тому замінив її заміною процесу. Спасибі!
Teresa e Junior

Чому б не вилучити перші два приклади, якщо whileрепродукція не потрібна? Це відволікає від точки.
Гонки легкості з Монікою

@LightnessRacesinOrbit whileПетля - дуже поширене використання для труби з return. Другий приклад є більш зрозумілим, але це те, що я не вірю, що хтось коли-небудь використає ...
Teresa e Junior

1
На жаль, моя правильна відповідь була видалена ... Ви знаходитесь у сірій зоні, коли робите щось, що не визначено. Поведінка залежить від того, як оболонка інтерпретує труби, і це навіть відрізняється між оболонкою Борна та шкаралупою Корну, хоча ksh отримано з джерел sh. У оболонці Bourne цикл while знаходиться в нижній частині, тому ви бачите відлуння, як у bash, In ksh цикл while - це процес переднього плану, і, таким чином, ksh не називає ехо вашим прикладом.
schily

Відповіді:


10

Пов’язано: /programming//a/7804208/4937930

Це не помилка, що ви не можете вийти зі скрипту або повернутися з функції за допомогою exitабо returnв підрозділах . Вони виконуються в іншому процесі і не впливають на основний процес.

Крім того, я гадаю, що ви бачите недокументовану поведінку баш на (ймовірно) невизначеній специфікації. У функції жодних помилок не встановлюється для returnверхніх рівнів команд підзарядки, і вона просто так поводиться exit.

IMHO - це баш-помилка щодо непослідовної поведінки 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

Відсутність багатослівної помилки може бути недокументованою. Але те, що returnвже не сподівається на мене, те, що не працює з командної послідовності вищого рівня в підклітині, і, зокрема, не виходить з підпакеті, - це те, що вже існували документи. ОП може використовувати exit 1 || return 1там, де їх намагаються використовувати return, і тоді має отримати очікувану поведінку. EDIT: Відповідь @ herbert вказує на те, що верхній рівень в нижній підрозділі returnфункціонує як exit(але тільки з нижньої частини).
сумнівним

1
@dubiousjim Оновлено мій сценарій. Я маю returnна увазі, що в простій послідовності додаткової оболонки слід стверджувати як помилку виконання в будь-яких випадках , але насправді це не тоді, коли вона виникає в fucntion. Ця проблема також обговорювалася в gnu.bash.bug , але висновку немає.
yaegashi

1
Ваша відповідь не правильна, оскільки не визначено, чи цикл while знаходиться в нижній частині корпусу чи це процес переднього плану. Незалежно від реалізації фактичної оболонки, returnтвердження є функцією і, таким чином, законним. Однак поведінка, що виникає, не визначена.
schily

Не слід писати, що це незадокументоване поведінка, в той час, як компоненти трубопроводів знаходяться в нижній частині корпусу, задокументовано на сторінці керівництва bash. Не слід писати поведінку, ймовірно, на основі невизначених специфікацій, тоді як POSIX вказує дозволену поведінку. Ви не повинні підозрювати баш-помилку, коли bash дотримується стандарту POSIX, дозволяючи повернути функцію, але не за її межами.
jlliagre

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

1
Оскільки returnне вийде з функції, чи не було б більше сенсу, якщо оболонка щойно надрукована bash: return: can only `return' from a function or sourced script, замість того, щоб дати користувачеві помилкове відчуття, що функція може повернутися?
Teresa e Junior

2
Я ніде в документації не бачу, що повернення всередині додаткової оболонки є дійсним. Б'юсь об заклад, що ця функція була скопійована з ksh, зовнішня операція return або зовнішній скрипт ведуть себе як вихід . Я не впевнений в оригінальній оболонці Борна.
cuonglm

1
@jlliagre: Можливо, Тереза ​​розгублена в термінології того, про що вона просить, але я не розумію, чому було б "хитро" для того, щоб баш поставити діагностику, якщо ви виконаєте returnасистент з передплати. Зрештою, він знає, що він знаходиться в нижній частині, про що свідчить $BASH_SUBSHELLзмінна. Найбільша проблема полягає в тому, що це може призвести до помилкових позитивних результатів; Користувач, який розуміє, як працюють returnпідрозділи, може писати сценарії, які використовуються замість того, exitщоб припинити підзарядку. (І, звичайно, є вагомі випадки, коли можна захотіти встановити змінні або зробити «а» cdв нижній частині.)
Скотт,

1
@Scott Я думаю, що я добре розумію ситуацію. Труба створює підшару і 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 та оші також поводяться так:

$ 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не виводив, тому що остання частина труби в цих оболонках була виконана в поточній оболонці замість нижньої оболонки. Заява повернення вплинула на поточне середовище оболонки, яка викликала функцію, викликаючи повернення функції негайно, не надрукуючи нічого.

В інтерактивному сеансі 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повідомляється про помилку, але не закінчує оболонку)


1
Можна стверджувати, що returnтут використовується всередині функції.
jlliagre

1
@jlliagre: Не впевнений , що ви маєте в виду, returnбуло використання всередині подоболочки всередині функції , за винятком kshі zsh.
cuonglm

2
Я маю на увазі перебування всередині підшару, яка сама знаходиться у функції, не обов'язково означає, що знаходиться поза цією функцією, тобто нічого в стандартних станах компоненти трубопроводу не слід вважати поза функцією, де вони знаходяться. Це заслуговує на роз'яснення Відкритою групою.
jlliagre

3
Я думаю, що ні. Це поза функцією. Оболонка, яка викликала функцію, і допоміжна частина, яка виконувала віддачу , різні.
cuonglm

Я розумію ваші міркування, які правильно пояснюють проблему, моя думка - відповідно до граматики оболонки, описаної в стандарті POSIX, конвеєр - це частина списку сполук, яка є частиною з'єднання-команди, яка є частиною функції. Ніде не зазначено, що компоненти трубопроводу не слід розглядати поза функцією. Так само, як якщо я в машині, і ця машина припаркована в гаражі, я можу припустити, що я теж у цьому гаражі ;-)
jlliagre

4

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

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

До речі, повернення працює, але повертається з нижньої частини. Ще раз ви можете перевірити це:

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

Виведе наступне:

1
This should not be printed.

Тож оператор return правильно вийшов з підшару

.


2
Отже, щоб вийти з функції, використовуйте, foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?і ви отримаєте результат 2. Але для наочності я б змусив return 1бути exit 1.
сумнівним

До речі, чи є якісь обґрунтування того, що всі члени трубопроводу (не всі, крім одного) виконуються в підпалах?
Incnis Mrsi

@IncnisMrsi: Дивіться відповідь jlliagre .
Скотт

1

Більш загальна відповідь полягає в тому, що баш та деякі інші оболонки зазвичай ставлять усі елементи трубопроводу в окремі процеси. Це розумно, коли є командний рядок

програма 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цикл, змушуючи цикл запускатись як допоміжної оболонки, а потім намагаючись повернутися з нижньої частини.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.