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 run there. How do I get the string that corresponds to the decoded (expanded)PS1
? For example, my default PS1 is
${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(__git_ps1 ' (%s)')$
and I'd like to echo the resulting prompt username@hostname:/path$
, preferably (but not necessarily) with the nice colors. A cursory look at the Bash manual didn't reveal any definite answer, and echo -e $PS1
only evaluates the colors.
One great advantage of open source software is that the source is, well, open :-)
Bash itself does not provide this functionality but there are various tricks you can use to provide a subset (such as substituting
\u
with$USER
and so on). However, this requires a lot of duplication of functionality and ensuring that the code is kept in sync with whateverbash
does in future.If you want to get all the power of prompt variables (and you don't mind getting your hands dirty with a bit of coding (and, if you do mind, why are you here?)), it's easy enough to add to the shell itself.
If you download the code for
bash
(I'm looking at version 4.2), there's ay.tab.c
file which contains thedecode_prompt_string()
function:This is the function that evaluates the
PSx
variables for prompting. In order to allow this functionality to be provided to users of the shell itself (rather than just used by the shell), you can follow these steps to add an internal commandevalps1
.First, change
support/mkversion.sh
so that you won't confuse it with a "real"bash
, and so that the FSF can deny all knowledge for warranty purposes :-) Simply change one line (I added the-pax
bit):Second, change `builtins/Makefile.in to add a new source file. This entails a number of steps.
(a) Add
$(srcdir)/evalps1.def
to the end ofDEFSRC
.(b) Add
evalps1.o
to the end ofOFILES
.(c) Add the required dependencies:
Third, add the
builtins/evalps1.def
file itself, this is the code that gets executed when you run theevalps1
command:The bulk of that is the GPL licence (since I modified it from
exit.def
) with a very simple function at the end to get and decodePS1
.Lastly, just build the thing in the top level directory:
The
bash
executable that appears can be renamed topaxsh
, though I doubt it will ever become as prevalent as its ancestor :-)And running it, you can see it in action:
When you put one of the
PSx
variables into the prompt, echoing$PS1
simply gives you the variable, while theevalps1
command evaluates it and outputs the result.Now, granted, making code changes to
bash
to add an internal command may be considered by some to be overkill but, if you want an perfect evaluation ofPS1
, it's certainly an option.Why don't you just process the
$PS1
escape substitutions yourself? A series of substitutions such as these:By the way, zsh has the ability to interpret prompt escapes.
or
I like the idea of fixing Bash to make it better, and I appreciate paxdiablo's verbose answer on how to patch Bash. I'll have a go sometime.
However, without patching Bash source-code, I have a one-liner hack that is both portable and doesn't duplicate functionality, because the workaround uses only Bash and its builtins.
Note that there's something strange going on with
tty
's andstdio
seeing as this also works:So although I don't understand what's going on with the
stdio
here, my hack is working for me on Bash 4.2, NixOS GNU/Linux. Patching the Bash source-code is definitely a more elegant solution, and it should be pretty easy and safe to do now that I'm using Nix.You may have to write a small C program that uses the same code bash does (is it a library call?) to display that prompt, and just call the C program. Granted, that's not very portable since you'll have to compile it on each platform, but it's a possible solution.
One more possibility: without editing bash source code, using
script
utility (part ofbsdutils
package on ubuntu):script
command generates a file specified & the output is also shown on stdout. If filename is omitted, it generates a file called typescript.Since we are not interested in the log file in this case, filename is specified as
/dev/null
. Instead the stdout of the script command is passed to awk for further processing.PROMPT_COMMAND
...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:
Explanation:
Running
bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1
May return something like:
The
sed
command will then:;$!{N;b};
), than<everything, terminated by end-of-line><prompt>end-of-line<prompt>exit
by<prompt>
. (s/^\(.*\n\)*\(.*\)\n\2exit$/\2/
).<everything, terminated by end-of-line>
become\1
<prompt>
become\2
.From there, you're in a kind of pseudo interactive shell (without readline facilities, but that's does not matter)...
(Last line print both
ubuntu
in green,@
,:
and$
in black and path (/tmp
) in blue)Pure bash
The
while
loop is required to ensure correct handling of multiline prompts:replace 1st line by:
or
The last multiline will print: