<p> PROGRAMMING SHELL SCRIPTS</p><p>Introduction</p><p>One of the nice things about the C-shell is that it provides access to the individual UNIX utilities (i.e., UNIX commands) that can be run from the command prompt. For example, a complex command using pipes, such as</p><p> who | cut –c-8 | sort –u | pr –l1 -8 –w78 –t</p><p> provides a compact list of all users who are currently logged on to Hercules. If the command is entered properly, a formatted list of users should be reported that looks similar to the following:</p><p> abraham appleton charlie frizzie howard jerry kosmo linda peterson root sutton</p><p>While this is one form of shell programming, the command would be more useful if it did not have to be entered at the command prompt each time it was needed. That is, the command would be more useful if it was contained in a shell script (i.e., a file that contains shell commands). For example, the following could be entered into an executable file called showUsers:</p><p>#! /usr/bin/tcsh -f who | cut -c-8 | sort -u | pr -l1 -8 -w78 -t exit 0</p><p>It would then merely require that the showUsers command be entered at the command prompt to obtain a compact list of current users.</p><p>In this section, we focus on the creation and execution of shell script files.</p><p>Shell Variables</p><p>1. Shell variables hold values which are actually lists containing zero or more elements (called words). - Words are just character strings.</p><p>- If a word contains only digits, without a leading zero and with no embedded spaces, it can be used as an operand in an arithmetic operation.</p><p>- Any shell variable can be used in a manner similar to a Boolean variable. If the variable is set (i.e., it has been given a value), it represents the value true. If the variable is unset (i.e., it has not been given a value), it represents the value false.</p><p>2. While there are numerous built-in shell variables (enter set at the command prompt to see them all or echo $<variable_name> to see the value of <variable_name>), a few representative examples are:</p><p>- prompt: Contains the string which prints as the command prompt when you log on to Hercules. The default value is %. You can change it by using the set command.</p><p>- Example</p><p> set prompt = ‘your name here>’ (or “your name here>”)</p><p>This command will change the command prompt to your name followed by >. If you entered the name jimbo, the resulting prompt would be</p><p> jimbo></p><p>- Example</p><p> set prompt = ‘your name here{\!}>’</p><p>This command will also show the command number, so the resulting prompt could be jimbo{23}></p><p>- history: Controls the number of previously executed commands maintained in the history list.</p><p>- Example</p><p> set history = 50</p><p>- path: Contains a list of directories that tcsh will search for the commands (i.e., programs) named in the command line.</p><p>- Example</p><p> set path = ( /bin /usr/bin /usr/ucb . )</p><p>Sets the first three elements of path to system directories where standard UNIX commands are usually located. The fourth element (i.e., the period) represents the current directory.</p><p>- status: Contains the completion status of the last command executed (i.e., usually zero and non- zero mean successful and unsuccessful completion, respectively.)</p><p>- $$: Contains the process id of the shell.</p><p>3. In a UNIX command, we can use characters called magic characters. Specifically, whenever the characters *, ?, [, and { are encountered, expansions are performed. The * character is a wild card character. For example, assume the command to be executed is</p><p>CC *.c The shell will use *.c as a pattern, attempt to match it against all of the file names in your directory, and compile all those files ending in .c (known as file name expansion). Similarly, the ? character is a wild card character for matching a single character. For example, assume the command to be executed is</p><p>CC file?.c</p><p>The shell will attempt to match file?.c against all of the file names in your directory, and compile all those whose names begin with file, followed by any single character, followed by .c. An alternative to the ? character uses square brackets to enumerate particular single characters to match. For example, assume the command to be executed is</p><p>CC file[134].c</p><p>The shell will compile file1.c, file3.c, and file4.c. Similarly, curly braces can be used to form a string product. For example, assume the command to be executed is</p><p>CC file{1,3,4}.c</p><p>The shell will compile file1.c, file3.c, and file4.c.</p><p>4. Example</p><p>Given what we now know from above in item 3, let’s consider the use of single quotes versus double quotes in UNIX commands. Consider the command</p><p> echo *** WAKEUP!! ***</p><p>This command has serious unintended problems because the shell will perform both history substitution (!! re-executes the most recent command) and file name substitution (for every asterisk encountered). The command could be written as</p><p> echo \*\*\* WAKEUP\!\! \*\*\* to resolve the problems. However, a better way to write the command is</p><p> echo ‘*** WAKEUP\!\! ***’ or echo “*** WAKEUP\!\! ***”</p><p>Single quotes protect the enclosed text from being split into words, suppress all forms of expansion, prevent the substitution of words that begin with $, and prevent the substitution of the history characters (i.e., !9, !! and !$). Double quotes protect the enclosed text from being split into words and suppress all forms of expansion. However, double quotes allow substitution. The bottom line: you have to be careful in using single and double quotes when magic characters and substitution characters are involved.</p><p>5. The set command is used to assign values to variables. It has several forms:</p><p>- set <variable_name>: Sets the variable <variable_name> to an empty list.</p><p>- Example</p><p> set arguments</p><p>- set <variable_name> = <value>: Sets the variable <variable_name> to <value>.</p><p>- Example</p><p> set arguments = abc</p><p>Sets the variable arguments to the string “abc”. More precisely, the variable arguments contains a list with one element whose value is the string “abc”.</p><p>- set <variable_name> = ( <value_1> <value_2> … <value_n> ): Sets the variable <variable_name> to a list containing n elements whose values are <value_1>, <value_2>, …, <value_n>, respectively. - Example</p><p> set arguments = ( abc 123 def 456 )</p><p>Sets the variable arguments to a four-element list containing the values shown above.</p><p>- set <variable_name>[<index_value>] = <value>: Sets one element of the previously set variable <variable_name> to <value>.</p><p>- Example</p><p> set arguments[2] = 789</p><p>Sets (changes) the value of the second word in the variable arguments (note that the indexes start at 1). That is, the variable arguments now contains the value</p><p> abc 789 def 456</p><p>- set: Generates a list of all currently set variables.</p><p>- @: An alternative form of the set command used exclusively for integer arithmetic and assignment.</p><p>- Example</p><p>@ i = 10 @ j = $i * 2 + 5 @ i++ @ j = ( $i – 5 ) / 2 Note: The first assignment is equivalent to set i = 10. However, none of the other assignment statements can be expressed using the set command.</p><p>6. The value of a shell variable is accessed by placing $ (i.e., the dollar symbol) before the name of the variable. It also has several forms:</p><p>- $<variable_name>: Represents the value of the variable <variable_name>.</p><p>- Example</p><p> set arguments = ( abc 789 def 456 )</p><p>We can use the echo command to see the value of a variable.</p><p> echo $arguments</p><p>Outputs the value</p><p> abc 789 def 456</p><p>- ${<variable_name>}: Has the same effect as $<variable_name>. The braces are used only when a string of alphanumeric characters is to be concatenated to the contents of a variable.</p><p>- Example</p><p> set arguments = ( abc 789 def 456 )</p><p>The command</p><p> echo $arguments outputs the value</p><p> abc 789 def 456</p><p>The command</p><p> echo $arguments xyz</p><p> outputs the value</p><p> abc 789 def 456 xyz</p><p>The command</p><p> echo $argumentsxyz</p><p> results in an undefined variable error message.</p><p>The command</p><p> echo ${arguments}xyz</p><p> outputs the value</p><p> abc 789 def 456xyz</p><p>- $<variable_name>[<index_value>]: Represents the value of one element of the variable <variable_name>.</p><p>- Example The command</p><p> echo $arguments[2]</p><p> outputs the value of the second element of arguments, namely</p><p>789</p><p>- $<variable_name>[<start_index_value>-<end_index_value>]: Represent the value of the elements in the range <start_index_value> to <end_index_value>.</p><p>- Example</p><p>The command</p><p> echo $arguments[2-4]</p><p> outputs the value</p><p>789f 456</p><p>- $<variable_name>[<start_index_value>-]: Represent the value of the elements in the range <start_index_value> to the last element.</p><p>- Example</p><p>The command</p><p> echo $arguments[2-] outputs the value</p><p>789f 456</p><p>7. Shell variables can also be tested in a couple of ways:</p><p>- $#<variable_name> and ${#<variable_name>}: Returns the number of words in the variable <variable_name>.</p><p>- Example</p><p> set arguments = ( abc 789 def 456 )</p><p>$#arguments returns the value 4.</p><p>- $?<variable_name> and ${?<variable_name>}: Returns 1 if the variable <variable_name> has been set. Otherwise, it returns 0.</p><p>Shell Expressions</p><p>Shell expressions are formed by combining variables and constants with operators that are similar to those in the C and C++ programming languages.</p><p>1. Arithmetic operators.</p><p>- *: Multiplication.</p><p>- /: Division.</p><p>- %: Modulo division. - +: Addition.</p><p>- -: Subtraction.</p><p>Note: Operator precedence is the same as in C/C++. Enclose expressions within parentheses to alter the precedence.</p><p>2. Assignment operators.</p><p>- =: Assign value.</p><p>- +=: Assign value after addition.</p><p>- -=: Assign value after subtraction.</p><p>- *=: Assign value after multiplication.</p><p>- /=: Assign value after division.</p><p>- %=: Assign value after modulo division.</p><p>- ++: Increment value.</p><p>- --: Decrement value.</p><p>3. Logical operators.</p><p>- &&: Logical AND.</p><p>- ||: Logical OR. - !: Logical negation.</p><p>4. Comparision operators.</p><p>- == and =~: Equality.</p><p>- != and !~: Inequality.</p><p>- <=: Less than or equal.</p><p>- >=: Greater than or equal.</p><p>- <: Less than.</p><p>- >: Greater than.</p><p>5. File inquiry operators.</p><p>- -d <file_name>: The file is a directory.</p><p>- -e <file_name>: The file exists.</p><p>- -f <file_name>: The file is a plain file.</p><p>- -o <file_name>: The user (i.e., whoever is trying to access the file) owns the file.</p><p>- -r <file_name>: The user has read permission.</p><p>- -w <file_name>: The user has write permission. - -x <file_name>: The user has execute permission.</p><p>- -z <file_name>: The file is empty (i.e., has a size of zero).</p><p>- !: Negates the result of each of the above tests.</p><p>Shell Control Structures</p><p>Shell control structures are similar to those found in the C and C++ programming languages.</p><p>1. if Command</p><p>- One-line form: The <simple_command> must be a command that does not use pipes or I/O redirection and the whole command must be contained on one line.</p><p> if ( <expression> ) <simple_command></p><p>- Example</p><p> if ( -w $file1 ) mv $file1 $file2</p><p>- Example</p><p> if ( $?file ) rm $file</p><p>- Multi-line form: The if and endif lines must be contained on one line (however, the if line can be split into multiple lines using a backslash (i.e., \) character).</p><p> if ( <expression> ) then zero or more lines containing shell commands endif</p><p>- Example</p><p> set file1 = $< set file2 = $< if ( -o $file1 && ! -e $file2 ) then mv $file1 $file2 endif</p><p>- Multi-line forms using else: Provide facilities for multi-way branching.</p><p> if ( <expression> ) then zero or more lines containing shell commands else zero or more lines containing shell commands endif</p><p> if ( <expression> ) then zero or more lines containing shell commands else if ( <expression> ) then zero or more lines containing shell commands else zero or more lines containing shell commands endif</p><p>2. while Command</p><p>- The general form: while ( <expression> ) zero or more lines containing shell commands end</p><p>- The while and end lines must be contained on one line.</p><p>- Within the loop body, break and continue commands may be used to terminate and short- circuit execution, respectively (i.e., they work the same as the like-named statements found in C and C++).</p><p>3. foreach Command</p><p>- The general form:</p><p> foreach <variable_name> ( <list_of_words> ) zero or more lines containing shell commands end</p><p>- The loop index variable <variable_name> takes on successive values from the <list_of_words> parameter.</p><p>- Example</p><p> foreach file ( *.c ) set size = `wc –l $file` if ( $size > 1000 ) then echo “$file skipped: Too large to print” continue endif echo “$file printing” pr $file | lpr -Pdept2 end Note: The expression *.c is expanded to the list of words containing all file names in the current directory whose extension is .c. Any command enclosed within a pair of single back quotes (i.e., `wc –l $file`) will be executed and its output treated as a list of words. However, in this case, the result is a list containing a single numeric value representing the number of lines in the file whose name is stored in the variable file.</p><p>4. switch Command</p><p>- The general form:</p><p> switch ( <expression> ) case <first_case>: zero or more lines containing shell commands breaksw case <second_case>: zero or more lines containing shell commands breaksw . . . case <last_case>: zero or more lines containing shell commands breaksw default: zero or more lines containing shell commands endsw</p><p>- The default clause is optional.</p><p>- The commands associated with a case must not be on the same line as the case. For example, case <first_case>: some command zero or more lines containing shell commands breaksw</p><p> will result in an error.</p><p>- Example</p><p> switch ( $#arguments ) case 0: zero or more lines containing shell commands breaksw case 1: zero or more lines containing shell commands breaksw case 2: zero or more lines containing shell commands breaksw default: zero or more lines containing shell commands endsw</p><p>- The value between the parentheses in the switch command does not need to be an integer, it can be a word.</p><p>- Example</p><p> switch ( $argv[$i] ) case end: breaksw case list: @ k = $i + 1 ls $argv[$k] breaksw case delete: case erase: @ k = $i + 1 rm $argv[$k] breaksw endsw</p><p>5. goto Command</p><p>- The general form:</p><p><label>: zero or more lines containing shell commands goto <label></p><p>- A label can be placed anywhere in a shell script file, but it must be on a line by itself.</p><p>- The goto command can be placed anywhere before or after the label.</p><p>Other Shell Variables</p><p>The following are a few special shell variables you might find useful. </p><p>1. $<: Indicates that the value of a variable should be read from standard input.</p><p>2. $0 … $9: These are special variable names for up to 10 arguments in a command line. 3. $argv and $*: Contain a list of command line arguments.</p><p>4. The shift command can be used as a convenient way to loop over all command line arguments.</p><p>- shift: Discards argv[1] and moves the remaining arguments one position to the left.</p><p>- shift <variable_name>: Discards <variable_name>[1] and moves the remaining arguments one position to the left.</p><p>- It is an error for argv not to be set or to have less than one argument. </p><p>5. $#argv and $#: Contain the number of command line arguments.</p><p>Examples</p><p>You can copy and paste each shell script in this example and the following examples into a file and run it. They demonstrate most of the useful features and characteristics of shell script programming.</p><p>1. SCRIPT = argDemo</p><p>#! /usr/bin/tcsh -f</p><p> if ( $#argv == 0 ) then echo “argDemo: usage is argDemo <yes_response> or argDemo <no_response>” exit 1 endif</p><p> switch ( $argv[1] ) case [yY][eE][sS]: echo “argv[1] is ‘yes’” breaksw case [nN][oO]: echo “argv[1] is ‘no’” breaksw default: echo “Enter yes or no in any sequence of upper- and lower-case” exit 1 endsw</p><p> exit 0</p><p>Note: Comments in a script file are lines that start with a #. However, due to a historical artifact in the development of the C- shell, it was decided that lines beginning with the directive #! (which looks like it should be a comment) would make a script file directly executable. That is, the kernel can start this program, even though it is not machine code. By default, if the first line does not contain such a directive, the script file would be executed by the (more primitive) Bourne shell (i.e., /bin/sh). Any options that would normally be supplied to the shell must then be given in the first-line shell directive. For example, the -f option in the example above, ignores the ~.tcshrc file resulting in faster execution time.</p><p>Background on ~.tcshrc: The ~.tcshrc file is a file in your home directory that is read and its contents executed every time tcsh is invoked. In addition, if a particular invocation of tcsh is the shell created at the time you login, a file named ~.login will be read and its contents executed immediately following the ~.tcshrc file. These files are typically used to set variables and aliases that you would like to use (basically, they “customize” your shell session). The ~.tcshrc file should be kept as short as possible because new shell instances are created fairly often. So, put the stuff that needs to be done once in the ~.login file (e.g., most shell variables like path and prompt, environment variables, and most aliases).</p><p>Note: Scripts should be terminated by an exit command. Optionally, but typically, exit should return an error code to the caller (e.g., exit 0 meaning no error and exit <n> meaning an error has occurred). This gives the caller an opportunity to catch faulty behavior. After each command executes, the status shell variable contains the exit value (i.e., $status) of that command. </p><p>2. SCRIPT = ifElseDemo #! /usr/bin/tcsh -f</p><p> set class set number = $argv[1]</p><p> if ( $number < 0 ) then @ class = 0 else if ( 0 <= $number && $number < 10 ) then @ class = 1 else @ class = 2 endif</p><p> echo “The number $number is in class $class”</p><p> exit 0</p><p>3. SCRIPT = forEachDemo1</p><p>#! /usr/bin/tcsh -f</p><p> foreach file ( `ls -1` ) echo $file wc $file end</p><p> exit 0</p><p>4. SCRIPT = foreachDemo2</p><p>#! /usr/bin/tcsh -f foreach i ( 10 15 20 40 ) echo $i end</p><p> foreach i ( a b c 17 ) echo $i end</p><p> exit 0</p><p>5. SCRIPT = zap</p><p>#! /usr/bin/tcsh -f</p><p> if ( $#argv != 1 ) then echo "zap: usage is zap <user_id>" exit 1 endif</p><p> set ps_output = "`ps -u $argv[1]`" @ i = 2</p><p> set ps_test while ( $i <= $#ps_output ) set line = ( $ps_output[$i] ) if ( $line[4] != "ps" && $line[4] != "tcsh" && $line[4] != "zap" ) then set ps_test = ( $ps_test $i ) endif @ i ++ end foreach i ( $ps_test ) set line = ( $ps_output[$i] ) set process_no = $line[1] RETRY: echo echo -n "zap: Kill CMD = ${line[4]} (PID = ${line[1]})? " set response = $< if ( $response =~ n* || $response =~ N* ) then continue else if ( $response =~ q* || $response =~ Q* ) then break else if ( $response =~ y* || $response =~ Y* ) then kill -9 $process_no continue else echo "zap: Invalid response: Enter y (yes), n (no), or q (quit)" goto RETRY endif end</p><p> exit 0</p><p>6. SCRIPT = saveFiles</p><p>#! /usr/bin/tcsh -f</p><p> if ( $#argv != 2 ) then echo “saveFiles: usage $0 dir1 dir2” exit 1 endif set dir1 = $argv[1] set dir2 = $argv[2] if ( ! -e $dir1 ) then echo “saveFiles: $dir1 does not exist” exit 1 endif if ( ! -d $dir1 ) then echo “saveFiles: $dir1 is not a directory” exit 1 endif if ( -e $dir2 ) then echo “saveFiles: $dir2 already exists” exit 1 endif mkdir $dir2 if ( $status != 0 ) exit 1 foreach file ( $dir1/* ) if ( -d $file ) then echo “saveFiles: $file (directory) not copied” else echo “saveFiles: $file (file) copied” cp $file $dir2 endif end exit 0 Debugging Shell Scripts</p><p>In what follows, assume the name of the script file being debugged is scriptFile.</p><p>7. To check the script file for syntax errors:</p><p>$ tcsh –n scriptFile</p><p>8. To output each line of the script file as it is checked for syntax errors:</p><p>$ tcsh –nv scriptFile</p><p>9. To output each line of the script file before it is executed:</p><p>$ tcsh –v scriptFile <parameters></p><p>10. To output each line of the script file after variable and command substitutions have occurred and before the line is executed:</p><p>$ tcsh –vx scriptFile <parameters></p><p>11. Use echo commands in a shell script to:</p><p>- Output statements or flags that indicate where the script is currently executing (like a trace).</p><p>- Output the values of variables.</p><p>- Prevent disasters by placing the echo command at the start of a line. - Example</p><p>Assuming that fileName contains junkola and trash contains /home/venus/hilder, then</p><p> echo mv $fileName $trash/fileName</p><p> will only print</p><p> mv junkola /home/venus/hilder/fileName</p><p> to the screen. That is, it does any expansions required, but does not actually execute the instruction. Note that what was probably intended was to have a $ sign at the beginning of the second fileName.</p><p>12. Use an exit command to prevent a shell script from executing passed a particular point in the script.</p>
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages26 Page
-
File Size-