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

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

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

它需要“真实”time命令,即二进制文件/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

问题是这一行:

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

您正在重定向标准错误以匹配标准输出。 bash 正在将其跟踪消息写入标准错误,并且(例如)在 bash 进程中使用其内置函数echo以及其他 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!

答案2

你也可以直接删除子shell。显然,这是嵌套的贝壳最终导致彼此不安:

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

如果你这样做:


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

...您最终会启动子 shell 来处理管道的该部分托管里面的子壳。因为 shell 甚至没有重定向子 shell 中的调试输出(它也适用于您可能选择使用的任何其他类型的{复合命令); } >redirect 在管道的其部分,您将混合流。它与重定向的顺序有关。

相反,如果您只是先重定向仅有的您尝试测量的命令的错误输出,并让主机 shell 的输出将其发送到 stderr,您就不会遇到同样的问题。

所以...


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

...主机 shell 可以自由地继续在其喜欢的地方写入其 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

相关内容