抑制echo命令的执行跟踪?


29

我正在运行Jenkins的shell脚本,该脚本使用shebang选项启动了shell脚本#!/bin/sh -ex

根据Bash Shebang所说的假人?-x“导致外壳程序打印执行跟踪”,这对大多数用途都非常有用-除了回声:

echo "Message"

产生输出

+ echo "Message"
Message

这有点多余,看起来有点奇怪。有没有办法保持-x启用状态,但仅输出

Message

而不是上面的两行,例如通过在echo命令前添加特殊命令字符,还是重定向输出?

Answers:


20

当您站着鳄鱼皮时,很容易忘记目标是排干沼泽。                   -俗语

问题是关于echo,但到目前为止,大多数答案都集中在如何潜入set +x命令上。有一个更简单,更直接的解决方案:

{ echo "Message"; } 2> /dev/null

(我承认,{ …; } 2> /dev/null 如果我在先前的答案中没有看到它,可能没有想到。)

这有点麻烦,但是,如果您有一组连续的echo命令,则不需要分别对每个命令执行此操作:

{
  echo "The quick brown fox"
  echo "jumps over the lazy dog."
} 2> /dev/null

注意,换行时不需要分号。

您可以通过使用kenorb的想法来减轻打字负担,该想法/dev/null在非标准文件描述符(例如3)上永久打开,然后一直说2>&3而不是说2> /dev/null


撰写本文时,前四个答案要求您每次执行时都要做一些特别的事情(在大多数情况下是麻烦的) echo。如果您确实希望所有 echo命令都抑制执行跟踪(为什么不这样做?),则可以全局执行此操作而无需花费很多代码。首先,我注意到没有跟踪别名:

