Як в сценарії Bash, як я можу вийти з цілого сценарію, якщо виникає певна умова?


717

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

Чи є спосіб я це зробити, не загортаючи весь скрипт у цикл часу і не використовуючи перерви? Щось на кшталт dun dun dun goto?

Відповіді:


809

Спробуйте це твердження:

exit 1

Замініть 1відповідними кодами помилок. Дивіться також Вихідні коди зі спеціальними значеннями .


4
@CMCDragonkai, зазвичай працює будь-який ненульовий код. Якщо вам не потрібно нічого особливого, ви можете просто використовувати 1послідовно. Якщо сценарій призначений для запуску іншого сценарію, ви можете визначити свій власний набір коду статусу з конкретним значенням. Наприклад, 1== тести не вдалися, 2== компіляція не вдалася. Якщо сценарій є частиною чогось іншого, вам може знадобитися відкоригувати коди, щоб вони відповідали практикам, які використовуються там. Наприклад, коли частина тестового набору працює за допомогою автоматичного коду, код 77використовується для позначення пропущеного тесту.
Michał Górny

21
ні це також не закриває вікно, не просто вийдіть із сценарію
Тоні Лі

7
@ToniLeigh bash не має поняття "вікно", ви, мабуть, плутаєтеся з приводу того, що робить ваша конкретна настройка - наприклад, емулятор термінала.
Майкл Фукаракіс,

3
@ToniLeigh Якщо ви закриваєте "вікно", швидше за все, ви ставите exit #команду всередині функції, а не сценарію. (У такому випадку використовуйте return #замість цього.)
Джеймі

1
@Sevenearths 0 означає, що він був успішним, тому exit 0вийдіть із сценарію і повертайте 0 (розповідаючи про інші сценарії, які могли використовувати результат цього сценарію, це вдалося)
VictorGalisson

689

Використовуйте set -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

Сценарій припиняється після виходу з ладу першого рядка (повертає ненульовий код виходу). У цьому випадку команда-that-fails2 не запуститься.

Якби ви перевіряли стан повернення кожної команди, ваш сценарій виглядав би так:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

З набором -e це виглядатиме так:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

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


10
З set -eвас ще можна зробити деякі команди виходу з помилками без зупинки сценарію: command 2>&1 || echo $?.
Adobe

6
set -eперерве сценарій, якщо конвеєр або структура команди поверне ненульове значення. Наприклад foo || bar, не вдасться лише в тому випадку, якщо обидва fooі barповернуть ненульове значення. Зазвичай добре написаний скрипт bash спрацює, якщо ви додасте set -eна початку, а додавання працює як автоматизована перевірка розумності: скасуйте сценарій, якщо щось піде не так.
Mikko Rantalainen

6
Якщо ви збираєте команди разом, ви також можете вийти з ладу, якщо будь-яка з них не вдасться, встановивши set -o pipefailпараметр.
Джейк Бізінгер

18
Насправді ідіоматичний код без set -eбув би справедливим make || exit $?.
tripleee

4
Також у вас є set -u. Подивіться на неофіційний Баш суворого режиму : set -euo pipefail.
Пабло А

235

Хлопець SysOps одного разу навчив мене техніці "Три пальця":

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

Ці функції: * NIX OS та оболонка надійна. Розмістіть їх на початку вашого сценарію (bash чи іншим чином),try() вашої заяви та коду.

Пояснення

(на основі коментаря літаючих овець ).

  • yell: надрукуйте ім'я сценарію та всі аргументи на stderr:
    • $0 - шлях до сценарію;
    • $* всі аргументи.
    • >&2означає, що >перенаправлення stdout на & pipe2 . труба1 була б stdoutсама.
  • dieробить те ж саме yell, але завершує роботу зі статусом виходу без 0 , що означає "невдача".
  • tryвикористовує ||(булева OR), яка оцінює праву сторону лише в тому випадку, якщо ліва не вдалася.
    • $@знову всі аргументи, але різні .

3
Я досить новичок сценаріїв Unix. Чи можете ви пояснити, як виконуються вищезазначені функції? Я бачу новий синтаксис, з яким я не знайомий. Дякую.
kaizenCoder

15
крик : $0це шлях до сценарію. $*всі аргументи. >&2означає " >перенаправлення &прокладки на трубу 2". труба 1 була б сама по собі. тому викрикуйте всі аргументи з ім'ям сценарію та друкує на stderr. die робить те саме, що кричить , але закінчується зі статусом виходу не 0, що означає "провал". спробуйте використовувати булевий або ||, який оцінює правий бік лише у тому випадку, якщо лівий не вийшов з ладу. $@знову всі аргументи, але різні . сподіваюся, що все пояснює
літаючі вівці

