bash Args, Signals, Functions Administrative Shell Scripting COMP2101 Fall 2017
Positional Arguments It is quite common to allow the user of a script to specify what the script is to operate on (e.g. a file, directory, host, interface, etc.) The method shown here requires the script writer to know or figure out how many command line arguments there are before working with them #!/bin/bash # identify files with setuid or setgid permissions in a # directory specified on the command line dirname="$1" if [ -z "$dirname" ]; then echo "You didn't give me a directory name on the command line" >&2 exit 1 fi [ -d "$dirname" ] (echo "$dirname is not a directory" >&2 ; exit 2) if [! -r "$dirname" ]; then echo "You don't have read permission for $dirname" >&2 exit 3 fi if [ -x "$dirname" ]; then echo "Setuid or setgid files:" find "$dirname" -type f -executable -perm -4000 -ls 2>/dev/null sort -k 3 find "$dirname" -type f -executable -perm -2000 -ls 2>/dev/null sort -k 4 exit 0 else echo "You don't have access permission for $dirname" >&2 exit 3 fi
Command Line Processing A loop can be used to cycle through the available command line arguments and interpret what is there We can use shift to renumber the command line variables each time through the loop The case statement can be better than the if statement for this debug=0 while [ $# -gt 0 ]; do if [ "$1" == "-h" ]; then echo "Usage: $0 [-d level] [-h]" exit 0 elif [ "$1" == "-d" ]; then if [[ "$2" =~ ^[1-9]$ ]]; then debug="$2" echo "Debug mode ON, level $debug" shift else echo "Cannot set debug without a level from 1 to 9" >&2 exit 2 fi else echo "Usage: $0 [-d level] [-h]" echo "Argument '$1' not recognized" >&2 exit 2 fi shift done [ $debug -gt 0 ] && echo "Debug set to $debug" echo "This script will now do some useful task"
Command Line Processing The case statement allows us to more clearly show what we are testing for debug=0 while [ $# -gt 0 ]; do case "$1" in -h --help ) echo "Usage: $0 [-d 1-9] [-h]" exit 0 ;; -d --debug ) if [[ "$2" =~ ^[1-9]$ ]]; then debug="$2" shift echo "Debug mode ON, level $debug" else echo "Cannot set debug without a debug level from 1 to 9" >&2 exit 2 fi ;; * ) echo "Usage: $0 [-d level] [-h]" echo "Argument '$1' not recognized" >&2 exit 2 ;; esac shift done echo "Command line processing complete." [ $debug ] && echo "Debug turned on and set to $debug." # rest of script...
Unnamed Arguments Sometimes you need one or more data items for a script and want it on the command line, but don't want the user to have to put option letters or names in front of it (e.g. fixmydir dirname1 dirname2) In your command line processing, assign things on the command line without a dash to a variable used for the list of things to work on declare -a stufftoprocess while [ $# -gt 0 ]; do case "$1" in -h --help ) echo "Usage: $0 [-d level] [-h]" exit 0 ;; * ) stufftoprocess+=("$1") ;; esac shift done [ ${#stufftoprocess[@]} ] && echo "Will do work on ${stufftoprocess[@]} (${#stufftoprocess[@]} items)"
Practice Modify rolldice.sh to accept a count of dice and a number of sides as command line options, only asking the user for those numbers if they didn't give them on the command line Modify your show interfaces script to accept an interface name on the command line and only display information for that interface, as well as adding options to show the routing and external IP information
Error Output Failed commands often generate unwanted or irrelevant error messages That output can be saved as a log, sent to whoever should see it, or discarded Logs are usually kept in /var for programs we care about managing Redirect output using >&, >>&, &, or #>, or #>&# Use the logger command to send messages to the system logging daemon grapsnag >& /tmp/errormessage.txt if [ $?!= 0 ]; then logger -t $(basename "$0") -i -p user.warning -f /tmp/errormessage.txt && rm /tmp/errormessage.txt fi
Substitution and Expansion Revisited Command substitution is used to execute a command list in a separate shell process, invoked using `cmd`, $(cmd) The back-quotes and $() place the output of the sub-shell on the command line using substitution Variable expansion (getting data from variables), command substitution (getting the output of a command), and arithmetic expansion (using the results of arithmetic) are all ways of getting data for use on your command line as inline data bash performs word splitting after doing these expansions and substitutions which can lead to unexpected results if the data contains word delimiters such as tabs or newlines because they get turned into spaces before being put on the command line myvar="line 1 line 2 line 3" echo Default expansion: $myvar echo "Quoted expansion: $myvar" cat <<EOF ----- HERE Document ----- $myvar EOF The bash environment variable IFS controls which characters are turned into spaces if it exists - set it to an empty string to prevent this data conversion, or quote the variable expansion to force bash to leave the data in its original form as a single word, or use a HERE document which works much like double-quoting
Functions A function is a named script block, it creates a command you can use elsewhere in your script Inside a function, the positional parameters hold the arguments given when invoking the function instead of the arguments given when the script itself was run Functions end with the status code of the last command to run in the function, data results can be passed back on stdout, or using an intermediary means such as storing data in a file or variable function myfunction { list }
Practice Create a function to display a message on stderr which was supplied on the function command line Create a function to display command syntax help for the rolldice script Implement the error message function and the command syntax help function in the rolldice script wherever they make sense
Signals Signal are a way of notifying a process you want it to do something Processes can catch and process or ignore most signals, see signal(7) KILL, STOP, CONT cannot be caught or ignored - STOP/CONT are used to pause/resume processes(jobs) Signals can be sent using the kill command $ kill -SIGNAL pid $ somecommand ^Z $ jobs $ fg %# $ bg %# The shell can send some signals (INT(^C), QUIT(^\), STOP(^Z)) based on keyboard input and manage processes using the jobs, fg, and bg commands
Trap In a shell script, catching signals is done with the trap command trap can run a command when a signal is caught, functions are often useful for this function cleanup { rm /tmp/mytemporaryfiles logger -t `basename "$0"` -i -p user.info -s Cleaning up and aborting exit 1 } trap cleanup SIGHUP trap cleanup SIGTERM trap cleanup SIGINT
Dialog boxes For more complex user interactions such as choosing files, selecting items from a list, or presenting graphics on text-only terminals, there is a the dialog command dialog can ask for input/decisions or display information dialog is useful when you are working on a terminal and want to present interactions in a more user-friendly way than just displaying text e.g. userpicked=$(dialog --menu "choose one" 0 0 0 a 1 b 2 c 3 d 4 e 5 --output-fd 1) (for ((i=0;i<=100;i+=10)) do echo $i;sleep 1;done) dialog --gauge "progress" 7 60;clear foo=$(dialog --rangebox "Pick a value" 8 80 1 9 5 --output-fd 1);clear;echo "You chose $foo" dialog's command line can be inscrutable
Practice Create a script that waits for a user-specified number of seconds updating a progress bar each second showing how many seconds are left, and catches the interrupt and the quit signals. If it gets the interrupt signal (like from a ^C), have it reset the timer and the progress bar to the initial number of seconds and print out a message saying it is doing that. It should simply exit with a message if it receives the quit signal (like from a ^\). Name the script countdown.sh.
Script Organization It is helpful to organize nontrivial scripts in a consistent fashion Scripts can be divided into sections Sections may be placed in separate files if those sections are reusable (using the source command) by other scripts, e.g. function definitions Documentation VARIABLE definitions including inline data Aliases and functions Main script commands