- Here's the ren script that was posed in the latest
homework.
#!/bin/bash
for i
do
newname=`echo "$i" | tr " " "."`
mv "$i" $newname
done
- In this class, we will make a series of incremental modifications
to this file, to obtain a much better script. First, let us
trap the case when there are no arguments given. In this case,
we want to print an error message to this effect, and exit the
script. So, here is version 2 of the script.
#!/bin/bash
if [ $# = 0 ]
then
echo "ren: No arguments given."
exit 2
fi
for i
do
newname=`echo "$i" | tr " " "."`
mv "$i" $newname
done
There are several new features of this script. First, note the
use of an if conditional to test for the value of
$# (this variable gives the number of command line
arguments). We check if this is zero (note that the check for
equality is =, not ==) and if so, print
a message. Second observe that
we exit with an error code of 2 (this is a number
cooked up by us; the convention is to use 0 for normal exits and
some non-zero number for abnormal exits). Finally, the spaces around
the [ and ] are important!
- The above code is rather ugly. For instance, if we
change the name of the script from ren to rename,
then the above error message (inside the echo) must be
changed. One choice is to replace ren with $0.
Recall that $0 by default gives the name of the command
that was invoked on the command line. So we replace the offending
line with:
echo "$0: No arguments given."
- But even this is not so elegant. If we cd
into some other directory and run the script from there, via
some long path, e.g., as somelongpathnamehere/ren
(without any arguments), then the
message will be printed as:
somelongpathnamehere/ren: No arguments given.
Ideally, we want only
ren: No arguments given.
- The command basename does exactly this. It takes
a long pathname and chops off all but the end stuff (which is usually
the name of the command). Do a man basename to learn more.
So, now our script becomes:
#!/bin/bash
if [ $# = 0 ]
then
echo "`basename $0`: No arguments given."
exit 2
fi
for i
do
newname=`echo "$i" | tr " " "."`
mv "$i" $newname
done
Note the use of the ` quotes to execute the
basename commmand.
- Next, we want to trap the case when the script
does have argument(s), but the argument (file)
has no spaces in it. In this case, we do not need to rename
the file. Here's a solution.
#!/bin/bash
if [ $# = 0 ]
then
echo "`basename $0`: No arguments given."
exit 2
fi
for i
do
newname=`echo "$i" | tr " " "."`
if [ "$i" = $newname ]
then
echo "$i has no spaces. No need to rename."
else
mv "$i" $newname
fi
done
In the above script, we construct the newname anyway, then
see if the newname is the same as the original name (because there
were no spaces to begin with). If so, we print a new form of
error message. Note that the quotes around $i inside the
if construct are important! (why?)
- We have been rather frivolous with one aspect: the error
messages are being printed onto the standard output. This is not
ideal, because they should be printed onto the standard error
stream. But by default echo prints onto the standard
output; so we must redirect its output, as shown below.
#!/bin/bash
if [ $# = 0 ]
then
echo "`basename $0`: No arguments given." 1>&2
exit 2
fi
for i
do
newname=`echo "$i" | tr " " "."`
if [ "$i" = $newname ]
then
echo "$i has no spaces. No need to rename." 1>&2
else
mv "$i" $newname
fi
done
The 1>&2 essentially means "redirect whatever
used to be in stream 1 onto stream 2". Stream 1, by default
is the standard output, and stream 2 is the standard error.
You will see the difference between such redirection and
non-redirection, if you try to redirect the output of the
script at the command prompt, e.g.,
ren > output
In the old script, the error messages will go to the
file output. In the new script, the error
messages will be printed onto the screen (this is because
the > symbol only redirects standard output, not
standard error).
- Next, let us trap the case when there is a filename
given in the argument list, but no such file exists.
#!/bin/bash
if [ $# = 0 ]
then
echo "`basename $0`: No arguments given." 1>&2
exit 2
fi
for i
do
if [ -e "$i" ]
then
newname=`echo "$i" | tr " " "."`
if [ "$i" = $newname ]
then
echo "$i has no spaces. No need to rename." 1>&2
else
mv "$i" $newname
fi
else
echo "$i does not exist. Are you kidding me?" 1>&2
fi
done
The -e "$i" conditional checks if a file
exists with the name given by $i. Only if the
file exists, it proceeds to do the usual steps from the
old script. Else, it prints a message.
- How do we find out about such cute conditional checks?
Do a man test and it will show all the checks you
can do; for testing if something is a file, if something
is a directory, the time of change, etc.
- Btw, why should we do a man test? We haven't used
any test command! Actually, we have. A line
like
if [ "$i" = $newname ]
is really shorthand for
if test "$i" = $newname
- More checks to do! For instance, what if a file already
exists with the name corresponding to $newname? You
must trap this and suitably print yet another error message
(left as an exercise).
- More bells and whistles: Currently the replacement
character is a ".". Let us add a command line option
to the script that allows for alternate replacement
characters. We will adopt a command line format such as:
ren -s "p" Hello\ World
which means that the spaces must be replaced with "p", so that the file
given in the argument list is replaced to HellopWorld. Here's
a script:
#!/bin/bash
if [$# = 0]
then
echo "`basename $0`: No arguments given." 1>&2
exit 2
fi
if [ "$1" = "-s" ]
then
shift
replacechar="$1"
shift
else
replacechar="."
fi
for i
do
if [ -e "$i" ]
then
newname=`echo "$i" | tr " " $replacechar`
if [ "$i" = $newname ]
then
echo "$i has no spaces. No need to rename." 1>&2
else
mv "$i" $newname
fi
else
echo "$i does not exist. Are you kidding me?" 1>&2
fi
done
The shift command moves arguments to the left,
so that $1 now contains what $2 used to have,
$2 contains what $3 used to have, and so on.
Note that the -s option is truly that, i.e.,
optional.