Як я можу змусити bash вийти з ладу backtick аналогічно шляху pipefail?


55

Тому я люблю загартовувати мої сценарії bash там, де можу (а коли не в змозі делегувати мову, подібну до Python / Ruby), щоб помилки не залишалися без уваги.

У цьому ключі у мене є строгий.sh, який містить такі речі, як:

set -e
set -u
set -o pipefail

І джерело його в інших сценаріях. Однак, поки pipefail підхопить:

false | echo it kept going | true

Він не підбере:

echo The output is '`false; echo something else`' 

Вихід був би

Вихід ''

False повертає ненульовий статус і no-stdout. У трубі це не вдалося б, але тут помилка не застала. Якщо це фактично обчислення, збережене в змінній на потім, і значення встановлено порожнім, це може призвести до пізніших проблем.

Отже - чи є спосіб отримати баш, щоб розглянути ненульовий зворотний код всередині задніх рядків як достатню причину для виходу?

Відповіді:


51

Точна мова, що використовується в єдиній специфікації UNIX для опису значенняset -e :

Якщо ця опція ввімкнена, якщо проста команда не спрацьовує з будь-якої з причин, перелічених у Послідках помилок оболонки або повертає значення статусу виходу> 0, і не є [умовною чи заперечною командою], оболонка негайно вийде.

Існує неоднозначність щодо того, що відбувається, коли така команда відбувається в нижній частині . З практичної точки зору, все, що використовується в допоміжній оболонці, - це вихід і повернення ненульового статусу до батьківської оболонки. Чи вийде вихідна оболонка у свою чергу, залежить від того, чи цей статус ненульового значення перетворюється на просту команду, яка не працює в батьківській оболонці.

Один з таких проблемних випадків - той, з яким ви стикалися: ненульовий стан повернення від підстановки команди . Оскільки цей статус ігнорується, це не спричиняє вихід батьківської оболонки. Як ви вже виявили , спосіб врахування статусу виходу полягає у використанні підстановки команд у простому призначенні : тоді статус виходу присвоєння - це статус виходу останньої підстановки команди у призначенні .

Зауважте, що це буде виконуватись за призначенням, лише якщо є одна заміна команд, оскільки враховується лише статус останньої заміни. Наприклад, наступна команда є успішною (як відповідно до стандарту, так і в кожній реалізації, яку я бачив):

a=$(false)$(echo foo)

Ще один випадок , щоб спостерігати за це явні подоболочкі : (somecommand). Згідно з вищезгаданою інтерпретацією, додаткова оболонка може повернути ненульовий статус, але оскільки це не проста команда в батьківській оболонці, батьківська оболонка повинна продовжуватися. Насправді всі снаряди, про які я знаю, змушують батьків повернутися в цей момент. Хоча це корисно у багатьох випадках, наприклад, (cd /some/dir && somecommand)коли круглі дужки використовуються для збереження операції, такої як поточна зміна каталогів на локальну, вона порушує специфікацію, якщо set -eвона вимкнена в підпакеті, або якщо підпрограшка повертає ненульовий статус таким чином, не припиняв би його, наприклад, використання !справжньої команди. Наприклад, всі золи, bash, pdksh, ksh93 та zsh виходять, не відображаючи fooнаступних прикладів:

set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"

Але жодна проста команда не відбулася, поки set -eвона діяла!

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

  • ATT ksh і zsh, які виконують останній елемент конвеєра в батьківській оболонці, діють як завжди: якщо проста команда не працює в останньому елементі конвеєра, оболонка виконує цю команду, яка, як буває, є батьківською оболонкою, виходи.
  • Інші оболонки наближають поведінку шляхом виходу, якщо останній елемент трубопроводу повертає ненульовий статус.

Як і раніше, відключення set -eабо використання заперечення в останньому елементі трубопроводу призводить до повернення ненульового статусу таким чином, що він не повинен закінчувати оболонку; снаряди, відмінні від ATT ksh і zsh, тоді вийдуть.

pipefailПараметр Баша змушує трубопровід негайно вийти, set -eякщо будь-який з його елементів повертає ненульовий статус.