$ myfunc()
> {
>     date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016  0:00:00 AM           # Happy Halloween!
$ myfunc
+ myfunc                                # Note that function call is traced.
+ date
Mon, Oct 31, 2016  0:00:01 AM
$ myalias
+ date                                  # Note that it doesn’t say  + myalias
Mon, Oct 31, 2016  0:00:02 AM

(请注意#!/bin/sh,即使shebang为/bin/shbash的链接,以下脚本片段也可以使用,即使shebang为bash的链接也可以。但是,如果shebang为#!/bin/bash,则需要添加shopt -s expand_aliases命令以获取别名以在脚本中工作。)

因此,对于我的第一个技巧:

alias echo='{ set +x; } 2> /dev/null; builtin echo'

现在,当我们说时echo "Message",我们正在调用别名,该别名不会被跟踪。别名关闭跟踪选项,同时抑制set命令中的跟踪消息(使用user5071535的答案中首先介绍的技术),然后执行实际echo命令。这使我们获得了类似于user5071535的答案的效果,而无需在每个 echo命令中编辑代码。但是,这将使跟踪模式关闭。我们不能将a set -x放入别名中(或至少不容易),因为别名仅允许将字符串替换为单词;在参数(例如)之后不能将别名字符串的任何部分注入到命令中 "Message"。因此,例如,如果脚本包含

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date

输出将是

+ date
Mon, Oct 31, 2016  0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016  0:00:04 AM           # Note that it doesn’t say  + date

因此,在显示消息后,您仍然需要重新打开trace选项,但是在每个连续命令之后仅需一次echo

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date


如果能在set -x之后制造自动机,那将是一件很不错的事,而我们可以做些echo棘手的事情。但是在我提出这一点之前,请考虑一下。OP从使用#!/bin/sh -exshebang的脚本开始。隐含地,用户可以x从shebang中删除,并使脚本正常运行,而无需执行跟踪。如果我们可以开发一种保留该属性的解决方案,那就太好了。此处的前几个答案使该属性失败,因为它们echo无条件地在after 语句中跟踪“返回” ,而不管其是否已打开。  此答案很明显无法识别该问题,因为它代替了 echo输出带跟踪输出;因此,如果关闭了跟踪,则所有消息都将消失。现在,我将提出一种解决方案,该解决方案在有条件echo语句之后(仅当该语句已经打开时)才重新打开。将其降级为可无条件启用“追溯”的解决方案是微不足道的,应留作练习。

alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
        builtin echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}

$-是选项列表;与设置的所有选项相对应的字母的串联。例如,如果设置了ex选项,$-则将是包含e和的字母混杂在一起x。我的新别名(上面)保存了$-关闭跟踪之前的值。然后,在关闭跟踪的情况下,它将控制权交给外壳函数。该函数执行实际操作echo ,然后检查x调用别名时是否打开了该选项。如果该选项打开,则该功能将其重新打开;否则,该功能将重新打开。如果已关闭,则该功能将其关闭。

您可以shopt在脚本的开头插入上面的七行(如果包含,则插入八行),其余的保持独立。

这可以让你

  1. 使用以下任何shebang行:
    #!/ bin / sh -ex
    #!/ bin / sh -e
    #!/ bin / sh -x
    或只是普通
    #!/ bin / sh
    它应该可以按预期工作。
  2. 具有类似的代码
    (shebang)
    命令1
    命令2
    命令3
    设置-x
    命令4
    命令5
    命令6
    设置+ x
    命令7
    命令8
    命令9
    • 将跟踪命令4、5和6,除非其中一个命令是an echo,在这种情况下将执行但不跟踪。(但即使命令5是echo,命令6仍将被跟踪。)
    • 不会跟踪命令7、8和9。即使命令8是echo,命令9仍然不会被跟踪。
    • 根据shebang是否包含,将跟踪命令1、2和3(如4、5和6)(如7、8和9)x

PS我发现,在我的系统上,我可以builtin在中间答案中忽略该关键字(该关键字只是的别名echo)。这不足为奇。bash(1)表示,在别名扩展期间,…

…与扩展别名相同的单词不会再次扩展。这意味着一个可能的别名lsls -F,例如,和bash不会尝试递归扩展替换文本。

毫不奇怪,echo_and_restore如果builtin省略关键字1,则最后一个答案(带有的答案)将失败。但是,奇怪的是,如果我删除builtin并切换顺序,它会起作用:

echo_and_restore() {
        echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'

__________
1  似乎引起了不确定的行为。我见过

  • 一个无限循环(可能是由于无限制的递归),
  • 一个/dev/null: Bad address错误消息,并且
  • 一个核心转储。

2
我已经看到一些使用别名完成的神奇魔术,所以我知道我的知识还不完整。如果有人可以提出echo +x; echo "$*"; echo -x一种别名中的等效方法,我想看看。
G-Man说'恢复莫妮卡'

11

我在InformIT找到了部分解决方案:

#!/bin/bash -ex
set +x; 
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

输出

set +x; 
shell tracing is disabled here 
+ echo "but is enabled here"
but is enabled here

不幸的是,那仍然回声set +x,但至少在那之后它是安静的。因此这至少是该问题的部分解决方案。

但是,也许有更好的方法吗?:)


2

通过摆脱set +x输出,这种方式可以改进您自己的解决方案:

#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

2

放在set +x方括号内,因此仅适用于本地范围。

例如:

#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true

将输出:

$ ./foo.sh 
+ exec
foo1
foo2
foo3
+ true

如果我错了,请纠正我,但我认为set +x子外壳内部(或完全使用子外壳)没有任何用处。您可以将其删除并获得相同的结果。是将stderr重定向到/ dev / null来暂时“禁用”跟踪的工作……似乎echo foo1 2>/dev/null,等等,同样有效,并且更具可读性。
泰勒·里克

在脚本中进行跟踪可能会影响性能。其次,当您预计会有其他错误时,将&2重定向到NULL可能会有所不同。
kenorb

如果我错了,请纠正我,但是在您的示例中,您的脚本中已经启用了跟踪功能(带有bash -x),并且您已经重定向&2到null(因为&3已重定向到null),所以我不确定该注释的相关性。也许我们只需要一个更好的例子来说明您的观点,但是至少在给定的例子中,似乎仍然可以简化它而不会失去任何好处。
泰勒·里克

1

我喜欢g-man提出的全面而详尽的答案,并认为它是迄今为止提供的最好的答案。它关心脚本的上下文,并且在不需要它们时不强制配置。因此,如果您先阅读此答案,请继续检查并确认所有优点。

但是,在该答案中缺少一个重要的方面:所提出的方法不适用于典型的用例,即报告错误:

COMMAND || echo "Command failed!"

由于别名的构造方式,它将扩展为

COMMAND || { save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore "Command failed!"

您猜到了,它总是无条件echo_and_restore执行。鉴于该部分没有运行,这意味着该函数的内容也将被打印出来。set +x

将最后一个更改;&&也不起作用,因为在Bash中,||并且&&left-associative

我发现了适用于此用例的修改:

echo_and_restore() {
    echo "$(cat -)"
    case "$save_flags" in
        (*x*) set -x
    esac
}
alias echo='({ save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore) <<<'

它使用子外壳((...)部件)来对所有命令进行分组,然后将输入字符串作为sthere字符串(事物)通过stdin传递,然后由打印。该是可选的,但是你知道,“ 显胜于隐 ”。<<<cat --

您也可以cat -直接使用而不使用echo,但是我喜欢这种方式,因为它允许我向输出添加任何其他字符串。例如,我倾向于这样使用它:

BASENAME="$(basename "$0")"  # Complete file name
...
echo "[${BASENAME}] $(cat -)"

现在,它可以很好地工作:

false || echo "Command failed"
> [test.sh] Command failed

0

执行跟踪转到,以stderr这种方式过滤:

./script.sh 2> >(grep -v "^+ echo " >&2)

逐步解释一下:

  • stderr 重定向…– 2>
  • …命令 –>(…)
  • grep 是命令...
  • …这需要行的开头…– ^
  • ……之后是+ echo……
  • …然后grep反转比赛…–-v
  • …并丢弃所有不需要的行。
  • 结果通常是stdout; 我们将其重定向到stderr它所属的位置。–>&2

问题是(我猜)此解决方案可能会使流不同步。因为过滤stderr可能相对而言要晚一些stdoutecho默认情况下,输出属于该位置)。要解决此问题,如果您不介意同时使用流,则可以先加入流stdout

./script.sh > >(grep -v "^+ echo ") 2>&1

您可以在脚本本身中构建这样的过滤器,但是这种方法肯定容易失步(即,它已经在我的测试中发生:命令的执行跟踪可能会在紧随其后的输出之后出现echo)。

代码如下:

#!/bin/bash -x

{
 # original script here
 # …
} 2> >(grep -v "^+ echo " >&2)

运行它没有任何技巧:

./script.sh

同样,使用> >(grep -v "^+ echo ") 2>&1来维持同步,但要以加入流为代价。


另一种方法。由于终端混合使用stdout和,因此您会得到“有点多余”的输出,而且看起来很奇怪stderr。这两个流出于某种原因是不同的动物。检查分析是否stderr仅满足您的需求;丢弃stdout

./script.sh > /dev/null

如果您的脚本中有echo打印调试/错误消息,stderr则您可以通过上述方式摆脱冗余。完整命令:

./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)

