< Day Day Up > |
4.4. Command SubstitutionFrom the discussion so far, we've seen two ways of getting values into variables: by assignment statements and by the user supplying them as command-line arguments (positional parameters). There is another way: command substitution, which allows you to use the standard output of a command as if it were the value of a variable. You will soon see how powerful this feature is. The syntax of command substitution is:[9]
$(UNIX command) The command inside the parentheses is run, and anything the command writes to standard output is returned as the value of the expression. These constructs can be nested, i.e., the UNIX command can contain command substitutions. Here are some simple examples:
Command substitution, like variable and tilde expansion, is done within double quotes. Therefore, our rule in Chapter 1 and Chapter 3 about using single quotes for strings unless they contain variables will now be extended: "When in doubt, use single quotes, unless the string contains variables or command substitutions, in which case use double quotes." Command substitution helps us with the solution to the next programming task, which relates to the album database in Task 4-1. The cut utility is a natural for this task. cut is a data filter: it extracts columns from tabular data. If you supply the numbers of columns you want to extract from the input, cut will print only those columns on the standard output. Columns can be character positions or—relevant in this example—fields that are separated by TAB characters or other delimiters.[11] Assume that the data table in our task is a file called albums and that it looks like this:
Depeche Mode|Speak and Spell|Mute Records|1981 Depeche Mode|Some Great Reward|Mute Records|1984 Depeche Mode|101|Mute Records|1989 Depeche Mode|Violator|Mute Records|1990 Depeche Mode|Songs of Faith and Devotion|Mute Records|1993 ... Here is how we would use cut to extract the fourth (year) column: cut -f4 -d\| albums The -d argument is used to specify the character used as field delimiter (TAB is the default). The vertical bar must be backslash-escaped so that the shell doesn't try to interpret it as a pipe. From this line of code and the getfield routine, we can easily derive the solution to the task. Assume that the first argument to getfield is the name of the field the user wants to extract. Then the solution is: fieldname=$1 cut -f$(getfield $fieldname) -d\| albums If we called this script with the argument year, the output would be: 1981 1984 1989 1990 1993 ... Task 4-6 shows another small task that makes use of cut.
The command who tells you who is logged in (as well as which terminal they're on and when they logged in). Its output looks like this: root tty1 Oct 13 12:05 michael tty5 Oct 13 12:58 cam tty23 Oct 13 11:51 kilrath tty25 Oct 13 11:58 The fields are separated by spaces, not TABs. Since we need the first field, we can get away with using a space as the field separator in the cut command. (Otherwise we'd have to use the option to cut that uses character columns instead of fields.) To provide a space character as an argument on a command line, you can surround it by quotes: $ who | cut -d' ' -f1 With the above who output, this command's output would look like this: root michael cam kilrath This leads directly to a solution to the task. Just type: $ mail $(who | cut -d' ' -f1) The command mail root michael cam kilrath will run and then you can type your message. Task 4-7 is another task that shows how useful command pipelines can be in command substitution.
Here is a function that allows you to list all files that were last modified on the date you give as argument. Once again, we choose a function for speed reasons. No pun is intended by the function's name: function lsd { date=$1 ls -l | grep -i "^.\{42\}$date" | cut -c55- } This function depends on the column layout of the ls -l command. In particular, it depends on dates starting in column 42 and filenames starting in column 55. If this isn't the case in your version of UNIX, you will need to adjust the column numbers.[12]
We use the grep search utility to match the date given as argument (in the form Mon DD, e.g., Jan 15 or Oct 6, the latter having two spaces) to the output of ls -l. This gives us a long listing of only those files whose dates match the argument. The -i option to grep allows you to use all lowercase letters in the month name, while the rather fancy argument means, "Match any line that contains 41 characters followed by the function argument." For example, typing lsd `jan 15' causes grep to search for lines that match any 41 characters followed by jan 15 (or Jan 15).[13]
The output of grep is piped through our ubiquitous friend cut to retrieve the filenames only. The argument to cut tells it to extract characters in column 55 through the end of the line. With command substitution, you can use this function with any command that accepts filename arguments. For example, if you want to print all files in your current directory that were last modified today, and today is January 15th, you could type: $ lp $(lsd 'jan 15') The output of lsd is on multiple lines (one for each filename), but LINEFEEDs are legal field separators for the lp command, because the environment variable IFS (see earlier in this chapter) contains LINEFEED by default. |
< Day Day Up > |