Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
---|---|---|
Prev | Chapter 34. Miscellany | Next |
To keep a record of which user scripts have run during a particular sesssion or over a number of sessions, add the following lines to each script you want to keep track of. This will keep a continuing file record of the script names and invocation times.
1 # Append (>>) following to end of each script tracked. 2 3 date>> $SAVE_FILE #Date and time. 4 echo $0>> $SAVE_FILE #Script name. 5 echo>> $SAVE_FILE #Blank line as separator. 6 7 # Of course, SAVE_FILE defined and exported as environmental variable in ~/.bashrc 8 # (something like ~/.scripts-run) |
The >> operator appends lines to a file. What if you wish to prepend a line to an existing file, that is, to paste it in at the beginning?
1 file=data.txt 2 title="***This is the title line of data text file***" 3 4 echo $title | cat - $file >$file.new 5 # "cat -" concatenates stdout to $file. 6 # End result is 7 #+ to write a new file with $title appended at *beginning*. |
Of course, sed can also do this.
A shell script may act as an embedded command inside another shell script, a Tcl or wish script, or even a Makefile. It can be invoked as as an external shell command in a C program using the system() call, i.e., system("script_name");.
Put together files containing your favorite and most useful definitions and functions. As necessary, "include" one or more of these "library files" in scripts with either the dot (.) or source command.
1 # SCRIPT LIBRARY 2 # ------ ------- 3 4 # Note: 5 # No "#!" here. 6 # No "live code" either. 7 8 9 # Useful variable definitions 10 11 ROOT_UID=0 # Root has $UID 0. 12 E_NOTROOT=101 # Not root user error. 13 MAXRETVAL=256 # Maximum (positive) return value of a function. 14 SUCCESS=0 15 FAILURE=-1 16 17 18 19 # Functions 20 21 Usage () # "Usage:" message. 22 { 23 if [ -z "$1" ] # No arg passed. 24 then 25 msg=filename 26 else 27 msg=$@ 28 fi 29 30 echo "Usage: `basename $0` "$msg"" 31 } 32 33 34 Check_if_root () # Check if root running script. 35 { # From "ex39.sh" example. 36 if [ "$UID" -ne "$ROOT_UID" ] 37 then 38 echo "Must be root to run this script." 39 exit $E_NOTROOT 40 fi 41 } 42 43 44 CreateTempfileName () # Creates a "unique" temp filename. 45 { # From "ex51.sh" example. 46 prefix=temp 47 suffix=`eval date +%s` 48 Tempfilename=$prefix.$suffix 49 } 50 51 52 isalpha2 () # Tests whether *entire string* is alphabetic. 53 { # From "isalpha.sh" example. 54 [ $# -eq 1 ] || return $FAILURE 55 56 case $1 in 57 *[!a-zA-Z]*|"") return $FAILURE;; 58 *) return $SUCCESS;; 59 esac # Thanks, S.C. 60 } 61 62 63 abs () # Absolute value. 64 { # Caution: Max return value = 256. 65 E_ARGERR=-999999 66 67 if [ -z "$1" ] # Need arg passed. 68 then 69 return $E_ARGERR # Obvious error value returned. 70 fi 71 72 if [ "$1" -ge 0 ] # If non-negative, 73 then # 74 absval=$1 # stays as-is. 75 else # Otherwise, 76 let "absval = (( 0 - $1 ))" # change sign. 77 fi 78 79 return $absval 80 } 81 82 83 tolower () # Converts string(s) passed as argument(s) 84 { #+ to lowercase. 85 86 if [ -z "$1" ] # If no argument(s) passed, 87 then #+ send error message 88 echo "(null)" #+ (C-style void-pointer error message) 89 return #+ and return from function. 90 fi 91 92 echo "$@" | tr A-Z a-z 93 # Translate all passed arguments ($@). 94 95 return 96 97 # Use command substitution to set a variable to function output. 98 # For example: 99 # oldvar="A seT of miXed-caSe LEtTerS" 100 # newvar=`tolower "$oldvar"` 101 # echo "$newvar" # a set of mixed-case letters 102 # 103 # Exercise: Rewrite this function to change lowercase passed argument(s) 104 # to uppercase ... toupper() [easy]. 105 } |
Use special-purpose comment headers to increase clarity and legibility in scripts.
1 ## Caution. 2 rm -rf *.zzy ## The "-rf" options to "rm" are very dangerous, 3 ##+ especially with wildcards. 4 5 #+ Line continuation. 6 # This is line 1 7 #+ of a multi-line comment, 8 #+ and this is the final line. 9 10 #* Note. 11 12 #o List item. 13 14 #> Another point of view. 15 while [ "$var1" != "end" ] #> while test "$var1" != "end" |
Using the $? exit status variable, a script may test if a parameter contains only digits, so it can be treated as an integer.
1 #!/bin/bash 2 3 SUCCESS=0 4 E_BADINPUT=65 5 6 test "$1" -ne 0 -o "$1" -eq 0 2>/dev/null 7 # An integer is either equal to 0 or not equal to 0. 8 # 2>/dev/null suppresses error message. 9 10 if [ $? -ne "$SUCCESS" ] 11 then 12 echo "Usage: `basename $0` integer-input" 13 exit $E_BADINPUT 14 fi 15 16 let "sum = $1 + 25" # Would give error if $1 not integer. 17 echo "Sum = $sum" 18 19 # Any variable, not just a command line parameter, can be tested this way. 20 21 exit 0 |
The 0 - 255 range for function return values is a severe limitation. Global variables and other workarounds are often problematic. An alternative method for a function to communicate a value back to the main body of the script is to have the function write to stdout the "return value", and assign this to a variable.
Example 34-6. Return value trickery
1 #!/bin/bash 2 # multiplication.sh 3 4 multiply () # Multiplies params passed. 5 { 6 7 local product=1 8 9 until [ -z "$1" ] # Until uses up arguments passed... 10 do 11 let "product *= $1" 12 shift 13 done 14 15 echo $product # Will not echo to stdout, 16 } #+ since this will be assigned to a variable. 17 18 val1=`multiply 15383 25211` 19 echo "val1 = $val1" # 387820813 20 21 val2=`multiply 25 5 20` 22 echo "val2 = $val2" # 2500 23 24 val3=`multiply 188 37 25 47` 25 echo "val3 = $val3" # 8173300 26 27 exit 0 |
The same technique also works for alphanumeric strings. This means that a function can "return" a non-numeric value.
1 capitalize_ichar () # Capitalizes initial character 2 { #+ of argument string(s) passed. 3 4 string0="$@" # Accepts multiple arguments. 5 6 firstchar=${string0:0:1} # First character. 7 string1=${string0:1} # Rest of string(s). 8 9 FirstChar=`echo "$firstchar" | tr a-z A-Z` 10 # Capitalize first character. 11 12 echo "$FirstChar$string1" # Output to stdout. 13 14 } 15 16 newstring=`capitalize_ichar "each sentence should start with a capital letter."` 17 echo "$newstring" # Each sentence should start with a capital letter. |
It is even possible for a function to "return" multiple values with this method.
Example 34-7. Even more return value trickery
1 #!/bin/bash 2 # sum-product.sh 3 # A function may "return" more than one value. 4 5 sum_and_product () # Calculates both sum and product of passed args. 6 { 7 echo $(( $1 + $2 )) $(( $1 * $2 )) 8 # Echoes to stdout each calculated value, separated by space. 9 } 10 11 echo 12 echo "Enter first number " 13 read first 14 15 echo 16 echo "Enter second number " 17 read second 18 echo 19 20 retval=`sum_and_product $first $second` # Assigns output of function. 21 sum=`echo "$retval" | awk '{print $1}'` # Assigns first field. 22 product=`echo "$retval" | awk '{print $2}'` # Assigns second field. 23 24 echo "$first + $second = $sum" 25 echo "$first * $second = $product" 26 echo 27 28 exit 0 |
Using the double parentheses construct, it is possible to use C-like syntax for setting and incrementing variables and in for and while loops. See Example 10-11 and Example 10-16.
A useful scripting technique is to repeatedly feed the output of a filter (by piping) back to the same filter, but with a different set of arguments and/or options. Especially suitable for this is tr.
1 # From "wstrings.sh" example. 2 3 wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \ 4 tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '` |
The run-parts command is handy for running a set of command scripts in sequence, particularly in combination with cron or at.
It would be nice to be able to invoke X-Windows widgets from a shell script. There happen to exist several packages that purport to do so, namely Xscript, Xmenu, and widtools. The first two of these no longer seem to be maintained. Fortunately, it is still possible to obtain widtools here.
![]() | The widtools (widget tools) package requires the XForms library to be installed. Additionally, the Makefile needs some judicious editing before the package will build on a typical Linux system. Finally, three of the six widgets offered do not work (and, in fact, segfault). |
For more effective scripting with widgets, try Tk or wish (Tcl derivatives), PerlTk (Perl with Tk extensions), tksh (ksh with Tk extensions), XForms4Perl (Perl with XForms extensions), Gtk-Perl (Perl with Gtk extensions), or PyQt (Python with Qt extensions).