Fancy Bash Prompts

Posted by DaveV on Sun 7 Aug 2005 at 12:24

Tags:

While most people never change their Bash prompt, some of us suffer from a mild form of insanity that drives us to configure every option as far as the system will let us. This article is an example of how far you can push what most people would consider a simple option and hopefully will start a discussion on other prompt customizations and tweaks.

NOTE: The Bash Prompt Howto can be found at:
http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/


This is the Bash prompt that I've been using to get a little more system status information on each line.

Save the script to a file and source it from your .bashrc.

#!/bin/bash
# Filename:      custom_prompt.sh
# Maintainer:    Dave Vehrs 
# Last Modified: 12 Jul 2005 13:29:40 by Dave Vehrs

# Current Format: USER@HOST [dynamic section] { CURRENT DIRECTORY }$ 
# USER:      (also sets the base color for the prompt)
#   Red       == Root(UID 0) Login shell (i.e. sudo bash)
#   Light Red == Root(UID 0) Login shell (i.e. su -l or direct login)
#   Yellow    == Root(UID 0) priviledges in non-login shell (i.e. su)
#   Brown     == SU to user other than root(UID 0)
#   Green     == Normal user
# @:
#   Light Red == http_proxy environmental variable undefined.
#   Green     == http_proxy environmental variable configured.
# HOST:
#   Red       == Insecure remote connection (unknown type)
#   Yellow    == Insecure remote connection (Telnet)
#   Brown     == Insecure remote connection (RSH)
#   Cyan      == Secure remote connection (i.e. SSH)
#   Green     == Local session
# DYNAMIC SECTION:  
#     (If count is zero for any of the following, it will not appear)
#   [scr:#] ==== Number of detached screen sessions
#     Yellow    == 1-2
#     Red       == 3+
#   [bg:#]  ==== Number of backgrounded but still running jobs
#     Yellow    == 1-10
#     Red       == 11+
#   [stp:#] ==== Number of stopped (backgrounded) jobs
#     Yellow    == 1-2
#     Red       == 3+
# CURRENT DIRECTORY:     (truncated to 1/4 screen width)
#   Red       == Current user does not have write priviledges
#   Green     == Current user does have write priviledges
# NOTE:
#   1.  Displays message on day change at midnight on the line above the
#       prompt (Day changed to...). 
#   2.  Command is added to the history file each time you hit enter so its
#       available to all shells.

# Configure Colors:
COLOR_WHITE='\033[1;37m'
COLOR_LIGHTGRAY='033[0;37m'
COLOR_GRAY='\033[1;30m'
COLOR_BLACK='\033[0;30m'
COLOR_RED='\033[0;31m'
COLOR_LIGHTRED='\033[1;31m'
COLOR_GREEN='\033[0;32m'
COLOR_LIGHTGREEN='\033[1;32m'
COLOR_BROWN='\033[0;33m'
COLOR_YELLOW='\033[1;33m'
COLOR_BLUE='\033[0;34m'
COLOR_LIGHTBLUE='\033[1;34m'
COLOR_PURPLE='\033[0;35m'
COLOR_PINK='\033[1;35m'
COLOR_CYAN='\033[0;36m'
COLOR_LIGHTCYAN='\033[1;36m'
COLOR_DEFAULT='\033[0m'

