Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
Is there a way to debug a Bash script?
E.g., something that prints a sort of an execution log, like "calling line 1", "calling line 2", etc.
sh -x script [arg1 ...]
bash -x script [arg1 ...]
These give you a trace of what is being executed. (See also 'Clarification' near the bottom of the answer.)
Sometimes, you need to control the debugging within the script. In that case, as Cheeto reminded me, you can use:
set -x
This turns debugging on. You can then turn it off again with:
set +x
(You can find out the current tracing state by analyzing $-, the current flags, for x.)
Also, shells generally provide options '-n' for 'no execution' and '-v' for 'verbose' mode; you can use these in combination to see whether the shell thinks it could execute your script — occasionally useful if you have an unbalanced quote somewhere.
There is contention that the '-x' option in Bash is different from other shells (see the comments). The Bash Manual says:
-x
Print a trace of simple commands, for commands, case commands, select commands, and arithmetic for commands and their arguments or associated word lists after they are expanded and before they are executed. The value of the PS4 variable is expanded and the resultant value is printed before the command and its expanded arguments.
That much does not seem to indicate different behaviour at all. I don't see any other relevant references to '-x' in the manual. It does not describe differences in the startup sequence.
Clarification: On systems such as a typical Linux box, where '/bin/sh' is a symlink to '/bin/bash' (or wherever the Bash executable is found), the two command lines achieve the equivalent effect of running the script with execution trace on. On other systems (for example, Solaris, and some more modern variants of Linux), /bin/sh is not Bash, and the two command lines would give (slightly) different results. Most notably, '/bin/sh' would be confused by constructs in Bash that it does not recognize at all. (On Solaris, /bin/sh is a Bourne shell; on modern Linux, it is sometimes Dash — a smaller, more strictly POSIX-only shell.) When invoked by name like this, the 'shebang' line ('#!/bin/bash' vs '#!/bin/sh') at the start of the file has no effect on how the contents are interpreted.
The Bash manual has a section on Bash POSIX mode which, contrary to a long-standing but erroneous version of this answer (see also the comments below), does describe in extensive detail the difference between 'Bash invoked as sh' and 'Bash invoked as bash'.
When debugging a (Bash) shell script, it will be sensible and sane — necessary even — to use the shell named in the shebang line with the -x option. Otherwise, you may (will?) get different behaviour when debugging from when running the script.
I've used the following methods to debug my script.
set -e makes the script stop immediately if any external program returns a non-zero exit status. This is useful if your script attempts to handle all error cases and where a failure to do so should be trapped.
set -x was mentioned above and is certainly the most useful of all the debugging methods.
set -n might also be useful if you want to check your script for syntax errors.
strace is also useful to see what's going on. Especially useful if you haven't written the script yourself.
Jonathan Leffler's answer is valid and useful.
But, I find that the "standard" script debugging methods are inefficient, unintuitive, and hard to use. For those used to sophisticated GUI debuggers that put everything at your fingertips and make the job a breeze for easy problems (and possible for hard problems), these solutions aren't very satisfactory.
I do use a combination of DDD and bashdb. The former executes the latter, and the latter executes your script. This provides a multi-window UI with the ability to step through code in context and view variables, stack, etc., without the constant mental effort to maintain context in your head or keep re-listing the source.
There is guidance on setting that up in DDD and BASHDB.
I found the shellcheck utility and maybe some folks find it interesting.
A little example:
$ cat test.sh
ARRAY=("hello there" world)
for x in $ARRAY; do
echo $x
done
$ shellcheck test.sh
In test.sh line 3:
for x in $ARRAY; do
^-- SC2128: Expanding an array without an index only gives the first element.
Fix the bug. First try...
$ cat test.sh
ARRAY=("hello there" world)
for x in ${ARRAY[#]}; do
echo $x
done
$ shellcheck test.sh
In test.sh line 3:
for x in ${ARRAY[#]}; do
^-- SC2068: Double quote array expansions, otherwise they're like $* and break on spaces.
Let's try again...
$ cat test.sh
ARRAY=("hello there" world)
for x in "${ARRAY[#]}"; do
echo $x
done
$ shellcheck test.sh
Found now!
It's just a small example.
You can also write "set -x" within the script.
Install Visual Studio Code, and then add the Bash debug extension and you are ready to debug in visual mode. See it here in action.
Use Eclipse with the plugins Shelled and BashEclipse.
DVKit - Eclipse-based IDE for design verification tasks
BashEclipse
For Shelled: Download the ZIP file and import it into Eclipse via menu Help → Install new software: local archive. For BashEclipse: Copy the JAR files into the dropins directory of Eclipse
Follow the steps provided in BashEclipse files
I wrote a tutorial with many screenshots at Bash: enabling Eclipse for Bash Programming | Plugin Shelled (shell editor)
I built a Bash debugger: Bash Debuging Bash. Just give it a try.
set +x = #ECHO OFF, set -x = #ECHO ON.
You can add the -xv option to the standard shebang as follows:
#!/bin/bash -xv
-x : Display commands and their arguments as they are executed.
-v : Display shell input lines as they are read.
ltrace is another Linux utility similar to strace. However, ltrace lists all the library calls being called in an executable or a running process. Its name itself comes from library-call tracing.
For example:
ltrace ./executable <parameters>
ltrace -p <PID>
Source
I think you can try this Bash debugger: http://bashdb.sourceforge.net/.
Some trick to debug Bash scripts:
Using set -[nvx]
In addition to
set -x
and
set +x
for stopping dump.
I would like to speak about set -v which dump as smaller as less developed output.
bash <<<$'set -x\nfor i in {0..9};do\n\techo $i\n\tdone\nset +x' 2>&1 >/dev/null|wc -l
21
for arg in x v n nx nv nvx;do echo "- opts: $arg"
bash 2> >(wc -l|sed s/^/stderr:/) > >(wc -l|sed s/^/stdout:/) <<eof
set -$arg
for i in {0..9};do
echo $i
done
set +$arg
echo Done.
eof
sleep .02
done
- opts: x
stdout:11
stderr:21
- opts: v
stdout:11
stderr:4
- opts: n
stdout:0
stderr:0
- opts: nx
stdout:0
stderr:0
- opts: nv
stdout:0
stderr:5
- opts: nvx
stdout:0
stderr:5
Dump variables or tracing on the fly
For testing some variables, I use sometime this:
bash <(sed '18ideclare >&2 -p var1 var2' myscript.sh) args
for adding:
declare >&2 -p var1 var2
at line 18 and running the resulting script (with args), without having to edit them.
Of course, this could be used for adding set [+-][nvx]:
bash <(sed '18s/$/\ndeclare -p v1 v2 >\&2/;22s/^/set -x\n/;26s/^/set +x\n/' myscript) args
It will add declare -p v1 v2 >&2 after line 18, set -x before line 22 and set +x before line 26.
A little sample:
bash <(sed '2,3s/$/\ndeclare -p LINENO i v2 >\&2/;5s/^/set -x\n/;7s/^/set +x\n/' <(
seq -f 'echo $#, $((i=%g))' 1 8)) arg1 arg2
arg1 arg2, 1
arg1 arg2, 2
declare -i LINENO="3"
declare -- i="2"
/dev/fd/63: line 3: declare: v2: not found
arg1 arg2, 3
declare -i LINENO="5"
declare -- i="3"
/dev/fd/63: line 5: declare: v2: not found
arg1 arg2, 4
+ echo arg1 arg2, 5
arg1 arg2, 5
+ echo arg1 arg2, 6
arg1 arg2, 6
+ set +x
arg1 arg2, 7
arg1 arg2, 8
Note: Care about $LINENO. It will be affected by on-the-fly modifications!
(To see resulting script without executing, simply drop bash <( and ) arg1 arg2)
Step by step, execution time
Have a look at my answer about how to profile Bash scripts.
There's a good amount of detail on logging for shell scripts via the global variables of the shell. We can emulate a similar kind of logging in a shell script: Log tracing mechanism for shell scripts
The post has details on introducing log levels, like INFO, DEBUG, and ERROR. Tracing details like script entry, script exit, function entry, function exit.
Sample log:
This question already has answers here:
What does `set -x` do?
(3 answers)
Closed 4 years ago.
So I have a file deploy.sh, and it has the shell script. Since I know about it, I have a little confusion, that is what does that set -x actually means.
After running the file I have observed that the command written after it in the file gets mentioned in the terminal with a + sign.
Like if I have this,
#!/bin/bash
set -x
ng build
So the output mentions +ng build, and when I comment the set -x from the file, everything executes, but the later commands does not show up in the terminal.
I have researched about it, but specifically couldn't find the real meaning and work of this particular command.
You can read the bash online manual for set:
-x
Print a trace of simple commands, for commands, case commands, select
commands, and arithmetic for commands and their arguments or
associated word lists after they are expanded and before they are
executed. The value of the PS4 variable is expanded and the resultant
value is printed before the command and its expanded arguments.
So it does exactly what you described.
It's a bit hard to find but this is from the bash man page:
set [+abefhkmnptuvxBCEHPT] [+o option-name] [arg ...]
Without options, the name and value of each shell variable are
displayed in a format that can be reused as input for setting or
resetting the currently-set variables. Read-only variables can‐
not be reset. In posix mode, only shell variables are listed.
The output is sorted according to the current locale. When
options are specified, they set or unset shell attributes. Any
arguments remaining after option processing are treated as val‐
ues for the positional parameters and are assigned, in order, to
$1, $2, ... $n. Options, if specified, have the following
meanings:
[...]
-x After expanding each simple command, for command, case
command, select command, or arithmetic for command, dis‐
play the expanded value of PS4, followed by the command
and its expanded arguments or associated word list.
So basically it's a debug option. It does not only execute all the commands but also prints them before it executes them (to stderr).
How can I print executed commands in fish shell?
I've tried solutions from In a shell script: echo shell commands as they are executed, but they are not compatible with fish shell.
Starting from fish-3.1.0, $fish_trace can be set to enable output similar to Bash’s set -x.
For example,
set fish_trace 1
before commands that should be traced.
Unfortunately fish doesn't yet have an analog of set -x to print commands. This is the issue requesting it. If you have ideas for what the syntax and output should be, please share them :)
The best answer today is, if you are trying to debug a problem you can invoke fish as fish -d 3 and it will show some debugging output as it runs.
In Bash, I'd like to create a binding in my .inputrc that makes use of the !! built-in to repeat the last command. But this doesn’t seem to properly expand the !!
bind -x '"\C-t": echo $(!!)'
When I invoke the above binding (Ctrl+t) I just get this:
-bash: !!: command not found
Likewise the simpler
bind -x '"\C-t": echo !!'
Just yields
!!
Instead of the actual command.
Obviously my real use-case is more substantive than this example, this is just an illustration of the problem.
Edit:
This question has nothing to do with echo "#!" fails -- "event not found" which it is claimed mine is a duplicate of. That question pertains to a generalized failure of !* expansion in regular bash due to quoting issues or lack of history. My question on the other hand is very specific to the context of being used inside an .inputrc file (or perhaps an alias), where a different set of factors come into play. On my regular command line, the so-called "bang expansions" have always worked fine. It's only in these special contexts where the problems arose, and hence led to this question.
"\C-t": "fc -s\n"
fc -s re-executes the last command, and fc is also a builtin:
$ type fc
fc is a shell builtin
Well it seems there's something odd going on with shell quoting.
This does not work
bind -x '"\C-l": "!! \n"'
But putting this in .inputrc does
"\C-l": "!! \n"
Don't really understand why the former doesn't work, but at least the latter does.
EDIT: OK got it. Apparently the "-x" isn't needed when defining on the cmd line. So we can simply write
bind '"\C-l": "!! \n"'
Using -x tells bind not to interfere with what you've already typed (almost like a primal ncurses approximation of a modal window!) which is not what I'm after. Thanks to the accepted answer on In bash, how do I bind a function key to a command? for this insight!
I came across $- and found that it is used to determine the flags set for the shell.So I just did an echo $- and it gave me ism . Now i is for interactive shell, m is for monitor mode but I dont know what s means.
Moreover I have sample script test.sh as below
#!/bin/ksh
echo "Hi I am shell and I am about to figure out the flags set for me :-)"
echo $-
When I execute the script like this-- ./test.sh I am getting the flag as h but upon executing sh -x test.sh
I am getting xh . I think this x is coming from sh -x but I am not sure how and why.
I tried to google reagrding the flags but found nothing (maybe because my seach keyword is not proper).
Any information on this will be helpful.Thanks in advance :-)
You are using Korn shell in your script, are you using that interactively as well?
man ksh
is your friend.
- Options supplied to the shell on invocation or by the set
command.
Search for Invocation, the correct term for these settings is options (not flags).
So yes, the x is from the set -x. This is a shortcut for set -o xtrace. To see all those options, and their current settings, set -o (note that only a few have single character shortcuts). Try
set --man
on the command-line (if you have a very old version of ksh, that won't work).
-s just means that commands come from stdin.