% Shell Variables -- quoting, local variables, environment variables, USER HOME SHELL PATH TERM $$ % Ian! D. Allen -- -- [www.idallen.com] % Winter 2016 - January to April 2016 - Updated 2017-04-11 12:48 EDT - [Course Home Page] - [Course Outline] - [All Weeks] - [Plain Text] Shell Variables -- `$foo` and `${foo}` ====================================== One of the things the Unix shell does to make work easier is let you define variables that can contain text (or numbers) that you can insert into your command lines and in shell scripts. Also, some variables affect how the shell operates, e.g. the `$PATH` variable tells the shell where to look for command names to execute. You assign to a variable using an equals sign `=` with no blanks around it: $ foo=hello $ foo="hello there this has blanks" $ foo='this has blanks too' $ foo=this\ also\ has\ blanks $ copy_of_foo=$foo The uses below are wrong because the shell splits the line on blanks: $ foo=hello there # WRONG bash: there: command not found # WRONG $ foo= hi there # WRONG bash: hi: command not found # WRONG $ foo =hi # WRONG bash: foo: command not found # WRONG Variable names must start with a letter or underscore. The shell will expand or substitute the value of a variable into a command line if you put a Dollar Sign `$` in front of the variable name, e.g. $ x=hello $ y=there $ echo "x" x $ echo "$x" hello $ echo "$x $y, $x" hello there, hello $ y="hello there this has blanks" $ echo "$y" hello there this has blanks If you want to expand a variable name with text immediately after it, you will need to surround the variable name in curly braces to separate it from the text: $ foo=FOO $ echo "$foo" FOO $ echo "$foobar" # no variable named "foobar" $ echo "${foo}bar" # use curly braces around the name FOObar Quoting to prevent variable expansion: hide the dollar signs ============================================================ Unless you protect the Dollar Signs (`$`) using single quotes or backslashes, the shell will find and expand variables by looking for Dollar Sign metacharacters, even inside double-quoted strings. If you don't want the shell to expand variables, you must hide the dollar signs from the shell: $ x=hello $ echo "the variable x contains $x" the variable x contains hello $ echo "protect the double-quoted Dollar Sign using backslash $x" protect the double-quoted Dollar Sign using backslash $x $ echo 'protect the Dollar Sign using single quotes $x' protect the Dollar Sign using single quotes $x Single quotes and backslashes will protect Dollar Signs (`'$'` and `$`) from expansion by the shell. Single quotes are "stronger" than double quotes. Nothing is special inside single quotes. Double-Quote all uses of variables ================================== When the shell finds and expands a variable (looking for the leading Dollar Sign), the expanded text may itself contain more shell meta-characters that the shell will act on, e.g. blanks or GLOB characters. This is almost *NEVER* what you want to have happen! You can prevent the shell from re-processing the text substituted by variables, by always expanding the variables inside double quotes, where the quotes hide the meaning of any other possible metacharacters that might be hidden inside the variable contents: $ x='*' # x contains an asterisk (possible GLOB character) $ echo "$x" # remember to double-quote the variable * # GLOB character does not expand (good!) Inside the double quotes, the shell expands the `$x` variable to be the string containing an asterisk `*`, but the double quotes prevent the asterisk character from being a GLOB pattern that matches file names. The original command line: $ echo "$x" becomes this after the shell expands the `$x` variable: $ echo "*" which outputs just `*`, not all the files in the current directory. The double quotes stop GLOB character expansion. Leaving the quotes off causes GLOB expansion: $ echo $x # unquoted variable is not safe! file1 file2 # GLOB character expands (BAD BAD BAD!) This is because the original command line (without quotes): $ echo $x becomes this after the shell expands the `$x` variable: $ echo * which is then GLOB expanded by the shell to become something like: $ echo file1 file3 and so you see file name on your screen instead of the asterisk `*`. Because there are no double quotes to hide the GLOB characters, the shell first expands the `$x` to be the string `*` and then expands the GLOB asterisk character `*` to match all the files in the current directory. This is never what you want! Always use double quotes around variables! More examples of variables containing GLOB characters: $ x='* hi *' # single quoted string protects all characters $ echo "$x" # use double quotes around the variable * hi * # the quoted string appears correctly $ echo $x # note the lack of double quotes (BAD! BAD! BAD!) file1 file2 hi file1 file2 $ msg='* Warning *' # set a prefix for a warning message $ echo "$msg test" # double-quoted variable is safe to use * Warning * test # GLOB characters do not expand (good!) $ echo $msg test # unquoted variable is not safe! file1 file2 warning file1 file2 test # GLOB characters expand (BAD!) You must remember to put double quotes around any shell variables that might contain shell metacharacters, otherwise the metacharacters will be expanded by the shell and the result may not be what you want. Without double quotes, the shell also splits variable expansions on blanks: $ foo='My Documents' # a name with a space in it $ mkdir "$foo" # create a name with a space in it $ rmdir $foo # WRONG - missing double quotes rmdir: failed to remove 'My': Not a directory rmdir: failed to remove 'Documents': No such file or directory $ rmdir "$foo" # RIGHT - always use double quotes Always double-quote your variables, to prevent the shell from splitting the interpolated text on blanks and from expanding any GLOB characters in the interpolated text. Always put double-quotes around variables when you expand them. Double-quote all uses of variables, to stop GLOB expansion! Unrecognized variables are empty ================================ By default, undefined variables are not an error and simply expand quietly to be nothing: $ echo "this is $nosuchvariable expanding" this is expanding I recommend that you set the Bourne shell `nounset` shell option that will cause the shell to signal an error for undefined variables: $ echo "this is $nosuchvariable expanding" this is expanding $ set -o nounset $ echo "this is $nosuchvariable expanding" bash: nosuchvariable: unbound variable With `nounset` set, typing mistakes in variable names will cause errors instead of silently doing the wrong thing. Consider this mistake: # DIR=/home/me # my home directory # rm -rf "$DIRR/bin" # typing error causes "/bin" to be removed With the shell`nounset` option set, the above typing mistake would result in an error message about the undefined `$DIRR` variable, instead of letting the unknown variable expand to be nothing and thus deleting the whole system `/bin` directory: # set -o nounset # DIR=/home/me # my home directory # rm -rf "$DIRR/bin" # typing error is caught this time bash: DIRR: unbound variable Consider this worse typing mistake: # INSTALL=opt/installdir # where to install the software # rm -rf "/$INSTAL" # small typing error wipes out everything Put `set -o nounset` in your `.bashrc` for all your shells! Appending to a variable ======================= If you wish to keep the content of a variable and add text to the end, you can use the variable as part of the assignment statement: $ echo "$PATH" /bin:/usr/bin $ PATH=$PATH:/sbin # appends the text :/sbin $ echo "$PATH" /bin:/usr/bin:/sbin $ PATH=$PATH:/usr/sbin # appends the text :/usr/sbin $ echo "$PATH" /bin:/usr/bin:/sbin:/usr/sbin You can prefix text to any variable using the same method: $ echo "$PATH" /sbin:/usr/sbin $ PATH=/usr/bin:$PATH # prepends the text /usr/bin: $ echo "$PATH" /usr/bin:/sbin:/usr/sbin $ PATH=/bin:$PATH # prepends the text /bin: $ echo "$PATH" /bin:/usr/bin:/sbin:/usr/sbin Each of the above examples uses the current value of the variable as part of the new value. You don't have to double-quote variable expansions that are used in assignment statements, since word-splitting and GLOB are turned off for variables expanded in assignment statements, but it doesn't hurt to do so: $ PATH="$PATH:/home/idallen/bin" # append my own bin directory $ PATH=$PATH:/home/idallen/bin # same -- no quotes needed here You *must* double-quote all other variable expansions that are not part of shell variable assignment statements! Local variables and Environment (global) variables ================================================== Variable definitions can be local to the current shell process (the default), or they can be exported (using the `export` built-in command) to the "environment" of child processes of the shell: $ x=foo # define a local variable named x containing foo $ /bin/bash # start a child process (another sub-shell) bash$ echo "see x $x" see x # variable x was not exported to child environment bash$ exit # exit subshell and return to the previous shell $ export x # export the variable we set earlier $ /bin/bash # start a child process (another sub-shell) bash$ echo "see x $x" see x foo # variable x was exported; value is inherited Exported variables are also called "environment" variables; since, they are part of the starting environment of a new child process. Local variables are not passed to child processes. By convention, Environment Variables (exported variables) have names that are all UPPER-CASE, e.g. `PATH, HOME, USER, LOGNAME, TERM` As with anything in a child process (also including such things as `umask` and current directory), setting a variable (local or environment) in a child process does not affect any parent processes. Parent processes cannot reverse-inherit shell variable values from child processes. Once the child has a copy of the variable, further changes to the variable in the child don't affect the parent shell (but will affect subsequent children of the child shell). Once a child process has taken a copy into its environment of all exported variables, the parent process has no more effect. Changing a variable in a parent process will not change the value in an already-running child process. New child processes only get a copy of the exported variables at the time the new process is started. Pre-set Environment Variables ============================= When you log in to a Unix system, your login shell has many variables already set for you. Some of these variables are local to your login shell; many are already exported to any child processes you start from the shell. Typing `set` with no arguments lists all the variables (both local and exported) and their current values. Typing `printenv` or `env` with no arguments lists only environment (exported) variables. $ set | wc 85 108 1697 $ printenv | wc 40 45 876 Some important Environment Variables ==================================== - `PATH` -- shell command search path (locations of commands) - `USER` -- the login userid of the current user (some systems use `LOGNAME`) - `HOME` -- the home directory of the current user (`cd` takes you here) - `TERM` -- the terminal type - `SHELL` -- your login shell (not necessarily your current shell) Examples: $ cd ; pwd /home/idallen $ HOME=/bin ; cd ; pwd /bin $ HOME=/usr/bin ; cd ; pwd /usr/bin $ HOME=/home/$USER ; cd ; pwd /home/idallen Variables you should know ========================= You should know the meaning of the following shell variables: $USER and/or $LOGNAME # environment variable $HOME # environment variable $SHELL # environment variable $PATH # environment variable $TERM # environment variable $$ # local to each shell `$USER` or `$LOGNAME` : Set during login to be your account name as given in the Unix password file, e.g. `idallen`, `abcd0001`, `root`, etc. Some systems set `$LOGNAME` instead of or in addition to `$USER`. `$HOME` : Set during login to be the absolute path to your HOME directory as set in the Unix password file. This path is where the command `cd` (with no arguments) takes you. The shell metacharacter tilde `~` is a synonym for `$HOME` if used at the start of a pathname, before a slash, e.g. `~/foo` is the same as `$HOME/foo`, but double-quoting the tilde turns off its meaning (and double-quoting a variable does not). `$SHELL` : Set during login to be the pathname of the Unix Shell you are assigned in the Unix password file. This may or may not be the shell you are currently running, since you are free to start other shells once you have logged in. `$PATH` : Set during login to be a colon-separated list of directories in which the shell will look when it tries to find an executable file that matches a command name. The initial list of directories is set by the systems administrator but you can change it at any time. `$TERM` : Set during login to contain the Unix name of the type of your terminal. Programs, such as VI/VIM, use this information to correctly place characters on your terminal screen. Common values are `vt100`, `xterm`, `ansi`, and `linux`. `$$` : The process ID of the current shell. Since every running process has a unique ID number, `$$` is often used when creating unique temporary file names in the `/tmp` directory, e.g. `tmp="/tmp/tempfile$$"`\ `date >"$tmp" ; wc "$tmp" ; sort "$tmp"` -- | Ian! D. Allen, BA, MMath - idallen@idallen.ca - Ottawa, Ontario, Canada | Home Page: http://idallen.com/ Contact Improv: http://contactimprov.ca/ | College professor (Free/Libre GNU+Linux) at: http://teaching.idallen.com/ | Defend digital freedom: http://eff.org/ and have fun: http://fools.ca/ [Plain Text] - plain text version of this page in [Pandoc Markdown] format [www.idallen.com]: http://www.idallen.com/ [Course Home Page]: .. [Course Outline]: course_outline.pdf [All Weeks]: indexcgi.cgi [Plain Text]: 320_shell_variables.txt [Pandoc Markdown]: http://johnmacfarlane.net/pandoc/