These few lines are not intended as a full-fledged debugging tutorial, but as assorted hints and comments about debugging a Bash script.
Do not name your script test, for example! Why? test is already a UNIX®-command, and most likely built into your shell (it's a built-in in Bash) - so you won't be able to run a script with the name test in a normal way.
Don't laugh! This is a classic mistake
Many people come into IRC and ask something like "Why does my script fail? I get an error!". And when you ask them what the error message is, they don't even know. Beautiful.
Reading and interpreting error messages is 50% of your job as debugger! Error messages actually mean something, and at the very least they can give you hints as to where to start your further debugging. READ YOUR ERROR MESSAGES!
You may ask yourself why this is mentioned as debugging tip? Well, you would be surprised how many shell users ignore the text of error messages! When I find some time, I'll paste 2 or 3 IRC log-snips here, just to show you that annoying fact.
Your choice of editor is a matter of personal preference, but you should use one with Bash syntax highlighting! Syntax highlighting helps you see (you guessed it …) syntax errors, such as unclosed quotes and braces, or typos.
From my personal experience, I can suggest vim or GNU emacs.
For more complex scripts, it's useful to write a logfile or even log to syslog. Nobody can debug something without knowing what actually happens and what breaks.
A possible syslog interface is logger ( online manpage).
Insert echos everywhere you can, and print to stderr:
echo "DEBUG: current i=$i" >&2
If you read input from anywhere, such as a file or command substitution, print the debug output with literal quotes, to see leading and trailing spaces!
pid=$(< fooservice.pid) echo "DEBUG: read from file: pid=\"$pid\"" >&2
Bash's printf command has the %q format, which is handy for verifying whether strings are what they appear to be.
foo=$(< inputfile) printf "DEBUG: foo is |%q|\n" "$foo" >&2 # exposes whitespace (such as CRs, see below) and non-printing characters
There are two useful debug outputs for that task (both are written to stderr):
set -v mode (set -o verbose)stderr just like they are read from input (script file or keyboard)set -x mode (set -o xtrace)+ (plus) sign to the shown command)'x y'Hint: These modes can be entered when calling Bash:
bash -vx ./myscript#!/bin/bash -vx
Here's a simple command (a string comparison using the classic test command) executed while in set -x mode:
set -x foo="bar baz" [ $foo = test ]
That fails. Why? Let's see the xtrace output:
+ '[' bar baz = test ']'
And now you see that it's ("bar" and "baz") recognized as two separate words (which you also might have realized if you READ THE ERROR MESSAGES ;) ). Let's check it…
# next try [ "$foo" = test ]
xtrace now gives
+ '[' 'bar baz' = test ']'
^ ^
word markers!
(by AnMaster)
xtrace output would be more useful if it contained source file and line number. Add this assignment to PS4 somewhere in the beginning of your script to include that information:
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
Be sure to use single quotes here!
The output would look like this when you trace code outside a function:
+(somefile.bash:412): echo 'Hello world'…and like this when you trace code inside a function:
+(somefile.bash:412): myfunc(): echo 'Hello world'
That helps a lot when the script is long, or when the main script source a lot of other files.
For general debugging purposes you can also define a function and a variable to use:
debugme() {
[[ $script_debug = 1 ]] && "$@" || :
# be sure to append || : or || true here or use return 0, since the return code
# of this function should always be 0 to not influence anything else with an unwanted
# "false" return code (for example the script's exit code if this function is used
# as the very last command in the script)
}
This function does nothing when script_debug is unset or empty, but it executes the given parameters as commands when script_debug is set. Use it like:
script_debug=1 # to turn off, best do script_debug=0 debugme logger "Sorting the database" database_sort debugme logger "Finished sorting the database, exit code $?"
Of course this can also be used to just execute something else than echo in case of debugging:
debugme set -x # ... some code ... debugme set +x
Imagine you have a script that runs FTP commands for some job using the installed standard FTP client:
ftp user@host <<FTP cd /data get current.log dele current.log FTP
A method to dry-run this with debug output is the following:
if [[ $DRY_RUN = yes ]]; then sed 's/^/DRY_RUN FTP: /' else ftp user@host fi <<FTP cd /data get current.log dele current.log FTP
This can of course be wrapped in a shell function, for more readable code.
script.sh: line 100: syntax error: unexpected end of file
Usually indicates exactly what it says: An unexpected end of file. It's unexpected because Bash waits for the closing of a compound command:
do with a done?if with a fi?case with a esac?{ with a }?( with a )?
Note: It seems that here-documents (tested on versions 1.14.7, 2.05b, 3.1.17 and 4.0) are correctly terminated when there is an EOF before the end-of-here-document tag (see redirection). The reason is unknown, but it seems to be done on purpose. UPDATE:
Bash 4.0 added an extra message for this: warning: here-document at line <N> delimited by end-of-file (wanted `<MARKER>')
script.sh: line 50: unexpected EOF while looking for matching `"' script.sh: line 100: syntax error: unexpected end of file
This one simply indicates that the double-quote opened in line 50 does not have a closing partner until the end of file at line 100.
These unmatched errors occour with, for example
$'string'!)} with parameter expansion syntax
bash: test: too many arguments
You most likely forgot to quote a variable expansion somewhere. See the example for xtrace output from above. Also external commands may spit out such an error message (though in our example it was the internal test-command).
$ echo "Hello world!" bash: !": event not found
This is not an error per se. It happens in interactive shells, when the C-Shell-styled history expansion ("!searchword") is enabled. This is the default. Disable by:
set +H # or set +o histexpand
When this happens during a function definition in a script or on the commandline, like
$ foo () { echo "Hello world"; }
bash: syntax error near unexpected token `('
…then you most likely have an alias defined for the name of the function (here: foo). Alias expansion happens before the real language interpretion, thus the alias is expanded and makes your function definition containing invalid syntax.
There's a big difference in the way that UNIX® and Microsoft® (and possibly others) handle the line endings of plain text files. The difference lies in the use of the CR (Carriage Return) and LF (Line Feed) characters.
\r\n (ASCII CR #13 ^M, ASCII LF #10)\n (ASCII LF #10)
Keep in mind that your script file is a plain text file, and that the CR character means nothing special to UNIX® - it is treated like any other character. If it's printed to your terminal, a carriage return will effectively place the cursor at the beginning of the current line. This can cause much confusion and many headaches, since lines containing CRs are not what they appear to be when printed. In summary, CRs are a pain.
Some possible sources of CRs:
CRs can be a nuisance in various ways. They are especially bad when present in the shebang/interpreter specified with #! in the very first line of a script. Consider the following script, written with a Windows® text editor (^M is a symbolic representation of the CR carriage return character!):
#!/bin/bash^M ^M echo "Hello world"^M ...
Here's what happens because of the #!/bin/bash^M in our shebang:
/bin/bash^M doesn't exist (hopefully), so …The error message can vary. If you're lucky, you'll get:
bash: ./testing.sh: /bin/bash^M: bad interpreter: No such file or directorywhich alerts you of the CR. But you may also get the following:
: bad interpreter: No such file or directoryWhy? Because when printed literally, the
^M makes the cursor go back to the beginning of the line. The whole error message is printed, but you see only a part of it!
^M is bad in other places, too. So if you get weird and illogical messages from your script, the chance is high that ^M is envolved. Find and eliminate it!
To display CRs (these are only a few examples)
:set listcat(1): cat -v FILETo eliminate them (only a few examples)
tr(1): tr -d '\r' <FILE >FILE.newrecode(1): recode MSDOS..latin1 FILEdos2unix(1): dos2unix FILE
Discussion
Debugger for Bash version 3(Bourne again shell). Plugin for Eclipse. http://sourceforge.net/projects/basheclipse/
not knowing of bash debugger existance I wrote a small script (I called debug.sh) that sets -x, -xv or -xvn (depending on the parameter passed debug.sh). The debug.sh script is (feel free to copy, use and evolve it as you see fit):