Set screen-title from shellscript - bash

Is it possible to set the Screen Title using a shell script?
I thought about something like sending the key commands ctrl+A shift-A Name enter
I searched for about an hour on how to emulate keystrokes in an shell script, but didn't find the answer.

You can set the screen / xterm title using the following lines:
#!/bin/bash
mytitle="Some title"
echo -e '\033k'$mytitle'\033\\'
[UPDATE] - by request I'm also including the solution proposed by #Espo below:
Depending on your xterm version or your linux distribution the line above may or may not work and you can try the xterm-defaults:
#!/bin/bash
mytitle="Some title"
echo -e '\033]2;'$mytitle'\007'
For more on the details see: http://www.faqs.org/docs/Linux-mini/Xterm-Title.html#s3 or refer to the answer by #Espo below.

From http://www.faqs.org/docs/Linux-mini/Xterm-Title.html#s3
xterm escape sequences
Window and icon titles may be changed
in a running xterm by using XTerm
escape sequences. The following
sequences are useful in this respect:
ESC]0;stringBEL -- Set icon name and window title to string
ESC]1;stringBEL -- Set icon name to string
ESC]2;stringBEL -- Set window title to string
where ESC is the escape character
(\033), and BEL is the bell character
(\007).
Printing one of these sequences within
the xterm will cause the window or
icon title to be changed.
Note: these sequences apply to most
xterm derivatives, such as nxterm,
color-xterm and rxvt. Other terminal
types often use different escapes; see
the appendix for examples. For the
full list of xterm escape sequences
see the file ctlseq2.txt, which comes
with the xterm distribution, or
xterm.seq, which comes with the rxvt
distribution.
Printing the escape sequences
For information that is constant
throughout the lifetime of this shell,
such as host and username, it will
suffice to simply echo the escape
string in the shell rc file:
echo -n "\033]0;${USER}#${HOST}\007"
should produce a title like
username#hostname, assuming the shell
variables $USER and $HOST are set
correctly. The required options for
echo may vary by shell (see examples
below).
For information that may change during
the shell's lifetime, such as current
working directory, these escapes
really need to be applied every time
the prompt changes. This way the
string is updated with every command
you issue and can keep track of
information such as current working
directory, username, hostname, etc.
Some shells provide special functions
for this purpose, some don't and we
have to insert the title sequences
directly into the prompt string. This
is illustrated in the next section.

The following are other ways to script the renaming of screen titles:
Adding the following settings to .ssh/config sets the screen title automatically upon logging in to a system using SSH:
Host *
PermitLocalCommand yes
LocalCommand [ "$TERM" == 'screen' ] && echo -ne "\033k%h\033\\"
Instead of %h, which represents the hostname of the machine you are connecting with, you may use %n, which is the actual name / alias you used to connect to the machine.
NOTE: You need OpenSSH >= v5.1 to be able to use the Localhost %n and %h parameters. Check out 'man ssh_config' for more info on LocalCommand.
To automatically revert the title, back to that of the hostname of the localhost, after closing the SSH session, you can add an escape sequence to you prompt variable PS1 in .bashrc :
export PS1='you_favorite_PS1_here'
if [ "$TERM" == 'screen' ]; then
export PS1=${PS1}'\[\033k\h\033\\\]'
fi
These tricks are especially useful when using a .screenrc config that shows you in what screen 'tab' you are currently working. Add something like the following to .screenrc to get this working:
caption always "%{= kY}%-w%{= Yk}%n %t%{-}%+w%{ kG} %-= #%H - %LD %d %LM - %c"

Try the below commands, no need to edit any file or configuration like ~/.bashrc, Can be used at runtime.
Set static text as title: (My Title)
export PS1='\[\e]0;My Title\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$ '
Set local/global variable as title: ($USER)
export PS1='\[\e]0;$USER\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$ '
Set command output as title: (hostname)
export PS1='\[\e]0;`hostname`\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$ '
Set to default (Revert back):
export PS1='\[\e]0;\u#\h: \w\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$ '

