Как сохранить и восстановить все параметры оболочки, включая errexit

9

Я прочитал много вопросов на различных сайтах обмена стека и справочных форумах Unix о том, как изменить параметры оболочки, а затем восстановить их - наиболее полный вопрос, который я нашел здесь, находится в разделе Как «отменить» `set -x`?

Получил мудрость , как представляется, либо сохранить от результата set +oили , shopt -poа затем evalпозже восстановить предыдущие настройки.

Однако, в моем собственном тестировании с bash 3.x и 4.x, errexitопция не сохраняется правильно при выполнении подстановки команд.

Вот пример сценария, чтобы показать проблему:

set -o errexit
set -o nounset
echo "LOCAL SETTINGS:"
set +o
OLDOPTS=$(set +o)
echo
echo "SAVED SETTINGS:"
echo "$OLDOPTS"

И вывод (я обрезал некоторые нерелевантные переменные):

LOCAL SETTINGS:
set -o errexit
set -o nounset

SAVED SETTINGS:
set +o errexit
set -o nounset

Это кажется чрезвычайно опасным. Большинство сценариев, которые я пишу, зависят от errexitостановки выполнения в случае сбоя какой-либо команды Я только что обнаружил ошибку в одном из моих сценариев, вызванную этим, когда функция, которая должна была быть восстановлена errexitв конце, завершила его переопределение, установив для него значение по умолчанию off в течение всего сценария.

То, что я хотел бы сделать, это написать функции, которые могут устанавливать параметры по мере необходимости, а затем правильно восстанавливать все параметры перед выходом. Но похоже, что в подоболочке, вызываемой подстановкой команды, errexitне наследуется.

Я в растерянности из-за того, как сохранить результат set +oбез использования подстановки команд или прыжков через обручи FIFO. Я могу читать из, $SHELLOPTSно это не для записи или в eval--формате.

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

Вероятно, связано: /programming/29532904/bash-subshell-errexit-semantics (кажется, есть обходной путь для Bash 4.4 и выше, но я бы предпочел портативное решение)

Элиот
источник
Это не восстанавливает shoptпараметры набора bash (например, nullglob).
Исаак

Ответы:

6

То, что вы делаете, должно работать. Но bash отключает errexitопцию в подстановках команд, поэтому сохраняет все опции, кроме этой. Это специфично для bash и специфично для errexitопции. Bash сохраняет errexitпри работе в режиме POSIX. Начиная с bash 4.4, bash также не очищает errexitподстановку команд, если shopt -s inherit_errexitдействует.

Поскольку эта опция отключена до запуска любого кода внутри подстановки команд, вы должны проверить его снаружи.

OLDOPTS=$(set +o)
case $- in
  *e*) OLDOPTS="$OLDOPTS; set -e";;
  *) OLDOPTS="$OLDOPTS; set +e";;
esac

Если вам не нравится эта сложность, используйте вместо нее zsh.

setopt local_options
Жиль "ТАК - перестань быть злым"
источник
3
Обратите внимание, что bash4.4теперь имеет local -(а-ля ash) в качестве эквивалента zsh's setopt localoptions(или что ksh88 делает по умолчанию)
Стефан
Смотрите также shopt -s lastpipe; set +o | IFS= read -rd '' OLDOPTS || :(два набора опций - еще одна bashособенность России).
Стефан
Simpler: OLDOPTS="$(set +o); set -$-".
Исаак
2

Попробовав старое из вышеперечисленного на alpine 3.6, я выбрал следующий гораздо более простой подход:

OLDOPTS="$(set +o); set -${-//c}"
set -euf -o pipefail

... my stuff

# restore original options
set +vx; eval "${OLDOPTS}"

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

Эрих Эйхингер
источник
@Isaac Я использую 'set + euf', потому что $ - содержит только список активных опций. т.е. при сбросе я сначала деактивирую все, а затем снова активирую только список ранее активных опций (я выбираю 'euf', потому что это единственные опции, которые я обычно изменяю - для общего использования, вероятно, он должен содержать полный список возможных опций). Хорошие замечания по поводу 'set + vx', я соответственно изменил приведенный выше пример
Эрих Эйхингер
это может работать для errexit, но не, например, для проверки границ или globbing ( set -uf)
Эрих Эйхингер
@ Исаак, я исправлюсь. Я не знаю, что я сделал не так. Большое спасибо за вашу помощь! Также приятно видеть, что вы столкнулись и решили проблему с флагом -c - хотелось бы, чтобы я видел ваш комментарий ранее;)
Эрих Эйхингер,
Я обновил вышеупомянутое решение, основываясь на отзывах @Isaac
Эрих Эйхингер,
1

errexit распространяется в процесс замены.

set -e

# Backup restore commands into an array
declare -a OPTS
readarray -t OPTS < <(shopt -po)

set +e

# Restore options
declare cmd
for cmd in "${OPTS[@]}"; do
    eval "$cmd"
done

Проверьте:

$  shopt -po errexit
set -o errexit

Bash версия:

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Сергей Аликов
источник
1

Простое решение - добавить errexitпараметр в OLDOPTS:

OLDOPTS="$(set +o)"
[ "${BASH_VERSION:+x}" ] && shopt -qo errexit && OLDOPTS+=";set -e" || true

Выполнено.

Исаак
источник
Смотрите также [[ -o errexit ]](ksh / bash / zsh) и [ -o errexit ](bash / ksh / yash)
Стефан Шазелас
Это shoptправильная команда для bash, нет веской причины ее избегать (я лишь в ваших личных предпочтениях относительно того, как должны быть написаны ответы). Не значимое изменение. @ StéphaneChazelas
Исаак