为什么“ bash -x”会破坏此脚本?


13

我有一个脚本,可以测量某些命令的执行时间。

它需要“ real” time命令,即,例如in中的二进制文件/usr/bin/time(因为内置的bash没有该-f标志)。

下面是一个可以调试的简化脚本:

#!/bin/bash

TIMESEC=$(echo blah | ( /usr/bin/time -f %e grep blah >/dev/null ) 2>&1 | awk -F. '{print $1}')

echo ABC--$TIMESEC--DEF

if [ "$TIMESEC" -eq 0 ] ; then
   echo "we are here!"
fi

另存为“ test.sh”并执行:

$ bash test.sh
ABC--0--DEF
we are here!

这样就行了。

现在,让我们尝试通过在bash命令行中添加“ -x”来调试它:

$ bash -x test.sh
++ echo blah
++ awk -F. '{print $1}'
+ TIMESEC='++ /usr/bin/time -f %e grep blah
0'
+ echo ABC--++ /usr/bin/time -f %e grep blah 0--DEF
ABC--++ /usr/bin/time -f %e grep blah 0--DEF
+ '[' '++ /usr/bin/time -f %e grep blah
0' -eq 0 ']'
test.sh: line 10: [: ++ /usr/bin/time -f %e grep blah
0: integer expression expected

为什么当我们使用“ -x”时,此脚本会中断,而没有它,它会正常工作吗?


1
h 看起来像-xon,该$()构造正在将-x输出包含为其结果值的一部分。不知道这是“预期的”行为还是错误……或者也许()是实际上提供-x输出的是子外壳。
杰夫·Y

另外:设置BASH_XTRACEFD可让您将set -x输出重定向到麻烦较小的地方。
查尔斯·达菲

Answers:


21

问题是这一行:

TIMESEC=$(echo blah | ( /usr/bin/time -f %e grep blah >/dev/null ) 2>&1 | awk -F. '{print $1}')

您将重定向标准错误以匹配标准输出的位置。bash正在将其跟踪消息写入标准错误,并且(例如)echo在bash进程中都使用其内置功能以及其他shell构造。

如果您将其更改为

TIMESEC=$(echo blah | sh -c "( /usr/bin/time -f %e grep blah >/dev/null )" 2>&1 | awk -F. '{print $1}')

它将解决该问题,并且可能是跟踪和工作之间的可接受折衷:

++ awk -F. '{print $1}'
++ sh -c '( /usr/bin/time -f %e grep blah >/dev/null )'
++ echo blah
+ TIMESEC=0                 
+ echo ABC--0--DEF
ABC--0--DEF
+ '[' 0 -eq 0 ']'
+ echo 'we are here!'
we are here!

7

您也可以只删除子外壳。显然是嵌套的壳使彼此不安:

TIMESEC=$(
    echo blah |
    /usr/bin/time -f %e grep blah 2>&1 >/dev/null |
    awk -F. '{print $1}'
)

如果您这样做:


...| ( subshell ) 2>pipe | ...

...最后,您将启动子外壳,以处理在其中托管子外壳的管道的这一部分。因为没有外壳的外壳甚至将子外壳的调试输出重定向 到管道的它的部分(就像您可能选择使用的任何其他种类的{复合命令一样; } >redirect,所以您也可以混合流。它与重定向的顺序有关。

相反,如果你只是第一个重定向只有你尝试计命令的错误输出,并让主机外壳的输出使其标准错误,你没有同样的问题拉闸。

所以...


... | command 2>pipe 1>/dev/null | ...

...主机外壳程序可以自由继续在自己喜欢的位置继续编写其stderr,而仅将其调用的命令输出重定向到管道中。


bash -x time.sh
+++ echo blah
+++ /usr/bin/time -f %e grep blah
+++ awk -F. '{print $1}'
++ TIMESEC=0
++ echo ABC--0--DEF
ABC--0--DEF
++ '[' 0 -eq 0 ']'
++ echo 'we are here!'
we are here!

对于这个问题...


TIMESEC=$(
    echo blah |
    /usr/bin/time -f %e grep blah 2>&1 >/dev/null
)
printf %s\\n "ABC--${TIMESEC%%.*}--DEF"
if [ "${TIMESEC%%.*}" -eq 0 ] ; then
   echo "we are here!"
fi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.