set_screen_title ()
{
echo -ne "\ek$1\e\\"
}

You can also call screen and tell it to set a title:
screen -X title "new title"
If you're in a screen window, it will set that window's name. If you're not in screen, it will set the most recently opened window's name.

To add to Espo's answer, the xterm escape sequences can also be applied to the Bash PS1 variable
ESC]0;stringBEL -- Set icon name and window title to string
ESC]1;stringBEL -- Set icon name to string
ESC]2;stringBEL -- Set window title to string
Example
PS1='\e]0;string\a'

To enable automatic title updating when jumping around with ssh, add this to ~/.bashrc:
ssh() {
echo -n -e "\033k$1\033\\"
/usr/bin/ssh "$#"
echo -n -e "\033k`hostname -s`\033\\"
}
echo -n -e "\033k`hostname -s`\033\\"
See http://linuxepiphany.blogspot.com.ar/2010/05/good-screenrc-config-setup.html

# add the following in your ~/.bashrc or ~/.bash_profile
PROMPT_COMMAND='printf "\033]0;%s#%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"'
or even better copy the whole concept for customizing your bash configs between a lot of hosts from here

My solution to this problem was to create a bash script and add it to my ~/.bashrc file:
set-title() {
ORIG==$PS1
TITLE="\e];$#\a"
PS1=${ORIG}${TITLE}
}
Now when I'm in any bash shell session, I type "set-title desired_title" and it changes to "desired title".
This works for multiple versions of Ubuntu, currently on Kinetic 16.04
I got this solution from here. I was looking for it again, couldn't find it and thought I'd post it here for anyone interested.

I got this solution from experimenting with others, like #flurin-arner I started the #weston-ganger set-title(). I also used #imgx64 PROMPT_DIRTRIM suggestion. I'm also using #itseranga git branch prompt, though this has nothing to do with the question it does show what you can do with the prompt.
First as shown by weston and above
TITLE="\[\e]2;$*\a\]"
can be used to manually set the Terminal Title, "$*" is commandline input, but not what we want.
2nd as stated I'm also adding git branch to my prompt, again not part of the question.
export PROMPT_DIRTRIM=3
parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
export PS1="\u#\h \[\033[32m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\] $ "
3rd, by experiment I copied the TITLE code above, set the $* to a fixed string and tried this:
see: \[\e]2;'SomeTitleString'\a\]
export PS1="\u#\h \[\033[32m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\]\[\e]2;'SomeTitleString'\a\] $ "
This had the desired effect! Ultimately, I wanted the base path as my title.
PS1 Params shows that \W is the base path so my solution is this:
export PS1="\u#\h \[\033[32m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\]\[\e]2;\W\a\] $ "
without the git branch:
export PS1="\u#\h \[\033[32m\]\w\[\033[33m\]\[\033[00m\]\[\e]2;\W\a\] $ "
resulting in a prompt with git-branch:
user#host ~/.../StudyJava (master) $
resulting in a prompt without parse_git_branch:
user#host ~/.../StudyJava $
where pwd gives
/home/user/somedir1/otherdir2/StudyJava
and Terminal Title
StudyJava
NOTE: From #seff above I am essentially replacing the "My Title" with "\W"
export PS1='\[\e]0;My Title\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$ '

I tried this on Ubuntu 18.10 and it only worked with PROMPT_COMMAND in ~/.bashrc.
And if you override PROMPT_COMMAND, the behavior of the title changes slightly. I decided to change only if necessary:
t() {
TITLE="$#"
PROMPT_COMMAND='echo -ne "\033]0;${TITLE}\007"'
}
enter image description here

Related

How to add the current git branch to the existing bash prompt

