Previous | Table of Contents | Next |
When dealing with any shell script, check the syntax of the script before trying to execute it. This enables you to fix most problems.
To enable syntax checking, use the -n option as follows:
/bin/sh -n script arg1 arg2 argN
Here script is the name of a script and arg1 through argN are the arguments for that script. This command generates output only if errors occur in the specified script.
Check the syntax of the following script (the line numbers are included for your reference):
1 #!/bin/sh 2 3 YN=y 4 if [ $YN = "yes" ] 5 echo "yes" 6 fi
Can you spot the error?
If this script is stored in the file buggy1.sh, check its syntax as follows:
$ /bin/sh -n ./buggy1.sh
The output looks like the following:
./buggy1.sh: syntax error at line 7: 'fi' unexpected
This tells you that when the shell tried to read line 7, it found that the fi statement on line 6 was unexpected. By now you have probably figured out that the reason the shell was trying read line 7 is that the if statement on line 4
if [ $YN = "y" ]
is not terminated with a then statement. This line should read as follows:
if [ $YN = "y" ] ; then
Making this change, you find that the syntax of the script is okay because the command
$ /bin/sh -n buggy1.sh
produces no output.
After looking at the shell script in the previous example, you might be wondering why you couldnt simply execute the shell script to determine the problem. After all, the command
$ /bin/sh ./buggy1.sh
produces the output
buggy1.sh: syntax error at line 7: 'fi' unexpected
This output is identical to the output of the following command:
$ /bin/sh -n ./buggy1.sh
For this script, it does not matter whether you use the syntax checking mode, but this is not always the case. As an example, consider the following script (the line numbers are included for your reference):
1 #!/bin/sh 2 3 Failed() { 4 if [ $1 -ne 0 ] ; then 5 echo "Failed. Exiting." ; exit 1 ; 6 fi 7 echo "Done." 8 } 9 10 echo "Deleting old backups, please wait \c" 11 rm -r backup > /dev/null 2>&1 12 Failed $? 13 14 echo "Make backup (y/n)? \c" 15 read RESPONSE 16 case $RESPONSE in 17 [yY]|[Yy][Ee][Ss]|*) 18 echo "Making backup, please wait \c" 19 cp -r docs backup 20 Failed 21 [nN]|[Nn][Oo]) 22 echo "Backup Skipped." ;; 23 esac
There are at least three errors in this script. See if you can find them.
If this script is in a file called buggy2.sh, executing it produces the following output:
Deleting old backups, please wait Done. Make backup (y/n)?
Entering y at the prompt produces the following error:
./buggy3.sh: syntax error at line 21: ')' unexpected
Due to a bug in the script, you cant make a backup, and you have already lost your previous backup. As you can imagine, this is a very bad situation.
The reason the script gets that far before detecting an error is that the shell reads and executes each line of a shell script individually, just like it does on the command line. Here the shell reads and executes lines until it encounters a problem.
By using the -n option, the script does not execute. Instead, each line is checked to make sure that it has the correct syntax. This helps you avoid the situation encountered by running the buggy2.sh script because only the error is reported:
./buggy2.sh: syntax error at line 21: ')' unexpected
Now that you know why syntax checking should be employed, you can track down the source of the problem.
Looking at line 21 of buggy2.sh
21 [nN]|[Nn][Oo])
it is hard to see why the shell thinks the parenthesis ) is unexpected. Sometimes knowing where a syntax error occurs is not enoughyou have to know the context in which the error occurs.
The shell provides you with the -v (v as in verbose) debugging mode in order to check the context in which a syntax error occurs. When this option is specified, the shell prints each line of a script as it is read.
If you issue the -v option by itself, every line in the script will execute. Because you want to check the syntax, you combine the -n and -v options as follows:
$ /bin/sh -nv script arg1 arg2 argN
If you execute buggy2.sh with the debugging options
$ /bin/sh -nv ./buggy2.sh
the output looks like the following (the line numbers are provided for your reference):
1 #!/bin/sh 2 3 Failed() { 4 if [ $1 -ne 0 ] ; then 5 echo "Failed. Exiting." ; exit 1 ; 6 fi 7 echo "Done." 8 } 9 10 echo "Deleting old backups, please wait \c" 11 rm -r backup > /dev/null 2>&1 12 Failed $? 13 14 echo "Make backup (y/n)? \c" 15 read RESPONSE 16 case $RESPONSE in 17 [yY]|[Yy][Ee][Ss]) 18 echo "Making backup, please wait \c" 19 cp -r docs backup 20 Failed 21 [nN]|[Nn][Oo]) ⇒./buggy2.sh: syntax error at line 21: ')' unexpected
From this output, the reason that the shell issues an error is apparent. The problem is on line 20; the first pattern of the case statement is not terminated with the ;;. Make the change
Failed ;;
or
Failed ;;
to fix this script. After making this change, the command
$ /bin/sh -n buggy2.sh
does not produce an error message. As you will see in the next section, this does not necessarily mean that the script is free from bugs.
However, this is not to say that you should not use these modes. You should always make sure that these commands do not complain about syntax errors. It is much easier to concentrate on the real bugs, either in logic or program flow, when you know that major syntax errors are not present in your script.
For readers familiar with the C programming language, syntax checking your shell scripts using sh -n or sh -nv is equivalent to checking your source files with lint.
Previous | Table of Contents | Next |