------------------------------------------------------------------------- PHYS210 2009-09-22 LAB SUMMARY Writing bash scripts ------------------------------------------------------------------------- 0) Create the directory ~/bin in which you will create the example scripts below. Since ~/bin is in your PATH, once you have created a script, and made it executable, you will be able to invoke it from anywhere within the filesystem hierarchy % cd % mkdir bin % cd bin % ls %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1) sequence Using your text editor of choice, create a file ~/bin/sequence containing the lines between the lines of dashes below. Here and in the following you do NOT have to enter the comment lines, i.e. any line except the first (which is essential) that has a # in the first column. ------------------------------------------------------------------------ #! /bin/bash # sequence # # Example script that illustrates simple sequence of commands. # # Note the use of the sleep function which causes the script # to "sleep" for the specified number of seconds. # # Also note that in all of the examples that follow, if the script # does not end with an explicit 'exit', it is equivalent to ending with # an 'exit 0' --- i.e. it returns "success" or "true" to the invoking # environment. date sleep 1 pwd sleep 1 ls -l ------------------------------------------------------------------------ Once you have saved the file, make it executable. First, ensure that you are in the ~/bin directory % cd ~/bin Now, make the file executable by everyone % chmod a+x sequence Check that the file *is* executable % -ls -l sequence -rwxr-xr-x 1 phys210t ugrad 224 2009-09-21 20:21 sequence* Now change to your home directory (just to illustrate the point that if the script is in ~/bin and ~/bin is in your PATH, you can invoke the script from anywhere) ... % cd ... and execute the script % sequence Mon Sep 21 20:20:15 PDT 2009 /home2/phys210t total 52 drwxr-xr-x 2 phys210t ugrad 4096 2009-09-21 20:21 bin -rwxr-xr-x 1 phys210t ugrad 53 2009-08-31 20:12 cmd drwxr-xr-x 2 phys210t ugrad 4096 2009-09-15 15:43 Desktop drwxr-xr-x 4 phys210t ugrad 4096 2009-08-31 20:15 dir1 drwxr-xr-x 2 phys210t ugrad 4096 2009-08-31 20:16 dir2 drwxr-xr-x 2 phys210t ugrad 4096 2009-09-15 15:33 Documents drwxr-xr-x 2 phys210t ugrad 4096 2009-09-17 16:41 grep drwxr-xr-x 2 phys210t ugrad 4096 2009-09-17 16:24 numbers drwxr-xr-x 2 phys210t ugrad 4096 2009-09-17 16:14 redirect drwxr-xr-x 6 phys210t ugrad 4096 2009-09-16 21:26 Save -rw-r--r-- 1 phys210t ugrad 1855 2009-09-15 15:43 screen-configurations.xml drwxr-xr-x 3 phys210t ugrad 4096 2009-09-15 15:59 tempdir -rw-r--r-- 1 phys210t ugrad 29 2009-09-17 15:38 thedate Note how the "sleep 1"'s in the script cause the script to pause (sleep) for a second between execution of the three non-sleep commands ************************************************************************ IMPORTANT: If you get the error message -bash: sequence: command not found when you enter % sequence then check the following 1) That ~/bin exists and is a directory 2) That ~/bin/sequence exists and is executable (i.e. that there are x's in the appropriate fields of the permission part of the long listing of the file) 3) That ~/bin is in your path (printenv PATH and ensure that /home/your-logname/bin appears in the output) If you can't figure out what's wrong, ask for help! ************************************************************************ There ... now you've written at least one script. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2) simple-case Using your text editor of choice, create a file ~/bin/simple-case containing the lines between the lines of dashes below. FROM THIS POINT FORWARD "Create the file ~/bin/XXX" WILL BE SHORTHAND FOR 'Using your text editor of choice, create a file ~/bin/XXX containing the lines between the lines of dashes below'. ------------------------------------------------------------------------ #! /bin/bash # simple-case # # Example script that illustrates basic use of 'case' statement. # # The script prints a message reporting how many arguments, n, were supplied # for cases n = 0, 1, 2, 3 and n > 3. # # Recall: $# always evaluates to the number of arguments supplied to # a script. # # Be careful to end every clause in the case construct with a double # semi-colon (;;) with no whitespace between the semi-colons. case $# in 0) echo "You supplied 0 arguments." ;; 1) echo "You supplied 1 argument." ;; 2) echo "You supplied 2 arguments." ;; 3) echo "You supplied 3 arguments." ;; # "Default case" *) echo "You supplied more than 3 arguments." ;; esac ------------------------------------------------------------------------ Again, once you have saved the script as ~/bin/simple-case, make it executable. From this point on, I will omit this step, SO REMEMBER TO ALWAYS MAKE THE SCRIPT EXECUTABLE ONCE YOU HAVE CREATED IT!! % cd ~/bin % chmod a+x simple-case and execute it with varying number of arguments (again, you should be able to do this from ANY directory, including ~/bin itself) % simple-case You supplied 0 arguments. % simple-case foo You supplied 1 argument. % simple-case foo bar You supplied 2 arguments. % simple-case foo bar 'woof be dog' You supplied 3 arguments. % simple-case 1 2 3 4 5 6 You supplied more than 3 arguments. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3) simple-if Create the file ~/bin/simple-if, and don't forget to make it executable once you've finished. ------------------------------------------------------------------------ #! /bin/bash # simple-if # # Example script that illustrates use of simple 'if' statement. # # Recall: The logical expression for a bash 'if' statement is ANY # COMMAND. All Linux/Unix commands (including the scripts that we # are writing) return a value to the invoking environment. # # A return value of 0 denotes success / true. # A return value of 1 denotes failure / false. # # One command that is often used to provide the logical expression is # 'test', which has MANY options, and is well-worth mastering, so see # 'man test' for full information. # # Here we test for the existence of a regular file named 'simple-if' # in the working directory, and output appropriate messages if we # do / don't find it. # # Note that the single semi-colon (;) between the test command and 'then # is MANDATORY if you want 'if' and 'then' to appear on the same line. # You can also omit the ';' but then you need to put the 'then' on # a separate line. if test -f simple-if; then echo "File 'simple-if' was found in the working directory." else echo "File 'simple-if' was not found in the working directory." fi ------------------------------------------------------------------------ First execute the script in ~/bin ... % cd ~/bin % simple-if File 'simple-if' was found in the working directory. % ... and then in your home directory % cd % simple-if File 'simple-if' was not found in the working directory. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4) complex-if Create the executable file ~/bin/complex-if ------------------------------------------------------------------------ #! /bin/bash # complex-if # # Example script that illustrates basic use of 'case' statement. # Example script that illustrates use of a more complex 'if' statement # with multiple elif clauses. # # This script reproduces the behaviour of 'simple-case' by using 'test' # with the 'INTEGER1 -eq INTEGER2' option, which tests whether # INTEGER1 is equal to INTEGER2 # # Again, recall that $# is the number of arguments supplied to the script. if test $# -eq 0; then echo "You supplied 0 arguments." elif test $# -eq 1; then echo "You supplied 1 argument." elif test $# -eq 2; then echo "You supplied 2 arguments." elif test $# -eq 3; then echo "You supplied 3 arguments." else echo "You supplied more than 3 arguments." fi ------------------------------------------------------------------------ Execute 'complex-if' in any directory you wish! % complex-if You supplied 0 arguments. % complex-if foo You supplied 1 argument. % complex-if foo bar You supplied 2 arguments. % complex-if foo bar 'woof be dog' You supplied 3 arguments. % complex-if 1 2 3 4 5 6 You supplied more than 3 arguments. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 5) nested-if Create the executable file ~/bin/nested-if ------------------------------------------------------------------------ #! /bin/bash # nested-if # # Example script that illustrates basic use of 'case' statement. # This script operates exactly as 'complex-if' does, but uses 'nested # if' statements rather than 'elif' clauses. if test $# -eq 0; then echo "You supplied 0 arguments." else if test $# -eq 1; then echo "You supplied 1 argument." else if test $# -eq 2; then echo "You supplied 2 arguments." else if test $# -eq 3; then echo "You supplied 3 arguments." else echo "You supplied more than 3 arguments." fi fi fi fi ------------------------------------------------------------------------ Execute 'nested-if' with varying numbers of arguments. % nested-if You supplied 0 arguments. % nested-if foo You supplied 1 argument. % nested-if foo bar You supplied 2 arguments. % nested-if foo bar 'woof be dog' You supplied 3 arguments. % nested-if 1 2 3 4 5 6 You supplied more than 3 arguments. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6) simple-for Create the executable file ~/bin/simple-for ------------------------------------------------------------------------ #! /bin/bash # simple-for # # Example script that illustrates use of a simple for loop. # # As with 'if ... then', there must be a semi-colon (;) after the list # of items over which the 'for' is to loop if you want the 'for' and # the 'do' to be on the same line --- if you omit the semi-colon, the # do must appear on a separate line. # # Also note that the 'do' marks the beginning of the sequence of commands # in the loop, while 'done' (not 'od'!) marks the end. Again, we use # judiciously placed 'sleep' commands so that it is easier to see what # is happening when the script is executed echo "Entering the for loop ..." sleep 1 for loopvar in foo bar 'woof be dog'; do # Note the use of the double quotes here to ensure that '$loopvar' # evaluates to the current value of the local variable 'loopvar' echo "In the for loop: loopvar=$loopvar" sleep 1 done echo "Exiting the for loop and the script ..." ------------------------------------------------------------------------ Execute the script % simple-for Entering the for loop ... In the for loop: loopvar=foo In the for loop: loopvar=bar In the for loop: loopvar=woof be dog Exiting the for loop and the script ... %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 7) complex-for Create the executable file ~/bin/complex-for ------------------------------------------------------------------------ #! /bin/bash # complex-for # # Example script that illustrates basic use of 'case' statement. # More complex script that uses a for loop, and that expects one # argument, n, a positive integer. If the single argument is valid # then the integers from 1 to n are generated using the 'seq' command # and used as the values for the 'for' loop. # # This script also includes a simple usage function. In addition, # as is done in the "improved" version of 'swap' discussed in the # Unix/Linux notes, it sets the local variable P to the name of # the script using backquotes, the 'basename' command, and the fact # that $0 evaluates to the name by which the script was invoked. # Get the name of the script: note that the quotes are BACKQUOTES P=`basename $0` # Usage function usage() { echo "usage: $P n" echo echo " n must be a positive integer" # Exit the script and return '1' (failure) to the invoking # environment. exit 1 } # Test for exactly one argument- this could equally well be done with # an 'if' statement. case $# in # If there is one argument, set the local variable 'n' to its value. 1) n=$1 ;; # In all other cases, invoke the 'usage' function, which will print # the usage message and exit the script. Recall that '*)' works as # a 'default' case since it will match anything. *) usage ;; esac # We really should test to see if 'n' is a positive integer, but that's # a bit advanced, so we will assume that it is. Again, note the use # of the backquotes in `seq $n` to generate the loop values # 1, 2, .... n. echo "Entering the for loop ..." for loopvar in `seq $n`; do echo "In the for loop: loopvar=$loopvar" sleep 1 done echo "Exiting the for loop and the script ..." ------------------------------------------------------------------------ Invoke script with no arguments; get the usage message % complex-for usage: complex-for n n must be a positive integer Verify that script returns fail (return value 1) to the invoking bash by using the command1 || command2 construct wherein 'command2' is executed if and only if 'command1' returns a non-zero value (failure). Note the redirection of the standard output of the script to /dev/null, which sends the usage message into the infinite capacity "bit bucket". % complex-for > /dev/null || echo "Script returned failure." Script returned failure. Invoke with a valid argument % complex-for 5 Entering the for loop ... In the for loop: loopvar=1 In the for loop: loopvar=2 In the for loop: loopvar=3 In the for loop: loopvar=4 In the for loop: loopvar=5 Exiting the for loop and the script ... Invoke with an invalid argument % complex-for foo Entering the for loop ... seq: invalid floating point argument: foo Try `seq --help' for more information. Exiting the for loop and the script ... %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 8) here-docs Create the executable file ~/bin/here-docs ------------------------------------------------------------------------ #! /bin/bash #*********************************************************************** # here-docs # # Example script that illustrates basic use of 'case' statement. # This script expects three arguments # # 1) Your first name # 2) Your birth year # 3) Your favorite color # # If there are exactly three arguments, it assigns them to appropriately # named local variables, and then uses a "here document" to write to # standard output a little blurb incorporating the three inputs, as well # as the date. # # It also uses a here document to generate the usage message within # the function 'usage', and captures the invocation name of the script # in the local variable 'P', as previously. #*********************************************************************** #*********************************************************************** # Get the name of the script. #*********************************************************************** P=`basename $0` #*********************************************************************** # Usage function that uses 'cat' and a 'here document'. # # Note that the END that marks the end of here document in this # function MUST start at the beginning of a line, and CANNOT be # followed by any other characters, including whitespace. # Also recall that 'END' is an arbitrarily chosen string. # You can use whatever string you wish so long as it is used to delimit # both the start and end of the here document. # # Recall that '$var' and `command` constructs are evaluated # within here documents. # # Finally, observe that In this implementation, the 'usage' function # expects one # argument, which is the actual number of arguments that # the script was invoked with. Note that within the function, # '$1' evaluates to the first argument supplied to the function, # not the first argument supplied to the script. #*********************************************************************** usage() { cat< outfile # #*********************************************************************** #*********************************************************************** # Get the name of the script. #*********************************************************************** P=`basename $0` #*********************************************************************** # Usage function that uses 'cat' and a 'here document'. #*********************************************************************** usage() { cat< for 1 argument # n1,n2 -> for 2 arguments # n1,n2,n3 -> for 3 arguments # # etc. # # Build up the string in the local variable fields. #*********************************************************************** fields=$1 #*********************************************************************** # Shift discards the first argument and renumbers the rest from 1 to # $# - 1. It can be used repeatedly in a script. #*********************************************************************** shift #*********************************************************************** # Now loop over the remaining arguments: Note that due to the 'shift', # $* now evaluates to exactly those arguments. #*********************************************************************** for n in $*; do fields="$fields,$n" done #*********************************************************************** # Now invoke the 'cut' command with the '-f' and '-d' options. The # character that follows '-d' is what 'cut' will use to delimit # columns, which in this case we want to be one or more spaces. # # Note that when invoked in this form (i.e. without a file argument), # 'cut' will read from standard input. #*********************************************************************** cut -d ' ' -f $fields ------------------------------------------------------------------------ First ensure that you are in your ~/bin directory, and copy some sample input from ~phys210/shellpgm/nth/nth-input % cd ~/bin % cp ~phys210/shellpgm/nth/nth-input . Look at the file, and note that it is in the appropriate form to supply (via input redirection) to 'nth' % cat nth-input 1 1 1 1 2 4 8 16 32 3 9 27 81 243 4 16 64 256 1024 5 25 125 625 3125 6 36 216 1296 7776 7 49 343 2401 16807 8 64 512 4096 32768 9 81 729 6561 59049 10 100 1000 10000 100000 11 121 1331 14641 161051 12 144 1728 20736 248832 13 169 2197 28561 371293 14 196 2744 38416 537824 15 225 3375 50625 759375 16 256 4096 65536 1048576 17 289 4913 83521 1419857 18 324 5832 104976 1889568 19 361 6859 130321 2476099 20 400 8000 160000 3200000 Invoke script with no arguments, get usage message (and note tracing output, prefixed by '+') % nth ++ basename ./nth + P=nth + test 0 -eq 0 + usage + cat usage: nth colno [colno] ... Filters (selects) columns from standard output and writes them to standard output. + exit 1 Select 2nd column from nth-input % nth 2 < nth-input ++ basename ./nth + P=nth + test 1 -eq 0 + fields=2 + shift + cut -d ' ' -f 2 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400 Select 1st, 3rd and 5th columns from nth-input and redirect output to nth-output. Notice how the tracing still appears on the terminal since it is directed to standard ERROR (stderr) rather than standard output (stdout) % nth 1 3 5 < nth-input > nth-output ++ basename ./nth + P=nth + test 3 -eq 0 + fields=1 + shift + for n in '$*' + fields=1,3 + for n in '$*' + fields=1,3,5 + cut -d ' ' -f 1,3,5 View the content of nth-output % cat nth-output 1 1 1 2 8 32 3 27 243 4 64 1024 5 125 3125 6 216 7776 7 343 16807 8 512 32768 9 729 59049 10 1000 100000 11 1331 161051 12 1728 248832 13 2197 371293 14 2744 537824 15 3375 759375 16 4096 1048576 17 4913 1419857 18 5832 1889568 19 6859 2476099 20 8000 3200000 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% AND WE'RE DONE. YOU SHOULD NOW BE ABLE TO FINISH THE FIRST HOMEWORK. NEXT LAB YOU WILL HAVE AN OPPORTUNITY TO ASK US QUESTIONS IF YOU ARE HAVING PROBLEMS - TRY NOT TO LEAVE TOO MUCH UNFINISHED FOR NEXT TUESDAY'S LAB - REMEMBER, THE ASSIGNMENT IS DUE AT THE END OF THAT LAB!! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%