- How to get the exit status a loop in bash
- 8 Answers 8
- t/README : suggest how to leave test early with failure
- Be careful when you loop
- Как использовать коды завершения в Bash-скриптах
- Что такое коды завершения
- Что происходит, когда коды завершения не определены
- Как использовать коды завершения в Bash-скриптах
- Проверяем коды завершения
- Создаём собственный код завершения
- Как использовать коды завершения в командной строке
- Дополнительные коды завершения
How to get the exit status a loop in bash
I know how to check the status of the previously executed command using $?, and we can make that status using exit command. But for the loops in bash are always returning a status 0 and is there any way I can break the loop with some status.
#!/bin/bash while true do if [ -f "/test" ] ; then break ### Here I would like to exit with some status fi done echo $? ## Here I want to check the status.
Yes that we can do, but what I am looking is there any way we can return out loop commands with some status.
As each and every command will exit with some status, Is there any possibility to exit my loop with some status. SO that I can check my status of loop using $? variable.
8 Answers 8
The status of the loop is the status of the last command that executes. You can use break to break out of the loop, but if the break is successful, then the status of the loop will be 0 . However, you can use a subshell and exit instead of breaking. In other words:
for i in foo bar; do echo $i; false; break; done; echo $? # The loop succeeds ( for i in foo bar; do echo $i; false; exit; done ); echo $? # The loop fails
You could also put the loop in a function and return a value from it. eg:
Instead of using a subshell, I’d recommend enclosing your code in a function, because (1) you can use return $returncode instead of exit $returncode and (2) by using a meaningful name for the function, your make your code more readable.
And (3), a function isn’t a subshell, which means it doesn’t fork. (Not every sh implementation forks for a subshell, but bash does.)
while true; do case $RANDOM in *0) exit 27 ;; esac done
rc=0 for file in *; do grep fnord "$file" || rc=$? done exit $rc
The real question is to decide whether the exit code of the loop should be success or failure if one iteration fails. There are scenarios where one make more sense than the other, and other where it’s not at all clear cut.
I have given example in my question, And in the example you have given if I use it will completely exit form my script instead of loop.
My second example translates straightforwardly to what you are attempting. The return code is in $rc , not in $? , though. Put the loop in a function as William Pursell suggests to work around that if it’s a problem.
while list-1; do list-2; done until list-1; do list-2; done [..]The exit status of the while and until commands is the exit status of the last command executed in list-2, or zero if none was executed.[..]
The last command that is executed inside the loop is break . And the exit value of break is 0 (see: help break ).
This is why your program keeps exiting with 0.
The break builtin for bash does allow you to accomplish what you are doing, just break with a negative value and the status returned by $? will be 1:
while true do if [ -f "./test" ] ; then break -1 fi done echo $? ## You'll get 1 here..
Note, this is documented in the help for the break builtin:
break: break [n] Exit for, while, or until loops.
Exit a FOR, WHILE or UNTIL loop. If N is specified, break N enclosing loops.
Exit Status: The exit status is 0 unless N is not greater than or equal to 1.
You can break out of n number of loops or send a negative value for breaking with a non zero return, ie, 1
I agree with @hagello as one option doing a sleep and changing the loop:
#!/bin/bash timeout=120 waittime=0 sleepinterval=3 until [[ -f "./test" || ($waittime -eq $timeout) ]] do $(sleep $sleepinterval) waittime=$((waittime + sleepinterval)) echo "waittime is $waittime" done if [ $waittime -lt $sleepinterval ]; then echo "file already exists" elif [ $waittime -lt $timeout ]; then echo "waited between $((waittime-3)) and $waittime seconds for this to finish. " else echo "operation timed out. " fi
break works as desired despite or even because of that error. That’s how you get the error status that we want. You can do break -1 2>/dev/null to avoid seeing the error. That honestly seems like the least cumbersome solution since moving code to a subshells can have undesirable effects too.
I think what you should be asking is: How can I wait until a file or a directory ( /test ) gets created by another process?
What you are doing up to now is polling with full power. Your loop will allocate up to 100% of the processing power of one core. The keyword is «polling», which is ethically wrong by the standards of computer scientists.
- insert a sleep statement in your loop; advantage: very simple; disadvantage: the delay will be an arbitrary trade-off between CPU load and responsiveness. («Arbitrary» is ethically wrong, too).
- use a notification mechanism like inotify (see: man inotify ); advantage: no CPU load, great responsiveness, no delays, no arbitrary constants in your code; disadvantage: inotify is a kernel API – you need some code to access it: inotify-tools or some C/Perl/Python code. Have a look at inotify and bash!
I would like to submit an alternative solution which is simpler and I think more elegant:
(while true do if [ -f "test" ] ; then break fi done Of course of this is part of a script then you could user return 1 instead of exit 1 exit 1 ) echo "Exit status is: $?"
Git 2.27 (Q2 2020), offers a good illustration of the exit status in a loop, here within the context of aborting a failing test early (e.g. by exiting a loop), which is to say «return 1».
t/README : suggest how to leave test early with failure
Helped-by: Jeff King
Over time, we added the support to our test framework to make it easy to leave a test early with failure, but it was not clearly documented in t/README to help developers writing new tests.
Be careful when you loop
You may need to verify multiple things in a loop, but the following does not work correctly:
test_expect_success 'test three things' ' for i in one two three do test_something "$i" done && test_something_else '
Because the status of the loop itself is the exit status of the test_something in the last round, the loop does not fail when » test_something » for » one » or » two » fails.
This is not what you want.
Instead, you can break out of the loop immediately when you see a failure.
Because all test_expect_* snippets are executed inside a function, » return 1 » can be used to fail the test immediately upon a failure:
test_expect_success 'test three things' ' for i in one two three do test_something "$i" || return 1 done && test_something_else '
Note that we still && -chain the loop to propagate failures from earlier commands.
Как использовать коды завершения в Bash-скриптах
Инструменты автоматизации и мониторинга удобны тем, что разработчик может взять готовые скрипты, при необходимости адаптировать и использовать в своём проекте. Можно заметить, что в некоторых скриптах используются коды завершения (exit codes), а в других нет. О коде завершения легко забыть, но это очень полезный инструмент. Особенно важно использовать его в скриптах командной строки.
Что такое коды завершения
В Linux и других Unix-подобных операционных системах программы во время завершения могут передавать значение родительскому процессу. Это значение называется кодом завершения или состоянием завершения. В POSIX по соглашению действует стандарт: программа передаёт 0 при успешном исполнении и 1 или большее число при неудачном исполнении.
Почему это важно? Если смотреть на коды завершения в контексте скриптов для командной строки, ответ очевиден. Любой полезный Bash-скрипт неизбежно будет использоваться в других скриптах или его обернут в однострочник Bash. Это особенно актуально при использовании инструментов автоматизации типа SaltStack или инструментов мониторинга типа Nagios. Эти программы исполняют скрипт и проверяют статус завершения, чтобы определить, было ли исполнение успешным.
Кроме того, даже если вы не определяете коды завершения, они всё равно есть в ваших скриптах. Но без корректного определения кодов выхода можно столкнуться с проблемами: ложными сообщениями об успешном исполнении, которые могут повлиять на работу скрипта.
Что происходит, когда коды завершения не определены
В Linux любой код, запущенный в командной строке, имеет код завершения. Если код завершения не определён, Bash-скрипты используют код выхода последней запущенной команды. Чтобы лучше понять суть, обратите внимание на пример.
#!/bin/bash touch /root/test echo created file
Этот скрипт запускает команды touch и echo . Если запустить этот скрипт без прав суперпользователя, команда touch не выполнится. В этот момент мы хотели бы получить информацию об ошибке с помощью соответствующего кода завершения. Чтобы проверить код выхода, достаточно ввести в командную строку специальную переменную $? . Она печатает код возврата последней запущенной команды.
Как видно, после запуска команды ./tmp.sh получаем код завершения 0 . Этот код говорит об успешном выполнении команды, хотя на самом деле команда не выполнилась. Скрипт из примера выше исполняет две команды: touch и echo . Поскольку код завершения не определён, получаем код выхода последней запущенной команды. Это команда echo , которая успешно выполнилась.
Если убрать из скрипта команду echo , можно получить код завершения команды touch .
Поскольку touch в данном случае — последняя запущенная команда, и она не выполнилась, получаем код возврата 1 .
Как использовать коды завершения в Bash-скриптах
Удаление из скрипта команды echo позволило нам получить код завершения. Что делать, если нужно сделать разные действия в случае успешного и неуспешного выполнения команды touch ? Речь идёт о печати stdout в случае успеха и stderr в случае неуспеха.
Проверяем коды завершения
Выше мы пользовались специальной переменной $? , чтобы получить код завершения скрипта. Также с помощью этой переменной можно проверить, выполнилась ли команда touch успешно.
#!/bin/bash touch /root/test 2> /dev/null if [ $? -eq 0 ] then echo "Successfully created file" else echo "Could not create file" >&2 fi
После рефакторинга скрипта получаем такое поведение:
- Если команда touch выполняется с кодом 0 , скрипт с помощью echo сообщает об успешно созданном файле.
- Если команда touch выполняется с другим кодом, скрипт сообщает, что не смог создать файл.
Любой код завершения кроме 0 значит неудачную попытку создать файл. Скрипт с помощью echo отправляет сообщение о неудаче в stderr .
Создаём собственный код завершения
Наш скрипт уже сообщает об ошибке, если команда touch выполняется с ошибкой. Но в случае успешного выполнения команды мы всё также получаем код 0 .
Поскольку скрипт завершился с ошибкой, было бы не очень хорошей идеей передавать код успешного завершения в другую программу, которая использует этот скрипт. Чтобы добавить собственный код завершения, можно воспользоваться командой exit .
#!/bin/bash touch /root/test 2> /dev/null if [ $? -eq 0 ] then echo "Successfully created file" exit 0 else echo "Could not create file" >&2 exit 1 fi
Теперь в случае успешного выполнения команды touch скрипт с помощью echo сообщает об успехе и завершается с кодом 0 . В противном случае скрипт печатает сообщение об ошибке при попытке создать файл и завершается с кодом 1 .
Как использовать коды завершения в командной строке
Скрипт уже умеет сообщать пользователям и программам об успешном или неуспешном выполнении. Теперь его можно использовать с другими инструментами администрирования или однострочниками командной строки.
В примере выше && используется для обозначения «и», а || для обозначения «или». В данном случае команда выполняет скрипт ./tmp.sh , а затем выполняет echo «bam» , если код завершения 0 . Если код завершения 1 , выполняется следующая команда в круглых скобках. Как видно, в скобках для группировки команд снова используются && и || .
Скрипт использует коды завершения, чтобы понять, была ли команда успешно выполнена. Если коды завершения используются некорректно, пользователь скрипта может получить неожиданные результаты при неудачном выполнении команды.
Дополнительные коды завершения
Команда exit принимает числа от 0 до 255 . В большинстве случаев можно обойтись кодами 0 и 1 . Однако есть зарезервированные коды, которые обозначают конкретные ошибки. Список зарезервированных кодов можно посмотреть в документации.
Адаптированный перевод статьи Understanding Exit Codes and how to use them in bash scripts by Benjamin Cane. Мнение администрации Хекслета может не совпадать с мнением автора оригинальной публикации.