# Function to set prompt_command to.
function promptcmd () {
    history -a 
    local SSH_FLAG=0
    local TTY=$(tty | awk -F/dev/ '{print $2}')
    if [[ ${TTY} ]]; then 
        local SESS_SRC=$(who | grep "$TTY "  | awk '{print $6 }')
    fi
    
    # Titlebar
    case ${TERM} in 
        xterm*  )  
            local TITLEBAR='\[\033]0;\u@\h: { \w }  \007\]'
            ;;
        *       )  
            local TITLEBAR=''                               
            ;;
    esac
    PS1="${TITLEBAR}"
  
    # Test for day change.
    if [ -z $DAY ] ; then
        export DAY=$(date +%A)
    else
        local today=$(date +%A)
        if [ "${DAY}" != "${today}" ]; then
            PS1="${PS1}\n\[${COLOR_GREEN}\]Day changed to $(date '+%A, %d %B %Y').\n"
            export DAY=$today
       fi
    fi
   
    # User
    if [ ${UID} -eq 0 ] ; then
        if [ "${USER}" == "${LOGNAME}" ]; then
            if [[ ${SUDO_USER} ]]; then
                PS1="${PS1}\[${COLOR_RED}\]\u"
            else
                PS1="${PS1}\[${COLOR_LIGHTRED}\]\u"
            fi
        else                               
            PS1="${PS1}\[${COLOR_YELLOW}\]\u"
        fi
    else
        if [ ${USER} == ${LOGNAME} ]; then     
            PS1="${PS1}\[${COLOR_GREEN}\]\u"
        else                               
            PS1="${PS1}\[${COLOR_BROWN}\]\u"
        fi
    fi

    # HTTP Proxy var configured or not
    if [ -n "$http_proxy" ] ; then
        PS1="${PS1}\[${COLOR_GREEN}\]@"
    else                               
        PS1="${PS1}\[${COLOR_LIGHTRED}\]@"
    fi

    # Host

    if [[ ${SSH_CLIENT} ]] || [[ ${SSH2_CLIENT} ]]; then 
        SSH_FLAG=1
    fi
    if [ ${SSH_FLAG} -eq 1 ]; then 
       PS1="${PS1}\[${COLOR_CYAN}\]\h "
    elif [[ -n ${SESS_SRC} ]]; then 
        if [ "${SESS_SRC}" == "(:0.0)" ]; then 
        PS1="${PS1}\[${COLOR_GREEN}\]\h "
        else 
            local parent_process=`cat /proc/${PPID}/cmdline`
            if [[ "$parent_process" == "in.rlogind*" ]]; then
                PS1="${PS1}\[${COLOR_BROWN}\]\h "
            elif [[ "$parent_process" == "in.telnetd*" ]]; then 
                PS1="${PS1}\[${COLOR_YELLOW}\]\h "
            else
                PS1="${PS1}\[${COLOR_LIGHTRED}\]\h "
            fi
        fi
    elif [[ "${SESS_SRC}" == "" ]]; then
        PS1="${PS1}\[${COLOR_GREEN}\]\h "
    else                                 
        PS1="${PS1}\[${COLOR_RED}\]\h " 
    fi

    # Detached Screen Sessions
    local DTCHSCRN=$(screen -ls | grep -c Detach )
    if [ ${DTCHSCRN} -gt 2 ]; then
        PS1="${PS1}\[${COLOR_RED}\][scr:${DTCHSCRN}] "
    elif [ ${DTCHSCRN} -gt 0 ]; then
        PS1="${PS1}\[${COLOR_YELLOW}\][scr:${DTCHSCRN}] "
    fi
   
    # Backgrounded running jobs
    local BKGJBS=$(jobs -r | wc -l )
    if [ ${BKGJBS} -gt 2 ]; then
        PS1="${PS1}\[${COLOR_RED}\][bg:${BKGJBS}]"
    elif [ ${BKGJBS} -gt 0 ]; then
        PS1="${PS1}\[${COLOR_YELLOW}\][bg:${BKGJBS}] "
    fi
    
    # Stopped Jobs
    local STPJBS=$(jobs -s | wc -l )
    if [ ${STPJBS} -gt 2 ]; then
        PS1="${PS1}\[${COLOR_RED}\][stp:${STPJBS}]"
    elif [ ${STPJBS} -gt 0 ]; then
        PS1="${PS1}\[${COLOR_YELLOW}\][stp:${STPJBS}] "
    fi
    
    # Bracket {
    if [ ${UID} -eq 0 ]; then              
        if [ "${USER}" == "${LOGNAME}" ]; then 
            if [[ ${SUDO_USER} ]]; then
                PS1="${PS1}\[${COLOR_RED}\]"
            else
                PS1="${PS1}\[${COLOR_LIGHTRED}\]"
            fi
        else                               
            PS1="${PS1}\[${COLOR_YELLOW}\]"
        fi
    else                                 
        if [ "${USER}" == "${LOGNAME}" ]; then 
            PS1="${PS1}\[${COLOR_GREEN}\]"
        else                               
            PS1="${PS1}\[${COLOR_BROWN}\]"
        fi
    fi
    PS1="${PS1}{ "
    
    # Working directory
    if [ -w "${PWD}" ]; then 
        PS1="${PS1}\[${COLOR_GREEN}\]$(prompt_workingdir)"
    else                                 
        PS1="${PS1}\[${COLOR_RED}\]$(prompt_workingdir)"
    fi
    
    # Closing bracket } and $\#
    if [ ${UID} -eq 0 ]; then              
        if [ "${USER}" == "${LOGNAME}" ]; then 
            if [[ ${SUDO_USER} ]]; then
                PS1="${PS1}\[${COLOR_RED}\]"
            else
                PS1="${PS1}\[${COLOR_LIGHTRED}\]"
            fi
        else                               
            PS1="${PS1}\[${COLOR_YELLOW}\]"
        fi
    else                                 
        if [ "${USER}" == "${LOGNAME}" ]; then 
            PS1="${PS1}\[${COLOR_GREEN}\]"
        else                               
            PS1="${PS1}\[${COLOR_BROWN}\]"
        fi
    fi
    PS1="${PS1} }\$\[${COLOR_DEFAULT}\] "
}     

