We already mentioned a couple of environment variables, such as PATH and HOME. Until now, we only saw examples in which they serve a certain purpose to the shell. But there are many other Linux utilities that need information about you in order to do a good job.
What other information do programs need apart from paths and home directories?
A lot of programs want to know about the kind of terminal you are using; this information is stored in the TERM variable. In text mode, this will be the linux terminal emulation, in graphical mode you are likely to use xterm. Lots of programs want to know what your favorite editor is, in case they have to start an editor in a subprocess. The shell you are using is stored in the SHELL variable, the operating system type in OS and so on. A list of all variables currently defined for your session can be viewed entering the printenv command.
The environment variables are managed by the shell. As opposed to regular shell variables, environment variables are inherited by any program you start, including another shell. New processes are assigned a copy of these variables, which they can read, modify and pass on in turn to their own child processes.
There is nothing special about variable names, except that the common ones are in upper case characters by convention. You may come up with any name you want, although there are standard variables that are important enough to be the same on every Linux system, such as PATH and HOME.
An individual variable's content is usually displayed using the echo command, as in these examples:
debby:~> echo $PATH /usr/bin:/usr/sbin:/bin:/sbin:/usr/X11R6/bin:/usr/local/bin debby:~> echo $MANPATH /usr/man:/usr/share/man/:/usr/local/man:/usr/X11R6/man |
If you want to change the content of a variable in a way that is useful to other programs, you have to export the new value from your environment into the environment that runs these programs. A common example is exporting the PATH variable. You may declare it as follows, in order to be able to play with the flight simulator software that is in /opt/FlightGear/bin:
debby:~> PATH=$PATH:/opt/FlightGear/bin |
This instructs the shell to not only search programs in the current path, $PATH, but also in the additional directory /opt/FlightGear/bin.
However, as long as the new value of the PATH variable is not known to the environment, things will still not work:
debby:~> runfgfs bash: runfgfs: command not found |
Exporting variables is done using the shell built-in command export:
debby:~> export PATH debby:~> runfgfs --flight simulator starts-- |
In Bash, we normally do this in one elegant step:
export VARIABLE=value
The same technique is used for the MANPATH variable, that tells the man command where to look for compressed man pages. If new software is added to the system in new or unusual directories, the documentation for it will probably also be in an unusual directory. If you want to read the man pages for the new software, extend the MANPATH variable:
debby:~> export MANPATH=$MANPATH:/opt/FlightGear/man debby:~> echo $MANPATH /usr/man:/usr/share/man:/usr/local/man:/usr/X11R6/man:/opt/FlightGear/man |
You can avoid retyping this command in every window you open by adding it to one of your shell setup files, see Section 7.2.2.
The following table gives an overview of the most common predefined variables:
Table 7-1. Common environment variables
Variable name | Stored information |
---|---|
DISPLAY | used by the X Window system to identify the display server |
DOMAIN | domain name |
EDITOR | stores your favorite line editor |
HISTSIZE | size of the shell history file in number of lines |
HOME | path to your home directory |
HOSTNAME | local host name |
INPUTRC | location of definition file for input devices such as keyboard |
LANG | preferred language |
LD_LIBRARY_PATH | paths to search for libraries |
LOGNAME | login name |
location of your incoming mail folder | |
MANPATH | paths to search for man pages |
OS | string describing the operating system |
OSTYPE | more information about version etc. |
PAGER | used by programs like man which need to know what to do in case output is more than one terminal window. |
PATH | search paths for commands |
PS1 | primary prompt |
PS2 | secondary prompt |
PWD | present working directory |
SHELL | current shell |
TERM | terminal type |
UID | user ID |
USER(NAME) | user name |
VISUAL | your favorite full-screen editor |
XENVIRONMENT | location of your personal settings for X behavior |
XFILESEARCHPATH | paths to search for graphical libraries |
A lot of variables are not only predefined but also preset, using configuration files. We discuss these in the next section.
When entering the ls -al command to get a long listing of all files, including the ones starting with a dot, in your home directory, you will see one or more files starting with a . and ending in rc. For the case of bash, this is .bashrc. This is the counterpart of the system-wide configuration file /etc/bashrc.
When logging into an interactive login shell, login will do the authentication, set the environment and start your shell. In the case of bash, the next step is reading the general profile from /etc, if that file exists. bash then looks for ~/.bash_profile, ~/.bash_login and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. If none exists, /etc/bashrc is applied.
When a login shell exits, bash reads and executes commands from the file ~/.bash_logout, if it exists.
This procedure is explained in detail in the login and bash man pages.
Let's look at some of these config files. First /etc/profile is read, in which important variables such as PATH, USER and HOSTNAME are set:
debby:~> cat /etc/profile # /etc/profile # System wide environment and startup programs, for login setup # Functions and aliases go in /etc/bashrc # Path manipulation if [ `id -u` = 0 ] && ! echo $PATH | /bin/grep -q "/sbin" ; then PATH=/sbin:$PATH fi if [ `id -u` = 0 ] && ! echo $PATH | /bin/grep -q "/usr/sbin" ; then PATH=/usr/sbin:$PATH fi if [ `id -u` = 0 ] && ! echo $PATH | /bin/grep -q "/usr/local/sbin" then PATH=/usr/local/sbin:$PATH fi if ! echo $PATH | /bin/grep -q "/usr/X11R6/bin" ; then PATH="$PATH:/usr/X11R6/bin" fi |
These lines check the path to set: if root opens a shell (user ID 0), it is checked that /sbin, /usr/sbin and /usr/local/sbin are in the path. If not, they are added. It is checked for everyone that /usr/X11R6/bin is in the path.
# No core files by default ulimit -S -c 0 > /dev/null 2>&1 |
All trash goes to /dev/null if the user doesn't change this setting.
USER=`id -un` LOGNAME=$USER MAIL="/var/spool/mail/$USER" HOSTNAME=`/bin/hostname` HISTSIZE=1000 |
Here general variables are assigned their proper values.
if [ -z "$INPUTRC" -a ! -f "$HOME/.inputrc" ]; then INPUTRC=/etc/inputrc fi |
If the variable INPUTRC is not set, and there is no .inputrc in the user's home directory, then the default input control file is loaded.
export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE INPUTRC |
All variables are exported, so that they are available to other programs requesting information about your environment.
for i in /etc/profile.d/*.sh ; do if [ -r $i ]; then . $i fi done unset i |
All readable shell scripts from the /etc/profile.d directory are read and executed. These do things like enabling color-ls, aliasing vi to vim, setting locales etc. The temporary variable i is unset to prevent it from disturbing shell behavior later on.
Then bash looks for a .bash_profile in the user's home directory:
debby:~> cat .bash_profile ################################################################# # # # .bash_profile file # # # # Executed from the bash shell when you log in. # # # ################################################################# source ~/.bashrc source ~/.bash_login |
This very straight forward file instructs your shell to first read ~/.bashrc and then ~/.bash_login. You will encounter the source built-in shell command regularly when working in a shell environment: it is used to apply configuration changes to the current environment.
The ~/.bash_login file defines default file protection by setting the umask value, see Section 3.4.2.2. The ~/.bashrc file is used to define a bunch of user-specific aliases and functions and personal environment variables. It first reads /etc/bashrc, which describes the default prompt (PS1) and the default umask value. After that, you can add your own settings. If no ~/.bashrc exists, /etc/bashrc is read by default:
debby:~> cat /etc/bashrc # /etc/bashrc # System wide functions and aliases # Environment stuff goes in /etc/profile # by default, we want this to get set. # Even for non-interactive, non-login shells. if [ `id -gn` = `id -un` -a `id -u` -gt 99 ]; then umask 002 else umask 022 fi # are we an interactive shell? if [ "$PS1" ]; then if [ -x /usr/bin/tput ]; then if [ "x`tput kbs`" != "x" ]; then # We can't do this with "dumb" terminal stty erase `tput kbs` elif [ -x /usr/bin/wc ]; then if [ "`tput kbs|wc -c `" -gt 0 ]; then # We can't do this with "dumb" terminal stty erase `tput kbs` fi fi fi case $TERM in xterm*) if [ -e /etc/sysconfig/bash-prompt-xterm ]; then PROMPT_COMMAND=/etc/sysconfig/bash-prompt-xterm else PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME%%.*}:\ ${PWD/$HOME/~}\007"' fi ;; *) [ -e /etc/sysconfig/bash-prompt-default ] && PROMPT_COMMAND=\ /etc/sysconfig/bash-prompt-default ;; esac [ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@\h \W]\\$ " if [ "x$SHLVL" != "x1" ]; then # We're not a login shell for i in /etc/profile.d/*.sh; do if [ -x $i ]; then . $i fi done fi fi |
Upon logout, the commands in ~/.bash_logout are executed, which can for instance clear the terminal, so that you have a clean window upon logging out of a remote session, or upon leaving the system console:
debby:~> cat .bash_logout # ~/.bash_logout clear |
Let's take a closer look at how these scripts work in the next section. Keep info bash close at hand.
The Bash prompt can do much more than displaying such simple information as your user name, the name of your machine and some indication about the present working directory. We can add other information such as the current date and time, number of connected users etc.
Before we begin, however, we will save our current prompt in another environment variable:
[jerry@nowhere jerry]$ MYPROMPT=$PS1 [jerry@nowhere jerry]$ echo $MYPROMPT [\u@\h \W]\$ [jerry@nowhere jerry]$ |
When we change the prompt now, for example by issuing the command PS1="->", we can always get our original prompt back with the command PS1=$MYPROMPT. You will, of course, also get it back when you reconnect, as long as you just fiddle with the prompt on the command line and avoid putting it in a shell configuration file.
In order to understand these prompts and the escape sequences used, we refer to the Bash Info or man pages.
export PS1="[\t \j] "
Displays time of day and number of running jobs
export PS1="[\d][\u@\h \w] : "
Displays date, user name, host name and current working directory. Note that \W displays only base names of the present working directory.
export PS1="{\!} "
Displays history number for each command.
export PS1="\[\033[1;35m\]\u@\h\[\033[0m\] "
Displays user@host in pink.
export PS1="\[\033[1;35m\]\u\[\033[0m\] \[\033[1;34m\]\w\[\033[0m\] "
Sets the user name in pink and the present working directory in blue.
export PS1="\[\033[1;44m\]$USER is in \w\[\033[0m\] "
Prompt for people who have difficulties seeing the difference between the prompt and what they type.
export PS1="\[\033[4;34m\]\u@\h \w \[\033[0m\]"
Underlined prompt.
export PS1="\[\033[7;34m\]\u@\h \w \[\033[0m\] "
White characters on a blue background.
export PS1="\[\033[3;35m\]\u@\h \w \[\033[0m\]\a"
Pink prompt in a lighter font that alerts you when your commands have finished.
export PS1=...
Variables are exported so the subsequently executed commands will also know about the environment. The prompt configuration line that you want is best put in your shell configuration file, ~/.bashrc.
If you want, prompts can execute shell scripts and behave different under different conditions. You can even have the prompt play a tune every time you issue a command, although this way it gets boring pretty soon. More information can be found in the Bash-Prompt HOWTO.
A shell script is, as we saw in the shell configuration examples, a text file containing shell commands. When such a file is used as the first non-option argument when invoking Bash, and neither the `-c' nor `-s' option is supplied, Bash reads and executes commands from the file, then exits. This mode of operation creates a non-interactive shell. When Bash runs a shell script, it sets the special parameter `0' to the name of the file, rather than the name of the shell, and the positional parameters are set to the remaining arguments, if any are given. If no additional arguments are supplied, the positional parameters are unset.
A shell script may be made executable by using the chmod command to turn on the execute bit. When Bash finds such a file while searching the PATH for a command, it spawns a sub-shell to execute it. In other words, executing
filename ARGUMENTS
is equivalent to executing
bash filename ARGUMENTS
if `filename' is an executable shell script. This sub-shell reinitializes itself, so that the effect is as if a new shell had been invoked to interpret the script, with the exception that the locations of commands remembered by the parent (see hash in the Info pages) are retained by the child.
Most versions of UNIX make this a part of the operating system's command execution mechanism. If the first line of a script begins with the two characters `#!', the remainder of the line specifies an interpreter for the program. Thus, you can specify bash, awk, perl or some other interpreter or shell and write the rest of the script file in that language.
The arguments to the interpreter consist of a single optional argument following the interpreter name on the first line of the script file, followed by the name of the script file, followed by the rest of the arguments. Bash will perform this action on operating systems that do not handle it themselves.
Bash scripts often begin with `#! /bin/bash' (assuming that Bash has been installed in `/bin'), since this ensures that Bash will be used to interpret the script, even if it is executed under another shell.
A very simple script consisting of only one command, that says hello to the user executing it:
[jerry@nowhere ~] cat hello.sh #!/bin/bash echo "Hello $USER" |
The script actually consists of only one command, echo, which uses the value of ($) the USER environment variable to print a string customized to the user issuing the command.
Another one-liner, used for displaying connected users:
#!/bin/bash who | cut -d " " -f 1 | sort -u |
Here is a script consisting of some more lines, that I use to convert wav-files to mp3-format using the lame command. The script first makes a list of all wav-files in the current directory and puts it in the variable LIST. Then it sets the new name for each file, and converts the file format. After that, it cleans up the wav-files which are replaced by their mp3 counterparts:
tille:~> cat /usr/local/bin/wav2mp3 #!/bin/bash # convert *.wav into *.mp3 LIST=$(ls *.wav) for i in $LIST; do ORIG=$i DEST=$(ls $i | cut -d "." -f 1).mp3 lame -h $ORIG $DEST echo "done converting $i, removing.." rm $i done |
The cut command is used in this example to separate the actual file name from the file name suffix. Just entering a line like mv *.wav *.mp3 won't work. An echo command was added in order to display some activity. echo's are generally useful when a script won't work: insert one after each doubted step and you will find the error in no time.
The /etc/rc.d/init.d directory contains loads of examples. Let's look at this script that controls the fictive ICanSeeYou server:
#!/bin/sh # description: ICanSeeYou allows you to see networked people # process name: ICanSeeYou # pidfile: /var/run/ICanSeeYou/ICanSeeYou.pid # config: /etc/ICanSeeYou.cfg # Source function library. . /etc/rc.d/init.d/functions # See how (with which arguments) we were called. case "$1" in start) echo -n "Starting ICanSeeYou: " daemon ICanSeeYou echo touch /var/lock/subsys/ICanSeeYou ;; stop) echo -n "Shutting down ICanSeeYou: " killproc ICanSeeYou echo rm -f /var/lock/subsys/ICanSeeYou rm -f /var/run/ICanSeeYou/ICanSeeYou.pid ;; status) status ICanSeeYou ;; restart) $0 stop $0 start ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 esac exit 0 |
First, with the . command (dot) a set of shell functions, used by almost all shell scripts in /etc/rc.d/init.d, is loaded. Then a case command is issued, which defines 4 different ways the script can execute. An example might be ICanSeeYou start. The decision of which case to apply is made by reading the (first) argument to the script, with the expression $1.
When no compliant input is given, the default case, marked with an asteriks, is applied, upon which the script gives an error message. The case list is ended with the esac statement. In the start case the server program is started as a daemon, and a process ID and lock are assigned. In the stop case, the server process is traced down and stopped, and the lock and the PID are removed. Options, such as the daemon option, and functions like killproc, are defined in the /etc/rc.d/init.d/functions file. This setup is specific to the distribution used in this example. The initscripts on your system might use other functions, defined in other files, or none at all.
Upon success, the script returns an exit code of zero to its parent.
This script is a fine example of using functions, which make the script easier to read and the work done faster. Note that they use sh instead of bash, to make them useful on a wider range of systems. On a Linux system, calling bash as sh results in the shell running in POSIX-compliant mode.
The bash man pages contain more information about combining commands, for- and while-loops and regular expressions, as well as examples. A comprehensible Bash course for system administrators and power users, with exercises, from the same author as this Introduction to Linux guide, is at http://tille.soti.org/training/bash/. Detailed description of Bash features and applications is in the reference guide Advanced Bash Scripting.