Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bash 4.4: using $PS0 #28

Open
d630 opened this issue Sep 19, 2016 · 14 comments
Open

bash 4.4: using $PS0 #28

d630 opened this issue Sep 19, 2016 · 14 comments

Comments

@d630
Copy link
Contributor

d630 commented Sep 19, 2016

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

@rcaloras
Copy link
Owner

@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.

@d630
Copy link
Contributor Author

d630 commented Oct 2, 2016

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.

@rcaloras
Copy link
Owner

rcaloras commented Oct 2, 2016

@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?

@d630
Copy link
Contributor Author

d630 commented Oct 3, 2016

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
Copy link
Contributor Author

d630 commented Oct 3, 2016

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
Copy link
Contributor Author

d630 commented Oct 3, 2016

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

@raimue
Copy link

raimue commented Oct 9, 2016

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.

@d630
Copy link
Contributor Author

d630 commented Oct 9, 2016

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.

@rcaloras
Copy link
Owner

Ahh bummer, was really hoping to move to something more reliable than the DEBUG trap due to #25 Hopes dashed for now :/

@d630
Copy link
Contributor Author

d630 commented Jan 9, 2017

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!

@tycho-kirchner
Copy link

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.

@akinomyoga
Copy link
Contributor

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.

@d630
Copy link
Contributor Author

d630 commented Oct 10, 2023

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

@akinomyoga
Copy link
Contributor

akinomyoga commented Oct 10, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants