I need to stop my program when an exception is raised in Python.
How do I implement this?
cHao
84.7k20 gold badges145 silver badges172 bronze badges
asked Jan 13, 2009 at 13:13
3
import sys
try:
print("stuff")
except:
sys.exit(1) # exiting with a non zero value is better for returning from an error
brian d foy
129k31 gold badges203 silver badges586 bronze badges
answered Jan 13, 2009 at 13:16
Loïc WolffLoïc Wolff
3,16625 silver badges26 bronze badges
5
You can stop catching the exception, or — if you need to catch it (to do some custom handling), you can re-raise:
try:
doSomeEvilThing()
except Exception, e:
handleException(e)
raise
Note that typing raise without passing an exception object causes the original traceback to be preserved. Typically it is much better than raise e.
Of course — you can also explicitly call
import sys
sys.exit(exitCodeYouFindAppropriate)
This causes SystemExit exception to be raised, and (unless you catch it somewhere) terminates your application with specified exit code.
answered Jan 13, 2009 at 14:33
AbganAbgan
3,69022 silver badges31 bronze badges
1
If you don’t handle an exception, it will propagate up the call stack up to the interpreter, which will then display a traceback and exit. IOW : you don’t have to do anything to make your script exit when an exception happens.
answered Jan 13, 2009 at 19:52
1
import sys
try:
import feedparser
except:
print "Error: Cannot import feedparser.n"
sys.exit(1)
Here we’re exiting with a status code of 1. It is usually also helpful to output an error message, write to a log, and clean up.
answered Nov 18, 2009 at 22:36
PranabPranab
2,0974 gold badges28 silver badges48 bronze badges
2
As far as I know, if an exception is not caught by your script, it will be interrupted.
answered Jan 13, 2009 at 13:15
KeltiaKeltia
14.5k3 gold badges29 silver badges30 bronze badges
0
Время на прочтение
13 мин
Количество просмотров 75K
Введение
Ошибки, увы, неизбежны, поэтому их обработка занимает очень важное место в программировании. И если алгоритмические ошибки можно выявить и исправить во время написания и тестирования программы, то ошибок времени выполнения избежать нельзя в принципе. Сегодня мы рассмотрим функции стандартной библиотеки (C Standard Library) и POSIX, используемые в обработке ошибок.
Переменная errno и коды ошибок
<errno.h>
errno – переменная, хранящая целочисленный код последней ошибки. В каждом потоке существует своя локальная версия errno, чем и обусловливается её безопасность в многопоточной среде. Обычно errno реализуется в виде макроса, разворачивающегося в вызов функции, возвращающей указатель на целочисленный буфер. При запуске программы значение errno равно нулю.
Все коды ошибок имеют положительные значения, и могут использоваться в директивах препроцессора #if. В целях удобства и переносимости заголовочный файл <errno.h> определяет макросы, соответствующие кодам ошибок.
Стандарт ISO C определяет следующие коды:
- EDOM – (Error domain) ошибка области определения.
- EILSEQ – (Error invalid sequence) ошибочная последовательность байтов.
- ERANGE – (Error range) результат слишком велик.
Прочие коды ошибок (несколько десятков) и их описания определены в стандарте POSIX. Кроме того, в спецификациях стандартных функций обычно указываются используемые ими коды ошибок и их описания.
Нехитрый скрипт печатает в консоль коды ошибок, их символические имена и описания:
#!/usr/bin/perl
use strict;
use warnings;
use Errno;
foreach my $err (sort keys (%!)) {
$! = eval "Errno::$err";
printf "%20s %4d %sn", $err, $! + 0, $!
}
Если вызов функции завершился ошибкой, то она устанавливает переменную errno в ненулевое значение. Если же вызов прошёл успешно, функция обычно не проверяет и не меняет переменную errno. Поэтому перед вызовом функции её нужно установить в 0.
Пример:
/* convert from UTF16 to UTF8 */
errno = 0;
n_ret = iconv(icd, (char **) &p_src, &n_src, &p_dst, &n_dst);
if (n_ret == (size_t) -1) {
VJ_PERROR();
if (errno == E2BIG)
fprintf(stderr, " Error : input conversion stopped due to lack of space in the output buffern");
else if (errno == EILSEQ)
fprintf(stderr, " Error : input conversion stopped due to an input byte that does not belong to the input codesetn");
else if (errno == EINVAL)
fprintf(stderr, " Error : input conversion stopped due to an incomplete character or shift sequence at the end of the input buffern");
/* clean the memory */
free(p_out_buf);
errno = 0;
n_ret = iconv_close(icd);
if (n_ret == (size_t) -1)
VJ_PERROR();
return (size_t) -1;
}
Как видите, описания ошибок в спецификации функции iconv() более информативны, чем в <errno.h>.
Функции работы с errno
Получив код ошибки, хочется сразу получить по нему её описание. К счастью, ISO C предлагает целый набор полезных функций.
<stdio.h>
void perror(const char *s);
Печатает в stderr содержимое строки s, за которой следует двоеточие, пробел и сообщение об ошибке. После чего печатает символ новой строки 'n'.
Пример:
/*
// main.c
// perror example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
if (file) {
// Do something useful.
fclose(file);
}
else {
perror("fopen() ");
}
return EXIT_SUCCESS;
}
<string.h>
char* strerror(int errnum);
Возвращает строку, содержащую описание ошибки errnum. Язык сообщения зависит от локали (немецкий, иврит и даже японский), но обычно поддерживается лишь английский.
/*
// main.c
// strerror example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
char *errorbuf = strerror(error_num);
fprintf(stderr, "Error message : %sn", errorbuf);
}
return EXIT_SUCCESS;
}
strerror() не безопасная функция. Во-первых, возвращаемая ею строка не является константной. При этом она может храниться в статической или в динамической памяти в зависимости от реализации. В первом случае её изменение приведёт к ошибке времени выполнения. Во-вторых, если вы решите сохранить указатель на строку, и после вызовите функцию с новым кодом, все прежние указатели будут указывать уже на новую строку, ибо она использует один буфер для всех строк. В-третьих, её поведение в многопоточной среде не определено в стандарте. Впрочем, в QNX она объявлена как thread safe.
Поэтому в новом стандарте ISO C11 были предложены две очень полезные функции.
size_t strerrorlen_s(errno_t errnum);
Возвращает длину строки с описанием ошибки errnum.
errno_t strerror_s(char *buf, rsize_t buflen, errno_t errnum);
Копирует строку с описание ошибки errnum в буфер buf длиной buflen.
Пример:
/*
// main.c
// strerror_s example
//
// Created by Ariel Feinerman on 23/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
#ifdef __STDC_LIB_EXT1__
size_t error_len = strerrorlen_s(errno) + 1;
char error_buf[error_len];
strerror_s(error_buf, error_len, errno);
fprintf(stderr, "Error message : %sn", error_buf);
#endif
}
return EXIT_SUCCESS;
}
Функции входят в Annex K (Bounds-checking interfaces), вызвавший много споров. Он не обязателен к выполнению и целиком не реализован ни в одной из свободных библиотек. Open Watcom C/C++ (Windows), Slibc (GNU libc) и Safe C Library (POSIX), в последней, к сожалению, именно эти две функции не реализованы. Тем не менее, их можно найти в коммерческих средах разработки и системах реального времени, Embarcadero RAD Studio, INtime RTOS, QNX.
Стандарт POSIX.1-2008 определяет следующие функции:
char *strerror_l(int errnum, locale_t locale);
Возвращает строку, содержащую локализованное описание ошибки errnum, используя locale. Безопасна в многопоточной среде. Не реализована в Mac OS X, FreeBSD, NetBSD, OpenBSD, Solaris и прочих коммерческих UNIX. Реализована в Linux, MINIX 3 и Illumos (OpenSolaris).
Пример:
/*
// main.c
// strerror_l example – works on Linux, MINIX 3, Illumos
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <locale.h>
int main(int argc, const char * argv[])
{
locale_t locale = newlocale(LC_ALL_MASK, "fr_FR.UTF-8", (locale_t) 0);
if (!locale) {
fprintf(stderr, "Error: cannot create locale.");
exit(EXIT_FAILURE);
}
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(tmpnam(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
char *error_buf = strerror_l(errno, locale);
fprintf(stderr, "Error message : %sn", error_buf);
}
freelocale(locale);
return EXIT_SUCCESS;
}
Вывод:
Error message : Aucun fichier ou dossier de ce type
int strerror_r(int errnum, char *buf, size_t buflen);
Копирует строку с описание ошибки errnum в буфер buf длиной buflen. Если buflen меньше длины строки, лишнее обрезается. Безопасна в многоготочной среде. Реализована во всех UNIX.
Пример:
/*
// main.c
// strerror_r POSIX example
//
// Created by Ariel Feinerman on 25/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MSG_LEN 1024
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
char error_buf[MSG_LEN];
errno_t error = strerror_r (error_num, error_buf, MSG_LEN);
switch (error) {
case EINVAL:
fprintf (stderr, "strerror_r() failed: invalid error code, %dn", error);
break;
case ERANGE:
fprintf (stderr, "strerror_r() failed: buffer too small: %dn", MSG_LEN);
case 0:
fprintf(stderr, "Error message : %sn", error_buf);
break;
default:
fprintf (stderr, "strerror_r() failed: unknown error, %dn", error);
break;
}
}
return EXIT_SUCCESS;
}
Увы, никакого аналога strerrorlen_s() в POSIX не определили, поэтому длину строки можно выяснить лишь экспериментальным путём. Обычно 300 символов хватает за глаза. GNU C Library в реализации strerror() использует буфер длиной в 1024 символа. Но мало ли, а вдруг?
Пример:
/*
// main.c
// strerror_r safe POSIX example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MSG_LEN 1024
#define MUL_FACTOR 2
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
errno_t error = 0;
size_t error_len = MSG_LEN;
do {
char error_buf[error_len];
error = strerror_r (error_num, error_buf, error_len);
switch (error) {
case 0:
fprintf(stderr, "File : %snLine : %dnCurrent function : %s()nFailed function : %s()nError message : %sn", __FILE__, __LINE__, __func__, "fopen", error_buf);
break;
case ERANGE:
error_len *= MUL_FACTOR;
break;
case EINVAL:
fprintf (stderr, "strerror_r() failed: invalid error code, %dn", error_num);
break;
default:
fprintf (stderr, "strerror_r() failed: unknown error, %dn", error);
break;
}
} while (error == ERANGE);
}
return EXIT_SUCCESS;
}
Вывод:
File : /Users/ariel/main.c
Line : 47
Current function : main()
Failed function : fopen()
Error message : No such file or directory
Макрос assert()
<assert.h>
void assert(expression)
Макрос, проверяющий условие expression (его результат должен быть числом) во время выполнения. Если условие не выполняется (expression равно нулю), он печатает в stderr значения __FILE__, __LINE__, __func__ и expression в виде строки, после чего вызывает функцию abort().
/*
// main.c
// assert example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
int main(int argc, const char * argv[]) {
double x = -1.0;
assert(x >= 0.0);
printf("sqrt(x) = %fn", sqrt(x));
return EXIT_SUCCESS;
}
Вывод:
Assertion failed: (x >= 0.0), function main, file /Users/ariel/main.c, line 17.
Если макрос NDEBUG определён перед включением <assert.h>, то assert() разворачивается в ((void) 0) и не делает ничего. Используется в отладочных целях.
Пример:
/*
// main.c
// assert_example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#NDEBUG
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
int main(int argc, const char * argv[]) {
double x = -1.0;
assert(x >= 0.0);
printf("sqrt(x) = %fn", sqrt(x));
return EXIT_SUCCESS;
}
Вывод:
sqrt(x) = nan
Функции atexit(), exit() и abort()
<stdlib.h>
int atexit(void (*func)(void));
Регистрирует функции, вызываемые при нормальном завершении работы программы в порядке, обратном их регистрации. Можно зарегистрировать до 32 функций.
_Noreturn void exit(int exit_code);
Вызывает нормальное завершение программы, возвращает в среду число exit_code. ISO C стандарт определяет всего три возможных значения: 0, EXIT_SUCCESS и EXIT_FAILURE. При этом вызываются функции, зарегистрированные через atexit(), сбрасываются и закрываются потоки ввода — вывода, уничтожаются временные файлы, после чего управление передаётся в среду. Функция exit() вызывается в main() при выполнении return или достижении конца программы.
Главное преимущество exit() в том, что она позволяет завершить программу не только из main(), но и из любой вложенной функции. К примеру, если в глубоко вложенной функции выполнилось (или не выполнилось) некоторое условие, после чего дальнейшее выполнение программы теряет всякий смысл. Подобный приём (early exit) широко используется при написании демонов, системных утилит и парсеров. В интерактивных программах с бесконечным главным циклом exit() можно использовать для выхода из программы при выборе нужного пункта меню.
Пример:
/*
// main.c
// exit example
//
// Created by Ariel Feinerman on 17/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void third_2(void)
{
printf("third #2n"); // Does not print.
}
void third_1(void)
{
printf("third #1n"); // Does not print.
}
void second(double num)
{
printf("second : before exit()n"); // Prints.
if ((num < 1.0f) && (num > -1.0f)) {
printf("asin(%.1f) = %.3fn", num, asin(num));
exit(EXIT_SUCCESS);
}
else {
fprintf(stderr, "Error: %.1f is beyond the range [-1.0; 1.0]n", num);
exit(EXIT_FAILURE);
}
printf("second : after exit()n"); // Does not print.
}
void first(double num)
{
printf("first : before second()n")
second(num);
printf("first : after second()n"); // Does not print.
}
int main(int argc, const char * argv[])
{
atexit(third_1); // Register first handler.
atexit(third_2); // Register second handler.
first(-3.0f);
return EXIT_SUCCESS;
}
Вывод:
first : before second()
second : before exit()
Error: -3.0 is beyond the range [-1.0; 1.0]
third #2
third #1
_Noreturn void abort(void);
Вызывает аварийное завершение программы, если сигнал не был перехвачен обработчиком сигналов. Временные файлы не уничтожаются, закрытие потоков определяется реализацией. Самое главное отличие вызовов abort() и exit(EXIT_FAILURE) в том, что первый посылает программе сигнал SIGABRT, его можно перехватить и произвести нужные действия перед завершением программы. Записывается дамп памяти программы (core dump file), если они разрешены. При запуске в отладчике он перехватывает сигнал SIGABRT и останавливает выполнение программы, что очень удобно в отладке.
Пример:
/*
// main.c
// abort example
//
// Created by Ariel Feinerman on 17/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void third_2(void)
{
printf("third #2n"); // Does not print.
}
void third_1(void)
{
printf("third #1n"); // Does not print.
}
void second(double num)
{
printf("second : before exit()n"); // Prints.
if ((num < 1.0f) && (num > -1.0f)) {
printf("asin(%.1f) = %.3fn", num, asin(num));
exit(EXIT_SUCCESS);
}
else {
fprintf(stderr, "Error: %.1f is beyond the range [-1.0; 1.0]n", num);
abort();
}
printf("second : after exit()n"); // Does not print.
}
void first(double num)
{
printf("first : before second()n");
second(num);
printf("first : after second()n"); // Does not print.
}
int main(int argc, const char * argv[])
{
atexit(third_1); // register first handler
atexit(third_2); // register second handler
first(-3.0f);
return EXIT_SUCCESS;
}
Вывод:
first : before second()
second : before exit()
Error: -3.0 is beyond the range [-1.0; 1.0]
Abort trap: 6
Вывод в отладчике:
$ lldb abort_example
(lldb) target create "abort_example"
Current executable set to 'abort_example' (x86_64).
(lldb) run
Process 22570 launched: '/Users/ariel/abort_example' (x86_64)
first : before second()
second : before exit()
Error: -3.0 is beyond the range [-1.0; 1.0]
Process 22570 stopped
* thread #1: tid = 0x113a8, 0x00007fff89c01286 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007fff89c01286 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
-> 0x7fff89c01286 <+10>: jae 0x7fff89c01290 ; <+20>
0x7fff89c01288 <+12>: movq %rax, %rdi
0x7fff89c0128b <+15>: jmp 0x7fff89bfcc53 ; cerror_nocancel
0x7fff89c01290 <+20>: retq
(lldb)
В случае критической ошибки нужно использовать функцию abort(). К примеру, если при выделении памяти или записи файла произошла ошибка. Любые дальнейшие действия могут усугубить ситуацию. Если завершить выполнение обычным способом, при котором производится сброс потоков ввода — вывода, можно потерять ещё неповрежденные данные и временные файлы, поэтому самым лучшим решением будет записать дамп и мгновенно завершить программу.
В случае же некритической ошибки, например, вы не смогли открыть файл, можно безопасно выйти через exit().
Функции setjmp() и longjmp()
Вот мы и подошли к самому интересному – функциям нелокальных переходов. setjmp() и longjmp() работают по принципу goto, но в отличие от него позволяют перепрыгивать из одного места в другое в пределах всей программы, а не одной функции.
<setjmp.h>
int setjmp(jmp_buf env);
Сохраняет информацию о контексте выполнения программы (регистры микропроцессора и прочее) в env. Возвращает 0, если была вызвана напрямую или value, если из longjmp().
void longjmp(jmp_buf env, int value);
Восстанавливает контекст выполнения программы из env, возвращает управление setjmp() и передаёт ей value.
Пример:
/*
// main.c
// setjmp simple
//
// Created by Ariel Feinerman on 18/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
static jmp_buf buf;
void second(void)
{
printf("second : before longjmp()n"); // prints
longjmp(buf, 1); // jumps back to where setjmp was called – making setjmp now return 1
printf("second : after longjmp()n"); // does not prints
// <- Here is the point that is never reached. All impossible cases like your own house in Miami, your million dollars, your nice girl, etc.
}
void first(void)
{
printf("first : before second()n");
second();
printf("first : after second()n"); // does not print
}
int main(int argc, const char * argv[])
{
if (!setjmp(buf))
first(); // when executed, setjmp returned 0
else // when longjmp jumps back, setjmp returns 1
printf("mainn"); // prints
return EXIT_SUCCESS;
}
Вывод:
first : before second()
second : before longjmp()
main
Используя setjmp() и longjmp() можно реализовать механизм исключений. Во многих языках высокого уровня (например, в Perl) исключения реализованы через них.
Пример:
/*
// main.c
// exception simple
//
// Created by Ariel Feinerman on 18/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <setjmp.h>
#define str(s) #s
static jmp_buf buf;
typedef enum {
NO_EXCEPTION = 0,
RANGE_EXCEPTION = 1,
NUM_EXCEPTIONS
} exception_t;
static char *exception_name[NUM_EXCEPTIONS] = {
str(NO_EXCEPTION),
str(RANGE_EXCEPTION)
};
float asin_e(float num)
{
if ((num < 1.0f) && (num > -1.0f)) {
return asinf(num);
}
else {
longjmp(buf, RANGE_EXCEPTION); // | @throw
}
}
void do_work(float num)
{
float res = asin_e(num);
printf("asin(%f) = %fn", num, res);
}
int main(int argc, const char * argv[])
{
exception_t exc = NO_EXCEPTION;
if (!(exc = setjmp(buf))) { // |
do_work(-3.0f); // | @try
} // |
else { // |
fprintf(stderr, "%s was hadled in %s()n", exception_name[exc], __func__); // | @catch
} // |
return EXIT_SUCCESS;
}
Вывод:
RANGE_EXCEPTION was hadled in main()
Внимание! Функции setjmp() и longjmp() в первую очередь применяются в системном программировании, и их использование в клиентском коде не рекомендуется. Их применение ухудшает читаемость программы и может привести к непредсказуемым ошибкам. Например, что произойдёт, если вы прыгните не вверх по стеку – в вызывающую функцию, а в параллельную, уже завершившую выполнение?
Информация
- стандарт ISO/IEC C (89/99/11)
- Single UNIX Specifcation, Version 4, 2016 Edition
- The Open Group Base Specifcations Issue 7, 2016 Edition (POSIX.1-2008)
- SEI CERT C Coding Standard
- cправочная информация среды программирования
- справочная информация операционной системы (man pages)
- заголовочные файлы (/usr/include)
- исходные тексты библиотеки (C Standard Library)
Инструменты автоматизации и мониторинга удобны тем, что разработчик может взять готовые скрипты, при необходимости адаптировать и использовать в своём проекте. Можно заметить, что в некоторых скриптах используются коды завершения (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
touch: cannot touch '/root/test': Permission denied
created file
$ echo $?
0
Как видно, после запуска команды ./tmp.sh получаем код завершения 0. Этот код говорит об успешном выполнении команды, хотя на самом деле команда не выполнилась. Скрипт из примера выше исполняет две команды: touch и echo. Поскольку код завершения не определён, получаем код выхода последней запущенной команды. Это команда echo, которая успешно выполнилась.
Скрипт:
#!/bin/bash
touch /root/test
Если убрать из скрипта команду echo, можно получить код завершения команды touch.
$ ./tmp.sh
touch: cannot touch '/root/test': Permission denied
$ echo $?
1
Поскольку 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.
Выполнение:
$ ./tmp.sh
Could not create file
Создаём собственный код завершения
Наш скрипт уже сообщает об ошибке, если команда touch выполняется с ошибкой. Но в случае успешного выполнения команды мы всё также получаем код 0.
$ ./tmp.sh
Could not create file
$ echo $?
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
Could not create file
$ echo $?
1
Как использовать коды завершения в командной строке
Скрипт уже умеет сообщать пользователям и программам об успешном или неуспешном выполнении. Теперь его можно использовать с другими инструментами администрирования или однострочниками командной строки.
Bash-однострочник:
$ ./tmp.sh && echo "bam" || (sudo ./tmp.sh && echo "bam" || echo "fail")
Could not create file
Successfully created file
bam
В примере выше && используется для обозначения «и», а || для обозначения «или». В данном случае команда выполняет скрипт ./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. Мнение администрации Хекслета может не совпадать с мнением автора оригинальной публикации.
@gratur asks in a comment on @skaffman’s answer.
So if I understand correctly, I let the exception bubble up by removing the try/catch block and adding a «throws IOException» to that method (and methods that call that method, and so on)? I feel kind of icky doing that, because now I have to add a bunch of «throws IOException»s everywhere — is my ickiness misguided?
I think it depends. If the exception only has to bubble up a small number of levels, and it makes sense for the methods to propagate an IOException, then that’s what you should do. There is nothing particularly «icky» about allowing an exception to propagate.
On the other hand, if the IOException has to propagate through many levels and there is no chance that it might be handled specifically beyond a certain point, you may want to:
- define a custom
ApplicationErrorExceptionthat is a subclass ofRuntimeException, - catch the
IOExceptionnear its source and throw anApplicationErrorExceptionin its place … with thecauseset of course, and - catch the
ApplicationErrorExceptionexception in yourmainmethod.
At the point in main where you catch the ApplicationErrorException, you can call System.exit() with a non-zero status code, and optionally print or log a stack trace. (In fact, you might want to distinguish the cases where you do and don’t want a stack trace by specializing your «application error» exception.)
Note that we are still allowing an exception to propagate to main … for the reasons explained in @skaffman’s answer.
One last thing that complicates this question is exceptions that are thrown on the stack of some thread other than the main thread. You probably don’t want the exception to be handled and turned into a System.exit() on the other thread’s stack … because that won’t give other threads a chance to shut down cleanly. On the other hand, if you do nothing, the default behaviour is for the other thread to just terminate with an uncaught exception. If nothing is join()-ing the thread, this can go unnoticed. Unfortunately, there’s no simple «one size fits all» solution.
Improve Article
Save Article
Like Article
Improve Article
Save Article
Like Article
The purpose of the exit() function is to terminate the execution of a program. The “return 0”(or EXIT_SUCCESS) implies that the code has executed successfully without any error. Exit codes other than “0”(or EXIT_FAILURE) indicate the presence of an error in the code. Among all the exit codes, the codes 1, 2, 126 – 165 and 255 have special meanings and hence these should be avoided for user-defined exit codes.
Syntax
void exit(int return_code)
Note: It is also to taken into consideration that an exit code with a value greater than 255 returns an exit code modulo 256.
For Example: If we execute a statement exit(9999) then it will execute exit(15) as 9999%256 = 15.
Some of the Exit Codes are:
- exit(1): It indicates abnormal termination of a program perhaps as a result a minor problem in the code.
- exit(2): It is similar to exit(1) but is displayed when the error occurred is a major one. This statement is rarely seen.
- exit(127): It indicates command not found.
- exit(132): It indicates that a program was aborted (received SIGILL), perhaps as a result of illegal instruction or that the binary is probably corrupt.
- exit(133): It indicates that a program was aborted (received SIGTRAP), perhaps as a result of dividing an integer by zero.
- exit(134): It indicates that a program was aborted (received SIGABRT), perhaps as a result of a failed assertion.
- exit(136): It indicates that a program was aborted (received SIGFPE), perhaps as a result of floating point exception or integer overflow.
- exit(137): It indicates that a program took up too much memory.
- exit(138): It indicates that a program was aborted (received SIGBUS), perhaps as a result of unaligned memory access.
- exit(139): It indicates Segmentation Fault which means that the program was trying to access a memory location not allocated to it. This mostly occurs while using pointers or trying to access an out-of-bounds array index.
- exit(158/152): It indicates that a program was aborted (received SIGXCPU), perhaps as a result of CPU time limit exceeded.
- exit(159/153): It indicates that a program was aborted (received SIGXFSZ), perhaps as a result of File size limit exceeded.
Hence the various exit codes help the user to debug the code. For instance, if one receives an exit code of 139, this implies that the code has a segmentation fault and the code can be debugged accordingly.
Program 1:
Below program will give Segmentation Fault:
CPP
#include <iostream>
using namespace std;
int main()
{
int arr[100] = { 0 };
cout << arr[100001];
return 0;
}
Output:
Below is the output of the above program:
Program 2:
Below program will give Floating Point Error:
CPP
#include <iostream>
using namespace std;
int main()
{
int a = 1, b = 0;
cout << a / b;
return 0;
}
Output:
Below is the output of the above program:
Program 3:
Below program will give Time Limit Exceed:
CPP
#include <iostream>
using namespace std;
int main()
{
for (;;) {
int arr[10000];
}
return 0;
}
Output:
Below is the output of the above program:
Last Updated :
23 Jun, 2021
Like Article
Save Article