这次我们stderr只使用,因此不再需要去同步。不幸的是,这样一来,您将看不到echo打印的痕迹或输出stdout(如果有的话)。我们可以尝试重建我们的过滤器来检测重定向(>&2),但如果你看一下echo foobar >&2echo >&2 foobarecho "foobar >&2"那么你可能会同意,事情就变得复杂。

很大程度上取决于脚本中的回声。在实施一些复杂的过滤器之前,请三思而后行,这可能适得其反。最好有一点冗余,而不是偶然丢失一些关键信息。


除了丢弃a的执行跟踪,echo我们还可以丢弃其输出–以及除跟踪之外的所有输出。要仅分析执行跟踪,请尝试:

./script.sh > /dev/null 2> >(grep "^+ " >&2)

万无一失?不,想想如果echo "+ rm -rf --no-preserve-root /" >&2脚本中有内容会发生什么。有人可能会心脏病发作。


最后...

幸运的是存在BASH_XTRACEFD环境变量。来自man bash

BASH_XTRACEFD
如果设置为与有效文件描述符对应的整数,则bash会将set -x启用时生成的跟踪输出写入该文件描述符。

我们可以这样使用它:

(exec 3>trace.txt; BASH_XTRACEFD=3 ./script.sh)
less trace.txt

注意第一行产生一个子shell。这样,文件描述符将不会保持有效,也不会在随后的当前shell中分配变量。

多亏了BASH_XTRACEFD您可以分析没有回声和任何其他输出的迹线,无论它们是什么。这并不是您想要的,但我的分析使我认为这(通常)是正确的方法。

当然,您可以使用另一种方法,尤其是当您需要分析stdout和/或stderr与踪迹一起使用时。您只需要记住存在某些限制和陷阱。我试图向他们展示(一些)。


0

在Makefile中,您可以使用@符号。

用法示例: @echo 'message'

这个 GNU文档中:

