shell

Bourne Shell Tutorial Sh - the Bourne Shell Last update Thu Nov 22 09:12:56 EST 2007 Thanks to suggestions/correectons from: Ryan Penn Helmut Neujahr Table of Contents Bourne Shell, and filename expansion Shell Concepts Verifying which shell you are running Shell basics Meta-characters and Filename expansion Finding the executable Quoting with the Bourne Shell Nested quotations Strong versus weak quoting Quoting over several lines Mixing quotation marks Quotes within quotes - take two Placing variables within strings Variables A subtle point The set command Environment Variables Special Environment Variables PATH - Sets searchpath HOME - Your home directory CDPATH - cd searchpath IFS - Internal Field Seperator PS1 - Normal Prompt PS2 - Secondary Prompt MAIL - Incoming mail MAILCHECK - How often to check for mail SHACCT - Accounting file MAILPATH - searchpath for mail folders Bourne Shell Variables - Alternate Formats Using quoting and shell variables Using curly braces with variables ${variable?value} - Complain if undefined ${variable-default} - Use default if undefined ${variable+value} - Change if defined ${variable=value} - Redefine if undefined Undefining Variables ${x:-y}, ${x:=y}, ${x:?y}, ${x:+y} forms Order of evaluation Special Variables in the Bourne Shell Positional Parameters $1, $2, ..., $9 $0 - Scriptname $* - All positional parameters $@ - All positional parameters with spaces $# - Number of parameters $$ - Current process ID $! - ID of Background job $? - error status $- Set variables Options and debugging Special options X - Bourne Shell echo flag V - Bourne Shell verbose flag Combining flags U - unset variables N - Bourne Shell non-execute flag E - Bourne Shell exit flag T - Bourne Shell test one command flag A - Bourne Shell mark for export flag K - Bourne Shell keyword flag H - Bourne Shell hash functions flag The $- variable - - Bourne Shell hyphen option Other options C - Bourne Shell command option S - Bourne Shell shell-session option I - Bourne Shell shell-interactive option R - Bourne Shell restricted shell option P - Bourne Shell privileged shell option unset Bourne Shell: Status, Pipes and branches Unnecessary process execution $@ versus ${1+$@} Status and Wasted Processes Simple Flow Control Changing Precedence Putting it all together Bourne Shell Flow Control Commands: If, While and Until Commands that must be first on the line While - loop while true Until - loop until true Bourne Shell Flow Control Commands For - Repeating while changing a variable Case - Checking multiple cases Break and continue Expr - Bourne Shell Expression Evaluator Arithmetic Operators Relational Operators Boolean Operators The string operator Precedence of the Operators Berkeley Extensions Bourne Shell -- Functions and argument checking Passing values by name Exiting from a function Checking the number of arguments UNIX conventions for command line arguments Checking for optional arguments Job Control Copyright 2001, 2005 Bruce Barnett and General Electric Company All rights reserved You are allowed to print copies of this tutorial for your personal use, and link to this page, but you are not allowed to make electronic copies, or redistribute this tutorial in any form without permission. How to build your own complex commands from the simple commands in the UNIX toolbox. This tutorial discusses of Bourne shell programming, describing features of the original Bourne Shell. The newer POSIX shells have more features. I first wrote this tutorial before the POSIX shells were standardized. So the information describe here should work in POSIX shells as it is a subset of the POSIX specifications. You're not getting the most out of UNIX if you can't write shell programs! Bourne Shell, and filename expansion This sections covers the Bourne shell. The manual pages are only 10 pages long, so it shouldn't be difficult to learn, right? Well, apparently I'm wrong, because most of the people I know learned one shell to customize their environment, and stayed with the C shell ever since. I understand the situation. It's hard enough to learn one shell language, and after struggling with one shell for a while, they are hesitant to learn another shell. After a few scripts, the new user decides the C shell is "good enough for now" and it ends right there. They never take the next step, and learn the Bourne shell. Well, perhaps this chapter will help. The Bourne shell is considered the primary shell in scripts. All UNIX systems have it, first of all. Second, the shell is small and fast. It doesn't have the interactive features of the C shell, but so what? Interactive features aren't much use in scripts. There are also some features the Bourne shell has that the C shell doesn't have. All in all, many consider the Bourne shell to be the best shell for writing portable UNIX scripts. Shell ConceptsWhat is a shell, anyway? It's simple, really. The UNIX operating system is a complex collection of files and programs. UNIX does not require any single method or interface. Many different techniques can be used. The oldest interface, which sits between the user and the software, is the shell. Twenty five years ago many users didn't even have a video terminal. Some only had a noisy, large, slow hard-copy terminal. The shell was the interface to the operating system. Shell, layer, interface, these words all describe the same concept. By convention, a shell is a user program that is ASCII based, that allows the user to specify operations in a certain sequence. There are four important concepts in a UNIX shell: ·The user interacts with the system using a shell. ·A sequence of operations can be scripted, or automatic, by placing the operations in a script file. ·A shell is a full featured programming language, with variables, conditional statements, and the ability to execute other programs. It can be, and is, used to prototype new programs. ·A shell allows you to easily create a new program that is not a "second-class citizen," but instead is a program with all of the privileges of any other UNIX program. The last two points are important. DOS does not have a shell that has as many features as the Bourne shell. Also, it is impossible to write a DOS script that emulates or replaces existing commands. Let me give an example. Certain DOS programs understand the meta-character "*." That is, the "RENAME" command can be told RENAME *.OLD *.NEW Files "A.OLD" and "B.OLD" will be renamed "A.NEW" and "B.NEW." The DOS commands "COPY" and "MOVE" also understand the character "*." The "TYPE" and "MORE" commands, however, do not. If you wanted to create a new command, it too, would not understand that an asterisk is used to match filenames. You see, each DOS program is given the burden of understanding filename expansion. Consequently, many programs do not. UNIX is a different story. The shell is given the burden of expanding filenames. The program that sees the filenames is unaware of the original pattern. This means all programs act consistently, as filename expansion can be used with any command. It also means a shell script can easily replace a C program, as far as the user is concerned. If you don't like the name of a UNIX utility, it is easy to create a new utility to replace the current program. If you wanted to create a program called "DIR" you could simply create a file containing #!/bin/sh ls $* The shell does the hard part. The other difference between the DOS batch file and the UNIX shell is the richness of the shell language. It is possible to do software development using the shell as the top level of the program. Not only is it possible, but it is encouraged. The UNIX philosophy of program development is to start with a shell script. Get the functionality you want. If the end results has all of the functionality, and is fast enough, then you are done. If it isn't fast enough, consider replacing part (or all) of the script with a program written in a different language (e.g. C, Perl). Just because a UNIX program is a shell script does not mean it isn't a "real" program. Verifying which shell you are runningBecause there are many shells available, it is important to learn to distinguish between the different shells. Typing commands for one shell when you are using another is bound to cause confusion. I know from personal experience. This was aggravated by the fact that many books I used to learn UNIX never mentioned that other shells existed. Therefore, the first step is to make sure you are using the proper shell. You can execute the following command to determine your default shell (The command you type is in boldface): % echo $SHELL /bin/csh While this identifies your default shell, it does not accurately identify the shell you are currently using. I will give you a better way to find out later. Because this column discusses the Bourne shell, any commands discussed here will only work right if you are using the Bourne shell. You have two choices: place these commands in a Bourne shell script. That is, create a file, make the first line read #!/bin/sh Make the second line, and those following, contain the commands you want to test. Then make it executable by typing chmod +x filename Once you do this, you can test the script by typing ./filename The second method is to create a new window (if you desire). Then type sh You may see a change in the characters the shell gives you as a prompt, like the example below: % /bin/sh $ The Bourne shell will execute each line you type, until an end of file is found. To put it another way, when you type Control-D, the Bourne shell will terminate, and return you to the shell you were previously using. This is the same action the shell takes when a script file is executed, and the end of the script file is reached. Shell basicsThe basic actions of the shell are simple. It reads a line. This is either from a file, a script, or from a user. First, meta-characters are "handled." Second, the name of the executable is found. Third, the arguments are passed to the program. Fourth, the file redirection is setup. Lastly, the program is executed. Meta-characters and Filename expansionAs the shell reads each line, it "handles" any special characters. This includes variable evaluation (variables start with a "$)," and filename expansion. Expansion of filenames occurs when the characters "*," "?," or "[" occur in a word. A question mark matches a single character. An asterisk matches any number of characters, including none. Square brackets are used to specify a range or particular combination of characters. Inside square brackets, a hyphen is used to specify a range or characters. Also, if the first character inside the square brackets is an exclamation point, the complement of the range is used. Let me give some examples: +-------------------------------------------------------------------------------+ | Table 1 | | Examples of filename expansion | +-------------------------------------------------------------------------------+ |Pattern Matches | |* Every file in the current directory | |? Files consisting of one character | |?? Files consisting of two characters | |??* Files consisting of two or more characters | |[abcdefg] Files consisting of a single letter from a to g. | |[gfedcba] Same as above | |[a-g] Same as above | |[a-cd-g] Same as above | |[a-zA-Z0-9] Files that consist of a single letter or number | |[!a-zA-Z0-9] Files that consist of a single character not a letter or number | |[a-zA-Z]* Files that start with a letter | |?[a-zA-Z]* Files whose second character matches a letter. | |*[0-9] Files that end with a number | |?[0-9] Two character filename that end with a number | |*.[0-9] Files that end with a dot and a number | +-------------------------------------------------------------------------------+ As you can see, the dot is not a special character. Filenames may or may not have a dot. UNIX Programers use the dot to standardize on the type of source code of each file, but that is just a convention. There is another convention, which concerns the shell: Files whose name starts with a dot are not normally listed. Again, it is a convention, but ls, find and the various shells follow this convention. This allows some files to be "secret," or perhaps invisible, by default. You must explicitly ask for these files, by including the dot as part of the filename. The pattern ".*" matches all hidden files. Remember that two hidden files are always in every directory, ".," which indicate the present directory, and "..," which indicates the directory above the current directory. If you want to match all hidden files except these two directories, there is no easy way to specify a pattern that will always match all files except the two directories. I use .?? or .[a-zA-Z]* As I said, this does not match all combinations, but works most of the time. Hackers (or crackers, if you prefer) break into computers and often use strange filenames, like ". " or ".. " to hide their traces. You may not have noticed, but there was a space in these filenames. Refering to files with spaces in the names require quoting, which I will cover later. Personally, all of my hidden files are matched by ".[a-z]*" and all of my hidden directories are matched by ".[A-Z]*." This works because I made up my own convention, and always follow it. The slash is also special, as it is used to indicate a directory path. Filename expansion does not expand to match a slash, because a slash can never be part of the filename. Also, the same rules for filename expansion of hidden files applies if the pattern follows a slash. If you want to match hidden files in a subdirectory, you must specify the explicit pattern. Table 2 lists some examples. +--------------------------------------------------------------+ | Table 2 | | Filename Expansion with directories | |Pattern Matches | +--------------------------------------------------------------+ |* All non-invisible files | |abc/* All non-invisible files in directory abc | |abc/.* All invisible files in directory abc | |*/* All non-invisible files in all subdirectories below | |*/.* All invisible files in all subdirectories below | +--------------------------------------------------------------+ Filename expansions are based on the current directory, unless the filename starts with a slash. The Bourne shell differs from the C shell if the meta-characters do not match any file. If this happens, the pattern is passed to the program unchanged. (The C shell will either do this, or generate an error, depending on a variable). If you are not sure how something will expand, use the echo command to check. It generates output more compact than ls, and it will not list contents of directories like ls will. You will also notice the output is sorted alphabetically. The shell not only expans filenames, but sorts them, for all applications. Finding the executableOnce the shell expands the command line, it breaks up the line into words, and takes the first word as the command to be executed. (The special Bourne variable "IFS" contains the characters used to "break up" the line. Normally, this variable contains a space and a tab.) Afterwards, the first word is used as the name of the program. If the command is specified without an explicit directory path, the shell then searches the different directories specified by the "PATH" variable, until it finds the program specified. If you have been following the points I made, it should not surprise you that a valid UNIX command might be * The contents of the directory determines the results, but if you created a file called "0," which contains #!/bin/sh echo Hey! You forgot to specify the command! Click here to get file: 0 and if it is the first file (alphabetically) in your directory, then executing "*" would give you an error message. (Provided the current directory was in your search path). So you see, filename expansion can be anywhere on a command line. You can execute programs with long names without typing the entire name. However, filename expansion only works if the file is in the directory you specify. If you wanted to execute the program "my_very_own_program" without typing the complete filename, you could type my_* arguments as long as "my_*" expanded to a unique program name. If this was in another directory, then you you have to specify the directory path: /usr/local/bin/my_* arguments Understanding the relationship between the shell and the programs allows you to add features. Some people create a file called "-i" in a directory. If someone then types rm * while in this directory, the first argument will probably be "-i." This filename is passed to the rm program, which assumes the hyphen indicates an argument. Therefore rm runs with the interactive option, protecting programs from accidental deletion. One last point. Many DOS users complain that they can't execute RENAME *.OLD *.NEW I admit that this is a little awkward to do in UNIX. I'd like to say two things in defense. First, the above usage of filename expansion is an "unnatural act," as far as the UNIX philosophy is concerned. There are many advantages to the shell handling the filename expansion, and perhaps one disadvantage in one case. I believe the advantages of the UNIX philosophy far outweight the disadvanges. Second, I don't believe it is a disadvantage anyway. Renaming files like that is wrong. The only reason to do so is because DOS does it that way, and you have to to this because you are limited to 11 characters. If you must rename them, append a string to the end instead of changing the original filename. This is UNIX. You can have filenames 256 characters long, so this approach isn't a problem. So if you must rename them, use for i in *.OLD do mv $i $i.NEW done This alows you to undo what you did, and retains the original filename. Even better, move the files into another directory, letting them keep their original name. I would suggest you type mkdir Old mv *.OLD Old This makes undoing your action very easy, and works for files of any name, and not just "*.OLD." Quoting with the Bourne ShellThe first problem shell programmers experience is quotation marks. The standard keyboard has three quotation marks. Each one has a different purpose, and only two are used in quoting strings. Why quote at all, and what do I mean by quoting? Well, the shell understands many special characters, called meta-characters. These each have a purpose, and there are so many, beginners often suffer from meta-itis. Example: The dollar sign is a meta-character, and tells the shell the next word is a variable. If you wanted to use the dollar sign as a regular character, how can you tell the shell the dollar sign does not indicate a variable? Answer: the dollar sign must be quoted. Why? Quoted characters do not have a special meaning. Let me repeat this with emphasis. Quoted characters do not have a special meaning A surprising number of characters have special meanings. The lowly space, often forgotten in many books, is an extremely important meta-character. Consider the following: rm -i file1 file2 The shell breaks this line up into four words. The first word is the command, or program to execute. The next three words are passed to the program as three arguments. The word "-i" is an argument, just like "file1." The shell treats arguments and options the same, and does not know the difference between them. In other words, the program treats arguments starting with a hyphen as special. The shell doesn't much care, except that it follows the convention. In this case, rm looks at the first argument, realizes it is an option because it starts with a hyphen, and treats the next two arguments as filenames. The program then starts to delete the files specifies, but firsts asks the user for permission because of the "-i" option. The use of the hyphen to indicate an option is a convention. There is no reason you can't write a program to use another character. You could use a forward slash, like DOS does, to indicate a hyphen, but then your program would not be able to distinguish between an option and a path to filename whose first characters is a slash. Can a file have a space in the name? Absolutely. This is UNIX. There are few limitations in filenames. As far as the operating system is concerned, You can't have a filename contain a slash or a null. The shell is a different story, and one I don't plan to discuss. Normally, a space delineates arguments. To include a space in a filename, you must quote it. Another verb used in the UNIX documentations is "escape;" this typically refers to a single character. You "quote" a string, but "escape" a meta-character. In both cases, all special characters are treated as regular characters. Assume, for a moment, you had a file named "file1 file2," This is one file, with a space between the "1" and the "f." If this file is to be deleted, one way to quote the space is rm 'file1 file2' There are other ways to do the same. Most people consider the quotation mark as something you place at the beginning and end of the string. A more accurate description of the quoting process is a switch, or toggle. The following variations are all equivalent: rm 'file1 file2' rm file1' 'file2 rm f'ile1 file'2 In other words, when reading a shell script, you must remember the current "quoting state." The quote toggles the state. Therefore if you see a quotation mark in the middle of a line, it may be turning the toggle on or off. You must start from the beginning, and group the quotation marks in pairs. There are two other forms or quoting. The second uses a backslash "," which only acts to "escape" the next character. The double quotation mark is similar to the single quotes used above, but weaker. I'll explain strong and weak quotation later on. Here is the earlier example, this time using the other forms of quoting: rm "file1 file2" rm file1 file2 rm file1" "file2 Nested quotationsA very confusing problem is placing quotation marks within quotation marks. It can be done, but it is not always consistent or logical. Quoting a double quote is perhaps the simplist, and does what you expect: echo '"' echo """ echo " The backslash is different. Look at the three variations: echo '' echo "" echo As you can see, single quotes and double quotes behave differently. A double quote is weaker, and does not quote a backslash. Single quotes are different again. You can escape them with a backslash, or quote them with double quotes: echo ' echo "'" The following does not work: echo ''' It is identical to echo ' Both examples start a quoting operation, but do not end the action. In other words, the quoting function will stay toggled, and will continue until another single quote is found. If none is found, the shell will read the rest of the script, until an end of file is found. Strong versus weak quotingEarlier I described single quotes as strong quoting, and double quotes as weak quoting. What is the difference? Strong quoting prevents characters from having special meanings, so if you put a character inside single quotes, what you see is what you get. Therefore, if you are not sure if a character is a special character or not, use strong quotation marks. Weak quotation marks treat most characters as plain characters, but allow certain characters (or rather meta-characters) to have a special meaning. As the earlier example illustrates, the backslash within double quotation marks is a special meta-character. It indicates the next character is not, so it can be used before a backslash and before a double quotation mark, escaping the special meaning. There are two other meta-characters that are allowed inside double quotation marks: the dollar sign, and the back quote. Dollar signs indicate a variable. One important variable is "HOME" which specifies your home, or starting directory. The following examples illustrates the difference: $ echo '$HOME' $HOME $ echo '$HOME' $HOME $ echo "$HOME" /home/barnett $ echo "$HOME" $HOME The back quote does command substitution. The string between backquotes is executed, and the results replaces the backquoted string: $ echo 'The current directory is `pwd`' The current directory is `pwd` $ echo 'The current directory is `pwd`' The current directory is `pwd` $ echo "The current directory is `pwd`" The current directory is `/home/barnett` $ echo "The current directory is `pwd`" The current directory is `pwd` Quoting over several linesThere is a large difference between the C shell and the Bourne shell when a quote is larger than a line. The C shell is best suited for interactive sessions. Because of this, it assumes a quote ends with the end of a line, if a second quoute character is not found. The Bourne shell makes no assumptions, and only stops quoting when you specify a second quotation mark. If you are using this shell interactively, and type a quotation mark, the normal prompt changes, indicating you are inside a quote. This confused me the first time it happened. The following Bourne shell example illustrates this: $ echo 'Don't do this' > ls > pwd > ' Dont do this ls pwd $ This is a minor inconvenience if you use the shell interactively, but a large benefit when writing shell scripts that contain multiple lines of quoted text. I used the C shell for my first scripts, but I soon realized how awkward the C shell was when I included a multi-line awk script insice the C shell script. The Bourne shell's handling of awk scripts was much easier: #!/bin/sh # Print a warning if any disk is more # than 95% full. /usr/ucb/df | tr -d '%' | awk ' # only look at lines where the first field contains a "/" $1 ~ /\// { if ($5 > 95) { printf("Warning, disk %s is %4.2f%% full\n",$6,$5); } }' Click here to get file: diskwarn.sh Mixing quotation marksHaving two types of quotation marks simplifies many problems, as long as you remember how meta-characters behave. You will find that the easiest way to escape a quotation mark is to use the other form of quotation marks. echo "Don't forget!" echo 'Warning! Missing keyword: "end"' Quotes within quotes - take twoEarlier I showed how to include a quote within quotes of the same kind. As you recall, you cannot place a single quote within a string terminated by single quotes. The easiest solution is to use the other type of quotation marks. But there are times when this is not possible. There is a way to do this, but it is not obvious to many people, especially those with a lot of experience in computer languages. Most languages, you see, use special characters at the beginning and end of the string, and has an escape to insert special characters in the middle of the string. The quotation marks in the Bourne shell are not used to define a string. There are used to disable or enable interpretation of meta-characters. You should understand the following are equivalent: echo abcd echo 'abcd' echo ab'c'd echo a"b"cd echo 'a'"b"'c'"d" The last example protects each of the four letters from special interpretation, and switches between strong and weak quotation marks for each letter. Letters do not need to be quoted, but I wanted a simple example. If I wanted to include a single quote in the middle of a string delineated by a single quote marks, I'd switch to the different form of quotes when that particular character is encountered. That is, I'd use the form 'string1'"string2"'string3' where string2 is a single quote character. Here is the real example: $ echo 'Strong quotes use '"'"' and weak quotes use "' Strong quotes use ' and weak quotes use " It is confusing, but if you start at the beginning, and following through, you will see how it works. Placing variables within stringsChange the quoting mid-stream is also very useful when you are inserting a variable in the middle of a string. You could use weak quotes: echo "My home directory is $HOME, and my account is $USER" You will find that this form is also useful: echo 'My home directory is '$HOME', and my account is '$USER When you write your first multi-line awk or sed script, and discover you want to pass the value of a variable to the middle of the script, the second form solves this problem easily. VariablesThe Bourne shell has a very simple syntax for variables: variable=value The characters used for variable names is limited to letters, numbers and the underscore character. It cannot start with a number. Unlike the C shell, spaces are important when defining Bourne shell variables. Whitespace (spaces, tabs or newlines) terminate the value. If you want whitespace in a variable, it must be quoted: question='What is the filename? ' Multiple assignments can be placed on one line: A=1 B=2 C=3 D=4 Do not put a space after the equals sign. This terminates the value. The command a=date sets the variable "a" to be equal to "date," but the command a= date sets "a" to be the empty string, and executes the date command. The "date" command? Yes. Which introduces... A subtle pointNotice how two commands are executed on one line: the variable is changed, and the "date" program is executed. It is not obvious that this is valid. The manual page doesn't mention this. Even stranger is some commands can be executed, while others cannot. The "date" command is an external program. That is, the command is not built into the shell, but is an external executable. Other commands are internal command, built into the shell. "Echo" and "export" are shell built-in commands, and can follow the variable assignment. You might see an environment variable defined like this: VAR=/usr/lib; export VAR But the following works just as well: VAR=/usr/lib export VAR Some of the built--in commands cannot be on the same line, like "for" or "if." The "echo" command does, but it may not do what you think. Don't believe me? I'll give you an example, and you have to guess what the results will be. I suspect that that 99.9999% of you would guess wrong. Put on your thinking caps. You'll need it. Ready? What does the following Bourne shell commands do? a=one; echo $a a=two echo $a a=three echo $a >$a I have to be honest. I failed the test myself. Well, I got partial credit. But I wrote the quiz. The first line is simple: "one" is output to the screen. The second line behaves differently. The value of variable "a" is set to "two," but the echo command outputs "one." Remember, the shell reads the lines, expands metacharacters, and then passes it to the programs. The shell treats built-in commands like external commands, and expands the meta-characters before executing the built-in commands. Therefore the second line is effectively a=two echo one and then the command is executed, which changes the value of the variable after it is used. Ready for a curve ball? What does the last line do? It creates a file. The file contains the word "two." For $64,000 and a trip to Silicon Valley, what is the name of the file that is created? For those to thought the answer is "two," I'm terrible sorry, you didn't win the grand price. We do have a nice home version of this game, and a year's supply of toothpaste. The correct answer is "three." In other words echo $a >$a is interpreted as echo second >third I am not fooling you. The variable "$a" has two different values on the same line! The C shell doesn't do this, by the way. The Bourne shell evaluates metacharacters twice: one for the commands and arguments, and a second time for file redirection. Perhaps Mr. Bourne designed the shell to behave this way because he felt a=output >$a ought to use the new value of the variable, and not the value before the line was executed, which might be undefined, and would certainly have undesirable resultsa. Although the real reason is that the above command treats the "a" variable like an environment variable, and sets the variable, marks it for export, and then executes the command. Because the variable is "passed" to the command by the environment, the shell simply sets the standard output to the appropriate file, and then processes the line for variables, and lastly executing the command on the line. More on this later. The set commandIf you with to examine the values of all of your current variables, use the command "set:" $ set DISPLAY=:0.0 HOME=/home/barnett IFS= LD_LIBRARY_PATH=/usr/openwin/lib:/usr/openwin/lib/server LOGNAME=barnett MAILCHECK=600 OPENWINHOME=/usr/openwin PATH=/home/barnett/bin:/usr/ucb:/usr/bin PS1=$ PS2=> PWD=/home/barnett SHELL=/bin/csh TERM=vt100 USER=barnett $ Notice the alphabetical order of the variables, and the equals character between the variable and the value. The "set" command is one way to determine which shell you are currently using. (The C shell puts spaces between the variable and the value.) Also note the assortment of variables already defined. These are environment variables. Environment VariablesUNIX provides a mechanism to pass information to all processes created by a parent process by using environment variables. When you log onto a system, you are given a small number of variables, predefined. You can add to this list in your shell start-up files. Every program you execute will inherit these variables. But the information flow is one-way. New UNIX users find this confusing, and cannot understand why a shell script can't change their current directory. Picture it this way: suppose you executed hundreds of programs, and they all wanted to change their environment to a different value. It should be obvious that they can't all control the same variable. Imagine hundreds of programs trying to change the directory you are currently using! Perhaps these variables ought to be called hereditary, and not environmental. Children processes inherit these values form the parents, but can never change how the parents were created. That would require time-travel, a feature not currentable available in commercial UNIX systems. As I mentioned before, the shell command "export" is used to update environment variables. The command export a b c marks the variables "a" "b" and "c," and all child processes will inherit the current value of the variable. With no arguments, it lists those variables so marked. The command "export" is necessary. Changing the value of an environment variable does not mean this change will be inherited. Example: HOME=/ myprogram When "myprogram" executes, the value of the "HOME" variable is not changed. However, in this example: HOME=/ export HOME myprogram the program does get the modified value. Another way to test this is to start a new copy of the shell, and execute the "export" command. No variables are reported. The command marks the variable. It does not copy the current value into a special location in memory. A variable can be marked for export before it is changed. That is, export HOME HOME=/ myprogram works fine. Special Environment VariablesThere are several special variables the shell uses, and there are special variables the system defined for each user. SunOS and Solaris systems use different environment variables. If in doubt, check the manual pages. I'll describe some the important Solaris variables. PATH - Sets searchpathThe "PATH" environment variable lists directories that contain commands. When you type an arbitrary command, the directories listed are searched in the order specified. The colon is used to separate directory names. An empty string corresponds to the current directory. Therefore the searchpath :/usr/bin:/usr/ucb contains three directories, with the current directory being searched first. This is dangerous, as someone can create a program called "ls" and if you change your current directory to the one that contains this program, you will execute this trojan horse. If you must include the current directory, place it last in the searchpath. /usr/bin:/usr/ucb: HOME - Your home directoryThe "HOME" variable defines where the "cd" goes when it is executed without any arguments. The HOME evvironment variable is set by the login process. CDPATH - cd searchpathWhen you execute the "cd" command, and specify a directory, the shell searches for that directory inside the current working directory. You can add additional directories to this list. If the shell can't find the directory in the current directory, it will look in the list of directories inside this variable. Adding the home directory, and the directory above the current directory is useful: CDPATH=$HOME:.. export CDPATH IFS - Internal Field SeperatorThe "IFS" variable lists the characters used to terminate a word. I discussed this briefly earlier. Normally, whitespace separates words, and this variable contains a space, a tab and a new line. Hackers find this variable interesting, because it can be used to break into computer systems. A poorly written program may carelessly execute "/bin/ps." A hacker may redefine the PATH variable, and define IFS to be "/." When the program executes "/bin/ps," the shell will treat this as "bin ps." In other words, the program "bin" is executed with "ps" as an argument. If the hacker has placed a program called "bin" in the searchpath, then the hacker gains privileged access. PS1 - Normal PromptThe "PS1" variable specifies the prompt printed before each command. It is normally "$ ." The current directory cannot be placed inside this prompt. Well, some people make a joke, and tell a new user to place a period inside this variable. A "." does signifies the current directory, however, most users prefer the actual name. PS2 - Secondary PromptThe "PS2" environment variable defines the secondary prompt, This is the prompt you see when you execute a multi-line command, such as "for" or "if." You also see it when you forget to terminate a quote. The default value is "> ." MAIL - Incoming mailThe "MAIL" variable specifies where your mailbox is located. It is set by the login process. MAILCHECK - How often to check for mailThe "MAILCHECK" variable specifies how often to check for mail, in seconds. The default value is 600 seconds (10 minutes). If you set it to zero, every time the shell types a prompt, it will check for mail. SHACCT - Accounting fileThis variable defines the accounting file, used by the acctcom and acctcms commands. MAILPATH - searchpath for mail foldersThe "MAILPATH" variable lists colon-separated filenames. You can add a "%" after the filename, and specify a special prompt for each mailbox. In addition, several environment variables are specified by the login process. "TERM" defines the terminal type, and "USER" or "LOGNAME" defines your user ID. "SHELL" defines your default shell, and "TZ" specifies your time zone. Check the manual pages, and test your own environment to find out for sure. The external program "env" prints all current environment variables. Bourne Shell Variables - Alternate FormatsEarlier, I discussed simple variables in the Bourne shell. Now is the time to go into more detail. Suppose you wanted to append a string to a variable. That is, suppose you had a variable "X" with the value of "Accounts," but you wanted to add a string like ".old," or "_new" making "Accounts.old" or "Accounts_new," perhaps in an attempt to rename a file. The first one is easy. The second requires a special action. In the first case, just add the string mv $X $X.old The second example, however, does not work: mv $X $X_new # WRONG! The reason? Well, the underscore character is a valid character in a variable name. Therefore the second example evaluates two variables, "X" and "X_new." If the second one is undefined, the variable will have a value of nothing, and the shell will convert it to mv Accounts The mv command will take the offered arguments, and complain, as it always wants two or more variables. A similar problem will occur if you wish to add a letter or number to the value of a variable. Using quoting and shell variablesThere are several solutions. The first is to use shell quoting. Remember, quoting starts and stops the shell from treating the enclosed string from interpretation. All that is needed is to have a quote condition start or stop between the two strings passed to the shell. Place the variable in one string, and the constant in the other. If the variable "x" has the value "home," and you want to add "run" to the end, all of the following combinations are equal to "homerun:" $x"run" $x'run' $xrun $x''run $x""run "$x"run Using curly braces with variablesThere is another solution, using curly braces: ${x}run This is a common convention in UNIX programs. The C shell also uses the same feature. The UNIX make utility uses this in makefiles, and requires braces for all variable references longer than a single letter. (Make uses either curly braces or parenthesis). This form for variables is very useful. You could standardize on it as a convention. But the real use comes from four variations of this basic form, briefly described below: +-------------------------------------------------------------+ |Form Meaning | +-------------------------------------------------------------+ |${variable?word} Complain if undefined | |${variable-word} Use new value if undefined | |${variable+word} Opposite of the above | |${variable=word} Use new value if undefined, and redefine. | +-------------------------------------------------------------+ Why are these forms useful? If you write shell scripts, it is good practice to gracefully handle unusual conditions. What happens if the variable "d" is not defined - and you use the command below? d=`expr $d + 1` You get "expr: syntax error" The way to fix this is to have it give an error if "d" is not defined. d=`expr "${d?'not defined'}" + 1` The "?" generates an error: "sh: d: not defined" If instead, you wanted it to silently use zero, use d=`expr "${d-0}" + 1` This uses "0" if "d" is undefined. If you wish to set the value if it's undefined, use "=" echo $z echo ${z=23} echo $z The first echo outputs a blank line. The next 2 "echo" commands output "23." Note that you can't use new=`expr "${old=0}" + 1` to change the value of "old" because the expr command is run as a subshell script, and changing the value of "old" in that shell doesn;t change the value in the parent shell. I've seen many scripts fail with strange messages if certain variables aren't defined. Preventing this is very easy, once you master these four methods of referring a Bourne shell variable. Let me describe these in more detail. ${variable?value} - Complain if undefinedThe first variation is used when something unusual happens. I think of it as the "Huh???" option, and the question mark acts as the mnemonic for this action. As an example, assume the following script is executed: #!/bin/sh cat ${HOME}/Welcome But suppose the environment variable "HOME" is not set. Without the question mark, you might get a strange error. In this case, the program cat would complain, saying file "/Welcome" does not exist. Change the script to be #!/bin/sh cat ${HOME?}/Welcome and execute it, and you will get the following message instead: script: HOME: parameter null or not set As you can see, changing all variables of the form "$variable" to "${variable?}" provides a simple method to improve the error reporting. Better still is a message that tells the user how to fix the problem. This is done by specifying a word after the question mark. Word? Yes, the manual pages says a word. In a typical UNIX-like way, that word is very important. You can place a single word after the question mark. But only one word. Perfect for one-word insults to those who forget to set variables: cat ${HOME?Dummy}/Welcome This is a perfect error message if you wish to develop a reputation. Some programmers, however, prefer to keep their jobs and friends. If you fall into that category, you may prefer to give an error message that tells the user how to fix the problem. How can you do that with a single word? Remember my discussion earlier on quoting? And how the shell will consider a whitespace to be the end of the word unless quoted? The solution should be obvious. Just quote the string, which makes the results one word: cat ${HOME?"Please define HOME, and try again"}/Welcome Simple, yet this makes a shell script more user-friendly. It's not a real shell error, as it doesn't cause the shell to exit, and is not a syntax error. If you want a real error to occur, then you must do this yourself with the "exit" command. ${variable-default} - Use default if undefinedThe next variation doesn't generate an error. It simply provides a variable that didn't have a value. Here is an example, with user commands in boldface: $ echo Y is $Y Y is $ echo Y is ${Y-default} Y is default $ Y=new $ echo Y is ${Y-default} Y is new $ Think of the hyphen as a mnemonic for an optional value, as the hyphen is used to specify an option on a UNIX command line. Like the other example, the word can be more than a single word. Here are some examples: ${b-string} ${b-$variable} ${b-"a phrase with spaces"} ${b-"A complex phrase with variables like $HOME or `date`"} ${b-`command`} ${b-`wc -l разделы измеритель петля фаза нуль измеритель петля фаза нуль оркестр креольский танго эдас-134 аденома предст.ж-зы кулер процессор срочный перевод бензопила dolmar жаростойкий краска бахила полиэтиленовый брэнд морозильный ларь измеритель петля фаза нуль управление ярославль 5440.13 (крышка) зона ограничение доступ кулер 478 индустриальный монитор shell