16

The question is simple. I want to evaluate current value of PS1 in my bash script.

All materials on google point to tutorials on pimping it up, but I want to evaluate to see how would it be rendered by my current terminal, or at least by some terminal.

Is there any software/function that would help me achieve that? Of course I'd like to have all escaped characters evaluated, so echo $PS1 is not that useful in my case.

tripleee
  • 139,311
  • 24
  • 207
  • 268
Piotr Zierhoffer
  • 5,034
  • 1
  • 35
  • 56

6 Answers6

12

Bash 4.4+ solution using parameter transformation for a prompt string: echo "${PS1@P}"

[adamhotep@tabasco ~]$ echo "the prompt is '${PS1@P}'"
the prompt is '[adamhotep@tabasco ~]$'
[adamhotep@tabasco ~]$ TEST_STRING='\u is dining at \t using \s \V'
[adamhotep@tabasco ~]$ echo "${TEST_STRING}"
\u is dining at \t using \s \V
[adamhotep@tabasco ~]$ echo "${TEST_STRING@P}"
adamhotep is dining at 21:45:10 using bash 5.0.3
[adamhotep@tabasco ~]$ 

From the Bash Reference Manual page on Shell Parameter Expansion:

${parameter@operator}

Parameter transformation. The expansion is either a transformation of the value of parameter or information about parameter itself, depending on the value of operator.
Each operator is a single letter:

Q  The expansion is a string that is the value of parameter quoted in a
   format that can be reused as input.
E  The expansion is a string that is the value of parameter with backslash
   escape sequences expanded as with the $'…' quoting mechanism.
P  The expansion is a string that is the result of expanding the value of
   parameter as if it were a prompt string (see PROMPTING below).
A  The expansion is a string in the form of an assignment statement or
   declare command that, if evaluated, will recreate parameter with its
   attributes and value.
a  The expansion is a string consisting of flag values representing
   parameter's attributes.

If parameter is @ or *, the operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable subscripted with @ or *, the operation is applied to each member of the array in turn, and the expansion is the resultant list.

(See also this answer from duplicate question Echo expanded PS1.)

 

Z Shell (zsh) can do this with ${(%%)PS1} or with its print builtin's -P flag:

[adamhotep@tabasco ~]% echo "the prompt is '${(%%)PS1}'"
the prompt is '[adamhotep@tabasco ~]%'
[adamhotep@tabasco ~]% print -P "the prompt is '$PS1'"
the prompt is '[adamhotep@tabasco ~]%'
[adamhotep@tabasco ~]% TEST_STRING="%n is dining at %* using %N $ZSH_VERSION"
[adamhotep@tabasco ~]% echo "$TEST_STRING"
%n is dining at %* using %N 5.7.1
[adamhotep@tabasco ~]% echo "${(%%)TEST_STRING}"
adamhotep is dining at 11:49:01 using zsh 5.7.1
[adamhotep@tabasco ~]% print -P "$TEST_STRING"
adamhotep is dining at 11:49:07 using zsh 5.7.1
[adamhotep@tabasco ~]% 

The Zsh Expansion and Subsitution manual tells us:

Parameter Expansion Flags. If the opening brace is directly followed by an opening parenthesis, the string up to the matching closing parenthesis will be taken as a list of flags. In cases where repeating a flag is meaningful, the repetitions need not be consecutive; for example, (q%q%q) means the same thing as the more readable (%%qqq). The following flags are supported:

%    Expand all % escapes in the resulting words in the same way as in prompts (see Prompt Expansion). If this flag is given twice, full prompt expansion is done on the resulting words, depending on the setting of the PROMPT_PERCENT, PROMPT_SUBST and PROMPT_BANG options.

From the Zsh Builtins documentation for print:

-P    Perform prompt expansion (see Prompt Expansion). In combination with -f, prompt escape sequences are parsed only within interpolated arguments, not within the format string.

Adam Katz
  • 10,689
  • 2
  • 49
  • 68
4

One more possibility, using script utility (part of bsdutils package on ubuntu):

