Add SHELL_ERROR and SHELL_EXIT_CODE magic variables to psql.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 21 Mar 2023 17:03:42 +0000 (13:03 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 21 Mar 2023 17:03:56 +0000 (13:03 -0400)
These are set after a \! command or a backtick substitution.
SHELL_ERROR is just "true" for error (nonzero exit status) or "false"
for success, while SHELL_EXIT_CODE records the actual exit status
following standard shell/system(3) conventions.

Corey Huinker, reviewed by Maxim Orlov and myself

Discussion: https://postgr.es/m/CADkLM=cWao2x2f+UDw15W1JkVFr_bsxfstw=NGea7r9m4j-7rQ@mail.gmail.com

doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/command.c
src/bin/psql/help.c
src/bin/psql/psqlscanslash.l
src/common/wait_error.c
src/include/port.h

index 7b8ae9fac304983665bdba29a46b1dd5fa52ac45..29bbec218866bd735b424e8203fc18f68adc37ef 100644 (file)
@@ -4267,6 +4267,34 @@ bar
         </listitem>
       </varlistentry>
 
+      <varlistentry id="app-psql-variables-shell-error">
+       <term><varname>SHELL_ERROR</varname></term>
+       <listitem>
+        <para>
+         <literal>true</literal> if the last shell command
+         failed, <literal>false</literal> if it succeeded.
+         This applies to shell commands invoked via the <literal>\!</literal>
+         meta-command or backquote (<literal>`</literal>) expansion.
+         See also <varname>SHELL_EXIT_CODE</varname>.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry id="app-psql-variables-shell-exit-code">
+       <term><varname>SHELL_EXIT_CODE</varname></term>
+       <listitem>
+        <para>
+         The exit status returned by the last shell command.
+         0&ndash;127 represent program exit codes, 128&ndash;255
+         indicate termination by a signal, and -1 indicates failure
+         to launch a program or to collect its exit status.
+         This applies to shell commands invoked via the <literal>\!</literal>
+         meta-command or backquote (<literal>`</literal>) expansion.
+         See also <varname>SHELL_ERROR</varname>.
+        </para>
+       </listitem>
+      </varlistentry>
+
       <varlistentry id="app-psql-variables-show-all-results">
         <term><varname>SHOW_ALL_RESULTS</varname></term>
         <listitem>
index 61ec049f0545c4dad991e84dff872c21f8cf0342..d7731234b63f919708c23b0666b7b4cf29944d90 100644 (file)
@@ -5041,6 +5041,21 @@ 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");
index e45c4aaca5ae69b9d95180c36b335eff5a46b8b3..48fd51592aa468c3a2158df9f9341277c73f2c98 100644 (file)
@@ -451,6 +451,10 @@ helpVariables(unsigned short int pager)
        HELP0("  SERVER_VERSION_NAME\n"
                  "  SERVER_VERSION_NUM\n"
                  "    server's version (in short string or numeric format)\n");
+       HELP0("  SHELL_ERROR\n"
+                 "    true if the last shell command failed, false if it succeeded\n");
+       HELP0("  SHELL_EXIT_CODE\n"
+                 "    exit status of the last shell command\n");
        HELP0("  SHOW_ALL_RESULTS\n"
                  "    show all results of a combined query (\\;) instead of only the last\n");
        HELP0("  SHOW_CONTEXT\n"
index 8449ee1a52e91920af9be8743767f381492f8376..fa2d0b2a5f4e169fae79e7ff25fdfbea38ca45f4 100644 (file)
@@ -19,6 +19,8 @@
 #include "postgres_fe.h"
 
 #include "psqlscanslash.h"
+#include "settings.h"
+
 #include "common/logging.h"
 #include "fe_utils/conditional.h"
 
@@ -772,6 +774,7 @@ evaluate_backtick(PsqlScanState state)
        PQExpBufferData cmd_output;
        FILE       *fd;
        bool            error = false;
+       int                     exit_code = 0;
        char            buf[512];
        size_t          result;
 
@@ -783,6 +786,7 @@ evaluate_backtick(PsqlScanState state)
        {
                pg_log_error("%s: %m", cmd);
                error = true;
+               exit_code = -1;
        }
 
        if (!error)
@@ -800,10 +804,19 @@ evaluate_backtick(PsqlScanState state)
                } while (!feof(fd));
        }
 
-       if (fd && pclose(fd) == -1)
+       if (fd)
        {
-               pg_log_error("%s: %m", cmd);
-               error = true;
+               /*
+                * Although pclose's result always sets SHELL_EXIT_CODE, we
+                * historically have abandoned the backtick substitution only if it
+                * returns -1.
+                */
+               exit_code = pclose(fd);
+               if (exit_code == -1)
+               {
+                       pg_log_error("%s: %m", cmd);
+                       error = true;
+               }
        }
 
        if (PQExpBufferDataBroken(cmd_output))
@@ -826,5 +839,10 @@ evaluate_backtick(PsqlScanState state)
                appendBinaryPQExpBuffer(output_buf, cmd_output.data, cmd_output.len);
        }
 
+       /* And finally, set the shell error variables */
+       snprintf(buf, sizeof(buf), "%d", wait_result_to_exit_code(exit_code));
+       SetVariable(pset.vars, "SHELL_EXIT_CODE", buf);
+       SetVariable(pset.vars, "SHELL_ERROR", (exit_code == 0) ? "false" : "true");
+
        termPQExpBuffer(&cmd_output);
 }
index 4a3c3c61af17ed29b493ce6ccd0b5e4df63f29af..a90b745f0777c5f531bec5fc125595cad2071265 100644 (file)
@@ -127,3 +127,22 @@ wait_result_is_any_signal(int exit_status, bool include_command_not_found)
                return true;
        return false;
 }
+
+/*
+ * Return the shell exit code (normally 0 to 255) that corresponds to the
+ * given wait status.  The argument is a wait status as returned by wait(2)
+ * or waitpid(2), which also applies to pclose(3) and system(3).  To support
+ * the latter two cases, we pass through "-1" unchanged.
+ */
+int
+wait_result_to_exit_code(int exit_status)
+{
+       if (exit_status == -1)
+               return -1;                              /* failure of pclose() or system() */
+       if (WIFEXITED(exit_status))
+               return WEXITSTATUS(exit_status);
+       if (WIFSIGNALED(exit_status))
+               return 128 + WTERMSIG(exit_status);
+       /* On many systems, this is unreachable */
+       return -1;
+}
index e66193bed9fd7818e67587aba3fecad7e535bcca..a88d403483eab927d25958d58872b7ec90329f04 100644 (file)
@@ -495,6 +495,7 @@ extern char *escape_single_quotes_ascii(const char *src);
 extern char *wait_result_to_str(int exitstatus);
 extern bool wait_result_is_signal(int exit_status, int signum);
 extern bool wait_result_is_any_signal(int exit_status, bool include_command_not_found);
+extern int     wait_result_to_exit_code(int exit_status);
 
 /*
  * Interfaces that we assume all Unix system have.  We retain individual macros