Bash provides another environment variable called PROMPT_COMMAND. The contents of this variable are executed as a regular Bash command just before Bash displays a prompt.
[21:55:01][giles@nikola:~] PS1="[\u@\h:\w]\$ "
[giles@nikola:~] PROMPT_COMMAND="date +%H%M"
2155
[giles@nikola:~] d
bin mail
2156
[giles@nikola:~]
What happened above was that I changed PS1 to no longer include the
\t
escape sequence, so the time was no longer a part of the
prompt. Then I used date +%H%M
to display the time in a format
I like better. But it appears on a different line than the prompt.
Tidying this up using echo -n ...
as shown below works with
Bash 2.0+, but appears not to work with
Bash 1.14.7: apparently the prompt is drawn in a different way, and the
following method results in overlapping text.
2156
[giles@nikola:~] PROMPT_COMMAND="echo -n [$(date +%H%M)]"
[2156][giles@nikola:~]$
[2156][giles@nikola:~]$ d
bin mail
[2157][giles@nikola:~]$ unset PROMPT_COMMAND
[giles@nikola:~]
echo -n ...
controls the output of the date
command and supress the trailing newline,
allowing the prompt to appear all on one line. At the end, I used the
unset
command to remove the PROMPT_COMMAND environment
variable.
Note that I use the $(<command>) convention for command substitution: that is,
$(date +%H%M)
means "substitute the output from the date +%H%M command
here." This works in Bash 2.0+. In some older versions of Bash, prior
to 1.14.7, you may need to use
backquotes (`date +%H%M`
). Backquotes can be used in
Bash 2.0+, but are being phased
out in favor of $(), which nests better. I will continue to use this
convention throughout this document. If you're using an earlier version
of Bash, you can usually substitute backquotes where you see $(). If the
command substitution is escaped (ie. \$(command) ), then use backslashes
to escape BOTH your backquotes (ie. \'command\' ).
You can use the output of regular Linux commands directly in the prompt as well. Obviously, you don't want to insert a lot of material, or it will create a large prompt. You also want to use a fast command, because it's going to be executed every time your prompt appears on the screen, and delays in the appearance of your prompt while you're working can be very annoying. (Unlike the previous example that this closely resembles, this does work with Bash 1.14.7.)
[21:58:33][giles@nikola:~]$ PS1="[\$(date +%H%M)][\u@\h:\w]\$ "
[2159][giles@nikola:~]$ ls
bin mail
[2200][giles@nikola:~]$
It's important to notice the backslash before the dollar sign of the command substitution. Without it, the external command is executed exactly once: when the PS1 string is read into the environment. For this prompt, that would mean that it would display the same time no matter how long the prompt was used. The backslash protects the contents of $() from immediate shell interpretation, so "date" is called every time a prompt is generated.
Linux comes with a lot of small utility programs like date, grep, or wc that allow you to manipulate data. If you find yourself trying to create complex combinations of these programs within a prompt, it may be easier to make a shell script of your own, and call it from the prompt. Escape sequences are often required in bash shell scripts to ensure that shell variables are expanded at the correct time (as seen above with the date command): this is raised to another level within the prompt PS1 line, and avoiding it by creating shell scripts is a good idea.
An example of a small shell script used within a prompt is given below:
#!/bin/bash # lsbytesum - sum the number of bytes in a directory listing TotalBytes=0 for Bytes in $(ls -l | grep "^-" | cut -c30-41) do let TotalBytes=$TotalBytes+$Bytes done TotalMeg=$(echo -e "scale=3 \n$TotalBytes/1048576 \nquit" | bc) echo -n "$TotalMeg"
I have at times kept this both as a function (much more efficient - unfortunately, explaining functions in detail is beyond the scope of this document), or as a shell script in my /bin directory, which is on my path. Used in a prompt:
[2158][giles@nikola:~]$ PS1="[\u@\h:\w (\$(lsbytesum) Mb)]\$ "
[giles@nikola:~ (0 Mb)]$ cd /bin
[giles@nikola:/bin (4.498 Mb)]$
You'll find I put username, machine name, time, and current directory name in most of my prompts. With the exception of the time, these are very standard items to find in a prompt, and time is probably the next most common addition. But what you include is entirely a matter of personal taste. Here are examples from people I know to help give you ideas.
Dan's prompt is minimal but very effective, particularly for the way he works.
[giles@nikola:~]$ cur_tty=$(tty | sed -e "s/.*tty\(.*\)/\1/")
[giles@nikola:~]$ echo $cur_tty
p4
[giles@nikola:~]$ PS1="\!,$cur_tty,\$?\$ "
1095,p4,0$
Dan doesn't like that having the current working directory can resize the prompt drastically as you move through the directory tree, so he keeps track of that in his head (or types "pwd"). He learned Unix with csh and tcsh, so he uses his command history extensively (something many of us weened on Bash do not do), so the first item in the prompt is the history number. The second item is the significant characters of the tty (the output of "tty" is cropped with sed), an item that can be useful to "screen" users. The third item is the exit value of the last command/pipeline (note that this is rendered useless by any command executed within the prompt - you could work around that by capturing it to a variable and playing it back, though). Finally, the "\$" is a dollar sign for a regular user, and switches to a hash mark ("#") if the user is root.
Torben Fjerdingstad wrote to tell me that he often suspends jobs, and then forgets about them, so he uses his prompt to remind him of suspended jobs:
[giles@nikola:~]$ function jobcount {
> jobs|wc -l| awk '{print $1}'
> }
[giles@nikola:~]$ export PS1='\W[`jobcount`]# '
giles[0]# man ls &
[1] 4150
[1]+ Stopped (tty output) man ls
giles[1]#
Torben uses awk to trim the whitespace from the output of wc, while I would have used sed or tr - not because they're better, but because I'm more familiar with them. There are probably other ways as well. Torben also surrounds his PS1 string in single quotes, which prevent Bash from immediately interpreting the backquotes, so he doesn't have to escape them as I have mentioned.
NOTE: There is a known bug in Bash 2.02 that causes the jobs command (a shell builtin) to return nothing to a pipe. If you try the above under Bash 2.02, you will always get a "0" back regardless of how many jobs you have suspended. Chet Ramey, one of the maintainers of Bash, tells me that this will be fixed for v2.03.
As mentioned earlier, PS1, PS2, PS3, PS4, and PROMPT_COMMAND are all stored in the Bash environment. For those of us coming from a DOS background, the idea of tossing big hunks of code into the environment is horrifying, because that DOS environment was small, and didn't exactly grow well. There are probably practical limits on what you can and should put in the environment, but I don't know what they are, and we're probably talking a couple of orders of magnitude larger than what DOS users are used to. As Dan put it:
"In my interactive shell I have 62 aliases and 25 functions. My rule of thumb is that if I need something solely for interactive use and can handily write it in bash I make it a shell function (assuming it can't be easily expressed as an alias). If these people are worried about memory they don't need to be using bash. Bash is one of the largest programs I run on my linux box (outside of Oracle). Run top sometime and press 'M' to sort by memory - see how close bash is to the top of the list. Heck, it's bigger than sendmail! Tell 'em to go get ash or something."
I guess he was using console only the day he tried that: running X and X apps, I have a lot of stuff larger than Bash. But the idea is the same: the environment is something to be used, and don't worry about overfilling it.
I risk censure by Unix gurus when I say this (for the crime of over-simplification), but functions are basically small shell scripts that are loaded into the environment for the purpose of efficiency. Quoting Dan again: "Shell functions are about as efficient as they can be. It is the approximate equivalent of sourcing a bash/bourne shell script save that no file I/O need be done as the function is already in memory. The shell functions are typically loaded from [.bashrc or .bash_profile] depending on whether you want them only in the initial shell or in subshells as well. Contrast this with running a shell script: Your shell forks, the child does an exec, potentially the path is searched, the kernel opens the file and examines enough bytes to determine how to run the file, in the case of a shell script a shell must be started with the name of the script as its argument, the shell then opens the file, reads it and executes the statements. Compared to a shell function, everything other than executing the statments can be considered unnecessary overhead."