Even a graphical file manager might not be enough to help you step through a complicated directory tree with multiple layers of subdirectories. Which directories have you visited so far, and which are left to go? This article shows a simple way, using shell arrays, to step through a tree directory-by-directory. The technique is also good for stepping through lists of files — or almost any collection of things, over a period of time — of which you don't want to miss any. At the end are a couple of related tips on using arrays.
Let's start with a quick overview of expanding array values; then we'll
look at specifics for each shell. A dollar sign ($
) before the name of a shell variable gives you its value.
In the C shells and zsh, that gives all
members of an array. But, in the Korn shell and bash2, expanding an array value without the index gives just
the first member. To pick out a particular member, put its number in square
brackets after the name; in ksh and
bash2, you also need to use curly
braces ({}
). A hash mark (#
) gives the number of members. Finally, you
can use range operators to choose several members of an array.
Here's a practical example that you might use, interactively, at a shell
prompt. You're cleaning your home directory tree. You store all the
directory names in an array named d
. When
you've cleaned one directory, you go to the next one. This way, you don't
miss any directories. (To keep this simple, I'll show an example with just
four directories.)
If you don't want to use shell commands to browse the directories, you could use a command to launch a graphical file browser on each directory in the array. For instance, make the nextdir alias launch Midnight Commander with mc $d[1].
%set d=(`find $home -type d -print`)
%echo $#d directories to search: $d
4 directories to search: /u/ann /u/ann/bin /u/ann/src /u/ann/lib %alias nextdir 'shift d; cd $d[1]; pwd; ls -l'
%cd $d[1]
...clean up first directory... %nextdir
/u/ann/bin total 1940 lrwxrwxrwx 1 ann users 14 Feb 7 2002 ] -> /usr/ucb/reset -r-xr-xr-x 1 ann users 1134 Aug 23 2001 addup ...clean up bin directory... %nextdir
/u/ann/src ...do other directories, one by one... %nextdir
d: Subscript out of range.
You store the array, list the number of directories, and show their names. You then create a nextdir alias that changes to the next directory to clean. First, use the C shell's shift command; it "throws away" the first member of an array so that the second member becomes the first member, and so on. Next, nextdir changes the current directory to the next member of the array and lists it. (Note that members of a C shell array are indexed starting at 1 — unlike the C language, which the C shell emulates, where indexes start at 0. So the alias uses cd $d[1].) At the end of our example, when there's not another array member to shift away, the command cd $d[1] fails; the rest of the nextdir alias isn't executed.
Bourne-type
shells have a different array syntax than the C shell. They don't have a
shift command for arrays, so we'll
use a variable named n
to hold the array
index. Instead of aliases, let's use a
more powerful shell function. We'll show ksh
and bash2 arrays, which are indexed starting at
0. (By default, the first zsh
array
member is number 1.) The first command that follows, to store the array, is
different in ksh and bash2 — but the rest of the example is the
same on both shells.
bash2$d=(`find $HOME -type d -print`)
ksh$set -A d `find $HOME -type d -print`
$echo ${#d[*]} directories to search: ${d[*]}
4 directories to search: /u/ann /u/ann/bin /u/ann/src /u/ann/lib $n=0
$nextdir( ) {
>if [ $((n += 1)) -lt ${#d[*]} ]
>then cd ${d[$n]}; pwd; ls -l
>else echo no more directories
>fi
>}
$cd ${d[0]}
...clean up first directory... $nextdir
/u/ann/bin total 1940 lrwxrwxrwx 1 ann users 14 Feb 7 2002 ] -> /usr/ucb/reset -r-xr-xr-x 1 ann users 1134 Aug 23 2001 addup ...do directories, as in C shell example... $nextdir
no more directories
If you aren't a programmer, this may look intimidating — like something you'd never type interactively at a shell prompt. But this sort of thing starts to happen — without planning, on the spur of the moment — as you learn more about Unix and what the shell can do.
We don't use quite all the
array-expanding operators in the
previous examples, so here's a quick overview of the rest. To expand a range
of members in ksh
and
bash2, give the first and last indexes
with a dash (-
) between them. For
instance, to expand the second, third, and fourth members of array arrname
, use ${arrname[1-3]}. In zsh
, use a comma (,) instead — and
remember that the first zsh array
member is number 1; so you'd use ${arrname[2-4]} in zsh.
C shell wants $arrname[2-4]. If the last number of a range is omitted
(like ${arrname[2-]} or $arrname[2-]), this gives you all members
from 2 through the last.
Finally, in all shells except
zsh, remember that expanded values
are split into words at space characters. So if members of an array have
spaces in their values, be careful to quote them. For instance, Unix
directory names can have spaces in them — so we really should have used
cd "$d[1]"
in the newdir alias and cd
"${d[$n]}"
in the newdir
function.[2] If we hadn't done this, the cd command could have gotten multiple argument words. But it
would only pay attention to the first argument, so it would probably
fail.
To expand a range of members safely, such as ${foo[1-3]}
in bash2 and
ksh, you need ugly expressions
without range operators, such as "${foo[1]}"
"${foo[2]}" "${foo[3]}"
. The C shell has
a :q
string modifier that says
"quote each word," so in csh you can
safely use $foo[1-3]:q
. It's hard to
quote array values, though, if you don't know ahead of time how many there
are! So, using ${foo[*]}
to give all
members of the foo array suffers from
word-splitting in ksh and bash2 (but not in zsh, by default). In ksh and bash2, though,
you can use "${foo[@]}"
, which expands
into a quoted list of the members; each member isn't split into separate
words. In csh, $foo[*]:q
does the trick.
— JP