7.2. Your text environment

7.2.1. Environment variables

7.2.1.1. General

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.

7.2.1.2. Exporting variables

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.

7.2.1.3. Reserved variables

The following table gives an overview of the most common predefined variables:

Table 7-1. Common environment variables

Variable nameStored information
DISPLAYused by the X Window system to identify the display server
DOMAINdomain name
EDITORstores your favorite line editor
HISTSIZEsize of the shell history file in number of lines
HOMEpath to your home directory
HOSTNAMElocal host name
INPUTRClocation of definition file for input devices such as keyboard
LANGpreferred language
LD_LIBRARY_PATHpaths to search for libraries
LOGNAMElogin name
MAILlocation of your incoming mail folder
MANPATHpaths to search for man pages
OSstring describing the operating system
OSTYPEmore information about version etc.
PAGERused by programs like man which need to know what to do in case output is more than one terminal window.
PATHsearch paths for commands
PS1primary prompt
PS2secondary prompt
PWDpresent working directory
SHELLcurrent shell
TERMterminal type
UIDuser ID
USER(NAME)user name
VISUALyour favorite full-screen editor
XENVIRONMENTlocation of your personal settings for X behavior
XFILESEARCHPATHpaths 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.

7.2.2. Shell setup files

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.

7.2.3. A typical set of setup files

7.2.3.1. /etc/profile example

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.

7.2.3.2. The profile.d directory


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.

7.2.3.3. .bash_profile example

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.

7.2.3.4. .bash_login example

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.

7.2.3.5. /etc/bashrc example

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

7.2.3.6. .bash_logout example

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.

7.2.4. The Bash prompt

7.2.4.1. Introduction

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.

7.2.4.2. Some examples

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.

7.2.5. Shell scripts

7.2.5.1. What are scripts?

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.

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.

7.2.5.2. Some simple examples

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:~> cat bin/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.