How does one make a recursive script in the bash shell? For instance, if I wanted a script that stepped through the directory structure starting with . and going to the deepest level, and ran make in each folder in the tree.
Yeah, I know a makefile could be setup to do this, but it's not allowed. No, I don't understand it either.
Nah, it's the government sector, which sometimes feels as pointless as homework, but without the "I'll escape here one day" feeling.
The system is setup so that some things have to be done in csh, some in bash, some as root, some absolutely never allowed as root, some that are best done rlogged into one machine, but that machine can never be used for actually running the sim, etc., etc. It's a monstrous conglomeration of homemade tools and outdated hardware.
This is why all your variables (with carefully selected exceptions) should be local. From the iron rules of software engineering:
Thou shalt maketh your variables local and declareth them for everything else will be an abomination in the eyes of every compiler and every interpreter you might or might not run across.
Exceptingeth a thell which doeth not thupport local, or PL/M compiler which every variable ith global unleth you declareth the prothedure reentrant, or RPG on your A-eth-400. And-lo, they altho remembered Bathic ath well.
I wrote this to fix windows directories (that come inside zip files people send me)
that have spaces in directory names. Renaming files is easier.
With directories you have to go to the bottom of the tree, and then
rename going upwards, on the way out. I couldn't find a script that worked
(I found this old request instead, and several others) so I wrote this
yesterday. It seems to work just fine.
#!/bin/bash
start=$1
from=$2
to=$3
fix_from_bottom_up()
{
for file in $1/*
do
if [ -d "$file" ]; then
fix_from_bottom_up "$file"
fi
done
if [ -d "$1" ]; then
base=`basename $1`
dir=`dirname $1`
fixedbase=`echo $base | sed "s/${from}/${to}/g"`
if [ "$base" != "$fixedbase" ];then
mv $1 $dir/$fixedbase
fi
fi
}
if [ -z "$1" ]; then
echo "use: seddirnames startdir from to"
exit;
fi
fix_from_bottom_up "$start"
Kudos, porter!! Thanks for sharing this sample code. It's very well written!
For those of you learning shell scripting, there's some good lessons in this short piece of code, so read on and I'll point out what I really like about porter's sample code:
1) Having the find command do the work, saving the trouble of writing and debugging a recursive function. Always use the tools already included in the shell and/or the UNIX/Linux command set before re-inventing the wheel!
2) Use of the read command non-interactively with the find command; very useful!
3) Use of parenthese to block all the code inside the do...done loop; this sure eliminates mucking about with semi-colons at the end of each line (and having some lines "break" because of unnecessary semi-colons).
4) Error handling: Good example of testing the exit code / return status of a command. e.g. if $test "$?" = "0" # if the exit code ($?) is True (0 zero), then the command succeeded.
------------
NOTES: The use of the double quotes around the variable $N makes the test code above necessary, should the directory (file) name contain one or more blank spaces. With the variable N in single quotes, the cd command would work regardless of blank spaces in the file name. Read about Quoting to find out more.
If a directory name contained non-printable charactes (symbols, device codes, etc.), and the read command never passed them into the variable N, then the cd command would fail.
Directory permissions might cause cd to fail; another reason to handle expected and unexpected errors!
The code below (as written anyway) won't always work,
I don't believe. Not if the purpose of run-some-script.sh
is to rename directories on the fly. Find (it must, I think)
make a list of names to iterate over. So, if you change a directory
name, then all the subdirectories below that one suddenly have
invalid names, and the script will blow up. There might be a way
to use find -depth......but at least as written, I don't think the code
below would work for directory renaming.....
find . -type d | while read N
do
(
cd "$N"
if test "$?" = "0"
then
run-some-script.sh
fi
)
done