当一行以“ @”开头时,该行的回显被抑制。在将行传递到外壳程序之前,将丢弃“ @”。通常,您可以将其用于唯一效果是打印某些内容的命令,例如echo命令,以指示生成文件的进度。


嗨,您要参考的文档是针对gnu make的,而不是shell。您确定它有效吗?我收到错误消息./test.sh: line 1: @echo: command not found,但是我正在使用bash。

哇,对不起,我完全误解了这个问题。是的,@仅当您在makefile中回显时才有效。
德里克

@derek我编辑了您的原始答复,因此现在它清楚地指出,该解决方案仅适用于Makefile。我实际上是在寻找这个,所以我希望您的评论没有负面的声誉。希望人们也会发现它也有帮助。
沙卡隆

-1

每个主持人于2016年10月29日编辑建议,认为原件没有足够的信息来了解正在发生的事情。

当xtrace处于活动状态时,此“技巧”仅在终端产生一行消息输出:

最初的问题是是否有办法使-x保持启用状态,但仅输出Message而不是这两行。这是问题的确切报价。

我理解的问题是,如何“启用set -x并为消息生成一行”?

  • 从全局意义上讲,这个问题基本上是关于美学的问题—发问者想产生一个单行,而不是在激活xtrace时产生的两个实际上重复的行。

因此,总而言之,OP要求:

  1. 一组-x生效
  2. 产生一条人类可读的消息
  3. 仅产生一行消息输出。

该任择议定书并没有要求该回声使用命令。他们列举了它作为一个例子消息生产,使用的缩写它代表拉丁文exempli恤,或“例如”。

我想到“在盒子外面”并放弃使用echo来生成消息,我注意到赋值语句可以满足所有要求。

将文本字符串(包含消息)分配给无效变量。

将此行添加到脚本不会改变任何逻辑,但在跟踪处于活动状态时会产生一行:(如果使用变量$ echo,只需将名称更改为另一个未使用的变量)

echo="====================== Divider line ================="

您将在终端上看到的只有1行:

++ echo='====================== Divider line ================='

不是两个,因为OP不喜欢:

+ echo '====================== Divider line ================='
====================== Divider line =================

这是示例脚本来演示。请注意,将变量替换为消息($ HOME目录名末尾4行)有效,因此可以使用此方法跟踪变量。

#!/bin/bash -exu
#
#  Example Script showing how, with trace active, messages can be produced
#  without producing two virtually duplicate line on the terminal as echo does.
#
dummy="====================== Entering Test Script ================="
if [[ $PWD == $HOME ]];  then
  dummy="*** "
  dummy="*** Working in home directory!"
  dummy="*** "
  ls -la *.c || :
else
  dummy="---- C Files in current directory"
  ls -la *.c || :
  dummy="----. C Files in Home directory "$HOME
  ls -la  $HOME/*.c || :
fi

这是在根目录和主目录中运行它的输出。

$ cd /&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ / == /g/GNU-GCC/home/user ]]
+ dummy='---- C Files in current directory'
+ ls -la '*.c'
ls: *.c: No such file or directory
+ :
+ dummy='----. C Files in Home directory /g/GNU-GCC/home/user'
+ ls -la /g/GNU-GCC/home/user/HelloWorld.c /g/GNU-GCC/home/user/hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/hw.c
+ dummy=---------------------------------

$ cd ~&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ /g/GNU-GCC/home/user == /g/GNU-GCC/home/user ]]
+ dummy='*** '
+ dummy='*** Working in home directory!'
+ dummy='*** '
+ ls -la HelloWorld.c hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 hw.c
+ dummy=---------------------------------

1
请注意,即使已删除答案的编辑版本(这是副本的副本)仍然无法回答问题,因为没有关联的echo命令(答案就是OP所要求的),答案不会仅输出消息。
DavidPostill

请注意,此答案正在meta上讨论。我是否因1主持人不喜欢的答案而受到惩罚?
DavidPostill

@David我在meta论坛上的评论中扩大了解释。
HiTechHiTouch '16

@David再一次,我鼓励您仔细阅读OP,注意拉丁语“ .eg”。发布者不需要使用echo命令。OP仅参考echo作为解决问题的潜在方法的示例(以及重定向)。原来您也不需要,
HiTechHiTouch

如果OP想要做类似的事情echo "Here are the files in this directory:" *怎么办?
G-Man说'Resstate Monica''Nov
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.