I'm trying to modify my bash prompt to include the current git branch. This is complicated by the fact that there is already a set of rules that determines what the prompt might be - whether it includes color text, whether the user is on xterm etc. ... so what I really want is to be able to combine the string representing the git branch, colors and all, with the previously set prompt.
But I can't even get the most basic implementation to work. The following includes several of my attempts as I try to simplify the script to eliminate possible problems.
# Add current git branch, if any
parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
}
#PS1="\[\e[34m\]\$(parse_git_branch)\[\e[00m\]$PS1 "
#PS1 = "\$(parse_git_branch) $PS1 "
#PS1 = "\$(parse_git_branch) "
#PS1 = "$(parse_git_branch) "
PS1 = $(parse_git_branch)
Essentially they all produce the same message:
PS1: command not found
I'm sure I am missing something simple. Can anyone tell me what it is?
The error you get is because of the spaces surrounding =, as bash attempts evaluates PS1 as a command, and fails.
You can fix it by just removing the spaces. Here is a sample prompt that prints the current user and directory, along with the prompt:
export PS1='[\u#\h] \W :: $(parse_git_branch)> '
Note the single quotes - if you instead use double quotes, the parse_git_branch will be evaluated and will not update on every prompt. As an alternative to single quotes, you can escape the $ if you prefer.
If you want to update your existing prompt to also have the git branch, you can print the current value of the PS1 environment variable and modify it to suit your needs, finally adding the modified version in say your .bashrc file.
In case you're willing to use an existing solution, there's a Git prompt function already included in many Linux distros. How to use:
PS1=…'$(__git_ps1 " (%s)")'…
The mixed quotes are intentional, and the single quotes are necessary to make this run every time the prompt is displayed. Replace the ellipses with the rest of the prompt, such as '\$ '. If __git_ps1 isn't available out of the box see the full code for a cross-platform solution (works on at least Ubuntu and Arch Linux; should work on recent NixOS).
The " (%s)" part is the format string, that is, a space, open parenthesis (because I want to keep this info separate from the rest of the prompt), the prompt string, and close parenthesis. The %s is replaced by a repository state string, by default just the branch name. See the source code for options and their meanings.

Can the bash "autocomplete" (via TAB TAB) entries be colored/formated?

Background:
The "autocomplete" feature I am talking about is when you half-type a command or filename in bash, if you press TAB twice it will print out suggestions.
Question:
The entries output looks allot like the default ls behavior.
How could I override this to give it different colors or format (like ls)?
EDIT: Copy paste of Answer on UnixStackExchange - vote for it there!
In bash 4.3 and later you can add
set colored-stats on
to ~/.inputrc.
See http://cnswww.cns.cwru.edu/php/chet/readline/rluserman.html:
colored-stats
If set to `on', Readline displays possible completions using different colors to indicate their file type. The color definitions are taken from the value of the LS_COLORS environment variable. The default is `off'.
You can use http://geoff.greer.fm/lscolors/ to generate both LS_COLORS (which is used by GNU ls and colored-stats) and LSCOLORS‏ (which is used by BSD ls).
Append set colored-stats on to your input-rc file.
[[ -f $INPUTRC ]] && echo set colored-stats on >> $INPUTRC
Or if you have it in the default location:
echo set colored-stats on >> ~/.inputrc
Then restart bash.
That's it.

emacs terminal bash (PS1) prompt duplicated

