bug-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] prompt: add \m, PROMPTTIMEFORMAT, shopt prompt_time_all


From: Evan Gates
Subject: [PATCH] prompt: add \m, PROMPTTIMEFORMAT, shopt prompt_time_all
Date: Mon, 16 Sep 2024 19:03:21 -0600

Expand \m in prompts according to PROMPTTIMEFORMAT for the last
timed command.  This is an easy way to get the result of time without
redirections by using @P expansions.

Add shopt prompt_time_all to time every command run.  Used in combination
with \m the time of the previous command can be added to PS1, or every
command in a script can be timed implicitly.
---

I did my best to follow existing style, please let me know if I need to
make changes.  I also took a whack at updating the man page, but didn't
touch the info page as I have no experience there.

I went back and forth as to whether extra formatting options would be
useful, similar to \D{format}.  In the end I decided that keeping this as
simple as possible for now was the best bet so I just reused everything
from TIMEFORMAT.  It is still possible to provide extra formatting,
for example I'm using this in my bashrc:

        sec2hms() {
                local sec=${1%.*} fracsec min hr
                [[ $1 = *.* ]] && fracsec=.${1#*.}
                ((min = sec/60, sec = sec%60, hr = min/60, min = min%60))
                printf '%d:%02d:%02d%s\n' "$hr" "$min" "$sec" "$fracsec"
        }

        ps1time() {
                local t='\m'
                t=$(sec2hms "${t@P}")
                t=${t##+([0:])}
                case $t in
                        .000) t=0 ;;
                        .*)   t=${t##.+(0)}ms ;;
                        *:*)  t=${t%.*} ;;
                        *)    t=${t}s ;;
                esac
                printf '%s\n' "$t"
        }

        shopt -s prompt_time_all
        PROMPTTIMEFORMAT=%3R
        PROMPT_COMMAND='ct=$(ps1time)'
        PS1='[... $ct ...]\$ '

While writing I realized this couldn't just be on all the time.
I tried turning it on only when \m was in a prompt, but this had a
number of issues.  Instead I settled on a new shopt for it, and was
pleasantly surprised to realize the side effect was the ability to get
the time from the last timed command without dealing with redirections.
This then becomes handy even in scripts e.g.:

        PROMPTTIMEFORMAT=%3lR
        pt='Slept for \m seconds.'

        echo Capture time of last explicitly timed command.
        time sleep .1
        echo interim command
        printf '%s\n' "${pt@P}"

        echo Or without printing the first time.
        TIMEFORMAT=
        time sleep .1
        echo interim command
        printf '%s\n' "${pt@P}"

        echo Or time every command implicitly.
        shopt -s prompt_time_all
        sleep 0.2
        printf '%s\n' "${pt@P}"
        sleep 0.3
        printf '%s\n' "${pt@P}"

And I'm sure someone smarter than I can figure out a fun way to use this
with the DEBUG trap as well.


 builtins/shopt.def | 11 +++++++++++
 command.h          |  1 +
 doc/bash.1         | 23 +++++++++++++++++++++++
 eval.c             | 12 ++++++++++++
 execute_cmd.c      | 36 ++++++++++++++++++++++++++++++++----
 execute_cmd.h      | 17 +++++++++++++++++
 parse.y            | 11 +++++++++++
 7 files changed, 107 insertions(+), 4 deletions(-)

diff --git a/builtins/shopt.def b/builtins/shopt.def
index 1c7a3bd7..d601a4b0 100644
--- a/builtins/shopt.def
+++ b/builtins/shopt.def
@@ -99,6 +99,10 @@ extern int singlequote_translations;
 extern int patsub_replacement;
 extern int bash_source_fullpath;
 
+#if defined (COMMAND_TIMING)
+extern int prompt_time_all;
+#endif
+
 #if defined (EXTENDED_GLOB)
 extern int extended_glob;
 #endif
