- Вопрос или проблема
- Решение
- Объяснение
- Объяснено
- Ответ или решение
- Предотвращение преждевременного завершения скрипта Bash с использованием grep
- Введение
- Пример проблемы
- Решение проблемы
- 1. Используйте || true
- 2. Ненавязчивое игнорирование кода выхода
- 3. Условная проверка кода выхода
- 4. С использованием sed
- Заключение
Вопрос или проблема
Этот скрипт не выводит after
:
#!/bin/bash -e
echo "до"
echo "что угодно" | grep e # он бы вывел, если бы я искал 'y' вместо этого
echo "после"
exit
Я мог бы решить это, удалив опцию -e
в строке shebang, но я хочу оставить её, чтобы мой скрипт останавливался в случае ошибки. Я не считаю отсутствие совпадения в grep
ошибкой. Как я могу предотвратить его резкое завершение?
echo "что угодно" | { grep e || true; }
Объяснение:
- Это вызовет ошибку
$ echo "что угодно" | grep e $ echo $? 1
- Это не вызовет ошибку
$ echo "что угодно" | { grep e || true; } $ echo $? 0
- Версия “no-op” от DopeGhoti (потенциально избегает создания процесса, если
true
не является встроенной командой), это не вызовет ошибку$ echo "что угодно" | { grep e || :; } $ echo $? 0
||
означает “или”. Если первая часть команды “проваливается” (то есть grep e
возвращает ненулевой код выхода), то выполняется часть после ||
, успешна и возвращает ноль как код выхода (true
всегда возвращает ноль).
Надежный способ безопасно и опционально использовать grep
для сообщений:
echo что угодно | grep e || [[ $? == 1 ]] ## выводит 'что угодно', $? равно 0
echo что угодно | grep x || [[ $? == 1 ]] ## нет вывода, $? равно 0
echo что угодно | grep --неправильный-аргумент e || [[ $? == 1 ]] ## вывод stderr, $? равно 1
Согласно posix мануалу, код выхода:
1
означает, что не было выбранных строк.> 1
означает ошибку.
Если вы используете grep с set -euo pipefail
и не хотите выходить, если запись не найдена (код выхода = 1), но все же хотите, чтобы он завершился, если это другой код выхода, вы можете использовать это:
#!/bin/bash
set -euo pipefail
echo "что угодно" | { grep e || test $? = 1; } | { grep e2 || test $? = 1; }
Грепы внутри пайпов будут ИСКЛЮЧИТЕЛЬНО игнорировать код выхода = 1. Таким образом, вам не нужно терять преимущества использования set -euo pipefail
.
В этом примере я намеренно включил два грепа с пайпами, которые могут завершиться с ошибкой, чтобы проиллюстрировать концепцию.
Обратитесь к посту myrdd там.
Еще один вариант — добавить другую команду в пайплайн — такую, которая не завершится с ошибкой:
echo "что угодно" | grep e | cat
Поскольку cat
теперь является последней командой в пайплайне, код выхода будет определяться не grep
, а cat
.
Другой вариант:
...
set +e
echo "что угодно" | grep e
set -e
...
Решение
#!/bin/bash -e
echo "до"
echo "что угодно" | grep e || : # он бы вывел, если бы я искал 'y' вместо этого
echo "после"
exit
Объяснение
set -e
или set -o errexit
Выходить немедленно, если пайплайн (который может состоять из одной простой команды), список или составная команда (см.
SHELL GRAMMAR
выше) завершится с ненулевым статусом. Оболочка не завершится, если команда, которая завершилась с ошибкой, является частью списка команд, непосредственно следующего за ключевыми словамиwhile
илиuntil
, частью условия после зарезервированных словif
илиelif
, частью любой команды, выполняемой в списке&&
или||
, кроме команды, следующие за последним&&
или||
, любой команды в пайплайне, кроме последней, или если значение возврата команды инвертируется с помощью!
. Если составная команда, отличная от подсистемы, возвращает ненулевой статус из-за того, что команда завершилась с ошибкой, когда-e
игнорировалась, оболочка не завершится. Захват наERR
, если установлен, выполняется перед выходом оболочки. Эта опция применяется к окружению оболочки и к каждой среде подсистемы отдельно (см.COMMAND EXECUTION ENVIRONMENT
выше) и может вызвать выход из подсистемы до выполнения всех команд в подсистеме.
Кроме того, :
это команда без эффекта в Bash.
Альтернатива: используйте sed
вместо grep:
echo "что угодно" | sed -n '/e/p' # не выводит ничего
echo "что угодно" | sed -n '/y/p' # выводит "что угодно"
Объяснение: sed
заставляет подавлять любой ввод (-n
), кроме как для вывода (p
), когда он соответствует регулярному выражению.
Возвратное значение равно 0 в обоих случаях. Работает на современном Linux и старом Solaris 8.
Проблема, которую я имею с большинством, а возможно, и со всеми ответами здесь, заключается в том, что они теряют истинный код выхода ($?) и в основном возвращают $? как 0. Нет способа отличить, было ли совпадение (код выхода 0) или не было совпадения (код выхода 1). Это решение решает эту проблему:
#!/bin/bash -e
echo "до"
exit_code=0
echo "что угодно" | grep e || exit_code=$?; if [[ $exit_code -ne 1 ]]; then (exit $exit_code); fi
echo "после"
exit
Объяснено
Код состояния grep
— это либо 0, 1 или 2:
0
означает, что было совпадение1
означает, что совпадения не было2
означает, что произошла ошибка
Теперь, с приведенным выше решением:
- Если
grep
возвращает0
, тест не выполняется и возвращает истинный код выхода $? равный 0, а также устанавливает$exit_code=0
. - Если
grep
возвращает1
, тест выполняется и возвращает фиктивный код выхода $? равный 0, но мы также устанавливаем$exit_code=1
, который может быть использован будущим кодом для различения совпадения и отсутствия совпадения. - Если
grep
возвращает любое другое значение, тест выполняется и возвращает истинный код выхода больше 1, который должен правильно остановить скрипт, мы также устанавливаем$exit_code
соответствующим образом, но это не имеет значения, потому что скрипт остановится.
Большое спасибо @rouble за то, что поделились своим решением – спасли меня, я боролся с этим в GitHub Workflow больше дня.
Для тех, кто хочет перехватить не только правильный код выхода, но и вывод команды grep, вот что сработало для меня:
echo "до"
grep_exit_code=0
grep_output=$(echo "a" | grep "b") || grep_exit_code=$? ; if [[ $grep_exit_code -ne 1 ]]; then (exit $grep_exit_code); fi
echo "grep_exit_code=$grep_exit_code"
echo "grep_output=$grep_output"
echo "после"
Ответ или решение
Предотвращение преждевременного завершения скрипта Bash с использованием grep
Введение
Скрипты на Shell очень популярны в системном администрировании и автоматизации задач. Однако использование set -e
(или -o errexit
) может привести к неожиданным результатам при работе с командами, которые возвращают ненулевые коды выхода. Это, в частности, проявляется при использовании команды grep
, когда она не находит совпадений. В данном руководстве мы обсудим, как избежать преждевременного завершения скрипта при использовании grep
, сохраняя при этом возможность отслеживать ошибки.
Пример проблемы
Рассмотрим следующий скрипт:
#!/bin/bash -e
echo "before"
echo "anything" | grep e # ничего не выводит, если искать 'y'
echo "after"
exit
Вывод будет следующим:
before
Скрипт не доходит до строки с echo "after"
потому что grep
возвращает код выхода 1, указывающий на отсутствие совпадений, что и приводит к завершению скрипта.
Решение проблемы
1. Используйте || true
Один из простых способов обработки этого случая — использовать || true
, чтобы гарантировать успешное завершение команды:
echo "anything" | grep e || true
Таким образом, если grep
не найдет совпадений, код выхода будет равен 0.
2. Ненавязчивое игнорирование кода выхода
Можно также использовать конструкцию с фигурными скобками, чтобы контролировать поведение выполнения:
echo "anything" | { grep e || true; }
Эта конструкция обрабатывает выходные данные с помощью операторов, позволяя избежать преждевременного завершения скрипта.
3. Условная проверка кода выхода
Для более точного контроля можно использовать условную проверку кода выхода:
exit_code=0
echo "anything" | grep e || exit_code=$?
if [[ $exit_code -ne 1 ]]; then
exit $exit_code
fi
Таким образом, вы сможете различать успешное выполнение (0
), отсутствие совпадений (1
) и ошибки (>1
).
4. С использованием sed
Другой возможный подход — использование sed
вместо grep
, если вам не критично, которая утилита используется:
echo "anything" | sed -n '/e/p' # Ничего не выводит
echo "anything" | sed -n '/y/p' # Выводит "anything"
sed
не возвращает код выхода 1 при отсутствии совпадений, что делает проблему с set -e
менее ощутимой.
Заключение
Использование set -e
в скриптах Bash может вызывать неожиданные проблемы при работе с командами, такими как grep
, которые могут возвращать ненулевые коды выхода. Однако, комбинация из правильного управления кодом выхода, использования условных конструкций и даже альтернативных команд позволит вам сохранить желаемое поведение скрипта и избежать его преждевременного завершения. Благодаря данным стратегиям, вы сможете писать более надежные и устойчивые к ошибкам скрипты.