Unix Power ToolsUnix Power ToolsSearch this book

28.9. Repeating and Varying Commands

28.9.1. A foreach Loop

When some people need to repeat a command on several files, the first thing they think of is command line editing (Section 30.14) or -- as we show here -- history substitution (Section 30.5):

-v Section 12.4, less Section 12.3

% cat -t -v /usr/fran/report | less
   ...
% ^fran/report^rob/file3
cat -t -v /usr/rob/file3 | less
   ...
% ^3^21
cat -t -v /usr/rob/file21 | less
   ...
%

The second substitution (changing 3 to 21) was quick to do, but the first one was longer. If there are lots of related commands like this, it can be easier to list all the variations at once -- then let the shell do the dirty work. To do that, use the shell's foreach loop in C-type shells -- or, in Bourne-type shells, use a for loop, shown later in this article. (zsh has both foreach and for loops.) You give the loop a list of the words that will change each time the command line is run. In this example, it's a list of filenames. The loop will step through the words, one by one, storing a word into a shell variable (Section 35.9), then running the command(s). The loop goes on until it has read all the words. For example:

% foreach file (/usr/fran/report /usr/rob/file3 /usr/rob/file21)
? cat -t -v $file | less
? end
   ...Shell runs cat -t -v /usr/fran/report | less...
   ...Shell runs cat -t -v /usr/rob/file3 | less...
   ...Shell runs cat -t -v /usr/rob/file21 | less...
%

The question marks (?) are secondary prompts (Section 28.12); the shell will keep printing them until you type the command end. Then the loop runs.

The list between the parentheses doesn't have to be filenames. Among other things, you can use wildcards (Section 1.13), backquotes (Section 28.14) (command substitution), variables (Section 35.9, Section 35.3), and the handy curly brace ({}) operators (Section 28.4). For example, you could have typed the above loop this way:

% foreach file (/usr/fran/report /usr/rob/file{3,21})
? cat -t -v $file | less
? end

If you want the loop to stop before or after running each command, add the C shell operator $<. It reads keyboard input and waits for a RETURN. In this case, you can probably ignore the input; you'll use $< to make the loop wait. For example, to make the previous loop prompt before each command line:

set Section 35.9

% foreach file (/usr/fran/report /usr/rob/file{3,21})
? echo -n "Press RETURN to see $file--"
? set x="$<"
? cat -t -v $file | less
? end
Press RETURN to see /usr/fran/report--RETURN
   Shell runs cat -t -v /usr/fran/report | less...
Press RETURN to see /usr/rob/file3--RETURN
   Shell runs cat -t -v /usr/rob/file3 | less...
Press RETURN to see /usr/rob/file21--RETURN
   Shell runs cat -t -v /usr/rob/file21 | less...

The loop parameters don't need to be filenames. For instance, you could send a personalized email (Section 1.21) message to five people this way:[87]

[87]If you're sending lots of mail messages with a loop, your system mailer may get overloaded. In that case, it's a good idea to put a command like sleep 5 (Section 25.9) on a separate line before the end. That will give the mailer five seconds to send each message.

cat - Section 12.2

% foreach person (John Cathy Agnes Brett Elma)
? echo "Dear $person," | cat - formletter | mail $person
? end

The first line of the first letter will be "Dear John,"; the second letter "Dear Cathy,"; and so on.

Want to take this idea further? It's a part of shell programming (Section 35.2). I usually don't recommend shell programming with the C shell, but this is a handy technique to use interactively.

28.9.2. A for Loop

The for loop in Bourne-type shells is like the foreach loop shown earlier: it loops through a list of words, running one or more commands for each word in the list. This saves time when you want to run the same series of commands separately on several files.

Let's repeat an earlier example:

$ for file in /usr/fran/report /usr/rob/file2 /usr/rob/file3
> do
> cat -t -v $file | less
> done
   ...Shell runs cat -t -v /usr/fran/report | less...
   ...Shell runs cat -t -v /usr/rob/file2 | less...
   ...Shell runs cat -t -v /usr/rob/file3 | less...
$

The greater-than signs (>) are secondary prompts (Section 28.12); the Bourne shell will keep printing them until you type the command done. Then it runs the loop. You don't have to press RETURN after the do; you can type the first command on the same line after it.

In a shell script, the loop body (the lines between do and done) is usually indented for clarity.

The list after the in doesn't have to be filenames. Among other things, you can use backquotes (Section 28.14) (command substitution), variables (Section 35.9, Section 35.3), wildcards (Section 33.1), and, on shells like bash that have them, curly brace ({}) operators (Section 28.4). For example, you could have typed the previous loop this way:

$ for file in /usr/fran/report /usr/rob/file[23]
> do cat -t -v $file | less
> done

If you want the loop to stop before or after running each command, add the shell's read command (Section 35.18). It reads keyboard input and waits for a RETURN. In this case, you can ignore the input; you'll use read just to make the loop wait. For example, to make the above loop prompt before each command line:

$ for file in /usr/fran/report /usr/rob/file[23]
> do
> echo -n "Press RETURN to see $file--"
> read x
> cat -t -v $file | less
> done
Press RETURN to see /usr/fran/report--RETURN
   Shell runs cat -t -v /usr/fran/report | less...
Press RETURN to see /usr/rob/file2--RETURN
   Shell runs cat -t -v /usr/rob/file2 | less...
Press RETURN to see /usr/rob/file3--RETURN
   Shell runs cat -t -v /usr/rob/file3 | less...

Section 35.21 has more information about the for loop. Section 36.12 shows how to make a for loop that varies several parameters at once.

-- JP



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.