Topics in this section are miscellaneous odds and ends which did not have direct relevance in other sections, or topics which are more advanced. The reader should have a good understanding of previous and related sections before preceding with these topics.
fname () { command1 ; command2 ; ... commandn ; }or split across multiple lines:
fname () { command1 command2 commandn }In either case, fname is the name used to define (and invoke) the function; the parenthesis inform the shell that the following is a function definition; and the commands compose the body of the function. Functions are typically defined for frequently used tasks that generally require more than one command. Frequently used functions can be defined within a startup file such as .profile so that they are available each time the user logs in. There are two distinct advantages to using functions:
Note however that functions only exist in the environment (i.e. shell/process) in which they are defined1. This means that their definition and usage cannot be inherited by child shells or processes. However, if the function is defined in a startup script (e.g. .profile), the function will be (re) defined when the child shell is created since the startup script will be executed (note this may take a command line option when starting the child shell such as ksh -l (ell)). Another option is to define frequently used functions in a single script that can easily be invoked in child processes.
If we wish to create a function to indicate the number of users on the system at a given time, we can do something like:
$ usernum () { who | wc -l ; } [Enter]The function is then run (or invoked) as follows:
$ usernum [Enter]Syntatically, if your function is going to be on a single line, (at least) a single space must follow the opening curly bracket and a semicolon must precede the closing curly bracket. Failure to follow this syntax will result in somewhat cryptic errors as follows:
9
$ usernum () {who | wc -l; } [Enter] ksh: syntax error: `}' unexpectedFunctions can also be split across multiple lines either from the command line or from within shell scripts. If a function is split across multiple lines from the command prompt, you will see the PS2 prompt until the function is completed. Thus the function from above might look as follows when entered on multiple lines:
$ usernum () { [Enter] > who | wc -l [Enter] > } [Enter]Parameters can be passed into functions using the positional parameter mechanism as described in a previous section. Thus we can create a function called sp (for show processes) to show all processes for a given user:
$
$ sp () { ps -eaf | grep $1 ; } [Enter]The function is then executed with the command line argument as follows:
$ sp mthomas [Enter] mthomas 10929 7632 0 22:34 pts/2 00:00:00 -ksh mthomas 9420 10929 0 22:36 pts/2 00:00:00 ps -eaf mthomas 9421 10929 0 22:36 pts/2 00:00:00 grep mthomasAs with other languages, shell functions can return a single value using the return command as follows:
#!/bin/ksh return n |
where n is an integer value. This value can then be queried using the $? construct. If no return statement is present, $? contains the return status from the last executed command.
Function definitions can be removed from existance using the undef command (as with undefining environment variables) as follows:
$ undef -f usernum [Enter] $ usernum [Enter]1 Some (but not all) newer shell implemenations allow for functions to be globally accessed, i.e. globally exporting the function definition. For example, the bash shell provides the
ksh: usernum: not found
export -f function_namestatement for making functions global. This capability has been purposfully omitted from some shell implemenations, e.g. ksh (more at opengroup.org).
find search_path selection_criteria action_if_found
$ find . -name foo -print [Enter]The dot (.) in this command specifies to start our search from our current directory (and implies all subdirectories); the -name option specifies we wish search for a file by its name (of foo); and the -print option tells the find command to print the results (note the -print option may be the default and thus be omitted in some newer versions of find, specifically those on Linux distros.)
Similarly, if we have no idea where a file might live, we can start at the top of the file system as follows:
$ find / -name services [Enter]In this example, we are starting the search from / (including all subdirectories), and looking for all files with the name of services. Typically when using find, we don't have good knowledge of where files live, or we wouldn't be searching to find them. Frequently then, it's useful to start our search from / (note that starting from / is time consuming on large filesystems).
While this command is useful, it brings up an interesting behavior of find. If you are starting from /, and not the root user, it is typical to get voluminous errors trying to search directories in which you don't have privilege to search through (which is understandable). In this case, erroneous output will overwhelm the valid output you are looking for, as in the following example:
$ find / -name services [Enter] find: /var/lib/slocate: Permission denied find: /var/lib/xdm/authdir: Permission denied find: /var/lib/nfs/statd: Permission denied find: /var/lib/ldap: Permission denied find: /var/log/samba: Permission denied find: /var/log/squid: Permission denied find: /var/log/iptraf: Permission denied find: /var/log/junkbuster: Permission denied find: /var/log/zebra: Permission denied find: /etc/zebra: Permission deniedA quick (and useful) workaround to this is the following:
$ find / -name services 2> /dev/null [Enter] /etc/services /etc/log.d/conf/services /etc/log.d/scripts/servicesRecall that 2> redirects the standard error to the destination file; in this example, the black hole known as /dev/null. Thus the only output in this example are the occurances of files named services.
The find command also allows the use of wildcard characters and pattern matching expressions with the -name selector, however when using this, the pattern should (usually) be enclosed in quotes (either as "pattern" or 'pattern' or escaped using \) as follows:
$ find / -name "serv*" 2> /dev/null [Enter] /etc/services /etc/pam.d/serviceconf /etc/security/console.apps/serviceconf /etc/CORBA/servers /etc/log.d/conf/services /etc/log.d/scripts/services /usr/bin/serviceconfLogical operators can also be used with the find command. Some of the common logical operators (in order of precedence, from highest to lowest) are:
Operator | Description |
( expr ) | forced precedence |
! expr | NOT expr, TRUE if expr is FALSE |
expr1 expr2 | implied AND, expr2 not evaluated if expr1 is FALSE |
expr1 -a expr2 | AND, expr2 not evaluated if expr1 is FALSE |
expr1 -o expr2 | OR, expr2 not evaluated if expr1 is TRUE |
If we examine the following find command:
$ find $HOME -name a.out -o -name "*.o" [Enter] ./temp/a.out ./temp/hello_world.owhere the intent is to search all directories below the home directory for files named a.out or files ending in a .o extension and display them (by default). We might also try to accomplish the same task with the following variation:
$ find $HOME -name a.out -o -name "*.o" -print [Enter] ./temp/hello_world.oNotice the difference in ouput from the two commands. While the second example seems logically similar to the first example, here is an example where the subtleties of find emerge. This command will not work as intended due to the fact that there exists an implied and between the -name "*.o" and the -print arguments. Thus, the implied and has a higher precedence than the -o (or) so only the *.o file matches will display. We can force the precedence we wish with the use of parentheses as follows:
$ find $HOME \( -name a.out -o -name "*.o" \) -print [Enter] ./temp/a.out ./temp/hello_world.oWhen using parentheses to force precedence, pay close attention to the mandatory escape characters preceding each parenthesis as well as the mandatory spaces following the first and preceding the terminating parenthesis.
The find command can also locate files based upon attributes other than file names. For example, one can use the find command to locate files based upon the time the file was last accessed. Thus to generate a list of files (residing in a temporary directory) older than 30 days (30 * 24 hours), we use find as follows:
$ find ./temp -atime +30 [Enter] ./temp/foo ./temp/data ./temp/hello_world.c ./temp/hello_world.o ./temp/a.outNote here that the + indicates more than 30 days, a - would match less than 30 days, and no leading sign would match exactly 30 days.
The find command allows actions other than printing on files matched from find. The most common action other than print is the -exec action, which allows the execution of other Unix commands performed on the matched files returned from find. The general syntax when using -exec is:
-exec command {} \;With -exec, the semicolon (;) terminates the command and the \ escapes the special meaning of the semicolon. It is impomrtant to note the space between the } and \. For example, if one wanted to change protection modes on all files ending with .html extensions in a directory to rwxr-xr-x (755), the find command would look as follows:
$ find ./html_docs -name "*.html" -exec chmod 755 {} \; [Enter] $Notice that no output appears from this command; find silently changes the protection modes and terminates. If one desires to see what files find has selected, you can add the -print command as follows:
$ find ./html_docs -name "*.html" -exec chmod 755 {} \; -print [Enter] ./html_docs/foo.html ./html_docs/bar.htmlCombining several of the features of find, we can examine the following example:
$ find $HOME \( -name a.out -o -name "*.o" \) -atime +30 -exec rm {} \; [Enter]where the search path begins in the $HOME directory; the search criteria are all files that match the name of a.out or *.o (or precedence forced or by inclusion of parentheses) and have not been accessed in the last 30 days (where the and is implied); the resulting action is to use -exec to remove all matched files.
Below you will find a table listing some of the more common find search criteria and resulting actions:
Search Criteria | Description |
-atime [+-]n | file was accessed [+-]n * 24 hours ago (note this is days) |
-mtime [+-]n | file was modified [+-]n * 24 hours ago |
-name pattern | selects files with names that match pattern |
-newer pattern | selects files newer than pattern |
-size [+-]n[bck] | file has size [+-]n, where b=512 byte blocks,c=bytes k=kBytes |
-type x | selects files of type x, f=ordinary file, d=directory |
Action | Description |
-exec command | execute command |
-ok command | like -exec, except prompt the user (Y/N) before running command |
displays results of find command |
Note: [+-] above implies an optional + or - character, where a + implies more than, a minus implies less than, and no preceding sign means exactly.
command1 && command2
where command2 is executed if and only if command1 returns a zero exit status.
Similarly, the OR command operator has the general form as follows:
command1 || command2
where command2 is executed if and only if command1 returns a non-zero exit status.
A simple example looks as follows:
An another example of using the conditional control operators with test follows:$ true && echo "Hello World!" [Enter] Hello World$ $ false && echo "Hello World!" [Enter]$ $ true || echo "Hello World!" [Enter]$ $ false || echo "Hello World!" [Enter] Hello World!
[ -d /proc/$1 ] || exit 1In this example, the test operator queries the existance of the directory file referenced by /proc/$1; if this directory does not exist, test will return a FALSE status, in which case the OR will cause the exit (with a status of 1) command to execute. If the file does exist (test returns zero), the exit command will not execute.
Yet another more sophisticated example comes from the (Linux) /etc/rc.d/init.d/network script as follows:
[ ! -x /sbin/ipx_internal_net -o ! -x /sbin/ipx_configure ] && IPX=While this code snippet may appear complex, if we examine the pieces, its intent should be quite understandable. First we identify the square brackets as those of the test operator. This tells us all text contained within the brackets are test commands/operators. Examining the pieces and referring to the test operators presented earlier, we note the -x is the file operator to test if the following file specification exists and has execute mode set; the -o is the or operator, and the ! is the NOT operator. Thus, the test portioncan be interpreted as:
return TRUE if either file /sbin/ipx_internal_net or /sbin/ipx_configure do not exist as executable filesThe next portion of the command is the AND conditional control opeator which will only force execution of the command following if the preceding command returns a zero exit status. And the final piece is the variable being assigned the value of NULL (see Env Variables). Thus, this entire commands can be interpreted as:
if either file /sbin/ipx_internal_net or /sbin/ipx_configure do not exist as executable files, set the variable IPX to NULL
©2024, Mark A. Thomas. All Rights Reserved.