$ TEST_PS1="\e[31;1m\u@\h:\n\e[0;1m\$ \e[0m"
$ RANDOM_STRING=some_random_string_here_that_is_not_part_of_PS1
$ script /dev/null <<-EOF | awk 'NR==2' RS=$RANDOM_STRING
PS1="$TEST_PS1"; HISTFILE=/dev/null
echo -n $RANDOM_STRING
echo -n $RANDOM_STRING
exit
EOF
<prints the formatted prompt properly here>

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.

  1. The entire code can also be encapsulated into a function.
  2. Also, the output prompt can also be assigned to a variable.
  3. This approach also supports parsing of PROMPT_COMMAND...

EDIT:
It appears that the new version of script echoes the piped stdin in the typescript. To handle that, the above mechanism can be changed to:

$ TEST_PS1="\e[31;1m\u@\h:\n\e[0;1m\$ \e[0m"
$ RANDOM_STRING=some_random_string_here_that_is_not_part_of_PS1
$ script /dev/null <<-EOF | awk '{old=current; current=$0;} END{print old}' RS=$RANDOM_STRING
PS1="$TEST_PS1"; HISTFILE=/dev/null
alias $RANDOM_STRING=true
$RANDOM_STRING
$RANDOM_STRING
EOF

<prints the formatted prompt properly here>

Explanation:

Try entering these commands manually on the terminal. Copy these commands under the heredoc as they are and paste with mouse middle click. The script command's stdout would contain something very similar.

e.g. With above case, the output of the script command gives this:

