bash-preexec
bash-preexec copied to clipboard
bash 4.4: using $PS0
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 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.
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 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?
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)'
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")'
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
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.
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.
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 :/
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!
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.
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.
ah, interesting. similiar to funsubs and valsubs in mksh, right?
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.