bash: set -e и арифметические выражения
Допустим, у меня есть код со следующей арифметической операцией:
(( TOKEN_COUNTER++ ))
При включенном set -e этот код совершенно правомерно может завалить скрипт, поскольку здесь (( воспринимается как бы как команда; в свою очередь, эта команда при (( 0 )) возвращает 1, и именно это сваливает скрипт.
Это один из вариантов решения:
(( TOKEN_COUNTER++ )) || :
Но, может быть, есть что-нибудь, что можно использовать, не вдаваясь в подробности насчёт того, является ли (( какой-то там встроенной командой и возвращает ли она что-то там при определённых условиях? Хочется чего-то такого:
do_it_carefree (( TOKEN_COUNTER++ ))
Как это сделано в выражениях вроде if, while, until — и им подобных. В них и инкремент к переменной можно сделать, и отрицательный код возврата не валит скрипт, а останавливает выполнение кода под выражением.
Я было подумал про let , но это оказалось тем же самым, что и (( :
((expression)) The expression is evaluated according to the rules described below under ARITHMETIC EVALUATION. If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. This is exactly equivalent to let «expression».
Алсо, есть ещё непонятки с вариантами стрельбы по ногам с включенной обработкой нештатных ситуаций.
$ a() < return 1; >$ : $(a); echo $? 0 $ b=$(a); echo $? 1
Может быть, вы можете что-нибудь сказать на этот счёт, что прояснило бы как-то такое поведение, чтобы не оставаться просто так с опытом «так надо делать, а так — не стоит»?

ABW ?
Безопасные bash-скрипты или set -euxo pipefail
В 2020 году когда есть такие языки как python, ruby и go, использовать bash приходится редко, но иногда без него не обойтись.
Bash не похож на высокоуровневые языки программирования, он не предоставляет привычных гарантий. К примеру, если в python обратиться к неинициализированной переменной, то скрипт тут же завершиться, не выполнив ни одной инструкции. В bash это не так, любая переменная, к которой вы обратились, но не инициализировали, будет заменена на пустую строку. Только представьте сколько можно наворотить дел, если подобная переменная была в параметрах у команды rm -rf .
К счастью, можно изменить поведение оболочки используя встроенные функции, в частности set. С ее помощью, можно значительно повысить безопасность.
set -e
Указав параметр -e скрипт немедленно завершит работу, если любая команда выйдет с ошибкой. По-умолчанию, игнорируются любые неудачи и сценарий продолжет выполнятся. Если предполагается, что команда может завершиться с ошибкой, но это не критично, можно использовать пайплайн || true .
#!/bin/bash ./non-existing-command echo "RUNNING" # output # ------ # line 3: non-existing-command: command not found # RUNNING
С использованием -e :
#!/bin/bash set -e ./non-existing-command || true ./non-existing-command echo "RUNNING" # output # ------ # line 4: non-existing-command: command not found # line 5: non-existing-command: command not found
set -o pipefail
Но -e не идеален. Bash возвращает только код ошибки последней команды в пайпе (конвейере). И параметр -e проверяет только его. Если нужно убедиться, что все команды в пайпах завершились успешно, нужно использовать -o pipefail .
#!/bin/bash set -e ./non-existing-command | echo "PIPE" echo "RUNNING" # output # ------ # PIPE # line 4: non-existing-command: command not found # RUNNING
С использованием -o pipefail :
#!/bin/bash set -eo pipefail ./non-existing-command | echo "PIPE" echo "RUNNING" # output # ------ # PIPE # line 4: non-existing-command: command not found
set -u
Наверно самый полезный параметр — -u . Благодаря ему оболочка проверяет инициализацию переменных в скрипте. Если переменной не будет, скрипт немедленно завершиться. Данный параметр достаточно умен, чтобы нормально работать с переменной по-умолчанию $ и условными операторами ( if , while , и др).
#!/bin/bash echo "$MY_VAR>" echo "RUNNING" # output # ------ # # RUNNING
С использованием -u :
#!/bin/bash set -u echo "$MY_VAR>" echo "RUNNING" # output # ------ # line 4: MY_VAR: unbound variable
set -x
Параметр -x очень полезен при отладке. С помощью него bash печатает в стандартный вывод все команды перед их исполнением. Стоит учитывать, что все переменные будут уже доставлены, и с этим нужно быть аккуратнее, к примеру если используете пароли.
#!/bin/bash MY_VAR="a" echo "$MY_VAR>" echo "RUNNING" # output # ------ # a # RUNNING
С использованием -x :
#!/bin/bash set -x echo "$MY_VAR>" echo "RUNNING" # output # ------ # + MY_VAR=a # + echo a # a # + echo RUNNING # RUNNING
Вывод
Не стоит забывать, что все эти параметры можно объединять и комбинировать между собой! Думаю, при работе с bash будет хорошим тоном начинать каждый сценарий с set -euxo pipefail .
Bash set e что это
Необязательные параметры — это дополнительные ключи (опции), которые оказывают влияние на поведение сценария и/или командной оболочки.
Команда set позволяет задавать дополнительные опции прямо внутри сценария. В том месте сценария, где необходимо, чтобы та или иная опция вступила в силу, вставьте такую конструкцию set -o option-name, или в более короткой форме — set -option-abbrev. Эти две формы записи совершенно идентичны по своему действию.
#!/bin/bash set -o verbose # Вывод команд перед их исполнением.
#!/bin/bash set -v # Имеет тот же эффект, что и выше.
Для того, чтобы отключить действие той или иной опции, следует вставить конструкцию set +o option-name, или set +option-abbrev.
#!/bin/bash set -o verbose # Вывод команд перед их исполнением. command . command set +o verbose # Запретить вывод команд перед их исполнением. command # команда не выводится. set -v # Вывод команд перед их исполнением. command . command set +v # Запретить вывод команд перед их исполнением. command exit 0
Как вариант установки опций, можно предложить указывать их в заголовке сценария (в строке sha-bang) — #!.
#!/bin/bash -x # # Далее следует текст сценария.
Так же можно указывать дополнительные ключи в командной строке, при запуске сценария. Некоторые из опций работают только если они заданы из командной строки, например -i — ключ интерактивного режима работы скрипта.
bash -v script-name
bash -o verbose script-name
Ниже приводится список некоторых полезных опций, которые могут быть указаны как в полной форме так и в сокращенной.
Таблица 30-1. Ключи Bash
| Назад | К началу | Вперед |
| Отладка сценариев | Наверх | Широко распространенные ошибки |
unixforum.org
патченный БАШ / bash — стал вести себя странно с оператором «set -e»
точнее, стал выбрасывать из скрипта по непонятным причинам, не смотря на то, что оператор, на котором происходил выход из скрипта — отрабатывал без ошибок
пример, скрипт монтирования (дан только как пример, не для пристального внимания)
раньше, т.е. все время — работал без проблем, а сейчас вываливается из скрипта после команды монтирования, и т.к. у меня после монитрования идут еще другие команды — то они не выполняются
#!/bin/sh clear; set -e HOST=xxx.xxx.xxx.xxx PTR=~/tmp/mnt_$ ( mkdir -p $ sudo umount -l $ > /dev/null ) sshfs root@$:/ $ -p XXXXX -o allow_other # . другие команды $ mount | grep $
сначал а установил самый последний БАШ
bash-4.3.0 patch to 4.3.30
и заметил, что перестали нормально работать slackBuild-скрипты, которые раньше нормально отрабатывали
подумал, что навый БАШ непереваривает старые оси, ладно, установил заплаты на «свой родной баш»
bash-3.2.48 patch to 3.2.57
и та же самая история, slackBuild-скрипты перестали отрабатывать. доходят до команды make — и вываливаются, несмотря на то, что сама команда отрабатывает без ошибок
в конечном итоге перестали работать около 100 пользовательских баш-скриптов
откатился на старый-дырявый баш
на десктопе такой откат — не существеннен, а вот на сервере.
что то случилось в мире опенсорса пока я отсутствовал? :о)
какие будут комментарии?
slackware 12.2
kernel 2.6.32.63-smp
kde-tde 3.5.12
bash-3.2.48
with patches: bash32-049 — bash32-057
bash-4.3
with patches: bash43-001 — bash43-030
основная проблема решена, остался один вопрос
>> видимо у меня наследование «set -e» в «Bashe» отключено
>> AFAIK зависит от дистрибутива.
как это «регулируется», ни кто не подскажет?
если я ошибаюсь, то поправьте а не критикуйте :о)
Спасибо сказали:
Bizdelnick Модератор Сообщения: 20642 Статус: nulla salus bello ОС: Debian GNU/Linux
Re: патченный bash & set -e, странное поведение
Сообщение Bizdelnick » 11.11.2014 10:21
11.11.2014 04:46
Во-первых, ! пропущен, во-вторых, если скрипт завязан на типичное только для bash поведение, то пишите
#!/bin/bash
Вы на 100% уверены, что /bin/sh у Вас — это bash?
P. S. Попозже поисследую поведение bash’а у себя.
Пишите правильно:
| в консоли вку́пе (с чем-либо) в общем вообще |
в течение (часа) новичок нюанс по умолчанию |
приемлемо проблема пробовать трафик |
Спасибо сказали:
SLEDopit Модератор Сообщения: 4823 Статус: фанат консоли (= ОС: GNU/Debian, RHEL
Re: патченный bash & set -e, странное поведение
Сообщение SLEDopit » 11.11.2014 10:50
11.11.2014 04:46
а сейчас вываливается из скрипта после команды монтирования
а вы не пробовали эту команду отдельно запустить и посмотреть exit code?
ну или отключить set -e и выводить exit code в скриптах?
UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity. © Dennis Ritchie
The more you believe you don’t do mistakes, the more bugs are in your code.
Спасибо сказали:
Bizdelnick Модератор Сообщения: 20642 Статус: nulla salus bello ОС: Debian GNU/Linux
Re: патченный bash & set -e, странное поведение
Сообщение Bizdelnick » 11.11.2014 12:13
Посмотрел на скрипт более внимательно.
11.11.2014 04:46
mkdir -p $ sudo umount -l $ > /dev/null
Если в каталог $ ничего не смонтировано (в том числе если он только что создан), то umount завершится с ненулевым статусом, и в случае set -e завершится и сам скрипт. Чтобы работало, как Вы хотели, надо делать как-то так:
sudo umount -l $ 2> /dev/null || true
Пишите правильно:
| в консоли вку́пе (с чем-либо) в общем вообще |
в течение (часа) новичок нюанс по умолчанию |
приемлемо проблема пробовать трафик |
Спасибо сказали:
Bizdelnick Модератор Сообщения: 20642 Статус: nulla salus bello ОС: Debian GNU/Linux
Re: патченный bash & set -e, странное поведение
Сообщение Bizdelnick » 11.11.2014 12:59
Что могло измениться в bash — это наследование сабшеллом опции -e. Если Ваш скрипт раньше работал, значит, она не наследовалась (или /bin/sh был не bash, а другим шеллом, в котором в принципе нет такой опции). Теперь наследуется (у меня тоже).
GNU bash, version 4.2.37(1)-release (x86_64-pc-linux-gnu)
bash 4.2+dfsg-0.1+deb7u3
Пишите правильно:
| в консоли вку́пе (с чем-либо) в общем вообще |
в течение (часа) новичок нюанс по умолчанию |
приемлемо проблема пробовать трафик |
Спасибо сказали:
sunjob Сообщения: 319 Контактная информация:
Re: патченный bash & set -e, странное поведение
Сообщение sunjob » 13.11.2014 07:09
1.
#!/bin/sh — правильно написано, конечно же была опечатка
2.
on slackware sh —> bash
эти строки взяты в скобки, раньше я думал (а собственно так и работало) что скобки
— сохраняют текущий каталог, при выходе из скобок
— не выбрасывают из скрипта, если внутри скобок произошла «ошибка»
и все время, пока я «писал скрипты» — «это правило» так и работала (да, собственно оно и сейчас так же работает, т.е. ошибка «umount» — не приводит к выходу из скрипта, скрипт далее работает, монтирует удаленную папку итд.
ошибку и выход вызывала далее идущая команда (я ее не показал в начале)
которая не была взята в скобки (далее вооще темная история)
дело в том, что вместе с башем был обнавлен и пакет
fuse-2.8.5
—>
fuse-2.9.3
все идущие следом за вер. 2.8.5 вт.ч. и последняя ver. fuse-2.9.3 — не показывавет точку
монтирования, точнее вывод точки монтирования в команде «mount» (если удаленный каталог был смонтирован с пом. «sshfs»)
ни чего не показывала, т.е. на этом мы и вываливались из скрипта
как только не крутил ключи конфигуратора, пересобирал, все равно не вернул «нормальное поведение» для «fuse» — точка монтирования не показывалась командой «mount»
ладушки, обновил, по рекомендациям сообщений конфигуратора «fuse»
util-linux-ng-2.14.1
—>
util-linux-2.19-i486
пересобрал все зависимости — итог, все равно fuse — не кажет точку монтирования
в итоге откатился до работающих версий
далее, как выяснилось большинство скриптов перестали работать из-за такого поведения команды «fuse-mount» (т.е. данная проблема решилась)
остались неск. скриптов, с непонятным «новым поведением», с ними разбираюсь (далее отпишусь)
появился новый вопрос:
КАК ВЕРНУТЬ ПРАВИЛЬНОЕ ПОВЕДЕНИЕ FUSE-MOUNT (т.е. что бы команда «mount» — нормально показывала смонтированный «sshfs» удаленный каталог)
спасибо за участие
если я ошибаюсь, то поправьте а не критикуйте :о)
Спасибо сказали:
Bizdelnick Модератор Сообщения: 20642 Статус: nulla salus bello ОС: Debian GNU/Linux
Re: патченный bash & set -e, странное поведение
Сообщение Bizdelnick » 13.11.2014 10:19
13.11.2014 07:09
эти строки взяты в скобки, раньше я думал (а собственно так и работало) что скобки
— сохраняют текущий каталог, при выходе из скобок
— не выбрасывают из скрипта, если внутри скобок произошла «ошибка»
Всё, что делают скобки, — это запускают то, что в них заключено, в отдельном сабшелле. Что там происходит с наследованием опции -e, из документации мне не понятно, но, как я писал выше, у меня на данный момент она наследуется.
Да, если нужно просто сохранить текущий каталог, более эффективно использовать pushd/popd.
13.11.2014 07:09
все идущие следом за вер. 2.8.5 вт.ч. и последняя ver. fuse-2.9.3 — не показывавет точку
монтирования, точнее вывод точки монтирования в команде «mount» (если удаленный каталог был смонтирован с пом. «sshfs»)
ни чего не показывала, т.е. на этом мы и вываливались из скрипта
А покажите-ка ls -l /etc/mtab .
Пишите правильно:
| в консоли вку́пе (с чем-либо) в общем вообще |
в течение (часа) новичок нюанс по умолчанию |
приемлемо проблема пробовать трафик |
Спасибо сказали:
sunjob Сообщения: 319 Контактная информация:
Re: патченный bash & set -e, странное поведение
Сообщение sunjob » 13.11.2014 15:45
$ ls -l /etc/mtab
-rw-r—r— 1 root root 505 2014-11-13 19:41 /etc/mtab
$ cat /etc/mtab
/dev/sda1 / reiserfs rw,noatime,data=writeback 0 0
proc /proc proc rw 0 0
sysfs /sys sysfs rw 0 0
/dev/sda6 /home reiserfs rw,noatime,data=writeback 0 0
/dev/sda2 /mnt/hdd2 reiserfs rw,noatime,data=writeback 0 0
tmpfs /dev/shm tmpfs rw,noatime,size=8192m,mode=1777 0 0
tmpfs /tmp tmpfs rw,nosuid,nodev,noatime,mode=1777 0 0
tmpfs /var/lock tmpfs rw,noexec,nosuid,nodev,noatime,mode=1777,size=10m 0 0
root@192.168.0.251:/ /home/sun/tmp/mnt_192.168.0.251 fuse.sshfs rw,nosuid,nodev,allow_other,user=sun 0 0
это вывод с нормально работающим sshfs
т..е не понятно что должно было быть или наоборот.
поясните ваши подозрения или мысли
p.s.
долго вспоминал, что где то проскакивала нечто связанное с «mtab»
проблема решена, короче, надо было
— установить новый «util-linux-2.19»
— пересобрать «fuse-2.8.5» с опцией разрешающей работу с «mtab»
— пересобрать «sshfs-fuse-2.5»
так что проблема с поведением «fuse» решена
осталось некоторое непонятное поведение баша с неск. скриптами
разбираюсь.
Всё, что делают скобки, — это запускают то, что в них заключено, в отдельном сабшелле. Что там происходит с наследованием опции -e, из документации мне не понятно, но, как я писал выше, у меня на данный момент она наследуется.
видимо у меня наследование отключено