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