有没有办法调试Bash脚本?
例如,打印一种执行日志,显示“调用第1行”、“调用第2行”等。
有没有办法调试Bash脚本?
例如,打印一种执行日志,显示“调用第1行”、“调用第2行”等。
sh -x script [arg1 ...]
bash -x script [arg1 ...]
这些给出了正在执行的迹象。(请参见答案底部附近的“澄清”部分。)
有时,您需要在脚本内部控制调试。在这种情况下,正如Cheeto提醒我的那样,您可以使用:
set -x
使用此命令打开调试模式。你可以用以下命令关闭调试模式:
set +x
您可以通过分析当前标志 $-
来查找 x
的当前跟踪状态。
此外,shell 通常提供 '-n
' 选项用于 'no execution','-v
' 选项用于 'verbose' 模式;您可以将它们组合使用以查看 shell 是否认为它可以执行您的脚本 - 如果您在某个地方有一个不平衡的引号,则偶尔会有用。
有争议称Bash中的 '-x
' 选项与其他 shell 不同(请参阅评论)。Bash 手册 中说:
-x
打印简单命令、 for
命令、 case
命令、 select
命令和算术 for
命令及其参数或关联的字列表,在扩展之后并在执行之前。 PS4
变量的值被扩展,并且在该命令及其扩展参数之前打印生成的值。
这些看起来没有表明有任何不同的行为。我没有看到手册中其他相关的 '-x
' 引用。它没有描述启动序列中的差异。
澄清: 在诸如典型的 Linux 系统之类的系统上,' /bin/sh
'是指向 '/bin/bash
'(或 Bash 可执行文件所在的位置)的符号链接,这两个命令行实现了使用执行跟踪运行脚本的等效效果。在其他系统上(例如 Solaris 和一些更现代的 Linux 变体),/bin/sh
不是 Bash,并且这两个命令行将给出(稍微)不同的结果。尤其是,'/bin/sh
' 会对 Bash 中完全不认识的结构感到困惑。(在 Solaris 上,/bin/sh
是 Bourne shell;在现代 Linux 上,它有时是 Dash - 更小、更严格的 POSIX-only shell。)当像这样通过名称调用时,文件开头的 'shebang' 行('#!/bin/bash
' vs '#!/bin/sh
')对内容的解释方式没有影响。
Bash 手册中有一节关于 Bash POSIX 模式,与长期存在但错误的版本(请参见下面的评论)相反,���模式详细描述了 'Bash 作为 sh
调用' 和 'Bash 作为 bash
调用' 之间的区别。
在调试(Bash)shell 脚本时,使用带有 -x
选项的 shebang 行中命名的 shell 是明智而理性的 - 必要甚至 - 否则,在调试与运行脚本时,可能会获得不同的行为。
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
。 (注:PS4是Bash shell中的一个环境变量,它可以控制在命令行中打印的调试信息的格式和内容。这个提示符会在每次执行命令时显示,并包含一些有用的调试信息,如文件名、行号和函数名。以上翻译旨在保留原意并简洁表述) - estaniset -e
如果任何外部程序返回非零退出状态,该脚本将立即停止。如果您的脚本尝试处理所有错误情况,并且无法处理某些情况,则此选项很有用。
set -x
如上所述,是所有调试方法中最有用的。
set -n
如果您想检查脚本是否存在语法错误,则可能也很有用。
strace
也很有用,可以查看正在发生什么。特别适用于您没有自己编写脚本的情况。strace -f
(这会使其更加冗长,但如果将其限制为您感兴趣的系统调用,则仍然很有用)。 - Random832set -e
是有争议的。 - Charles DuffyJonathan Leffler的回答是有效和有用的。
但是,我发现“标准”脚本调试方法效率低下、不直观且难以使用。对于习惯了复杂 GUI 调试器并能轻松解决简单问题(甚至是困难问题)的人来说,这些解决方案并不是很令人满意。
我使用 DDD 和 bashdb 的组合。前者执行后者,后者再执行您的脚本。这提供了一个多窗口 UI,可以在上下文中逐步查看代码并查看变量、堆栈等,而无需不断地努力维护头脑中的上下文或重新列出源代码。
在 DDD 和 BASHDB 中有关于设置的指导。
我发现了 shellcheck 工具,或许有些人会觉得它很有趣。
这里有一个小例子:
$ 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.
修复这个错误。首先尝试...
$ 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.
让我们再试一次...
$ cat test.sh
ARRAY=("hello there" world)
for x in "${ARRAY[@]}"; do
echo $x
done
$ shellcheck test.sh
现在找到了!
这只是一个小例子。
您也可以在脚本中写入"set -x"。
使用Shelled和BashEclipse插件的Eclipse。
对于Shelled:下载ZIP文件并通过菜单帮助→安装新软件:本地存档将其导入到Eclipse中。对于BashEclipse:将JAR文件复制到Eclipse的dropins目录中
按照BashEclipse files中提供的步骤进行操作
我在Bash:启用Eclipse进行Bash编程 | Plugin Shelled(shell编辑器)上撰写了一篇有许多截图的教程。
https://sourceforge.net/projects/shelled/?source=directory
会重定向到https://sourceforge.net/projects/dvkit/?source=directory
("DVKit"),外表上看起来并没有与“Shelled”有任何关系。需要进行解释。 - Peter Mortensenset -[nvx]
除了
set -x
并且
set +x
用于停止转储。
我想谈谈 set -v
,它会将输出减少到较小的未开发输出。
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
为了测试一些变量,我有时会使用以下方法:
bash <(sed '18ideclare >&2 -p var1 var2' myscript.sh) args
用于添加:
declare >&2 -p var1 var2
在第18行运行生成的脚本(带有args),无需编辑即可运行。
当然,这也可以用于添加set [+-][nvx]
:
bash <(sed '18s/$/\ndeclare -p v1 v2 >\&2/;22s/^/set -x\n/;26s/^/set +x\n/' myscript) args
在第18行之后,将添加declare -p v1 v2 >&2
,在第22行之前添加set -x
,在第26行之前添加set +x
。
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
注意: 关注 $LINENO
。它会受到即时修改的影响!
(要查看结果脚本而不执行,只需删除 bash <(
和 ) arg1 arg2
)
查看关于如何分析Bash脚本的答案:my answer about how to profile Bash scripts