【问题标题】:How to debug a bash script and get execution time per command如何调试 bash 脚本并获取每个命令的执行时间
【发布时间】:2013-08-05 01:45:38
【问题描述】:

我有一个 bash 脚本,它需要将近 5 秒才能运行。我想调试它,并确定哪些命令花费的时间最长。这样做的最佳方法是什么?有我可以设置的标志吗?设置#!/bin/bash -vx 并没有真正的帮助。我想要的基本上是按行号的执行时间。

【问题讨论】:

  • 你为什么这么急躁?运行脚本时要花一个小时来减少奇数秒吗?
  • 脚本运行频繁,需要优化。
  • 多久一次?速度很重要,也许用 C++(例如)编写是一个更好的选择。除了为什么不发布脚本?
  • Perl、Scala 和 Clojure 比 bash 快吗?只是一个想法。
  • bash -x myscript.sh 2> >(ts -i) 当您安装了 moreutils 并且您的脚本没有输出到 stderr 时。但是给出的持续时间是前一个命令的。

标签: linux bash


【解决方案1】:

这是尽可能接近内置 bash 调试工具的答案,因为它提供了从脚本执行开始时间开始的总体时间信息。

在脚本顶部添加第二次计数:

export PS4='+[${SECONDS}s][${BASH_SOURCE}:${LINENO}]: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'; set -x;

相同,但用毫秒代替:

N=`date +%s%N`; export PS4='+[$(((`date +%s%N`-$N)/1000000))ms][${BASH_SOURCE}:${LINENO}]: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'; set -x;

最后一个示例可以达到微秒精度,请记住您使用的是 bash :)。

示例脚本:

#!/bin/bash
N=`date +%s%N`
export PS4='+[$(((`date +%s%N`-$N)/1000000))ms][${BASH_SOURCE}:${LINENO}]: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'; set -x;
sleep 1
exit

示例调试输出:

+[3ms][/root/db_test.sh:5]: sleep 1
+[1012ms][/usr/local/bin/graphite_as_rand_stat.sh:6]: exit

请记住,您可以选择性地调试脚本的特定部分,方法是在调试开始时将其包含在“set -x”中,在调试结束时将其包含在“debug +x”中。计时数据仍会显示从执行开始算起的正确计数。

附录

为了完整起见,如果您确实需要差分时序数据,您可以将调试信息重定向到文件并在之后进行处理。

鉴于此示例脚本:

#!/bin/bash
N=`date +%s%N`
export PS4='+[$(((`date +%s%N`-$N)/1000000))ms][${BASH_SOURCE}:${LINENO}]: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'; set -x;
sleep 1
for ((i=0;i<2;i++)); do
        o=$(($RANDOM*$RANDOM/$RANDOM))
        echo $o
        sleep 0.$o
done
exit

在将调试重定向到文件时运行它:

./example.sh 2>example.dbg

并以此输出差分调试时序(涵盖多行):

p=0; cat example.dbg | while read l; do [[ ! ${l%%[*} =~ ^\+ ]] && echo $l && continue; i=`echo $l | sed 's#[^0-9]*\([0-9]\+\).*#\1#'`; echo $l | sed "s#${i}ms#${i}ms+$(($i-$p))ms#"; p=$i; done

输出:

+[2ms+2ms][./example.sh:5]: sleep 1
+[1006ms+1004ms][./example.sh:6]: (( i=0 ))
+[1009ms+3ms][./example.sh:6]: (( i<2 ))
+[1011ms+2ms][./example.sh:7]: o=19258
+[1014ms+3ms][./example.sh:8]: echo 19258
+[1016ms+2ms][./example.sh:9]: sleep 0.19258
+[1213ms+197ms][./example.sh:6]: (( i++ ))
+[1217ms+4ms][./example.sh:6]: (( i<2 ))
+[1220ms+3ms][./example.sh:7]: o=176
+[1226ms+6ms][./example.sh:8]: echo 176
+[1229ms+3ms][./example.sh:9]: sleep 0.176
+[1442ms+213ms][./example.sh:6]: (( i++ ))
+[1460ms+18ms][./example.sh:6]: (( i<2 ))
+[1502ms+42ms][./example.sh:11]: exit

【讨论】:

  • 做得很好。问题被标记为linux,但由于这些技术在所有支持 bash 的平台上都可能有用,让我补充一下:遗憾的是,%N 不适用于 BSD/OSX 上的date,因此毫秒变体不起作用不在那里工作。
  • 毫秒变体也可以做到这一点,但要让$SECONDS变体从运行set -x的点开始测量,首先执行SECONDS=0。最后,值得一提的是,测量本身的行为会稍微增加执行时间,$SECONDS 方法的影响会更小。
  • 非常好的答案。另见stackoverflow.com/questions/5014823/…
【解决方案2】:

您可以使用time 实用程序来测量各个命令/函数的运行时间。

例如:

[ben@imac ~]$ cat times.sh
#!/bin/bash

test_func ()
{
    sleep 1
    echo "test"
}

echo "Running test_func()"
time test_func
echo "Running a 5 second external command"
time sleep 5

运行该脚本会产生如下结果:

[ben@imac ~]$ ./times.sh
Running test_func()
test

real    0m1.003s
user    0m0.001s
sys     0m0.001s
Running a 5 second external command

real    0m5.002s
user    0m0.001s
sys     0m0.001s

【讨论】:

  • 不要使用test 作为函数名。这是一个内置的shell。将其重命名为 test_time 或其他名称...
  • 感谢您的评论。固定。
  • 挑剔:time 默认是 shell 关键字(同时也作为外部实用程序存在,/usr/bin/time)。只有 shell 关键字能够测量任意 shell 命令作为一个整体(比较 time ls | sleep 1/usr/bin/time ls | sleep 1 的输出)。
【解决方案3】:

您可以使用set -x 让脚本在执行之前打印每个命令。我不知道自动添加命令计时的方法。您可以在整个脚本中添加date 命令来标记时间。

【讨论】:

  • 您可以将其与 moreutils 中的 ts 结合使用
【解决方案4】:

试试这个:

sed 's/^\([^#]\)/time \1/' script.sh>tmp.sh && ./tmp.sh

它在所有非命令行前添加一个时间命令

【讨论】:

  • 这并不总是有效:考虑for i in {1..3}\ndo\necho $i\ndone。一些简单的事情,但 time dotime done 无效
  • 这是真的,但它至少允许某种程度的自动化。我喜欢这个主意,但它必须改进......
猜你喜欢
  • 1970-01-01
  • 2014-08-31
  • 2014-02-27
  • 2021-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-09
  • 2016-12-28
相关资源
最近更新 更多