bash-preexec icon indicating copy to clipboard operation
bash-preexec copied to clipboard

bash 4.4: using $PS0

Open d630 opened this issue 9 years ago • 18 comments

As far I can see, we may avoid the DEBUG trap in version 4.4:

       PS0    
             The value of this parameter is expanded  (see  PROMPTING
              below) and displayed by interactive shells after reading
              a command and before the command is executed.

PS0 is expanded before DEBUG

d630 avatar Sep 19 '16 18:09 d630

@D630 thanks for opening! Was thinking about this when I saw http://superuser.com/a/1052132 a little while back. Probably makes sense to add some version detection and then hook into the appropriate interface.

rcaloras avatar Sep 19 '16 18:09 rcaloras

Ok, I have updated my repo to use PS0 only.

Note: PSO is meant to be expanded; there is no PROMPT_COMMAND like thing for it. So we have to write something like PSO='$(preexec)'. Then there are the following problems:

  • BASH_COMMAND is unusable;
  • you can not set any further strings in PSO via preexec (you can set PS1 in precmd);
  • the command substitution removes the trailing newline.

d630 avatar Oct 02 '16 11:10 d630

@D630 awesome. Giving it a look.

Could you elaborate on your second bullet?

  • you can not set any further strings in PSO via preexec (you can set PS1 in precmd);

Do you mean you can't alter PS0 itself while it's invoked?

rcaloras avatar Oct 02 '16 20:10 rcaloras

That's possible:

foo () { printf '%s\n\r' FOO; }
PSO='\h\n\T$(foo)'

PS0='$(printf "%s\n%s\n%s\n\r" \h \T FOO)'

But not:

foo () { printf '%s\n%s\n%s\n\r' \\h \\T FOO; }
PS0='$(foo)'

PSO='$(PS0=FOO)'

d630 avatar Oct 03 '16 06:10 d630

Also possible:

foo () { printf '%s\n\r' "${paa[u]}" "${paa[V]}" "${pa[0]}" "$PROMPT_w" "$1"; }
PS0='$(declare -A paa=([u]="\u" [V]="\V"); pa=("\w") ;PROMPT_w="\w" foo "\H")'

d630 avatar Oct 03 '16 06:10 d630

I would go with something like:

foo ()
{ 
        for i in "${!BPX_PROMPT[@]}"
        do 
            printf '%s -> %s\n\r' "$i" "${BPX_PROMPT[$i]}"
        done
}

PS0='$(
        builtin unset -v BPX_PROMPT;
        builtin unset -f typeset;
        builtin unalias typeset 2>/dev/null;
        typeset -A BPX_PROMPT=(
                [h]="\h"
                [H]="\H"
                [j]="\j"
                [l]="\l"
                [s]="\s"
                [u]="\u"
                [v]="\v"
                [V]="\V"
                [w]="\w"
                [W]="\W"
                [\\!]="\!"
                [#]="\#"
                [\$]="\$"
                [d]="\d"
                [t]="\t"
                [T]="\T"
                [\\@]="\@"
                [A]="\A"

                [unixtime]="\D{%s}"
        );
        foo
)'

What is then:

! -> 1445
# -> 14
$ -> $
unixtime -> 1475483154
@ -> 09:25 AM
A -> 09:25
H -> ICH3
T -> 09:25:54
V -> 4.4.0
W -> ~
d -> Mon Oct 03
h -> ICH3
j -> 0
l -> 0
s -> bash
t -> 09:25:54
u -> user1
v -> 4.4
w -> ~

edit:

In bash 4.4 we can use "parameter transformation". Most of these prompt strings can then be expanded within the command substitution in PS0.

v='\T \W \#'
echo "${v@P}"

> 08:09:59 ~ 70

d630 avatar Oct 03 '16 08:10 d630

I doubt PS0 can ever be used for an implementation of preexec(). PS0 is treated as a prompt string, not a list of commands. The only way to inject commands is by substitution with $().

However, command substitutions run in their own subshell, therefore no variables in the original shell can be modified from this context. This would be different behavior than running commands in the trap handler as it is now, from where the state of the shell can be modified. Within PS0, commands can only print data for stdout, or at most write to file descriptors.

raimue avatar Oct 09 '16 13:10 raimue

Thanks for pointing it out again. I think, it's obviously a preprompt, not preexecution thing!

PS0 will be set before the hole command line is beeing executed, which may also be a command list; it's expanded after the list from the line has been read. That is, you have only got access to the hole line (when you have enabled the command history feature). The DEBUG trap applies to every command pipeline from the list, just before their executions.

Even though you might have a similar thing like PROMPT_COMMAND for it, it wouldn't be useful without having access to the commands from the list, which will be executing. So, you also need a second thing like BASH_COMMAND, that indicates, which command is beeing read, right?!

Unfortunately yes, PS0 cannot replace the DEBUG trap.

d630 avatar Oct 09 '16 16:10 d630

Ahh bummer, was really hoping to move to something more reliable than the DEBUG trap due to https://github.com/rcaloras/bash-preexec/issues/25 Hopes dashed for now :/

rcaloras avatar Oct 10 '16 04:10 rcaloras

I have been thinking about the preexec hook for quite a while (again). Maybe I am wrong, but in the end I found, that PS0 cannot replace the DEBUG trap as key to emulate the zsh prexec itself, (since you need command substitution for functions in it) but with the help of Readline key sequences you can! I have just now updated my bpx repo. Check it out and let me know, what do you think!

d630 avatar Jan 09 '17 22:01 d630

Haven't checked further but one idea could be to send a signal from the PS0-subshell.

test.sh:

counter=0

trap_handler(){
    counter=$((counter+1))
    echo "hi from trap_handler: $counter: $(history 1)" >&2

}


trap trap_handler SIGRTMIN

PS0='$(echo "sending signal... "; kill -SIGRTMIN  $$; )'

Terminal session:

$ source test.sh
$ echo ok
sending signal... hi from trap_handler: 1:  4118  echo ok
ok
$ echo two
sending signal... hi from trap_handler: 2:  4119  echo two
two
$

In the bash manual I don't see a dealbreaker:

If Bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes. When Bash is waiting for an asynchronous command via the wait builtin, the reception of a signal for which a trap has been set will cause the wait builtin to return immediately with an exit status greater than 128, immediately after which the trap is executed.

tycho-kirchner avatar Oct 16 '21 15:10 tycho-kirchner

The next version of Bash, 5.3 (still under development), supports function substitutions (and value substitutions) of the form ${ command; } (and ${| command; }), which are variants of the command substitution but executed in the main shell.

I guess PS0 can be combined to the value substitutions ${| command; } to provide a more robust preexec in Bash >= 5.3.

akinomyoga avatar Oct 06 '23 12:10 akinomyoga

ah, interesting. similiar to funsubs and valsubs in mksh, right?

d630 avatar Oct 10 '23 09:10 d630

Right. Chet indeed mentioned mksh:

https://lists.gnu.org/archive/html/bug-bash/2023-05/msg00078.html

By the way, I wrote above that a value substitution can be used, but I noticed that the shell variable REPLY is rewritten and there is no way to prevent it. I now think function substitution should be used. Something like this:

PS0+='${ __bp_preexec_invoke_from_ps0 >/dev/tty; }'

One possible caveat of this approach is that preexec is executed before the PS0 content is output to the terminal, though I'm not sure if that is really an issue.

akinomyoga avatar Oct 10 '23 10:10 akinomyoga