Зауважте, що як подальше ускладнення, bash вимикається set -eв підрозділах, якщо він не знаходиться в режимі POSIX ( set -o posixабо POSIXLY_CORRECTв середовищі, коли запускається bash).

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


Дякую за це У мене був досвід, що деякі помилки, виявлені в більш пізній версії bash, в більш ранніх версіях з set -e були ігноровані. Мій намір тут - загартувати сценарії настільки, що будь-яка умова повернення / відмови помилки призведе до виходу сценарію. Це в застарілій системі, яка, як відомо, створює файли виведення сміття та "0" код щасливого виходу після півгодини хаосу з неправильним оточенням - якщо тільки ви не спостерігали вихід як яструб (і не всі помилки знаходяться на stderr, деякі на stdout, а деякі - / dev / null'd), ви просто не знаєте.
Danny Staple

"Наприклад, всі золи, bash, pdksh, ksh93 та zsh виходять, не відображаючи foo на наступних прикладах": Ash BusyBox не веде себе таким чином, він відображає вихід відповідно до специфікації. (Тестовано за допомогою BusyBox 1.19.4.) Не виходить і з set -e; (cd /nonexisting).
sumiousjim

27

(Відповідаючи на власне, тому що я знайшов рішення) Одне рішення - це завжди призначати це проміжній змінній. Таким чином встановлюється return code ( $?).

Тому

ABC=`exit 1`
echo $?

Виведе 1(або замість цього, якщо set -eє), проте:

echo `exit 1`
echo $?

Виведеться 0після порожнього рядка. Код повернення ехо (або інша команда, яка виконувалась з вивороту зворотного вибору) замінить код повернення 0.

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


23

Як вказував ОП у власній відповіді, присвоєння виходу підкоманди змінній дійсно вирішує проблему; $?залишаються неушкодженими.

Однак один крайній випадок все ще може здивувати вас помилковими негативами (тобто команда не вдається, але помилка не спливає), localзмінною декларацією:

local myvar=$(subcommand)буде завжди повертатися 0!

bash(1) вказує на це:

   local [option] [name[=value] ...]
          ... The return status is 0 unless local is used outside a function,
          an invalid name is supplied, or name is a readonly variable.

Ось простий тестовий випадок:

#!/bin/bash

function test1() {
  data1=$(false) # undeclared variable
  echo 'data1=$(false):' "$?"
  local data2=$(false) # declaring and assigning in one go
  echo 'local data2=$(false):' "$?"
  local data3
  data3=$(false) # assigning a declared variable
  echo 'local data3; data3=$(false):' "$?"
}

test1

Вихід:

data1=$(false): 1
local data2=$(false): 0
local data3; data3=$(false): 1

дякую, непослідовність між мене VAR=...і local VAR=...справді сполохав мене!
Джонні

13

Як говорили інші, localзавжди буде повертати 0. Рішення полягає в тому, щоб спочатку оголосити змінну:

function testcase()
{
    local MYRESULT

    MYRESULT=$(false)
    if (( $? != 0 )); then
        echo "False returned false!"
        return 1
    fi

    return 0
}

Вихід:

$ testcase
False returned false!
$ 

4

Для виходу з помилки підстановки команди ви можете явно встановити -eв підпакеті, як це:

set -e
x=$(set -e; false; true)
echo "this will never be shown"

1

Цікавий момент!

Я ніколи не натрапляв на це, тому що я не є другом set -e(натомість я вважаю за краще trap ... ERR), але вже перевірив, що: trap ... ERRтакож не ловити помилки в межах $(...)(або старомодних задників).

Я думаю, що проблема (як так часто) полягає в тому, що тут викликається підзарядка і -eявно означає поточну оболонку.

Єдиним іншим рішенням, яке прийшло на думку в цей момент, було б використання прочитаного:

 ls -l ghost_under_bed | read name

Це кидки ERRі з -eоболонкою буде припинено. Єдина проблема: це працює лише для команд з одним рядком виводу (або ви передаєте через щось, що з'єднує рядки).


1
Не впевнений у хитрість читання, спроби цього призводять до того, що змінна не зв'язана. Я думаю, що це може бути тому, що інша сторона труби фактично є нижньою оболонкою, назва не буде доступною.
Danny Staple
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.