Guide to Unix/Explanations/Environment
Environment Variables
editEvery program inherits "environment variables" from the program that started it. This means that each program has the same environment variables as the program that started it. The environment of a program can change, in which case other programs later started by that program also have a changed environment.
The environment variable called SHELL contains the name of the default shell. (You might be running some other shell, but the default shell is always in "SHELL".) The "SHELL" environment variable is set during login and inherited by the shell. When we started the "printenv" program, it inherited the entire environment, including "SHELL", and showed "SHELL" to us.
Using printenv with no arguments gives the entire environment:
$ printenv ... SHELL=/bin/bash ...
See Environment Variables to learn about specific variables.
Process limits
editEach process has limits on the following.
Limits are inherited from the parent process. The normal way to set limits is to change the limits of some shell, and then start the program from that shell. Each shell provides a different way to set limits: Bourne shell uses the ulimit command while C shell uses limit.
The following is the list of available limits, given with their C shell name ("Bourne shell option"). We need the name ("option") to show or change the limit in the C ("Bourne") shell.
* cputime ("-t SECONDS") * filesize ("-f BLOCKS") * datasize ("-n KILOBYTES") * stacksize ("-s KILOBYTES") * coredumpsize ("-c BLOCKS") can also accept "unlimited" instead of BLOCKS * memoryuse ("-m KILOBYTES") * memorylocked ("-l KILOBYTES") * maxproc ("-p COUNT") * openfiles ("-n COUNT")
Scenario: A program crashes with a message about "resource temporarily unavailable" or "too many open files".
Raise the limits in the shell from which you start the program. For example, to allow 512 open files per process:
Bourne shell $ ulimit -Sn 512
C shell $ limit openfiles 512
Shell Parameters
editThe shell's environment can be accessed through its parameters. There are three types of parameter:
- named parameters
- positional parameters
- special parameters
Named parameters are known as environment variables. Some of these variables are initialised when the user logs in and which the shell uses to determines its behavior. The shell also maintains certain variables throughout the login session, some of which are defined by the shell and are given special meaning, while others can be user defined.
Positional parameters enable reference to arguments supplied to shell scripts. The shell communicates process information through special parameters. Special parameters can be accessed by the user, but unlike named and positional parameters, they can only be set by the shell itself.
Environment variables are named parameters. Variables are assigned using the command line statement in the form name=value. Variable names are alphanumeric strings, they can contain numeric characters or underscores but must begin with an alphabetic character. Four examples are presented below:
$ n=2 $ n2=4 $ N=3 $ MYNAME=alan $ greeting="hello world"
Note that Unix is case sensitive, so the variable "name" is not the same as "Name". A variable can be expanded (evaluated) by preceding the variable name with a $, for example:
$ echo $greeting hello world
Variables can be concatenated:
$ day=21 month=01 year=2006 $ echo $day$month$year 21012006
However, there can be problem with concatenating literals with variables, using variables for the day and month but hard-coding the year gives
$ echo $day$month2006 05
The problem is that the shell cannot distinguish between the variable name month and the literal value 2006. It therefore tries to expand a variable with the variable name month2006, which is not set. This can be rectified by surrounding the variables names with braces {}, for example:
$ echo ${day}${month}2006 05112006
Although the braces are not always necessary it is good practice to use them when evaluating variables.
Quoting
editConsider the following example, where the variable expansion phrase is enclosed in double quotes:
$ echo "echo $greeting", displays $greeting echo hello world, displays hello world
Variables enclosed in double quotes are still expanded by the shell. To prevent expansion, variables should be enclosed in single quotes. To display the string "echo $greeting" verbatim, use the statement:
$ echo ’echo $greeting’, displays $greeting echo $greeting, displays hello world
More Parameter Expansion
editThe shell supports quite sophisticated parameter expansion. The construct NAME-literal expands to the value of the parameter if it is set, otherwise it expands to literal value. For example:
$ echo ${OPSYS-unix} unix
The variable OPSYS has not been assigned a value so the result of the command is to echo the string "unix" to the screen. If the variable is assigned a value:
$ OPSYS=Linux $ echo ${OPSYS-unix} Linux
If the variable is set to NULL, a blank link is displayed rather than the literal value:
$ OPSYS="" $ echo ${OPSYS-unix} (blank line)
However, if we use name:-literal, the variable evaluates to the literal rather than its NULL value:
$ echo ${OPSYS:-unix} unix
Using the construct name=literal the parameter is assigned the value of the literal if it has not been set. Suppose that the variable DAY has not been previously set:
$ echo $DAY (blank line)
then the command line below will assign the string "tuesday" to the variable DAY and echo it to screen:
$ echo ${DAY=tuesday} tuesday
The variable DAY has now been set, so if the command line is repeated with a different literal value its value remains unchanged
$ echo ${DAY=wednesday} tuesday
If DAY is assigned a NULL value, the following parameter expansion results in a NULL value being returned:
$ DAY="" $ echo ${DAY=tuesday} (blank line)
An associated expansion construct is name-value, which only returns the literal if the parameter is unassigned, it returns the assigned value even if the value is NULL.
Using the expansion construct name:=literal the parameter is assigned the value of the literal if it has not been set. For example:
$ echo $DISTRO # DISTRO not assigned echo ${DISTRO:=ubuntu} ubuntu
Also, the literal is assigned if the parameter is NULL, the expansion is therefore:
$ DISTRO="" $ echo ${DISTO:=ubuntu} ubuntu
If the parameter is set to a non-NULL value then the literal is not assigned:
$ echo ${DISTRO:=debian} ubuntu
The expansion construct name=value is similar to name:=value, however the literal is only assigned if the parameter was formerly unassigned. The literal is not assigned if the parameter value is NULL. Using the name:?messagestring construct, an error message can be displayed if parameter is unset or NULL:
$ echo ${DISTVER:?distribution version unknown} -bash: DISTVER: distribution version unknown $ DISTVER="" $ echo ${DISTVER:?distribution version unknown} -bash: DISTVER: distribution version unknown
The error message is not displayed if the parameter is assigned a non-NULL value:
$ DISTVER="hoary hedgehog" $ echo ${DISTVER:?distribution version not set} hoary hedgehog
Likewise, there is an associated construct name?messagestring which returns the message string only if the parameter is unset. Parameters can be expanded to substrings of their value. The expansion constructs name%pattern and name%%pattern expand to the largest and smallest substrings to the left of the pattern respectively. For example, set the variable TIME:
$ TIME=10:29:39
The command line below displays only the hour value:
$ echo the hours is ${TIME%%:*} the hours is 10
whereas the following the command line display the hours and minutes
$ echo the hour and minutes are ${TIME%:*} the hour and minutes are 10:29
The constructs name#pattern and name##pattern expand to the largest and smallest substring to the right of the pattern respectively. So the command line below expands to the seconds:
$ echo the seconds are ${TIME##*:} the seconds are 39
The minutes and seconds can be extracted with:
$ echo the minutes and seconds are ${TIME#*:} the minutes and seconds are 29:39
The length of a string can be evaluated with the #name construct. For example:
$ echo ${#TIME} 8
Arrays
editVariables can be one-dimensional arrays and are referenced in the form name[index]. Arrays can be assigned using the name=(value1 value2 ... ) construct, so for example:
DATE=($(date))
$(command) expands to the result of command on the command line. So DATE is assigned output of the command date, where each space separated string is assigned to an element in the array. So if we only want the year, then we expand the sixth element of the array:
$ echo ${DATE[5]} 2006
We can change, say the time information with the assignment:
DATE[3]=19:16:00 $ echo ${DATE[*]} Wed May 17 19:16:00 NZST 2006
If we need to count the number or assigned array elements, then we use the expression:
$ echo ${#DATE[*]} 6
Positional Parameters
editPositional parameters provide a means of accessing arguments to shell scripts. They are referenced by the numbers 1,2,3,... etc., where the numbers reflect the order in which the arguments appeared on the command line (from left to right). They cannot be assigned in the same way as named parameters, that is you cannot do this: 1=hello. Positional parameters are assigned when a shell script is invoked with command line arguments. They can, however, be assigned with the set command:
$ set $(uname -rmv)
This causes the space separated strings from the output of uname -rmv to be assigned to the positional parameters. The statement below shows the values of all positional parameters that were assigned:
$ echo ${*} 2.6.14.4 #1 SMP Thu Dec 22 01:04:35 NZDT 2005 x86_64
And this statement gives the number of assigned positional parameters (equivalent to argc in C and C++):
$ echo $# 10
If we specifically want the kernel version then we expand positional parameter 1:
$ echo $1 2.6.14.4
Double digit positional parameters need to be enclosed in braces. So if we want the architecture of the machine, we cannot do this:
$ echo $10 2.6.14.40
The expression above is expanded to positional parameter 1 followed by a literal 0. The correct way is:
$ echo ${10} x86_64
Finally, positional parameter 0 is set to the name of the current process (equivalent to argv[0], which in this case should be the name of the shell command itself:
$ echo $0 -bash
Special Parameters
editSpecial parameters can be accessed by the user but can only be assigned (directly) by the shell - typically in response to some event. We have already encountered the parameters * and # which are set when a command is invoked with arguments (or the set command is used).
The ? parameter expands to the return code of the process that last exited. If we enclose a command line statement in brackets it is executed in a subshell. The command line below forks a new shell and immediately issues exit. The argument following exit is the value of the subshell’s return code (255 in this case):
$ (exit 255)
The return code of the child process can be accessed by the parent by the expansion of ?. If a process terminated normally then the return code is likely to be zero. If however, the process terminated due to some error condition, it is useful if process sets a non-zero return code so that the parent can determine the reason for the termination:
$ echo $? 255
The process ID of the shell is stored in $. The ps command shows that the process ID of my shell is 10005, which is what is given by the expansion of $:
$ ps | grep bash 10005 pts/77 00:00:00 bash $ echo $$ 10005
The ! parameter evaluates to the process ID of the last background process. To demonstrate, the statement below invokes the sleep command (which sleeps for 60 seconds) in the background:
$ sleep 60 & [1] 24835
The process ID of the background process is displayed on the screen (in this case 24835). Expansion of the ! parameter confirms this:
$ echo $! 24835
Exporting Parameters
editProcesses do not inherit environment variables from their parent unless the variables are exported. The command line below displays the process ID of the current process followed by the value of greeting (which was assigned earlier):
$ echo ${greeting?parameter not set} hello world
If we start a new shell and issue the command again we see that greeting is not set in the child process:
$ bash $ echo ${greeting?parameter not set} bash: greeting: parameter not set
Terminate the current shell process and return to the parent, then export greeting:
$ exit $ export greeting
You can confirm that a variable has the export attribute set by typing:
$ export | grep greeting declare -x greeting="hello world"
Now start a new shell again and evaluate greeting:
$ bash $ echo ${greeting?parameter not set} hello world
The new shell has inherited the variable greeting and can evaluate it. A variable retains its export attribute (until the shell is terminated) even if it is reassigned, but it can be removed using the typeset command:
$ typeset +x greeting $ export | grep greeting # yields no result
In Bash (and Kornshell, but not Bourne shell), a variable can be assigned and exported in one command line statement:
$ export greeting="hi there" $ export | grep greeting declare -x greeting="hi there"