From bc936340ba99f9b7d2bbdb36a24f2fcb531d720c Mon Sep 17 00:00:00 2001 From: coreyhuinker Date: Mon, 23 Jan 2023 16:46:16 -0500 Subject: [PATCH v7 3/3] Add psql variables SHELL_ERROR and SHELL_EXIT_CODE which report the success or failure of the most recent psql OS-command executed via the \! command or `backticks`. These variables are roughly analogous to ERROR and SQLSTATE, but for OS level operations instead of SQL commands. --- doc/src/sgml/ref/psql-ref.sgml | 21 ++++++++++++++++++ src/bin/psql/command.c | 14 ++++++++++++ src/bin/psql/help.c | 4 ++++ src/bin/psql/psqlscanslash.l | 35 +++++++++++++++++++++++++----- src/test/regress/expected/psql.out | 29 +++++++++++++++++++++++++ src/test/regress/sql/psql.sql | 25 +++++++++++++++++++++ 6 files changed, 122 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index dc6528dc11..3d8a7353b3 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -4264,6 +4264,27 @@ bar + + SHELL_ERROR + + + true if the last shell command failed, false if + it succeeded. This applies to shell commands invoked via either \! + or `. See also SHELL_EXIT_CODE. + + + + + + SHELL_EXIT_CODE + + + The exit code return by the last shell command, invoked via either \! or `. + 0 means no error. See also SHELL_ERROR. + + + + SHOW_ALL_RESULTS diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index b5201edf55..ea79d4fe57 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -5032,6 +5032,20 @@ do_shell(const char *command) else result = system(command); + if (result == 0) + { + SetVariable(pset.vars, "SHELL_EXIT_CODE", "0"); + SetVariable(pset.vars, "SHELL_ERROR", "false"); + } + else + { + int exit_code = wait_result_to_exit_code(result); + char buf[32]; + snprintf(buf, sizeof(buf), "%d", exit_code); + SetVariable(pset.vars, "SHELL_EXIT_CODE", buf); + SetVariable(pset.vars, "SHELL_ERROR", "true"); + } + if (result == 127 || result == -1) { pg_log_error("\\!: failed"); diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index e45c4aaca5..6d5226f793 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -455,6 +455,10 @@ helpVariables(unsigned short int pager) " show all results of a combined query (\\;) instead of only the last\n"); HELP0(" SHOW_CONTEXT\n" " controls display of message context fields [never, errors, always]\n"); + HELP0(" SHELL_ERROR\n" + " true if last shell command failed, else false\n"); + HELP0(" SHELL_EXIT_CODE\n" + " Exit code of the last shell command\n"); HELP0(" SINGLELINE\n" " if set, end of line terminates SQL commands (same as -S option)\n"); HELP0(" SINGLESTEP\n" diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l index 8449ee1a52..3545787d31 100644 --- a/src/bin/psql/psqlscanslash.l +++ b/src/bin/psql/psqlscanslash.l @@ -23,6 +23,7 @@ #include "fe_utils/conditional.h" #include "libpq-fe.h" +#include "settings.h" } %{ @@ -774,6 +775,7 @@ evaluate_backtick(PsqlScanState state) bool error = false; char buf[512]; size_t result; + int exit_code = 0; initPQExpBuffer(&cmd_output); @@ -783,6 +785,7 @@ evaluate_backtick(PsqlScanState state) { pg_log_error("%s: %m", cmd); error = true; + exit_code = -1; } if (!error) @@ -790,7 +793,8 @@ evaluate_backtick(PsqlScanState state) do { result = fread(buf, 1, sizeof(buf), fd); - if (ferror(fd)) + exit_code = ferror(fd); + if (exit_code) { pg_log_error("%s: %m", cmd); error = true; @@ -800,10 +804,21 @@ evaluate_backtick(PsqlScanState state) } while (!feof(fd)); } - if (fd && pclose(fd) == -1) + if (fd) { - pg_log_error("%s: %m", cmd); - error = true; + /* + * Capture exit code for SHELL_EXIT_CODE, but to not erase an existing + * nonzero exit_code. + */ + int close_exit_code = pclose(fd); + + if (close_exit_code && !exit_code) + { + error = true; + exit_code = close_exit_code; + if (close_exit_code == -1) + pg_log_error("%s: %m", cmd); + } } if (PQExpBufferDataBroken(cmd_output)) @@ -824,7 +839,15 @@ evaluate_backtick(PsqlScanState state) cmd_output.data[cmd_output.len - 1] == '\n') cmd_output.len--; appendBinaryPQExpBuffer(output_buf, cmd_output.data, cmd_output.len); + SetVariable(pset.vars, "SHELL_EXIT_CODE", "0"); + SetVariable(pset.vars, "SHELL_ERROR", "false"); + } + else + { + char exit_code_buf[32]; + snprintf(exit_code_buf, sizeof(exit_code_buf), "%d", + wait_result_to_exit_code(exit_code)); + SetVariable(pset.vars, "SHELL_EXIT_CODE", exit_code_buf); + SetVariable(pset.vars, "SHELL_ERROR", "true"); } - - termPQExpBuffer(&cmd_output); } diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 8fc62cebd2..59ff0d1dbd 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -1306,6 +1306,35 @@ execute q; +----+-------------+ deallocate q; +-- test SHELL_ERROR / SHELL_EXIT_CODE +\getenv pg_os_target PG_OS_TARGET +SELECT :'pg_os_target' = 'WIN32' AS windows_target \gset +\if :windows_target + \set bad_cmd 'nosuchcommand 2> NUL' +\else + \set bad_cmd 'nosuchcommand 2> /dev/null' +\endif +\set bad_shbang '\\! ' :bad_cmd +:bad_shbang +\echo :SHELL_ERROR +true +-- Exit codes are shell dependent, we can only test nonzero +SELECT :'SHELL_EXIT_CODE' != '0' AS nonzero \gset +\echo :nonzero +t +\set SHELL_ERROR 'clear' +\set SHELL_EXIT_CODE 'clear' +\echo :SHELL_ERROR +clear +\echo :SHELL_EXIT_CODE +clear +\set nosuchvar `:bad_cmd` +\echo :SHELL_ERROR +true +SELECT :'SHELL_EXIT_CODE' != '0' AS nonzero \gset +\echo :nonzero +t +\unset nonzero -- test single-line header and data prepare q as select repeat('x',2*n) as "0123456789abcdef", repeat('y',20-2*n) as "0123456789" from generate_series(1,10) as n; \pset linestyle ascii diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 2da9665a19..c4eab6d89a 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -291,6 +291,31 @@ execute q; deallocate q; +-- test SHELL_ERROR / SHELL_EXIT_CODE +\getenv pg_os_target PG_OS_TARGET +SELECT :'pg_os_target' = 'WIN32' AS windows_target \gset +\if :windows_target + \set bad_cmd 'nosuchcommand 2> NUL' +\else + \set bad_cmd 'nosuchcommand 2> /dev/null' +\endif +\set bad_shbang '\\! ' :bad_cmd + +:bad_shbang +\echo :SHELL_ERROR +-- Exit codes are shell dependent, we can only test nonzero +SELECT :'SHELL_EXIT_CODE' != '0' AS nonzero \gset +\echo :nonzero +\set SHELL_ERROR 'clear' +\set SHELL_EXIT_CODE 'clear' +\echo :SHELL_ERROR +\echo :SHELL_EXIT_CODE +\set nosuchvar `:bad_cmd` +\echo :SHELL_ERROR +SELECT :'SHELL_EXIT_CODE' != '0' AS nonzero \gset +\echo :nonzero +\unset nonzero + -- test single-line header and data prepare q as select repeat('x',2*n) as "0123456789abcdef", repeat('y',20-2*n) as "0123456789" from generate_series(1,10) as n; -- 2.39.0