Tuesday Tiny Techie Tip
Command-line looping
One of the coolest things about the UNIX shells is that
they are full-fledged programming languages (unlike the
popular bitty-box user interfaces), so you can do stuff
at the command line using the shell that would have to
be hard coded in every program on a PC.
For example, in DOS, you can change the extension on
a bunch of files at once with one command:
P:\> REN *.FOO *.BAR
In UNIX, the rename command (mv(1)) doesn't know about
parallel renames like this, so if you try to do the similar thing
you'll probably get an error:
% mv *.foo *.bar
usage: mv [-if] f1 f2 or mv [-if] f1 ... fn d1 (`fn' is a file or directory)
You get this error because unlike in DOS where the
wildcards are passed on unchanged to the program which has
to know how to expand them, the shell expands the wildcards
before it calls mv. So mv gets called
as "mv this.foo that.foo other.foo old.bar new.bar
junk.bar" which just doesn't make sense. And, even
worse, if there are only two files with a .foo extension,
and no .bar files, then mv takes you at your word
that "mv one.foo two.foo" is really what you
wanted to do. ("one.foo" gets renamed to "two.foo" which
get's destroyed! oops.)
-
Brief aside: If the shell is doing something which
you find incomprehensible, you can see what command it's
really trying to call by setting the verbosity level so
that the command is printed after the shell is finished
messing with it, but before it is called. To do this in
csh give the command "set echo", in
sh do "set -x".
Since the shell is a programming language, you can do the
bulk rename by writing a loop at the shell prompt:
% ls
althea.foo fire.foo kcj.foo playin.foo
% foreach file (*.foo)
? echo $file
? mv $file $file:r.bar
? end
althea.foo
fire.foo
kcj.foo
playin.foo
% ls
althea.bar fire.bar kcj.bar playin.bar
So what this does, is take the list provided inside the
parentheses on the foreach line, and run through
the following commands up to end once for each
value in the list, setting the variable file to
that value.
I often start these loops off with an "echo $file"
so I can see as each loop is completed. The mv
command above uses the csh(1) variable modifier to
return the filename without its extension (as described in
the TTTT from December 17th
1996) then adds the new extension.
If you use a Bourne shell-derived shell (like sh(1), bash(1),
or ksh(1)), the syntax is slightly different:
% sh
$ ls
althea.bar fire.bar kcj.bar playin.bar
% for file in *.bar ; do
> echo $file
> mv $file `echo $file | sed 's/\.bar$/.foo/'`
> done
althea.bar
fire.bar
kcj.bar
playin.bar
% ls
althea.foo fire.foo kcj.foo playin.foo
Since the Bourne shells don't support the variable
modifiers like csh(1), we have to get a little
more creative with our rename operation. Here we're using
command substitution as described in last week's tip to take the
existing name, and transform it into the new name using
sed(1).
Just to clarify, here's the general syntax of for-style
loops in each shell:
csh-like shells:
foreach var ( list of values for var )
commands using $var to give current value
end
sh-like shells:
for var in list of values for var ; do
commands using $var to give current value
done
Here's another example of using a loop at the shell prompt.
This time I'll use tcsh(1) so you can see what
the default prompts look like in that shell:
% foreach user ( `who | awk '{print $1}' | sort -u` )
foreach? echo -n "Here is how many processes $user has running:"
foreach? ps aux | grep ^$user | wc -l
foreach? end
Here is how many processes jeffy has running: 54
Here is how many processes vobadm has running: 33
That one is kind of complicated so if it gave me
information that I wanted to get more than once or twice a
year I'd probably want to write a permanent script to do it
so I wouldn't have to re-type it (and mis-type it) at the
command line every time. This is why so many people start
off writing scripts in the same shell they use
interactively, despite the relative shortcomings of
csh for script writing.
So if it's longer than you want to type and you want to
make a script use the Bourne shell form as detailed above
and you'll be much happier when it comes time to write
scripts longer than three lines.
Since we've jumped from UNIX usage to shell programming,
this tip may be rather baffling to some of you. If you
found any of it baffling, send me some email (click on my
name below) so I can either go over the baffling bits in a
future tip, or tailor future tips to be less baffling.
Tuesday Tiny Techie Tip -- 21 January 1997
Forward to (01/28/97)
Back to (01/14/97)
Written by Jeff Youngstrom
Up to the TTTT index
Tuesday Tiny Techie Tips are all © Copyright
1996-1997 by Jeff Youngstrom. Please ask permission before
reproducing any of this material.