------------------------ Week 8 Notes for DAT2330 ------------------------ -Ian! D. Allen - idallen@idallen.ca Remember - knowing how to find out an answer is more important than memorizing the answer. Learn to fish! RTFM! (Read The Fine Manual) ------ Review ------ In Week 7 (week07notes.txt): You learned the format of the Unix password file. - You can extract any field from this file using awk and/or cut. You learned more about Unix processes, PATH, and umask. You learned the three basics for a working Shell Script header. You learned the meaning of a dozen important shell variables. You know the shell Order of Processing (expansion order) of the command line. You learned how to do Command Substitution of standard output of any command. You learned how to put error messages (e.g. from echo) onto standard error. You learned how to use the built-in "read" command to read standard input. You read how to build the nine parts of a DAT2330 shell script. You know how to use the "mesg" and "write" commands to communicate. ------------------ This Week (Week 8) ------------------ In the lab, we first wrote a small script to produce this output: $ ./my.sh foo Enter input: bar The first argument is foo and the variable is bar. The script (minus documentation): #!/bin/sh -u PATH=/bin:/usr/bin ; export PATH umask 022 echo 1>&2 "Enter input:" read var echo "The first argument is $1 and the variable is $var." - note how we issue prompts for input on stderr, never stdout * Control structures: "test", IF/ELIF/ELSE/FI - Running Linux: "Shell Programming" Chapter 13, p.448 - Notes: exit_status.txt - Return Code, Exit Status, test, if, and while - Notes: iftest1.sh.txt Compare two files in an IF statement - shells are designed to find and run commands, not to do arithmetic - therefore IF statements run commands and check return codes - a good return code (zero) from a command means execute the IF - a bad return code (non-zero) from a command means execute the ELSE - C Programmers note that this zero/nonzero is backwards from C Language if grep nosuchstring /etc/passwd ; then echo "code $? - return code zero means found it" else echo code $? - non-zero return code means did not find it" fi We prepared a shell script to execute the above code with a command line argument: The script (minus documentation): #!/bin/sh -u PATH=/bin:/usr/bin ; export PATH umask 022 if grep "$1" /etc/passwd ; then echo "code $? - return code zero means found $1" else echo "code $? - non-zero return code means did not find $1" fi - note how we always DOUBLE QUOTE *all* our variables!! * Testing strings, integers, file permissions, etc. - Notes: exit_status.txt - Return Code, Exit Status, test, if, and while - Notes: iftest2.sh.txt - numeric test in an IF statement - Notes: iftest3.sh.txt - string tests in an IF statement - shells run commands; they do not do arithmetic comparisons - to compare numbers or strings, run the "test" command and check the return status: if test $# -gt 1 ; then echo "count $# - there is more than one argument" else echo "count $# - there is zero or one argument" fi - Like C language, comparing integers differs from comparing strings. Six "test" integer operators: -lt -gt -le -ge -eq -ne Two "test" string operators: = != (note: do not use == in shell scripts) See: "man test" for other operators - do not compare strings with any of the six numeric operators $ test abc -eq abc sh: test: abc: integer expression expected - all the comparison operators for two numbers or two strings require *exactly* three separate arguments to test: $ x="a b c" ; y="abc" ; test "$x" = "$y" ; echo $? 1 $ x="a b c" ; y="abc" ; test $x = "$y" # MISSING QUOTES!! bash: test: too many arguments $ test abc=def ; echo $? # ONLY ONE ARGUMENT!! 0 $ test abc = def ; echo $? # 3 arguments required 1 Explain these outputs: $ test 0 = 00 ; echo $? # echo output is 1 (bad status; failed; FALSE) $ test 0 -eq 00 ; echo $? # echo output is 0 (good status; TRUE) - do not use the string operators to compare integers; use -eq or -ne * Control structures: WHILE/DO/DONE - as with IF, a WHILE is followed by a Unix command, NOT by arithmetic count=1 while test $count -le 10 ; do echo "count is $count" let count=count+1 done - the "let" command allows the shell to do some simple integer arithmetic - shells do NOT handle decimal points! no floating point numbers! - as with IF, you can use *any Unix command* after WHILE - only the return status of the command matters to IF and WHILE * Sample script (written and submitted by everyone during lab period): Week 8 Lab Script Specifications: Take one optional argument and look for it in the password file. If the argument is missing, prompt for it and read it. Exit non-zero if there are not 0 or 1 arguments or if the string is empty. To view the script, see the Notes: passwd_string_finder.sh.txt * More "test" operators: Empty (zero-length) string: test -z "str" Non-empty (non-null) string: test -n "str" Pathname is accessible and exists: test -e "path" Pathname is accessible and a file: test -f "path" Pathname is accessible and a directory: test -d "path" Pathname is accessible and has a size bigger than zero: test -s "path" Pathname is accessible and readable: test -r "path" Pathname is accessible and writable: test -w "path" Pathname is accessible and executable: test -x "path" A pathname may be inaccessible if the directories leading to it are not passable by you (missing execute [search] permissions for you). Test command negation: test ! -r path test ! -w path Boolean operators to combine tests: -a (AND) -o (OR) test -n "$foo" -a -x "$foo" # non-empty string AND executable test -r "$bar" -o -w "$bar" # readable OR writable * Quick test shortcuts: || and && - like the semicolon ";" both "&&" and "||" separate different commands - "&&" executes the command on the right only if the command on the left succeeds (has a zero return code) - "||" executes the command on the right only if the command on the left fails (has a non-zero return code) $ grep root /etc/passwd && echo found it root:x:0:0:root:/root:/bin/bash found it $ grep nosuchstring /etc/passwd || echo not found not found Use "|| exit 1" after important commands in your scripts if you don't want to use the full IF statement to test the return status. Read notes: quick_tests.txt - Return Code, Exit Status, ||, &&, test, and if * Two silly little command names: true and false - no output; they only set the return code $ true && echo hi $? hi 0 $ false || echo ho $? ho 1 * Syntactic sugar: using the "[" command alias for "test" - Notes: exit_status.txt - Return Code, Exit Status, test, if, and while - everyone writes "[" and "]" instead of "test" in scripts: if [ $# -gt 1 ] ; then echo "count $# is greater than one" fi if [ -r "$bar" -o -w "$bar" ] ; then echo "path $bar is readable OR writable" fi * Review of shell Input Redirection from files (useful in command substitution): Read notes: redirection.txt - Unix Shell I/O Redirection (including Pipes) - a pipe allows a command to get standard input from some other program, e.g. $ who | tr a-z A-Z - the shell also has a "<" input redirection syntax that allows a command to get standard input from a file instead of a program: $ tr a-z A-Z