# Trim working dir to 1/4 the screen width
function prompt_workingdir () {
  local pwdmaxlen=$(($COLUMNS/4))
  local trunc_symbol="..."
  if [[ $PWD == $HOME* ]]; then
    newPWD="~${PWD#$HOME}" 
  else
    newPWD=${PWD}
  fi
  if [ ${#newPWD} -gt $pwdmaxlen ]; then
    local pwdoffset=$(( ${#newPWD} - $pwdmaxlen + 3 ))
    newPWD="${trunc_symbol}${newPWD:$pwdoffset:$pwdmaxlen}"
  fi
  echo $newPWD
}

# Determine what prompt to display:
# 1.  Display simple custom prompt for shell sessions started
#     by script.  
# 2.  Display "bland" prompt for shell sessions within emacs or 
#     xemacs.
# 3   Display promptcmd for all other cases.

function load_prompt () {
    # Get PIDs
    local parent_process=$(cat /proc/$PPID/cmdline | cut -d \. -f 1)
    local my_process=$(cat /proc/$$/cmdline | cut -d \. -f 1)

    if  [[ $parent_process == script* ]]; then
        PROMPT_COMMAND=""
        PS1="\t - \# - \u@\H { \w }\$ "
    elif [[ $parent_process == emacs* || $parent_process == xemacs* ]]; then
        PROMPT_COMMAND=""
        PS1="\u@\h { \w }\$ "
    else
        export DAY=$(date +%A)
        PROMPT_COMMAND=promptcmd
     fi 
    export PS1 PROMPT_COMMAND
}

load_prompt


How about you? What interesting snippits or tricks do you have in your prompt?

 

 


Posted by Steve (82.41.xx.xx) on Sun 7 Aug 2005 at 11:37
[ View Steve's Scratchpad | View Weblogs ]

I have to say that I find that prompt quite ugly! Still if you like it then I guess that's enough :)

The use of colour for conveying information is very nice - but having the components split up is quite distracting.

Personally I use the "standard" setup of having:

user@hostname:/my/full/path [#$]

This is very useful for me because it allows me to cut and paste parts of the prompt for use in other shell windows. Usually that's one of the following commands:

  • "ssh user@hostname"
  • "scp user@hostname:/my/full/path"

Both of those I do sufficiently often that the thought of not including these bits of information like that is quite distracting.

I did think that I'd seen a discussion on shell prompts on Slashdot in the semi-recent past. The closest I could find was this poll on shell prompt length - there are some interesting comments there.

However it's worth cautioning users to not blindly type in arbitary shell propmt examples they see - those control codes and dense snippets sometimes contain badnesstm ...

Steve
-- Steve.org.uk

[ Parent | Reply to this comment ]

Posted by DaveV (24.8.xx.xx) on Sun 7 Aug 2005 at 17:07

I can see how your format would be useful but plain white with a long path on the end isn't ugly?

1. I like long directory names and so the path piece will often be longer than the screen width so I truncate it. If I need the path for a command, I can just call $PWD.

2. I find bash completion lets me tab-complete the base ssh/scp commands faster than grabbing the mouse to cut/paste espeically if you assign host based configurations in your ~/.ssh/config to account for different username, etc. For example:

(From ~/.ssh/config)
Host work_email
  Hostname emailserver.mywork.com
  User dave
  ForwardAgent no
  Port 220

3. Given that a user should never execute a shell script that they do not understand from an untrusted source and given that your site includes a lot of shell scripts without similar warnings does this mean there is something in my prompt that you dont understand or concerns you? Or did you just pick this spot to throw out a random warning?

[ Parent | Reply to this comment ]

Posted by Steve (82.41.xx.xx) on Sun 7 Aug 2005 at 17:28
[ View Steve's Scratchpad | View Weblogs ]

Perhaps I wasn't as careful as I should have been - what I meant to say wasn't "ugly", but "distracting". But as this is a matter of taste it is probably something I shouldn't have mentioned.

Bash completion for SSH / SCP comments isn't as useful as it could be, unless you have agents setup, or passwordless logins. Because whilst you can complete the remote hostnames you cannot complete the PATH. Unless I'm mistaken.

Whilst I take your point that users shouldn't execute anything that they don't trust. I was mostly mentioning this proactively because a lot of shell prompt examples, or samples, I've seen use control codes, or escape characters, in such a way that it's easy for malicious scripts to hide.

As a perfect example see comment #4. Whilst that isn't a malicious example it is completely unreadable to me - short of running it and that is something that users should be in the habit of considering.

So whilst it was an unfocussed warning, not aimed at you specifically, it wasn't randomly thrown in!

Steve
-- Steve.org.uk

[ Parent | Reply to this comment ]

Posted by wilk (80.65.xx.xx) on Sun 7 Aug 2005 at 12:53
Very good idea, it should be activated by default on any system for security !

[ Parent | Reply to this comment ]

Posted by shufla (83.30.xx.xx) on Sun 7 Aug 2005 at 13:30
Hi,

I'm using colorized prompts, with specific color on \h for each remote host. Too many 'reboot' or 'dd if=/dev/zero of=/dev/hda' on wrong machine ;)

[ Parent | Reply to this comment ]

Posted by Anonymous (80.185.xx.xx) on Sun 7 Aug 2005 at 15:01
I use this:
export PS1="\n\[\033[0;30m\]\[\033[1;34m\](\[\033[1;32m\]\u\[\033[1;37m\ ]@\[\033[1;32m\]\h\[\033[1;34m\])\[\033[0;30m\] \[\033[1;30m\](\[\033[0;37m\]$OSTYPE\[\033[1;30m\])\[\033[0;30 m\] \[\033[1;31m\](\[\033[1;34m\]\t \d\[\033[1;31m\])\[\033[0;30m\] \[\033[0;30m\]\n \[\033[1;30m\](\[\033[1;37m\]\w/\[\033[1;30m\])\[\033[0;30m\]\[\0 33[0;32m\] "
Regards nion

[ Parent | Reply to this comment ]

Posted by Piem (81.178.xx.xx) on Sun 7 Aug 2005 at 18:03
[ View Piem's Scratchpad ]
piem@pomme:/home/piem
$ echo $PROMPT_COMMAND
echo -e "\033[${COLOR}${USER}@${HOSTNAME}\033[0;00m:${PWD}"
piem@pomme:/home/piem
$ echo $PS1
$
ciao, piem

[ Parent | Reply to this comment ]

Posted by eric (194.2.xx.xx) on Mon 8 Aug 2005 at 07:39
[ View Weblogs ]
I use the same command to set the xterm title bar to "user@host:dir"
Very useful when you reun a lot of xterms connnected to a lot of diferent servers.

eric:

[ Parent | Reply to this comment ]

Posted by DaveV (24.8.xx.xx) on Mon 8 Aug 2005 at 06:36

OK, I was inspired by this idea (from the Slashdot link Steve posted) and I added one more feature to my prompt.

URL: http://slashdot.org/comments.pl?sid=108424&cid=9219400

Now in the dynamic section, it will also list the exit status code of any application that exit with an error.

Simply add the following lines just above the Detached Screen Sessions comment and after the end of the previous if block:

    ...
    fi

    # Exit status of the last command run.
    PS1="${PS1}\[\`let exitstatus=\$? ; if [[ \${exitstatus} != 0 ]] ; then echo \"\[${COLOR_RED}\][es:\${exitstatus}] \" ; fi\`\]"

    # Detached Screen Sessions
    ...

[ Parent | Reply to this comment ]

Posted by legooolas (144.173.xx.xx) on Mon 8 Aug 2005 at 09:32
It's good to get people adding more stuff to their bash prompt - it's a good way to get people to start tweaking configuration files which they may otherwise be loathe to start doing :)

It can be useful to have a multi-line prompt because having the point at which you type being different according to the length of the displayed path can be quite annoying (to me, at least:)
I have something like:

-- user@host $smiley [$30-chars-of-path] --
=> (type here)

Where the $smiley is happy if the last command completed ok, and a frownie if it exited in a non-zero way :)
(and there are some colours in there too, of course)

[ Parent | Reply to this comment ]

Posted by Anonymous (68.214.xx.xx) on Mon 8 Aug 2005 at 14:11
http://gentoo-wiki.com/TIP_Prompt_Magic

That has a nifty dynamic prompt.

[ Parent | Reply to this comment ]

Posted by eaglex (86.125.xx.xx) on Mon 8 Aug 2005 at 19:16

This looks like a nice prompt generator:http://www.linuxhelp.net/guides/bashprompt/bashprompt-print.php

[ Parent | Reply to this comment ]

Posted by Anonymous (66.55.xx.xx) on Mon 8 Aug 2005 at 20:09
Here is my prompt:

[14:57:14] davis@davis:[~]: grep PS1 .bashrc
PS1="\[\033[36m\][\t] \[\033[1;33m\]\u\[\033[0m\]@\h:\[\033[36m\][\w]:\[\033[0m\] "

The only change to the root shell is the user changes and the trailing : turns into a #


[ Parent | Reply to this comment ]

Posted by eaglex (86.125.xx.xx) on Mon 8 Aug 2005 at 20:45

My prompt: PS1="\033[0;37m\u\[\033[0;32m\][\033[0;37m@\[\033[0;32m\]]\[\033[ 0;37m\]\h\[\033[0;32m\](\[\033[0;37m\]\t\[\033[0;32m\])\[\033[0;3 7m\]\[\033[0;32m\][\[\033[0;37m\]\w\[\033[0;32m\]]\[\033[0;37m\]\ n\[\033[0;32m\]$\[\033[0;37m\]"

[ Parent | Reply to this comment ]

Posted by kecsi (152.66.xx.xx) on Thu 11 Aug 2005 at 19:43
    red='\e[0;31m'
    RED='\e[1;31m'
    blue='\e[0;34m'
    BLUE='\e[1;34m'
    green='\e[0;32m'
    GREEN='\e[1;32m'
    cyan='\e[0;36m'
    CYAN='\e[1;36m'
    NC='\e[0m' # No Color

function powerprompt() {
_powerprompt() { LOAD=$(uptime|sed -e "s/.*: \([^,]*\).*/\1/" -e "s/ //g") TIME=$(date +%H:%M:%S) OPENSHELLS=$(who|wc -l|sed -e "s/ //g") UPTIME=$(uptime|sed -e "s/.*up\([^,]*\).*/\1/" -e "s/ //g") }
PROMPT_COMMAND=_powerprompt
case $TERM in xterm | dtterm | rxvt ) PS1="${cyan}[Time: ${green}\${TIME}${cyan} | OpenShells: ${green}\${OPENSHELLS}${cyan} | Load: ${green}\${LOAD}${cyan} | Uptime: ${green}\${UPTIME}${cyan}]${NC}\n[\#.][\u@\h]:\w> \[\033]0;[\u@\h] \w\007\ ]" ;; linux ) PS1="${cyan}[Time: ${green}\${TIME}${cyan} | OpenShells: ${green}\${OPENSHELLS}${cyan} | Load: ${green}\${LOAD}${cyan} | Uptime: ${green}\${UPTIME}${cyan}]${NC}\n[\#.][\u@\h]:\w>" ;; * ) PS1="[Time: \${TIME} - Load: \${LOAD}]\n[\#.][\u@\h]:\w> " ;; esac }

[ Parent | Reply to this comment ]

Posted by txemi (83.213.xx.xx) on Sat 20 Aug 2005 at 16:54
I use screen session started by cron on reboot ("@reboot /usr/bin/screen -s /bin/bash -dmS boot..." on your crontab) and this scripts fails in this situation, I attach a patch.

--- /home/txemi/bash.prompt 2005-08-18 20:55:37.000000000 +0200
+++ bash.prompt 2005-08-20 18:37:24.000000000 +0200
@@ -101,7 +101,7 @@
PS1="${PS1}\[${COLOR_YELLOW}\]\u"
fi
else
- if [ ${USER} == ${LOGNAME} ]; then
+ if [ "${USER}" == ${LOGNAME} ]; then
PS1="${PS1}\[${COLOR_GREEN}\]\u"
else
PS1="${PS1}\[${COLOR_BROWN}\]\u"

[ Parent | Reply to this comment ]

Posted by DaveV (24.8.xx.xx) on Sun 4 Sep 2005 at 16:42

For Screen users, this will change the screen window title (which we can use to change the xterm window title).

Replace the Titlebar segment with:

    # Titlebar
    case ${TERM} in
        screen* )
            local TITLEBAR='\[\033k\w\033\134\]'
            ;;
        xterm*  )  
            local TITLEBAR='\[\033]0;\u@\h: { \w }  \007\]'
            ;;
        *       )  
            local TITLEBAR=''                               
            ;;
    esac

Then we can enable a caption with tabs for each window and set the xterm title with the following added to your .screenrc

# Caption  (use lastline for tabs)
caption always "%?%F%{-b gk}%:%{-b bb}%?%C | %D | %M %d |%H |%?%F%{-b gk}%? %L=%-Lw%45>%{-b kg}%n%f* %t%{-}%+Lw%-0<"


# Hardstatus (update xterm title)
hardstatus string "SCREEN @ %H: %-n - %t"

[ Parent | Reply to this comment ]

Posted by Anonymous (205.145.xx.xx) on Fri 30 Sep 2005 at 19:17
Thank you! I use two different screen sessions at work in xterm, and this has been a TREMENDOUS help keeping everything straight.

[ Parent | Reply to this comment ]

Posted by DaveV (24.8.xx.xx) on Sun 2 Oct 2005 at 21:39

To track zombie processes, just add the following snippit between the Exit status and Detached screen sessions snippets:

# Zombie process counts
local ZOMBIES=$(ps hr -Nos | awk '$1=="Z" {print $1}' | wc -l)
if [ ${ZOMBIES} -gt 0 ]; then
   PS1="${PS1}\[${COLOR_RED}\][Z:${ZOMBIES}] "
fi

Enjoy.

[ Parent | Reply to this comment ]

Posted by Anonymous (81.56.xx.xx) on Mon 25 Feb 2008 at 23:10
hi, here's mpine, eyecandy, but usefull since you have always a big place for the command : my facy bash prompt

[ Parent | Reply to this comment ]

Sign In

Username:

Password:

[Register|Advanced]

 

Flattr

 

Current Poll

What do you use for configuration management?








( 467 votes ~ 5 comments )