1
Я змінив це die() { yell "$1"; exit $2; }так, щоб ви могли передавати повідомлення та вихідний код за допомогою die "divide by zero" 115.
Марк Лаката

3
Я бачу, як я б користувався yellі die. Однак tryне так вже й багато. Чи можете ви навести приклад, яким ви користуєтесь?
кшеной

2
Хм, але як ти їх використовуєш у сценарії? Я не розумію, що ви маєте на увазі під "спробувати () свою заяву та код на".
TheJavaGuy-Ivan Milosavljević

33

Якщо ви будете викликати скрипт source, ви можете використовувати return <x>де<x> буде статус виходу скрипту (використовуйте ненульове значення для помилки чи помилки). Але якщо ви будете викликати виконуваний скрипт (тобто безпосередньо з його ім'ям файлу), оператор return призведе до скарги (повідомлення про помилку "return: може лише" повернутися "з функції або скрипту з джерелом").

Якщо exit <x>використовується замість цього, коли сценарій викликається source, це призведе до виходу з оболонки, яка запустила сценарій, але виконуваний скрипт просто закінчиться, як очікувалося.

Для обробки будь-якого випадку в одному сценарії ви можете використовувати

return <x> 2> /dev/null || exit <x>

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

Примітка: <x>передбачається, що це просто число.


Не працює для мене в сценарії із поверненням / виходом всередині функції, тобто чи можна вийти всередину функції без наявної оболонки, але без того, щоб абонент функції піклувався про правильну перевірку коду повернення функції ?
січня

@jan Те, що абонент робить (або не робить) зі значеннями повернення, є повністю ортогональним (тобто незалежним від) того, як ви повертаєтесь з функції (... без виходу з оболонки, незалежно від виклику). Це головним чином залежить від коду абонента, який не є частиною цього запитання. Ви навіть можете пристосувати повернене значення функції до потреб абонента, але ця відповідь не обмежує те, яким може бути це повернене значення ...
kavadias

11

Я часто включаю функцію, яку називають run () для обробки помилок. Кожен дзвінок, який я хочу здійснити, передається цій функції, тому весь скрипт завершується, коли відбувається помилка. Перевага цього перед рішенням set -e полягає в тому, що скрипт не виходить мовчки, коли рядок не працює, і може сказати вам, у чому проблема. У наступному прикладі 3-й рядок не виконується, оскільки сценарій виходить при виклику в false.

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"

1
Людина, мені чомусь дуже подобається ця відповідь. Я визнаю, що це трохи складніше, але це здається настільки корисним. І з огляду на те, що я не фахівець з басу, це змушує мене вважати, що моя логіка несправна, і в цій методиці щось не так, інакше я вважаю, що інші похвалили б її. Отже, у чому проблема з цією функцією? Чи є щось, на що я повинен тут звертати увагу?

Я не пам'ятаю свою причину використання eval, функція прекрасно працює з cmd_output = $ ($ 1)
Джозеф Шеді

Я щойно реалізував це як частину складного процесу розгортання, і він спрацював фантастично. Дякую і ось коментар та відгук.
fuzzygroup

Воістину дивовижна робота! Це найпростіше і найчистіше рішення, яке добре працює. Для мене я додав це перед командою в циклі FOR, оскільки петлі FOR не підхоплять set -e option. Тоді команда, так як з аргументами, я використовував одиничні лапки , щоб уникнути проблем Баш подобається так: runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'. Примітка. Я змінив назву функції на runTry.
Тоні-Кафе

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

5

Замість ifпобудови можна використовувати оцінку короткого замикання :

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

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


1
якщо у мене command -that --fails || exit $?це працює без дужок, що це зважає на те, що вони echo $[4/0]нам потрібні?
Анентроп

2
@Anentropic @skalee Дужки не мають нічого спільного з пріоритетом, а обробкою виключень. Ділення на нуль призведе до негайного виходу з оболонки з кодом 1. Без дужок (тобто простий echo $[4/0] || exit $?) bash ніколи не виконає echo, не кажучи вже про підкорення ||.
bobbogo

1

У мене те саме питання, але не можу його задати, оскільки це було б дублікат.

Прийнята відповідь, використовуючи вихід, не працює, коли сценарій трохи складніше. Якщо ви використовуєте фоновий процес для перевірки стану, вихід тільки виходить з цього процесу, оскільки він працює в підколонці. Щоб вбити скрипт, ви мусите його явно вбити (принаймні, це єдиний спосіб, який я знаю).

Ось невеликий сценарій, як це зробити:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

Це краща відповідь, але все ще неповна. Я дійсно не знаю, як позбутися частини стріли .

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