@@ -258,6 +262,9 @@ static struct {
 #  if defined (ALIAS)
   { "progcomp_alias", &progcomp_alias, (shopt_set_func_t *)NULL },
 #  endif
+#endif
+#if defined (COMMAND_TIMING)
+  { "prompt_time_all", &prompt_time_all, (shopt_set_func_t *)NULL },
 #endif
   { "promptvars", &promptvars, (shopt_set_func_t *)NULL },
 #if defined (RESTRICTED_SHELL)
@@ -381,6 +388,10 @@ reset_shopt_options (void)
   singlequote_translations = 0;
   patsub_replacement = PATSUB_REPLACE_DEFAULT;
 
+#if defined (COMMAND_TIMING)
+  prompt_time_all = 0;
+#endif
+
 #if defined (JOB_CONTROL)
   check_jobs_at_exit = 0;
 #endif
diff --git a/command.h b/command.h
index 1c068148..7e3dae1e 100644
--- a/command.h
+++ b/command.h
@@ -192,6 +192,7 @@ typedef struct element {
 #define CMD_LASTPIPE       0x2000
 #define CMD_STDPATH        0x4000      /* use standard path for command lookup 
*/
 #define CMD_TRY_OPTIMIZING  0x8000     /* try to optimize this simple command 
*/
+#define CMD_TIME_NOPRINT    0x10000    /* Don't print CMD_TIME_PIPELINE, 
prompt \m */
 
 /* What a command looks like. */
 typedef struct command {
diff --git a/doc/bash.1 b/doc/bash.1
index cd4fd4ef..edd9dfd3 100644
--- a/doc/bash.1
+++ b/doc/bash.1
@@ -2663,6 +2663,18 @@ trailing directory components to retain when expanding 
the \fB\ew\fP and
 .B PROMPTING
 below).  Characters removed are replaced with an ellipsis.
 .TP
+.B PROMPTTIMEFORMAT
+The format for \fB\\m\fP (see
+.SM
+.B PROMPTING
+below) using the same format specifiers as the
+.SM
+.B TIMEFORMAT
+variable.
+If this variable is not set, \fBbash\fP acts as if it had the
+value \fB\*'%2R\*'\fP.
+If the value is null, \fBbash\fP does not display any timing information.
+.TP
 .B PS0
 The value of this parameter is expanded (see
 .SM
@@ -5830,6 +5842,9 @@ the number of jobs currently managed by the shell
 .B \el
 the basename of the shell's terminal device name
 .TP
+.B \em
+the time of the last timed command formatted according to PROMPTTIMEFORMAT
+.TP
 .B \en
 newline
 .TP
@@ -11234,6 +11249,14 @@ name that doesn't have any completions as a possible 
alias and attempts
 alias expansion. If it has an alias, \fBbash\fP attempts programmable
 completion using the command word resulting from the expanded alias.
 .TP 8
+.B prompt_time_all
+If set, every command is timed so \fB\\m\fP can be used as described in
+.SM
+.B PROMPTING
+.ie \n(zZ=1 in \fIbash\fP(1).
+.el above to always display the time of the previous command instead of
+the last explicitly timed command.
+.TP 8
 .B promptvars
 If set, prompt strings undergo
 parameter expansion, command substitution, arithmetic
diff --git a/eval.c b/eval.c
index 0fc6cfca..3e04cca7 100644
--- a/eval.c
+++ b/eval.c
@@ -52,6 +52,12 @@
 #  include "jobs.h"
 #endif
 
+#if defined (COMMAND_TIMING)
+/* If non-zero, all commands are timed so the results can be used with \m in
+   prompt expansion. */
+int prompt_time_all = 0;
+#endif
+
 static void send_pwd_to_eterm (void);
 static sighandler alrm_catcher (int);
 
@@ -409,6 +415,12 @@ read_command (void)
   current_command_line_count = 0;
   result = parse_command ();
 
+  if (global_command && !(global_command->flags & CMD_TIME_PIPELINE) && 
prompt_time_all)
+    {
+      global_command->flags |= CMD_TIME_PIPELINE;
+      global_command->flags |= CMD_TIME_NOPRINT;
+    }
+
   if (interactive && tmout_var && (tmout_len > 0))
     {
       alarm(0);
diff --git a/execute_cmd.c b/execute_cmd.c
index 6402f894..dc71cce2 100644
--- a/execute_cmd.c
+++ b/execute_cmd.c
@@ -222,6 +222,11 @@ volatile int last_command_exit_value;
    was terminated by a signal, and, if so, which one. */
 int last_command_exit_signal;
 
+#if defined (COMMAND_TIMING)
+/* Store the time results of the last command for use with \m in prompts. */
+struct command_time last_command_time;
+#endif
+
 /* Are we currently ignoring the -e option for the duration of a builtin's
    execution? */
 int builtin_ignoring_errexit = 0;
@@ -1330,8 +1335,8 @@ mkfmt (char *buf, int prec, int lng, time_t sec, long 
sec_fraction)
    result is printed to FP, a pointer to a FILE.  The other variables are
    the seconds and thousandths of a second of real, user, and system time,
    resectively. */
-static void
-print_formatted_time (FILE *fp, char *format,
+char *
+format_time (char *format,
                      time_t rs, long rsf, time_t us, long usf, time_t ss, long 
ssf,
                      int cpu)
 {
@@ -1399,7 +1404,7 @@ print_formatted_time (FILE *fp, char *format,
            {
              internal_error (_("TIMEFORMAT: `%c': invalid format character"), 
*s);
              free (str);
-             return;
+             return ((char *)NULL);
            }
          RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64);
          strcpy (str + sindex, ts);
@@ -1408,6 +1413,18 @@ print_formatted_time (FILE *fp, char *format,
     }
 
   str[sindex] = '\0';
+  return str;
+}
+
+static void
+print_formatted_time (FILE *fp, char *format,
+                     time_t rs, long rsf, time_t us, long usf, time_t ss, long 
ssf,
+                     int cpu)
+{
+  char *str = format_time(format, rs, rsf, us, usf, ss, ssf, cpu);
+  if (!str)
+    return;
+
   fprintf (fp, "%s\n", str);
   fflush (fp);
 
@@ -1417,7 +1434,7 @@ print_formatted_time (FILE *fp, char *format,
 static int
 time_command (COMMAND *command, int asynchronous, int pipe_in, int pipe_out, 
struct fd_bitmap *fds_to_close)
 {
-  int rv, posix_time, nullcmd, code;
+  int rv, posix_time, noprint_time, nullcmd, code;
   time_t rs, us, ss;           /* seconds */
   long rsf, usf, ssf;          /* microseconds */
   int cpu;
@@ -1455,6 +1472,7 @@ time_command (COMMAND *command, int asynchronous, int 
pipe_in, int pipe_out, str
 
   old_subshell = subshell_environment;
   posix_time = command && (command->flags & CMD_TIME_POSIX);
+  noprint_time = command && (command->flags & CMD_TIME_NOPRINT);
 
   nullcmd = (command == 0) || (command->type == cm_simple && 
command->value.Simple->words == 0 && command->value.Simple->redirects == 0);
   if (posixly_correct && nullcmd)
@@ -1537,8 +1555,18 @@ time_command (COMMAND *command, int asynchronous, int 
pipe_in, int pipe_out, str
 #  endif
 #endif
 
+  last_command_time.rs = rs;
+  last_command_time.rsf = rsf;
+  last_command_time.us = us;
+  last_command_time.usf = usf;
+  last_command_time.ss = ss;
+  last_command_time.ssf = ssf;
+  last_command_time.cpu = cpu;
+
   if (posix_time)
     time_format = POSIX_TIMEFORMAT;
+  else if (noprint_time)
+    time_format = (char *)NULL;
   else if ((time_format = get_string_value ("TIMEFORMAT")) == 0)
     {
       if (posixly_correct && nullcmd)
diff --git a/execute_cmd.h b/execute_cmd.h
index cd2bad86..3176f0cf 100644
--- a/execute_cmd.h
+++ b/execute_cmd.h
@@ -48,6 +48,17 @@ extern int return_catch_flag;
 extern int return_catch_value;
 extern volatile int last_command_exit_value;
 extern int last_command_exit_signal;
+
+#if defined (COMMAND_TIMING)
+struct command_time
+  {
+    time_t rs, us, ss;
+    long rsf, usf, ssf;
+    int cpu;
+  };
+extern struct command_time last_command_time;
+#endif
+
 extern int builtin_ignoring_errexit;
 extern int executing_builtin;
 extern int interrupt_execution;
@@ -76,6 +87,12 @@ extern void close_fd_bitmap (struct fd_bitmap *);
 extern int executing_line_number (void);
 extern int execute_command (COMMAND *);
 extern int execute_command_internal (COMMAND *, int, int, int, struct 
fd_bitmap *);
+
+#if defined (COMMAND_TIMING)
+extern char *format_time (char *format, time_t rs, long rsf, time_t us,
+                             long usf, time_t ss, long ssf, int cpu);
+#endif
+
 extern int shell_execve (char *, char **, char **);
 extern void setup_async_signals (void);
 extern void async_redirect_stdin (void);
diff --git a/parse.y b/parse.y
index 6ed8b5f0..6133ce6c 100644
--- a/parse.y
+++ b/parse.y
@@ -6177,6 +6177,7 @@ prompt_history_number (char *pmt)
        \H      the hostname
        \j      the number of active jobs
        \l      the basename of the shell's tty device name
+       \m>     the time of last timed command formatted with  PROMPTTIMEFORMAT
        \n      CRLF
        \r      CR
        \s      the name of the shell
@@ -6376,6 +6377,16 @@ decode_prompt_string (char *string, int is_prompt)
              else
                temp = savestring (timebuf);
              goto add_string;
+
+           case 'm':
+             if ((timefmt = get_string_value ("PROMPTTIMEFORMAT")) == 0)
+               timefmt = "%2R";
+             temp = format_time (timefmt,
+                                     last_command_time.rs, 
last_command_time.rsf,
+                                     last_command_time.us, 
last_command_time.usf,
+                                     last_command_time.ss, 
last_command_time.ssf,
+                                     last_command_time.cpu);
+             goto add_string;
              
            case 'n':
              temp = (char *)xmalloc (3);
-- 
2.46.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]