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/bindebby:~>
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 PATHdebby:~>
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/mandebby:~>
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 2.2, “Shell setup files”.
The following table gives an overview of the most common predefined variables:
Table 7.1. Common environment variables
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 4.2.2, “Logging on to another group”. 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.
Your /etc/bashrc
file might look like this:
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
These lines set the umask value. Then, depending on the type of shell, the prompt is set:
# 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 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 (everything following the name of the script) 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.
#! /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 make backup copies of all files in a directory.
The script first makes a list of all the files in the current directory and puts it in the variable LIST
. Then it sets the name of the copy for each file, and then it copies the file. For each file, a message is printed:
tille:~>
catbin/makebackupfiles.sh
#!/bin/bash # make copies of all files in a directory LIST=`ls` for i in $LIST; do ORIG=$i DEST=$i.old cp $ORIG $DEST echo "copied $i" done
Just entering a line like mv *
*.old
won't work, as you will notice when trying this on a set of test files. 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 asterisk, 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.garrels.be/training/bash/. Detailed description of Bash features and applications is in the reference guide Advanced Bash Scripting.