This is a bit of a convoluted question, but here goes nothing!
I've recently updated my bash prompt to the nice version appearing in the last post of this thread: Bash: custom PS1 with nice working directory path.
The relevant bash code (from that thread post) is copied here:
# define the awk script using heredoc notation for easy modification
MYPSDIR_AWK=$(cat << 'EOF'
BEGIN { FS = OFS = "/" }
{
if (length($0) > 16 && NF > 4)
print $1,$2,".." NF-4 "..",$(NF-1),$NF
else
print $0
}
EOF
)
# my replacement for \w prompt expansion
export MYPSDIR='$(echo -n "${PWD/#$HOME/~}" | awk "$MYPSDIR_AWK")'
# the fancy colorized prompt: [0 user#host ~]%
# return code is in green, user#host is in bold/white
export PS1='[\[\033[1;32m\]$?\[\033[0;0m\] \[\033[0;1m\]\u#\h\[\033[0;0m\] $(eval "echo ${MYPSDIR}")]% '
# set x/ssh window title as well
export PROMPT_COMMAND='echo -ne "\033]0;${USER}#${HOSTNAME%%.*} $(eval "echo ${MYPSDIR}")\007"'
This prompt looks roughly like so (in non-emacs terminals):
[0 user#host ~/my_dir]%
Where the "0" above is green and the "user#host" is bold.
(Note that the "0" can be all sorts of numbers, and represents the return value of the last command.)
The issue I'm experiencing is specific to shells running within emacs (and it occurs for most variants of terminal-interaction within emacs: 'term', 'ansi-term', 'shell', and 'eshell').
The prompt appears twice (and slightly broken) in emacs terminals, like so:
0;user#host ~/my_dir[0 user#host ~/my_dir]%
The 'second' version of the prompt, starting from and including the "[" looks just fine.
It's the preceding text, which appears without any styling (i.e. no green and no bold).
So, emacs must be interpreting some portion of the prompt as input, and my guess is the color or bold escaped indicators attached to the "0" and "user#host" portions of the prompt?
Might anyone know how to tell emacs to interpret the escapes correctly?
Or, alternatively, how to modify the prompt-setting commands such that both emacs will not hate it and it'll still work in non-emacs terminals?
And maybe even another alternative: how to add a test for the terminal type ('eterm-color' within emacs) with a modified string that is emacs-friendly?
The error comes from the export PROMPT_COMMAND=... statement.
You can avoid this being read in your configuration, by checking whether you have a shell running inside emacs or not. Here the environment variable INSIDE_EMACS becomes handy. From the Emacs manual (Sect. 32.7):
Emacs sets the environment variable INSIDE_EMACS in the subshell to ‘version,comint’, where version is the Emacs version (e.g., ‘24.1’). Programs can check this variable to determine whether they are running inside an Emacs subshell
In your example, you want
export PROMPT_COMMAND='echo -ne "\033]0;${USER}#${HOSTNAME%%.*} $(eval "echo ${MYPSDIR}")\007" only being executed when you are not in emacs, otherwise you get this nasty "double prompt". The following conditional statement in your code will help.
if [ -z "$INSIDE_EMACS" ];
then
export PROMPT_COMMAND='echo -ne "\033]0;${USER}#${HOSTNAME%%.*} $(eval "echo ${MYPSDIR}")\007"'
else
export PROMPT_COMMAND=''
fi
It checks whether you are not inside emacs, and only then the PROMPT_COMMAND variable is set to your desired value.
The extra display is coming from the PROMPT_COMMAND variable's contents. emacs appears not to understand the OSC 0 title setting xterm escape sequence and so prints out the output.

Is it possible to specify the bash prompt using a command-line option?

I use vim a lot and often find it useful to drop into the command line using !bash.
However, I need to type exit to return to vim and sometimes I'm not sure whether I'm in a subshell or whether that will close my session.
What I'd really like to do is type something like !bash -prompt "subshell" so that I get something like this:
subshell$ <commands go here>
Is this possible?
The most direct way to do this is to set the PS1 environment variable within vim:
:let $PS1="subshell$ "
And start your sub-shells using the command :shell instead of :!bash.
Using the $ sign with let modifies an environment variable. Add this to your .vimrc to persist the setting.
Alternately, using :shell you can specify a more specific command, including arguments, using the shell option, see help shell and help 'shell'.
So:
:set shell=bash\ --rcfile\ ~/.vimbashrc
In .vimbashrc add PS1="subshell ", and invoke the sub-shells using :shell instead of !bash. Add this to your .vimrc to persist the setting.
So you have two options:
Add let $PS1="subshell " to your .vimrc, and start sub-shells using :shell instead of :!bash.
Make a custom rc file for your vim sub-shells, add your specific PS1="subshell " to it, and modify the shell option in your .vimrc: set shell=bash\ --rcfile\ ~/.vimbashrc.
Finally, if you must use :!bash to start the sub-shells there are a couple of more options. Note that you can also pass a more specific command line using !, e.g.:
:PS1="subshell$ " bash should work.
:!bash\ --rcfile\ ~/.vimbashrc, and set PS1 in .vimbashrc as above
But you'll need to type these every time, or define a mapping for it.
Use shell variable $SHLVL seems to be another option here. Add $SHLVL in your $PS1:
export PS1="$PS1 $SHLVL"
so your prompt looks like this:
[tim#RackAblade47 ~]$ 2
when you start shell from VIM, $SHLVL will increase:
[tim#RackAblade47 ~]$ 4
Yes - you can change the prompt inside the shell before running your commands
PS1="subshell"
checkout this guide for all the options http://www.linuxselfhelp.com/howtos/Bash-Prompt/Bash-Prompt-HOWTO-2.html
If you really must do it via the bash command you can use '--rcfile' to specify an RC file that runs the PS1 command for you (you usually put the PS1= line your .bashrc to customize the prompt at login)
To answer your original question, you can say inside Vim:
:!VIMPROMPT="(vim) " bash
and change your prompt (in your .bashrc, presumably) from something like
PS1='\u#\h:\w\$ '
to
PS1='$VIMPROMPT\u#\h:\w\$ '
this will change your prompt from
me#host:~$
to
(vim) me#host:~$
if run inside Vim.
I'm personally using
case $(ps $PPID) in *vim|*bash)
PS1="$(ps $PPID | awk '{print $NF}' | sed 1d) $PS1" ;;
esac
in my prompt script that's sourced by my .bashrc, taken from triplee's comment.
I have been able to change the prompt for a vim subshell process by checking for the MYVIMRC variable which is exported inside vim, and will then update PS1 accordingly. I updated my .bashrc file with the following.
PS1='\$ '
# when in vim subshell change PS1 for clarity
if [[ $MYVIMRC ]]; then PS1='>> '; fi;
Put this in your .bashrc after any existing PS1= statements.
if ps | grep -q vim; then
export PS1="[VIM]$PS1"
fi
Tested on Ubuntu.
You can send it to the background with CTRL+Z and then bring it back with the fg command. With jobs you see all the jobs you have stopped.
This was you can have multiple instances of vim in parallel and chose which one you want to bring back. If there's no running you will just get a no current job error and that's it.
This doesn't specifically answer your question, but addresses the problem underneath it.

What is the difference between PS1 and PROMPT_COMMAND?

While taking a look at this awesome thread I noticed that some examples use
PS1="Blah Blah Blah"
and some use
PROMPT_COMMAND="Blah Blah Blah"
(and some use both) when setting the prompt in a Bash shell. What is the difference between the two? A Stack Overflow search and even a bit of broader Google searching aren't getting me results, so even a link to the right place to look for the answer would be appreciated.
PROMPT_COMMAND can contain ordinary Bash statements whereas the PS1 variable can also contain the special characters, such as '\h' for hostname, in the variable.
For example, here is my Bash prompt that uses both PROMPT_COMMAND and PS1. The Bash code in PROMPT_COMMAND works out what Git branch you might be in and displays that at the prompt, along with the exit status of the last run process, hostname and basename of the pwd.
The variable RET stores the return value of the last executed program. This is convenient to see if there was an error and the error code of the last program I ran in the terminal. Note the outer ' surrounding the entire PROMPT_COMMAND expression. It includes PS1 so that this variable is reevaluated each time the PROMPT_COMMAND variable is evaluated.
PROMPT_COMMAND='RET=$?;\
BRANCH="";\
ERRMSG="";\
if [[ $RET != 0 ]]; then\
ERRMSG=" $RET";\
fi;\
if git branch &>/dev/null; then\
BRANCH=$(git branch 2>/dev/null | grep \* | cut -d " " -f 2);\
fi;
PS1="$GREEN\u#\h $BLUE\W $CYAN$BRANCH$RED$ERRMSG \$ $LIGHT_GRAY";'
Example output looks like this in a non-Git directory:
sashan#dhcp-au-122 Documents $ false
sashan#dhcp-au-122 Documents 1 $
And in a Git directory you see the branch name:
sashan#dhcp-au-122 rework mybranch $
Update
After reading the comments and Bob's answer, I think that writing it as he describes is better. It's more maintainable than what I originally wrote above, where the PS1 variable is set inside the PROMPT_COMMAND, which itself is a super complicated string that is evaluated at runtime by Bash.
It works, but it's more complicated than it needs to be. To be fair, I wrote that PROMPT_COMMAND for myself about 10 years ago and it worked and didn't think too much about it.
For those curious as to how I've amended my things, I've basically put the code for the PROMPT_COMMAND in a separate file (as Bob described) and then echo the string that I intend to be PS1:
GREEN="\[\033[0;32m\]"
CYAN="\[\033[0;36m\]"
RED="\[\033[0;31m\]"
PURPLE="\[\033[0;35m\]"
BROWN="\[\033[0;33m\]"
LIGHT_GRAY="\[\033[0;37m\]"
LIGHT_BLUE="\[\033[1;34m\]"
LIGHT_GREEN="\[\033[1;32m\]"
LIGHT_CYAN="\[\033[1;36m\]"
LIGHT_RED="\[\033[1;31m\]"
LIGHT_PURPLE="\[\033[1;35m\]"
YELLOW="\[\033[1;33m\]"
WHITE="\[\033[1;37m\]"
RESTORE="\[\033[0m\]" #0m restores to the terminal's default colour
if [ -z $SCHROOT_CHROOT_NAME ]; then
SCHROOT_CHROOT_NAME=" "
fi
BRANCH=""
ERRMSG=""
RET=$1
if [[ $RET != 0 ]]; then
ERRMSG=" $RET"
fi
if which git &>/dev/null; then
BRANCH=$(git branch 2>/dev/null | grep \* | cut -d " " -f 2)
else
BRANCH="(git not installed)"
fi
echo "${GREEN}\u#\h${SCHROOT_CHROOT_NAME}${BLUE}\w \
${CYAN}${BRANCH}${RED}${ERRMSG} \$ $RESTORE"
And in my .bashrc file:
function prompt_command {
RET=$?
export PS1=$(~/.bash_prompt_command $RET)
}
PROMPT_DIRTRIM=3
export PROMPT_COMMAND=prompt_command
From the GNU Bash documentation page (Bash Reference Manual):
PROMPT_COMMAND
If set, the value is interpreted as a command to execute before
the printing of each primary prompt ($PS1).
I never used it, but I could have used this back when I only had sh.
The difference is that PS1 is the actual prompt string used, and PROMPT_COMMAND is a command that is executed just before the prompt. If you want the simplest, most flexible way of building a prompt, try this:
Put this in your .bashrc file:
function prompt_command {
export PS1=$(~/bin/bash_prompt)
}
export PROMPT_COMMAND=prompt_command
Then write a script (Bash, Perl, or Ruby: your choice), and place it in file ~/bin/bash_prompt.
The script can use any information it likes to construct a prompt. This is much simpler, IMO, because you don't have to learn the somewhat baroque substitution language that was developed just for the PS1 variable.
You might think that you could do the same by simply setting PROMPT_COMMAND directly to ~/bin/bash_prompt, and setting PS1 to the empty string.
This at first appears to work, but you soon discover that the readline code expects PS1 to be set to the actual prompt, and when you scroll backwards in history, things get messed up as a result.
This workaround causes PS1 to always reflect the latest prompt (since the function sets the actual PS1 variable used by the invoking instance of the shell), and this makes readline and command history work fine.
From man bash:
PROMPT_COMMAND
If set, the value is executed as a command prior to issuing each primary prompt.
PS1
The value of this parameter is expanded (see PROMPTING below) and used as the primary prompt string. The default value is ''\s-\v\$ ''.
If you simply want to set the prompt string, using PS1 alone is enough:
PS1='user \u on host \h$ '
If you want to do something else just before printing the prompt, use PROMPT_COMMAND. For example, if you want to sync cached writes to disk, you can write:
PROMPT_COMMAND='sync'
Yeah, so to try to really nail this down:
PROMPT_COMMAND is a handy Bash convenience variable/function, but there is, strictly speaking, nothing that cannot also be done using PS1 alone, correct?
I mean, if one wants to set another variable with scope outside the prompt: depending on the shell, that variable would probably need to be declared first outside $PS1 or (worst case) one might have to get fancy with something waiting on a FIFO prior to calling $PS1 (and armed again at the end of $PS1); the \u \h might cause some trouble, particularly if you're using some fancy regex; but otherwise: one can accomplish anything PROMPT_COMMAND can by using command substitution within $PS1 (and, maybe in corner cases, explicit subshells)?
Right?
The difference is that
if you output an incomplete line from PROMPT_COMMAND, it will screw your Bash prompt
PS1 substitutes \H and friends
PROMPT_COMMAND runs its contents, and PS1 uses its contents as the prompt.
PS1 does variable expansion and command substitution at each prompt. There isn't any need to use PROMPT_COMMAND to assign a value to PS1 or to run arbitrary code. You can easily do export PS1='$(uuidgen) $RANDOM' once in file .bash_profile. Just use single quotes.
I spent so much time on this I just wanted to share what worked for me. I looked at a lot of the SO posts about PROMPT_COMMAND and PS1 and tried many combinations of single quotes, double quotes, calling functions... I could not get the prompt to update each time without printing control characters or the literal expanded but not processed prompt string, or without just setting PS1 in PROMPT_COMMAND as we are advised not to do. My problem was setting variables (colors) that contained control characters; these had to be hard-coded after the variable name in PS1. PROMPT_COMMAND is set to a function that sets variables and they are used (escaped) in a double-quoted PS1 string. This is for a powerline-style prompt that changes colors with each command.
icon1=#unicode powerline char like
#these: https://github.com/ryanoasis/powerline-extra-symbols#glyphs
icon2=#same
#array of ANSI colors. 2 for rgb mode then the rgb values
#then 'm' without '\]' control character. these are from
#the solarized theme https://ethanschoonover.com/solarized/
declare -a colors=(
"2;220;50;47m"
"2;203;75;22m"
"2;181;137;0m"
"2;133;153;0m"
"2;42;161;152m"
"2;38;139;210m"
"2;108;113;196m"
"2;211;54;130m"
"2;0;43;54m"
"2;7;54;66m"
"2;88;110;117m"
"2;101;123;131m"
"2;131;148;150m"
"2;147;161;161m"
)
#outside of vars set in PROMPT_COMMAND it's ok to have control chars
LEN=${#colors[#]}
BG="\[\e[48;"#set bg color
FG="\[\e[38;"#set fg color
TRANSP="1m\]"#transparency
BASE2="2;238;232;213m\]"#fg (text) color
myfunc(){
RAND=$(($RANDOM % $LEN))
COLOR1=${colors[$RAND]}
COLOR2=${colors[($RAND + 1) % $LEN]}
COLOR3=${colors[($RAND + 2) % $LEN]}
}
PROMPT_COMMAND=myfunc
#note double quotes and escaped COLOR vars followed by hard-coded '\]' control chars
PS1="$BG$TRANSP$FG\$COLOR1\]$icon1$BG\$COLOR1\]$FG$TRANSP$BG\$COLOR1\]$FG$BASE2
[username hard-coded in unicode] $BG\$COLOR2\]$FG\$COLOR1\]$icon2$BG\$COLOR2\]$FG$BASE2
\w $BG\$COLOR3\]$FG\$COLOR2\]$icon2$BG\$COLOR3\]$FG$BASE2 [more unicode]
\[\e[0m\]$FG\$COLOR3\]$icon2\[\e[0m\] "
That ought to get you going!

Resources