PS1="\e[31;1m\u@\h:\n\e[0;1m$ \e[0m"; HISTFILE=/dev/null
alias some_random_string_here_that_is_not_part_of_PS1=true
some_random_string_here_that_is_not_part_of_PS1
some_random_string_here_that_is_not_part_of_PS1
 \e[0m"; HISTFILE=/dev/nullhsane-dev : ~/Desktop $ PS1="\e[31;1m\u@\h:\n\e[0;1m$ 
anishsane@anishsane-dev:
$ alias some_random_string_here_that_is_not_part_of_PS1=true
anishsane@anishsane-dev:
$ some_random_string_here_that_is_not_part_of_PS1
anishsane@anishsane-dev:
$ some_random_string_here_that_is_not_part_of_PS1
anishsane@anishsane-dev:
$ exit

Split that stdout with "some_random_string_here_that_is_not_part_of_PS1" as delimiter (record separator of awk) and print the last but one record.

EDIT2:

Another mechanism (using bash source code and gdb):

$ gdb -batch -p $$ -ex 'call bind_variable("expanded_PS1", decode_prompt_string (get_string_value ("PS1")), 0)'
$ echo "$expanded_PS1"
<prints the formatted prompt properly here>
  1. There is a tiny issue here though. The \[ or \] strings in PS1 will get printed as \1/\2 respectively. You can remove those with tr -d '\1\2' <<< "$expanded_PS1"
  2. If you get error like gdb failed to attach to the process (seems to happen in ubuntu :-\ ), run gdb with sudo.
anishsane
  • 17,770
  • 5
  • 33
  • 64
  • +1 Note that you'll need to view `my_filename` with something like `less` to see any escape characters from the prompt rendered properly. – chepner Mar 11 '14 at 12:11
  • Edited to output only parsed `PS1` on the screen. :) – anishsane Mar 12 '14 at 05:32
  • 1
    You can partially clean up escape codes with something like `colcrt`. It's meant for `man` pages, not `script` output, so it's not perfect, but it's a start. – tripleee Mar 12 '14 at 05:46
  • What does the `echo -n $RANDOM_STRING` do? Is it required? Why is it done twice? What does `awk` do with the the random string? Some more explanation of how the commands above work would be great. – Nathan Craike Jan 05 '18 at 03:58
  • Added the explanation. – anishsane Jan 05 '18 at 14:20
3

Another way to do this would be to eval echoing your prompt to handle any expansion (was not sure why the brackets remain). This is most likely less robust than @anishsane's method, but may be a little quicker:

show-prompt() {
    eval 'echo -en "'$PS1'"' | sed -e 's#\\\[##g' -e 's#\\\]##g'
}
# To show it in a function registered with `complete -F` on
# a single tab, and keep the user's input:
show-prompt
echo -n "${COMP_WORDS[@]}"

Had tinkered with that regarding this GitHub issue.

Eric Cousineau
  • 1,161
  • 7
  • 14
  • What do you exactly mean by "registered with `complete -F`"? I'm trying to use it from Octave, such that the regular prompt shows up, and then the user's input is stored as if it were a command, but the prompt doesn't show up; like this: `octave --eval "system('./showprompt'); usercommand = input('', 's')"` – nightcod3r Sep 20 '18 at 06:03
  • `complete -F` is a bash idiom for tab completion; to see more info, see `bash -c 'help complete'`. My above comment was terse, but I meant that you could inject those two lines into a function that is passed to bash's `complete -F `. In this way, you could make a completion method that has a long-running update (e.g. initialization for the `bazel` build system's CLI), but you can at least show the user that it's working and not just frozen. Unfortunately, I don't have any recommendations for your particular application with Octave :( – Eric Cousineau Sep 25 '18 at 21:33
  • This seems work for my prompt, except for the escape sequences `\u`, `\h` and `\W` (which I happen to have in my `PS1` variable; maybe there are more sequences like these). – HelloGoodbye Mar 12 '19 at 15:41
2

I would get it like this:

echo $PS1

And then edit it with an editor. After that for the test (this is set while the session is active):

PS1='\[\033[1m\]\[\033[34m\]\u\[\033[90m\]@\[\033[01;35m\]\h:\[\033[01;32m\]\W\[\033[0m\]$ '

(\u is for user, \h is for host, \w is for full path and \W is for short path)

And if I like it I will make it permanent by changing the value of PS1 in ~/.bashrc

P.S.:

If you want to see all global variables:

printenv

OR:

printenv <name_of_var_to_see>
NFSpeedy
  • 88
  • 1
  • 6
  • But that does exactly what I do not want to achieve - it prints out the raw value of the variable, and I needed it to be rendered - that is with all the colors, \x placeholders etc evaluated. @anishsane answer, however complex, works fine. An even better approach was posted in the "possible duplicate" question linked in the comments. – Piotr Zierhoffer Feb 06 '19 at 10:34
  • Do step two. I am giving you a way to see your current version so you can revert to it if needed. Step two is PS1=.... – NFSpeedy Feb 06 '19 at 16:56
0

Try the below command

echo $PS1 | 
sed -e s/'\\d'/"$(date +'%a %b %_d')"/g | 
sed -e s/'\\t'/"$(date +'%T')"/g | 
sed -e s/'\\@'/"$(date +'%r')"/g | 
sed -e s/'\\T'/"$(date +'%r'| awk {'print $1'})"/g | 
sed -e s/'\\e'//g | sed -e s/'\\h'/"$HOSTNAME"/g | 
sed -e s/'\\h'/"$HOSTNAME"/g | 
sed -e s/'\\H'/"$HOSTNAME"/g | 
sed -e s/'\\u'/"$USER"/g | 
sed -e s@'\\W'@"$(pwd)"@g | 
sed -e s/'\\w'/"$(pwd | sed -e s@$HOME@'~'@g )"/g | 
sed -e s/"\\\\"//g | 
sed -e s/"\\["//g | 
sed -e s/"\\]"/*/g | 
cut -d'*' -f2 | 
cut -d';' -f2 | 
sed s/\ //g | 
sed -e s/[a-z]$/"$([ "$USER" != "root" ] && echo \$ || echo \#)"/g
iamsrijon
  • 76
  • 6
  • Could you explain what should happen if your code is run? Some of us don't want to just "try it" before we know what it's supposed to do. :-) – EJ Mak Sep 06 '18 at 18:21
  • I don't like this answer, but it looks like they're searching for PS1 escape sequences, and replacing them with the correct value. It doesn't really check the boxes like the others do – Matthew Strasiotto Mar 04 '21 at 11:12
-1

Edit the /etc/bashrc file

you can use this as example and check the output

    # If id command returns zero, you’ve root access.
if [ $(id -u) -eq 0 ];
then # you are root, set red colour prompt
  PS1="\\[$(tput setaf 1)\\]\\u@\\h:\\w #\\[$(tput sgr0)\\]"
else # normal
  PS1="[\\u@\\h:\\w] $"
fi
Adrian
  • 5
  • 6