Project 52. Define Shell Functions"How do I define a shortcut for a commonly used command line, to which I can pass arguments?" This project explores Bash functions: how to define them; how to handle arguments; and how to list, export, and undefine them. Project 51 covers aliases. Learn More
Functions are similar to aliases. Bash philosophy sees functions replacing aliases in almost all circumstances. The declarative syntax of a function lends itself to a more complex definition than would ever be assigned to an alias. Functions are closer to mini shell scripts but execute in the environment of the current shell. (Scripts are executed by new shell instances.) Learn More
Define a FunctionLet's illustrate functions by defining one. A good candidate for a simple function is this command line. $ grep -riw "dear janet" . ./text/jan.txt:Dear Janet The command performs a recursive, case-insensitive search for whole-word matches, rooted in the current directory. We'll make this into a function and parameterize the search text, thereby encapsulating this useful sequence as a custom command. To define a function, we must use the correct syntax. Start the definition with the function name followed by (), and enclose the body of the function in braces. Each command in the body, including the last, must be terminated by a semicolon. Be aware that the space after the opening brace must be present. $ function-name () { command; command; ...;} A function definition may be split across lines, making entry easier when the body of the function contains many commands. Equivalent to the above example, we could have typed $ function-name () { > command > command > ... > } $ Tip
You'll notice that when Bash expects more input to complete the command, it'll prompt with > instead of $. Tip
Our new function, which we'll call grepx, can be defined by typing $ grepx () { > grep -riw $* . > } The sequence $* expands to the text of all arguments passed to the function. To use our new function, we simply type its name followed by a list of arguments (just as you would for any Unix command or shell script). $ grepx janet Binary file ./iChats/Janet Forbes on ....ichat matches Binary file ./iChats/Janet Forbes on ....ichat matches ./text/jan.txt:Dear Janet Debug a FunctionThere's a problem with this function, which illustrates a common and easy mistake to make. Type the following command. $ grepx "dear janet" grep: janet: No such file or directory Binary file ./iChats/Janet Forbes on ....ichat matches ./text/jan.txt:Dear Janet The error grep: janet: No such file or directory was perhaps not something you expected to see. The function as defined has grep searching for the word dear in the files janet and the current directory (.). It's equivalent to the command line $ grep -riw dear janet . The problem arises because quoting is lost when $* is expanded. To solve the problem, we must quote $* itself. Redefine the function by typing $ grepx () { > grep -riw "$*" . > } Now try again. $ grepx "dear janet" ./text/jan.txt:Dear Janet Tip
Function ArgumentsSo far, we've expanded "all arguments" by using $*. Argument (or, more strictly, parameter) expansion within a function is very similar to that in a shell script, and Bash functions support the following parameter expansions:
Enter and run this example function, which illustrates parameter expansion. $ params () { > echo "$# parameters given" > echo "the second is $2 and the first is $1" > echo "my name is $FUNCNAME and I was called from $0." > } $ params number-one "number two" 2 parameters given the second is number two and the first is number-one my name is params and I was called from -bash. Let's extend our simple grepx function to take additional arguments, which will be options we pass through to grep. A naïve attempt such as that shown next will not work. $ grepx -l "dear janet" grep: invalid option -- Usage: grep [OPTION]... PATTERN [FILE]... Try `grep --help' for more information. Our attempt forms the following command line. $ grep -ir "-l dear janet" . We must change grepx to expand its parameters in the form $1 "$2", not "$*". Additionally, the passing of extra options to grep must be made optional, so we have two situations:
Here's our new function, which employs an if-else statement. Remember that the body of a Bash function is just like a shell script. $ grepx () { > if [ "${1:0:1}" = "-" ] > then > grep -riw $1 "$2" . > else > grep -riw "$1" . > fi > } Learn More
We'll test the new function by specifying option -l to grep, which says to list the names of matching files but not display the matching line of text. $ grepx -l "dear janet" ./text/jan.txt Learn More
We used a neat trick to test the first character of parameter 1 instead of the whole string. The special expansion ${1:0:1} expands $1 but considers only a partial string from character 0 (the first character) of length 1. Recursive FunctionsA function can be recursive that is, it can call itself. I'll not teach recursive programming here but will point out a gotcha that is a side effect of the recursive nature of functions. We define a function ls to be shorthand for ls -al argument-list. The following attempt doesn't work but gets stuck in a loop. $ ls () { ls -al $*; } Function ls attempts to call the Unix command ls, but because recursion is allowed, the function ends up calling itself instead. Futile attempts to call command ls continue foreveror until we interrupt the process by pressing Control-c. The next version works. We use Bash's built-in command command to make function ls call the Unix command ls instead of itself. $ ls () { command ls -al $*; } List All FunctionsTo list all currently defined functions, type $ declare -F declare -f grepx declare -f params Alternatively, specify option -f (lowercase) to list function names and their definitions. Delete a FunctionDelete a function (or two) by using the unset command. $ unset grepx params Tcsh Shell FunctionsThe Tcsh shell does not support functions. However, Tcsh aliases support arguments (see Project 51). |