How to expand PS1?

后端 未结 7 1179
孤城傲影
孤城傲影 2020-11-27 17:57

I have a shell script that runs the same command in several directories (fgit). For each directory, I would like it to show the current prompt + the command which will be ru

7条回答
  •  死守一世寂寞
    2020-11-27 18:32

    Two answer: "Pure bash" and "bash + sed"

    As doing this by using sed is simplier, the first answer will use sed.

    See below for pure bash solution.

    bash prompt expansion, bash + sed

    There is my hack:

    ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
                  sed ':;$!{N;b};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"
    

    Explanation:

    Running bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1

    May return something like:

    To run a command as administrator (user "root"), use "sudo ".
    See "man sudo_root" for details.
    
    ubuntu@ubuntu:~$ 
    ubuntu@ubuntu:~$ exit
    

    The sed command will then

    • take all lines into one buffer (:;$!{N;b};), than
    • replace end-of-lineexit by . (s/^\(.*\n\)*\(.*\)\n\2exit$/\2/).
      • where become \1
      • and become \2.
    Test case:
    while ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
              sed ':;$!{N;b};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"
        read -rp "$ExpPS1" && [ "$REPLY" != exit ] ;do
        eval "$REPLY"
      done
    

    From there, you're in a kind of pseudo interactive shell (without readline facilities, but that's does not matter)...

    ubuntu@ubuntu:~$ cd /tmp
    ubuntu@ubuntu:/tmp$ PS1="${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$ "
    ubuntu@ubuntu:/tmp$ 
    

    (Last line print both ubuntu in green, @, : and $ in black and path (/tmp) in blue)

    ubuntu@ubuntu:/tmp$ exit
    ubuntu@ubuntu:/tmp$ od -A n -t c <<< $ExpPS1 
     033   [   1   ;   3   2   m   u   b   u   n   t   u 033   [   0
       m   @ 033   [   1   ;   3   2   m   u   b   u   n   t   u 033
       [   0   m   : 033   [   1   ;   3   4   m   ~ 033   [   0   m
       $  \n
    

    Pure bash

    ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1)"
    ExpPS1_W="${ExpPS1%exit}"
    ExpPS1="${ExpPS1_W##*$'\n'}"
    ExpPS1_L=${ExpPS1_W%$'\n'$ExpPS1}
    while [ "${ExpPS1_W%$'\n'$ExpPS1}" = "$ExpPS1_W" ] ||
          [ "${ExpPS1_L%$'\n'$ExpPS1}" = "$ExpPS1_L" ] ;do
        ExpPS1_P="${ExpPS1_L##*$'\n'}"
        ExpPS1_L=${ExpPS1_L%$'\n'$ExpPS1_P}
        ExpPS1="$ExpPS1_P"$'\n'"$ExpPS1"
      done
    

    The while loop is required to ensure correct handling of multiline prompts:

    replace 1st line by:

    ExpPS1="$(bash --rcfile <(echo "PS1='${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$ '") -i <<<'' 2>&1)"
    

    or

    ExpPS1="$(bash --rcfile <(echo "PS1='Test string\n$(date)\n$PS1'") -i <<<'' 2>&1)";
    

    The last multiline will print:

    echo "$ExpPS1"
    Test string
    Tue May 10 11:04:54 UTC 2016
    ubuntu@ubuntu:~$ 
    
    od -A n -t c  <<<${ExpPS1}
       T   e   s   t       s   t   r   i   n   g  \r       T   u   e
           M   a   y       1   0       1   1   :   0   4   :   5   4
           U   T   C       2   0   1   6  \r     033   ]   0   ;   u
       b   u   n   t   u   @   u   b   u   n   t   u   :       ~  \a
       u   b   u   n   t   u   @   u   b   u   n   t   u   :   ~   $
      \n
    

提交回复
热议问题