Shell Scripting • A script is a list of instructions that is interpreted one instruction at a time – we have to specify the interpreter as the first line of the script as in #!/bin/bash or #!/bin/csh • We write scripts as text files – file must be executable, use 755 or 745 permissions • With Bash, our scripts can include – any Linux operation (e.g., date, chmod, useradd, mount, stat, etc) – with the Bash scripting language’s instructions Sample Script #!/bin/bash date du –s ~ find ~ -empty • This script outputs to the terminal window – The date – A summary of disk usage for the user’s home directory – Any empty files found under the user’s home directory • Once written, we might save this in a file, say report.sh and then execute it upon logging in – we could type ./report.sh from the command line or put the command report.sh in a file like .bashrc or .profile Controlling Script Output • We might want to insert blank lines between output – add echo after date and du –s ~ • The find command could potentially give us a lengthy list – we might want to send the output to a textfile • We could modify the script so that each instruction is followed by >> outputfile • Or we could invoke the script and redirect the output – ./report.sh > outputfile Scripting Errors • Scripts can generate run-time errors and continue to run – We would want to eliminate all errors before giving the script to anyone to use – Unfortunately there is no compiler to tell us syntax errors and scripting error run-time messages can be cryptic #!/bin/bash – Consider the script to the right NAME=Frank Zappa – No syntax error but running it yields echo $NAME • ./somescript: line 3: Zappa command not found – where somescript is the script name – Why that error? What does it mean? Scripting Errors • The script below, when run by a non-root user, gives us a different error – wc: /etc/shadow: Permission denied • In the previous script’s error, the error message was caused by that script so the error reports the script’s name • Here, the error arises because wc does not have permission to access the specified file #!/bin/bash wc –l /etc/passwd wc –l /etc/group wc –l /etc/shadow Variables • Variables can be used at any time in a script – You do not have to declare them • Variable names consist of letters, digits, underscores – Must start with a letter or underscore • legal variables names: x, y, z, first_name, last_name, file1 • incorrect file names: 1_file, 2file, file 3 (no spaces allowed) • Bash is case sensitive so first_name is not the same as FIRST_NAME • Variables store strings by default but can also store integers – Variables can also store arrays of strings/integers Assignment Statements • Used to store a value in a variable – Format: VAR=VALUE • VAR does not have to be previous declared • VALUE can be – literal value (placed in “” or ‘’ if there is at least one blank space) – the value stored in another variable, in which case we retrieve the value using $VAR – the result of some arithmetic or string operation – the value returns by some function call or Linux command – use $(command) or `command` Assignment Statement: Examples X=5 X stores the string 5 NAME=Frank NAME stores Frank NAME="Frank Zappa" NAME stores Frank Zappa NAME=Frank Zappa An error arises because of the space NAME=FrankZappa No quote marks required here NAME=ls * An error should arise as Bash tries to execute * X=1.2345 X stores 1.2345, but the value is a string, not a number Assignment Statement: Examples Assume FIRST_NAME stores Frank and LAST_NAME stores Zappa NAME="$FIRST_NAME $LAST_NAME" NAME stores Frank Zappa NAME='$FIRST_NAME $LAST_NAME' NAME stores $FIRST_NAME $LAST_NAME NAME=$FIRST_NAME$LAST_NAME Name stores FrankZappa NAME=$FIRST_NAME $LAST_NAME Results in an error because there is a space and no quote marks NAME="FIRST_NAME LAST_NAME" NAME stores FIRST_NAME LAST_NAME NAME='FIRST_NAME LAST_NAME' NAME stores FIRST_NAME LAST_NAME Assignment Statement: With Linux Commands • The following assignment statement assigns to the variable DATE a combination of – literal strings (Hello space, Today’s date and time is space) – the value stored in FIRST_NAME – the result of calling the date command – DATE="Hello $FIRST_NAME, today's date and time is $(date) " – DATE="Hello $FIRST_NAME, today's date and time is `date`" • the ` is known as a back tick or back quote mark, located on the upper left key of the keyboard with the ~ Assignment Statement: With Linux Commands • Consider the assignment statement LIST=$(ls) • What is stored in LIST? – the list of files/subdirectories of the current directory, stored as a list • LIST=$(ls –l) – since this is stored as a list, the items are stored on after another without line breaks – performing echo $LIST to output the value stored in LIST gives us output like the following (line breaks inserted here for readability) total 44 -rwxr--r--. 1 foxr foxr 448 Sep 6 10:20 addresses.txt -rwxr--r--. 1 foxr foxr 253 Aug 6 08:29 computers.txt -rw-rw-r--. 1 foxr foxr 235 Sep 11 14:23 courses.dat … Declaring Variables • As mentioned earlier, you do not have to declare variables – But you can • Format: declare [options] name[=value] • Options are – – – – – – – + to turn off the attribute and – to turn on the option a – declare as an array f – declare as a function i – declare as an integer r – declare as read-only x – export the variable to the environment declare –rx ARCH=386 declares ARCH as a read-only, exported variable with the value 386 (since its read-only, ARCH can not be changed except through another declare statement) Assignment Statement: Arithmetic Operations • By default, variables store strings even if the strings they store are numbers – By default, operations are taken to be string operations (for instance, + is treated as a string not an operator) so that if Y stores 5 then X=Y+1 results in X storing 5+1 instead of 6 – To override the default and have arithmetic operations performed, start the assignment statement with the word let or embed the operation on the right hand side of the = in $((…)) – We can also use expr $((operation)) or where expr returns a value which can be used in an echo statement or the right hand side of an assignment statement Assignment Statement: Arithmetic Operations N=1 N=$((N+1)) let N+=1 let Y=$N+1 let Z=Y%N Y=$(($Y+1)) let Y=$((Y<<2)) ((Y++)) ((Q--)) N=$N+1 N is now 2 Reassign N to be 1 greater, N is now 3 Y is 4, the $ in front of N is not needed so this could be let Y=N+1 Z is 1 (4 / 3 has a remainder of 1) Y is now 5, we can omit the $ before Y Y is now 20 (<< is a left shift or doubling, so this doubles Y twice) Y is now 21 As Q had no value, it is now -1 N has the value of 3+1 X=$((Y+Z*)) yields the error message Y+Z*: syntax error: operand expected (error token is “*”) X=1 Y=2 Z=3 this statement assigns 3 variables their values Assignment Statement: String Operations • The expr statement can also be applied to one of several string operations: substr, index, length – expr command string [param(s)] • the number of params depends on the command – expr length string – returns the integer length of the string – expr substr string index length – returns the portion of the string starting at index and length characters long – expr index string string2 – return the index of the first character in string that is also in string2 Assignment Statement: String Operations • Assume PHRASE stores "hello world" • X=`expr substr "$PHRASE" 4 6` – X would store “lo wor” • X=`expr index "$PHRASE" nopq` – X would store 5 • X=`expr length "$PHRASE"` – X would store 11 Parameters • We can pass parameters to a script (similar to how we pass parameters to a function) • The parameters are supplied via the command line after the script’s name as in – ./somescript param1 param2 param3 • To utilize the parameters in the script use $1, $2, $3 etc • $0 is the name of the script itself • $# is the number of parameters supplied – we might use this for error checking to make sure the script received the right number of parameters • $@ is the entire list of parameters – we will use this in for loops Parameters: Example • This script receives 2 parameters which are used to control three substr operations #!/bin/bash STR1="Hello World" STR2="Frank Zappa Rocks!" STR3="abcdefg" expr substr "$STR1" $1 $2 expr substr "$STR2" $1 $2 expr substr $STR3 $1 $2 Assuming the script is called sub, ./sub 2 3 outputs ell ran bcd Notice the use of “” in the substr statements – required because STR1 and STR2 have blank spaces, but not needed in the third substr because STR3 does not include any blank spaces echo • We explored this earlier when we looked at Bash • In a script, echo serves much the same purpose • Here are several examples – echo Hello $NAME, how are you? – echo Your current directory is $PWD, your home is $HOME – echo Your current directory is `pwd`, your home is ~ – echo The date and time are $(date) – echo The number of seconds in a year is $((365*24*60*60)) • echo has a couple of options – -e – output escape characters non-literally (see next slide) – -n – do not end with line break (next echo will start on same line as this echo) echo Escape Character \\ \a \b \n \t \v \0### \xHH Meaning Backslash Bell (alert) Backspace New line Horizontal tab Vertical tab ASCII character matching the given octal value ### ASCII character matching the given hexadecimal value HH echo –e "Hello World!\nHow are you today?" Hello World How are you today? echo –e "January\tFebruary\tMarch\tApril" January February March April echo –e "\x40\x65\x6C\x6C\x6F" Hello echo –e Hello\\World Hello\World (recall that the quote marks are not needed for \\) echo • Here is an interesting example that demonstrates how “” and ‘’ can differ – LIST=*.txt – assume the current directory contains three .txt files, file1.txt, foo.txt, someotherfile.txt echo $LIST file1.txt foo.txt someotherfile.txt echo "$LIST" *.txt echo '$LIST' $LIST read • The input statement • After read, place the variable name(s) to store the items input – you can have as many variables in one read statement as desired • You should prompt the user before a read by outputting a message indicating what the user should input – this can either be through an echo (or echo –n) statement or by using –p “string” with read • see the next slide • The option –N number limits input to number characters as in –N 10 to limit the input to the first 10 characters entered • The option –t seconds causes the script to only wait seconds before timing out and continuing with the rest of the script – placing NULL values in any variables in the read statement read • echo Enter your name • read NAME Enter your name Frank • echo –n Enter your name • read NAME Enter your nameFrank • echo –n “Enter your name • read NAME ” • read –p “Enter your name ” NAME Enter your name Enter your name Frank Frank read • What happens if the user’s input doesn’t match the number of variables? – read X – read Y – user inputs 5 10 <enter> 20 <enter>, then X stores the list 5 10, Y stores 20 – read X Y – user inputs 5 <enter>, X stores 5, Y is the NULL value – read X Y – user inputs 5 10 15 <enter>, X stores 5, Y stores the list 10 15 • While lists or the NULL value are acceptable for variables, your script may cause an error if you try to operate on a list as if it were assumed that each variable is storing one number – Z=$((X+Y)) causes an error if X or Y is NULL or stores a list Selection Statement: Conditions • We compare two strings using == and != • We compare two integers using –eq, -ne, -lt, -le, gt, -ge • Conditions are placed in [ ] and each part of the condition must be separated by spaces – [ $x –gt 0 ] – [ $FIRST == Frank ] • notice that [$FIRST==Frank] yields a bizarre error message: [Frank: command not found – If we are comparing strings and the string has a blank space, you must put the string in “” as in – [ “$NAME” == “Frank Zappa” ] • omitting the “” around $NAME yields an error if $NAME is a string with at least one blank space Selection Statement: Conditions • A new syntax in Bash allows you to forego the $, spaces and to use <, >, !=, <=, >=, == in place of the 2-letter codes – Place the entire condition in ((…)) [ $X –ne $Y ] [ $Z –eq 0 ] [ $AGE –ge 21 ] [ $((X*Y)) –le $((Z+1)) ] ((X!=Y)) ((Z==0)) ((AGE>21)) ((X*Y<Z+1)) Selection Statement: Conditions • We can also use compound conditions by combining conditions using boolean operators AND (&&) and OR (||) • The condition must be placed inside [[ ]] notation or (( )) notation [[ $X –eq 0 && $Y –ne 0 ]] – true if X is 0 and Y is not 0 [[ $X –eq $Y || $X –eq $Z ]] – true if X equals either Y or Z [[ $X –gt $Y && $X –lt $Z ]] – true if X falls between Y and Z (( X > Y || X < Y – 1 )) – true if X is greater than Y or less than Y-1 NOTE: These are wrong: [[ $X –eq $Y –eq $Z ]] [[ $X –eq $Y && $Z ]] Selection Statement: File Conditions • We can also test properties of files using the notation – [ -condition filename ] or [ ! –condition filename ] Comparison Operator -e -f -d -b -c -p -h, -L -S -r, -w, -x -u, -g -O, -G -N Meaning File exists File is regular (not a directory or device type file) File is a directory File is a block device File is a character device File is a named pipe File is a symbolic link File is a domain socket File is readable, writable, executable User ID/Group ID set (the ‘s’ under executable permissions) You or your group owns the file File has been modified since last read Selection Statement: File Conditions • [ -f file1.txt ] is true if file1.txt exists and is a regular file. • [ -h file1.txt ] is true if file1.txt is a symbolic link to another file. • [[ -r file1.txt && -w file1.txt ]] is true if file1.txt is both readable and writable. • [ ! –e $FILENAME ] is true if the file whose name is stored in the variable FILENAME does not exist. • [ -O file1.txt ] is true if file1.txt is owned by the current user. • [ ! file1.txt –nt file2.txt ] is true if file1.txt is not newer than file2.txt. • [ $FILE1 –ef $FILE2 ] is true if the file whose name is stored in the variable FILE1 is the same as the file whose name is stored in the variable FILE2 • We can also test variables to see if they are NULL [ -z $VAR ] or have a value [ -n $VAR ] Selection Statement: if-then • Format: if [ condition ]; then action(s); fi • The ; can be omitted if each part of this statement is placed on a separate line – if [ -e file1.sh ]; then ./file1.sh; fi • if the file exists, execute it – if [ $X –gt 0 ]; then COUNT=$((COUNT+1)); fi • if the value in X is greater than 0 add 1 to COUNT – if [ $NAME == $USER ]; then echo Hello master, I am ready for you; fi • specialized greeting if the value in NAME is equal to the user’s login – if [[ $X –ne 100 && $USER != Frank ]]; then X=0; Y=100; NAME=Joe; fi Selection Statement: if-then if [ $NAME == $USER ]; then COUNT=$((COUNT+1)); fi if [ $NAME == $USER ] then COUNT=$((COUNT+1)) fi if [[ $X –ne 100 && $USER != Frank ]]; then X=0; Y=100; NAME=Joe; fi if [[ $X –ne 100 && $USER != Frank ]] then X=0 Y=100 Note: indentation is strictly for NAME=Joe our readability and ignored by fi the Bash interpreter Selection Statement: if-then-else • In many situations, if the condition is false we have an alternative action to perform – We use an if-then-else statement – format: if [ condition ]; then action(s); else action(s); fi • only one fi statement to end the entire instruction – If the condition is true, do the then actions (known as the then clause) – If the condition is false, do the else actions (known as the else clause) Selection Statement: if-then-else • if [ $SCORE –ge 60 ]; then GRADE=pass; else GRADE=fail; fi – the variable GRADE is assigned either pass or fail based on the value in SCORE • if [ $AGE –ge 21 ]; then echo You are allowed to gamble, good luck; else echo You are barred from the casino, come back in $((21-AGE)) years; fi – this statement selects between two outputs based on comparing your age to 21, notice the use of $((21-AGE)) in the echo statement, this computes the number of years under 21 that the user is • if [ $X –le 0 ]; then echo Warning, X must be greater than 0; else SUM=$((SUM+X)); fi – we are adding X to SUM if the value in X is greater than 0 Select Statement: Nested Statements • We can nest inside a then or else clause another if-then or if-then-else statement • This creates nested statements – We usually do this when the logic is complex – In the case of an “else if”, we use the word elif • if [ $SCORE –ge 90 ]; then GRADE=A; elif [ $SCORE –ge 80 ]; then GRADE=B; elif [ $SCORE –ge 70 ]; then GRADE=C; elif [ $SCORE –ge 60 ]; then GRADE=D; else GRADE=F; fi – Notice only one fi statement for the entire construct Select Statement: Nested Statements In this example, we input 3 numbers and use nested if-then-else logic To locate the largest of the three numbers, outputting the result #!/bin/bash read –p “Enter three numbers: ” X Y Z if [ $X –gt $Y ] then if [ $X –gt $Z ] then Largest=$X else Largest=$Z fi elif [ $Y –gt $Z ] then Largest=$Y else Largest=$Z fi echo The largest number is $Largest Select Statement: Nested Statements #!/bin/bash if [ $# -ne 3 ] then echo Illegal input, did not receive 3 parameters elif [ $2 == "+" ]; then echo $(($1+$3)) elif [ $2 == "-" ]; then echo $(($1-$3)) elif [ $2 == "*" ]; then echo $(($1*$3)) elif [ $2 == "/" ]; then echo $(($1/$3)) elif [ $2 == "%" ]; then echo $(($1%$3)) else echo Illegal arithmetic operator fi If the script is called calculator, we might call it with ./calculator 315 / 12 or ./calculator 41 + 815 but ./calculator 3 * 4 will lead to a problem because Bash will perform filename expansion on the *! so we replace the 3rd elif with elif [ $2 == "m" ]; then echo $(($1*$3)) Case Statement • The nested if-then-elif-else statement is one form of an n-way selection • The case statement is the other – Present each case as a list of possible values to match against an expression – Each case has one or more actions – Format: case expression in list1) action(s);; list2) action(s);; … listn) action(s);; esac Case Statement #!/bin/bash if [ $# -ne 3 ] then echo Illegal input, did not receive 3 parameters else case $2 in +) echo $(($1+$3));; -) echo $(($1-$3));; m) echo $(($1*$3));; /) echo $(($1/$3));; %) echo $(($1%$3));; *) echo Illegal arithmetic operator;; esac fi #!/bin/bash Case Statement echo What is your favorite color? read color Two additional examples case $color in (partial scripts) red ) echo Like red wine ;; blue ) echo Do you know there are no blue foods? ;; green ) echo The color of life ;; purple ) echo Mine too! ;; * ) echo That’s nice, my favorite color is purple ;; esac echo –n Do you agree to the licensing terms of the program? read answer case $answer in [yY] | [yY][eE][sS] ) ./program ;; [nN] | [nN][oO] ) echo I cannot run the program ;; * ) echo Invalid response ;; esac Case Statement • We use * ) to indicate a default pattern so that if no other pattern is selected, the last one is by default • Also notice that a set of actions for a given pattern ends with the notation ;; – In some programming languages, after the action(s) is performed, the next pattern is tested – In this way, it is possible that multiple pattern/actions can be selected – For the Bash Case statement, we can indicate “continue with further pattern checking” using ;& instead of ;; as shown below *[a-z]* ) … ;& *[A-Z]* ) … ;& *[0-9]* ) … ;; *)… Conditions Outside Selection Statements • A shortcut approach to writing if statements is available by combining a condition and an action • There are two forms – [ condition ] && action • perform the action if the condition is true – [ condition ] || action • Perform the action if the condition is false • We could use this to replace – if [ -f somescript.sh ]; then ./somescript.sh; fi – [ -f somescript.sh ] && ./somescript.sh Loops • There are four forms of loops in Bash – While loop – a conditional loop that tests a condition and if true, executes the loop body before testing the condition again – Until loop – a conditional loop that tests a condition and if false, executes the loop body before testing the condition again – For in loop – an iterator loop which iterates for each item in a list – For loop – newer syntax, similar to the C/C++/Java for loop Conditional Loops Example: Format: while [ condition ]; do action(s); done until [ condition ]; do action(s); done Notice the two loops use opposite conditions SUM=0 read –p "Enter the first number, negative to exit" VAL while [ $VAL –ge 0 ]; do SUM=$((SUM+VAL)) read –p "Enter next number, negative to exit" VAL done SUM=0 read –p "Enter the first number, negative to exit" VAL until [ $VAL –lt 0 ]; do SUM=$((SUM+VAL)) read –p "Enter next number, negative to exit" VAL done Conditional Loops • The previous loops were sentinel loops – Iterating based on a sentinel value, input by the user – We can also have user-controlled yes/no loops SUM=0 read –p "Do you have numbers to sum? " ANSWER while [ $ANSWER == Yes ]; do read –p "Enter the next number" VALUE SUM=$((SUM+VALUE)) read –p "Do you have additional numbers?" ANSWER done – The following loop is controlled by computation VALUE=1 while [ $VALUE –lt 1000 ]; do echo $VALUE $VALUE=$((VALUE*2)) done Counter Controlled Loop • Format: – for (( initialization; condition; increment)); do action(s); done • As with other Bash instructions in (( )), we can use arithmetic operations, omit $ for variables and use <, >, etc – We can also use shortcut statements for the increment as in x++, n--, z+=3 • Examples – for (( i=0; i<100; i++ )) – iterate from 0 to 99 – for(( i=0; i<100; i=i+2 )) – iterate from 0 to 99 by 2s – for ((i=100;i>j;i--)) – iterate from 100 down to j, whatever value j is storing Iterator For Loop • The traditional for loop in Bash iterates over a list – format: for VAR in LIST; do action(s); done – LIST can be an enumerated list like (1 2 3 4 5), a list stored in a variable, the value $@ (the input parameters), or the result returned from a function call or a Linux instruction like ls – The loop iterates one time per LIST item – For each loop iteration, the next value in LIST is stored in VAR which we can use in the loop body Iterator For Loop • Examples – Sum up all of the parameters of the script #!/bin/bash SUM=0 for VALUE in $@; do SUM=$((SUM+VALUE)) done echo $SUM – Output the word count for each file passed as a parameter (revised to the right) #!/bin/bash #!/bin/bash for filename in $@; do wc $filename done for filename in $@; do if [ -e $filename ]; then wc $filename fi done Shift Statement • We can also iterate through the input parameters by using a counter-controlled for loop and the shift statement – shift causes each parameter to move one position down so that $2 moves into $1, $3 into $2, etc #!/bin/bash NUMBER=$# SUM=0 for (( i=0; i<NUMBER; count++ )); do SUM=$((SUM+$1)) shift done echo $SUM The seq Instruction • You can also generate a list of numbers to control an iterator for loop using seq – seq [first] [step] last – returns the list of numbers from 1 to last (only given last) – returns the list of numbers from first to last (if given first and last) – returns the list of numbers from first to last skipping over step each time (if given first, step and last) #!/bin/bash for number in `seq 1 2 10`; do echo $number done Iterating Over Files • We use the iterator for loop and either filename expansion, or an ls command (or both) #!/bin/bash for filename in *.txt; do if [ -w $filename ]; then echo $filename fi done #!/bin/bash TOTAL=0 for filename in *; do if [ -f $filename ]; then ./$filename TOTAL=$((TOTAL+1)) fi done echo $TOTAL scripts started Iterating Over Files • Here we are adding up the file sizes of all regular files in the current directory • We use awk to withdraw the size as provided by the du command #!/bin/bash for file in *; do if [ -f $file ]; then TEMP=`du $file | awk '{print $1}'` TOTAL=$((TOTAL+TEMP)) fi done echo Total file size for regular files in ~ is $TOTAL Example • Here we see a script that receives two parameters, a filename and a string • It counts the number of occurrences of the string ($2) in the file ($1) #!/bin/bash count=0 if [ $# -ne 2 ]; then echo ERROR, illegal number of parameters elif [ ! –f $1 ]; then echo ERROR, $1 is not a regular file else for word in `cat $1`; do if [ $word == $2 ]; then count=$((count+1)) fi done echo The number of occurrences of $2 in $1 is $count fi The while read Statement • We can combine the while and the read – This loops while there is still input available – Format: while read varlist; do action(s); done • The user will have to input every item in varlist for every iteration, ending with control+d – Or, we can redirect a text file to be used as input by calling the script as ./script < inputfile – Or, we can redirect the input from a textfile by specifying done < inputfile Examples #!/bin/bash Input from keyboard unless executed with SUM=0 a redirection (/script < inputfile) while read X Y; do SUM=$((SUM+X*Y)) done echo $SUM #!/bin/bash Input must come from data file data.txt SUM=0 while read X Y; do SUM=$((SUM+X*Y)) done < data.txt echo $SUM Arrays • An array is a container structure – it stores multiple values • An array in most languages is a homogenous structure in that each value must be of the same type • In Bash, the array stores string or integers or a combination of the two • We access into the array by using an array index where the first index is given the number 0 – the notation is ${a[index]} as in ${a[0]} or ${a[5]} – an array of n items is numbered 0 to n-1 Arrays: Declaration, Initialization • You can declare an array using either – declare –a arrayname – arrayname=( ) • You can initialize an array using – – – – – arrayname=(value1 value2 value3 …) arrayname[index1]=value1 arrayname[index2]=value2 … or arrayname=([0]=value1 [1]=value2 [2]=value3…) • And you can combine the two – declare –a ARR=(apple banana cherry date) Arrays: Initialization • You do not have to use consecutive or ordered indices when initializing an array – ARR=([0]=apple [2]=banana [4]=cherry [6]=date) • although there is no good reason to do this either! – In this example, ARR[1], ARR[3] and ARR[5] are currently empty and can be used later Arrays: Accessing Elements • To put a value into an array element – ARR[index]=… • To pull a value out of an array element – =${ARR[index]} – echo ${ARR[index]} • You can also obtain the full array as a list – – – – ${array[@]} ${array[*]} “${array[@]}” – returns the list with each item quoted “${array[*]}” – returns the entire list as one quoted item – ${#array[@]} and ${#array[*]} – return number of elements in the array Array: Iterating Over • for value in ${a[@]}; do ...$value… ; done – $value stores each array value • for (( i=0; i<${#a[@]}; i++)); do …${a[i]}… ; done – ${a[i]} stores each array value • We would use the former case usually because it is easier (less code, easier to understand) • However, if we wanted to skip over elements we might use a variation of the second approach, for instance initializing i to 1 and increment i=i+2 would access every odd element Array: Example Script #!/bin/bash list=(www.nku.edu/~foxr/CIT371/file1.txt www.uky.edu/cse/welcome.html www.uc.edu/cs/csce310/hw1.txt www.xu.edu/welcome.txt www.cs.ul.edu/~wul/music/lyrics.txt) for i in ${list[@]}; do wget $i if [ ! –e $i ]; then echo Warning, $i not found! fi done Use wget to retrieve a number of URLs and output a warning if a particular URL is not found Alternate for loop: for (( i=0; i<${#list[@]}; i++)); do url=${list[i]} wget $url if [ ! –e $url ]; then echo Warning, … fi done String Manipulation • Recall expr substr string index length • A shortcut is ${string:index:length} – Except that index in this latter approach starts counting at 0 instead of 1 – So `expr substr $string 3 4` = ${string:2:4} • Example to obtain initials from First, Middle, Last – Initials="`expr substr $First 1 1` `expr substr $Middle 1 1` `expr substr $Last 1 1`" – Initials=${First:0:1}${Middle:0:1}${Last:0:1} String Regular Expression Matching • Format: `expr match string ‘\(regex\)’ echo `expr match "abcdefg" '\([a-dg]*\)'` abcd our regex only matches, in order, a, b, c, d and g, so the match stops after d echo `expr match "abcdefg" '\([a-dg]*$\)'` matches nothing because the $ requires that it match at the end of the string but the match begins at the front of the string and the g is separated from abcd by two characters that are not part of the regex echo `expr match "abcdef123" '\([a-z]*\)'` abcdef cannot match the digits echo `expr match "abcdef123" '\([a-z]*[0-9]\)'` abcdef1 only matches one digit echo `expr match "abcdef123" '\([a-z][0-9]\)'` will not match because there is no digit in the second position Functions • • • • • A function is a subroutine It supports reusable code and modularity A function is a stand-alone piece of code When called, we can pass it parameters In Bash, we define a function either as – name ( ) { … } • No parameters are listed in the ( ), the ( ) must be empty – or – function name {…} Functions: Defining a Function foo( ) { echo Function foo echo Illustrates syntax } function foo { echo Function foo echo Illustrates syntax } foo( ) { echo Function foo; echo Illustrates syntax; } Functions: Calling a Function • The function must be defined in the script before it can be called – To call a function, just list it by name with no parens • foo – Or, supply it with parameters like we supply parameters to a script, list them after the function name • foo 1 2 3 4 5 – The function can be defined in another script as long as that script is executed before we call the function • For instance, if we have a function, foo, defined in the file bar, we would need to have – ./bar (or source bar) • Before we have – foo Functions: Example #!/bin/bash // script instructions // function f1 defined // more script instructions // call to f1 // more script instructions The format of a script that has a function defined and then used #!/bin/bash runIt( ) { if [[ -f $1 && -x $1 ]]; then ./$1 else echo Error, $1 does not exist or not executable fi } for file in $@; do runIt $file done Functions: Local Variables • We can declare variables to be local to a function – local var1 var2 var3 … – If we do so, then these variables are known only within the function – Otherwise, if variables are used in the function, they are also known in the script after the function terminates • the code to the right outputs 10 5 5 • notice how TEMP retains its value after the function terminates #!/bin/bash X=5 Y=10 swap( ) { TEMP=$X X=$Y Y=$TEMP } swap echo $X $Y $TEMP Functions: return Statement • Functions in most programming languages return a value – We might for instance write a function to compute and return a value like a function to compute the square root of a parameter – In Bash, we can explicitly use a return statement – The return statement returns an integer value • this value is stored in the special variable $? • To view its value, use an echo $? after the function call – foo 1 2 3 4 5 – echo $? Functions: exit Statement • The exit statement is very similar to the return statement in that it returns an integer – however, with exit, the integer should be 0 or positive only • We will use exit to return an error code – again, we can see the result in $? • If the result is not 0, assume the function yielded an error, we can use this for error checking and correction Functions: example #!/bin/bash maximum( ) { if [ $# -eq 0 ]; then exit 9999 else max=$1 shift for num in $@; do if [ $num –gt $max ]; then max=$num; fi done fi return $max } maximum 5 1 2 6 4 0 3 echo $? Functions: Example #!/bin/bash X=0 #!/bin/bash X=0 foo( ) { foo( ) { X=$1 X=$((X+1)) echo $X } local X X=$1 X=$((X+1)) echo $X Prints 6 Prints 6 } echo $X foo 5 echo $X Prints 0 Prints 6 echo $X foo 5 echo $X Prints 0 Prints 0