如何在执行时输出Shell命令

1269
在一个shell脚本中,如何输出所有被调用的shell命令并展开所有变量名?例如,给定以下行:
ls $DIRNAME

我希望脚本可以运行命令并显示以下内容

ls /full/path/to/some/dir

目的是保存所有调用的shell命令及其参数的日志。也许有更好的方法来生成这样的日志吗?


另请参阅(重复):如何在执行之前打印每个命令? - Gabriel Staples
17个回答

1452

set -xset -o xtrace 展开变量并在行前打印一个小加号。

set -vset -o verbose 在打印之前不展开变量。

使用 set +xset +v 来关闭上述设置。

在脚本的第一行中,可以写入 #!/bin/sh -x(或 -v) 以达到与后面的 set -x(或 -v) 相同的效果。

以上方法同样适用于 /bin/sh

请参阅 bash-hackers' wiki 上关于 set 属性调试 的内容。

$ cat shl
#!/bin/bash                                                                     

DIR=/tmp/so
ls $DIR

$ bash -x shl 
+ DIR=/tmp/so
+ ls /tmp/so
$

19
如果您想查看正在执行的编号行,也可以参见https://dev59.com/dWMm5IYBdhLWcg3wMssp#17805088。 - user13107
9
如果我想在回显时为命令着色以区分命令和其结果输出,该怎么办? - Lewis Chan
2
@LewisChan:您可以为您的命令添加带颜色的静态或动态前缀,例如时间戳,请参见https://dev59.com/SKzka4cB1Zd3GeqP7V1C#62620480。 - Greg Dubicki
2
@AndreasDietrich 谢谢。如果您真的找到了一种实现我的笑话问题的方法,那将让我一整天都很开心。最好使用一些强大的cli工具,并且只需要几行代码。 - ADJenks
3
“bash -x foo.sh” 是我所需的关键。 (作为评论发布,因为不清楚是否需要修改脚本本身才能运行;但实际上不需要修改。) - ijoseph
显示剩余6条评论

443

set -x会给你想要的结果。

这里是一个演示脚本的示例:

#!/bin/bash
set -x #echo on

ls $PWD

这将展开所有变量并在输出命令之前打印完整的命令。

输出:

+ ls /home/user/
file1.txt file2.txt

27
用那样的方式使用“verbose”这个词并没有起到任何作用。你可以使用以下命令:set -o verbose 或者 set -v(只有“verbose”)或者 set -o xtrace 或者 set -x(只有“xtrace”)或者 set -xv(两者都有)或者 set -o xtrace -o verbose(两者都有)。 - Dennis Williamson
这个程序运行良好,但请注意,“verbose”将覆盖$1。 - JasonS

136
我使用一个函数来输出并运行命令:

我使用一个函数来回显和运行命令:

#!/bin/bash
# Function to display commands
exe() { echo "\$ $@" ; "$@" ; }

exe echo hello world

输出哪些内容

$ echo hello world
hello world

对于更复杂的命令,例如管道等,您可以使用eval:

#!/bin/bash
# Function to display commands
exe() { echo "\$ ${@/eval/}" ; "$@" ; }

exe eval "echo 'Hello, World!' | cut -d ' ' -f1"

输出什么内容

$  echo 'Hello, World!' | cut -d ' ' -f1
Hello

1
这个答案没有得到很多投票。有什么原因是不好的想法吗?对我来说有效,而且似乎正是我正在寻找的... - fru1tbat
3
如果您不想打印每个命令,这是最好的答案。它避免了在关闭时输出 ++ set +x ,同时看起来更干净。但是,对于只有一两个语句的情况,bhassel 的答案使用子 shell 是最方便的。 - Brent Faust
20
这样做的一个主要缺点是输出会失去引用信息。例如,你无法区分 cp "foo bar" bazcp foo "bar baz"。 因此,它适用于向用户显示进度信息,但不适用于调试输出或记录可重现命令等场景。不同的使用情况。在 zsh 中,你可以使用 :q 修改符来保留引号:exe() { echo '$' "${@:q}" ; "$@" ; } - Andrew Janke
1
我喜欢这个解决方案,已经使用了一段时间。 但是,在复杂的命令中似乎会失败:例如exe(cut -d ' ' -f9,10 --complement ${folder}/hog_crop_px.txt) | paste - ${folder}/lbp_crop_px.txt > ${folder}/crop_px.tx给出了syntax error near unexpected token cut'`错误,尽管在没有exe()命令的情况下可以完美执行。有什么建议吗? - penelope
3
我不喜欢这个答案。有许多边缘情况,你看到的并不是你得到的(特别是在空格、引号、转义字符、变量/表达式替换等方面),所以不要盲目地将回显的命令粘贴到终端,并假设它会以相同的方式运行。 此外,第二种技术只是一个hack,会剥离出命令中其他eval单词的实例。因此,不要期望它能正确处理 exe eval "echo 'eval world'" - mwfearnley
显示剩余11条评论

103

您也可以通过将脚本中需要调试的代码放在set -xset +x之间来实现选定行的切换,例如:

#!/bin/bash
...
if [[ ! -e $OUT_FILE ]];
then
   echo "grabbing $URL"
   set -x
   curl --fail --noproxy $SERV -s -S $URL -o $OUT_FILE
   set +x
fi

9
但是,脚本还会打印出set +x - ilw
2
提示:我们可以使用{ set +x; } &> /dev/null来消除set +x的输出。 - Kjuly
1
很好的提示 @Kjuly - undefined

81

shuckc的回答用于回显特定行有一些缺点:你最终会看到set +x命令的输出,而且你失去了使用$?测试退出码的能力,因为它被set +x覆盖了。

另一个选择是在子shell中运行命令:

echo "getting URL..."
( set -x ; curl -s --fail $URL -o $OUTFILE )

if [ $? -eq 0 ] ; then
    echo "curl failed"
    exit 1
fi

它将会给你输出,如下所示:

getting URL...
+ curl -s --fail http://example.com/missing -o /tmp/example
curl failed

尽管如此,这会产生为该命令创建新子 shell 的开销。


10
避免++ set +x输出的好方法。 - Brent Faust
8
жӣҙеҘҪзҡ„ж–№жі•жҳҜе°Ҷif [ $? -eq 0 ]жӣҝжҚўдёәif (set -x; COMMAND)гҖӮ - Amedee Van Gasse
太棒了!非常好的答案。感谢您的见解。 - phyatt
在我的问题中,有人推荐了这个很酷的解决方案:https://superuser.com/questions/806599/suppress-execution-trace-for-echo-command/1141026 - Ferran Maylinch

47
根据TLDPBash初学者指南:第2章。编写和调试脚本,如下所述:

2.3.1. Debugging on the entire script

$ bash -x script1.sh

...

There is now a full-fledged debugger for Bash, available at SourceForge. These debugging features are available in most modern versions of Bash, starting from 3.x.

2.3.2. Debugging on part(s) of the script

set -x            # Activate debugging from here
w
set +x            # Stop debugging from here

...

Table 2-1. Overview of set debugging options

    Short  | Long notation | Result
    -------+---------------+--------------------------------------------------------------
    set -f | set -o noglob | Disable file name generation using metacharacters (globbing).
    set -v | set -o verbose| Prints shell input lines as they are read.
    set -x | set -o xtrace | Print command traces before executing command.

...

Alternatively, these modes can be specified in the script itself, by adding the desired options to the first line shell declaration. Options can be combined, as is usually the case with UNIX commands:

#!/bin/bash -xv

38

另一个选择是在脚本顶部放置“-x”,而不是在命令行中使用:

$ cat ./server
#!/bin/bash -x
ssh user@server

$ ./server
+ ssh user@server
user@server's password: ^C
$

请注意,在“./myScript”和“bash myScript”之间似乎并不完全相同。仍然是一个很好的指出,谢谢。 - altendky

37

你可以使用-x选项在调试模式下执行 Bash 脚本。

这会打印出所有的命令。

bash -x example_script.sh

# Console output
+ cd /home/user
+ mv text.txt mytext.txt

您也可以在脚本中保存-x选项。只需在shebang中指定-x选项即可。

######## example_script.sh ###################
#!/bin/bash -x

cd /home/user
mv text.txt mytext.txt

##############################################

./example_script.sh

# Console output
+ cd /home/user
+ mv text.txt mytext.txt

3
bash -vxset -x 相同,都可以打开调试模式,但不会进行变量替换。 - loa_in_
1
这很不错,但比我想要的更加专业。它似乎“下降”到由我的顶级脚本运行的所有命令中。我真的只想要我的顶级脚本的命令被回显,而不是 bash 运行的绝对所有内容。 - Ben Farmer

23

综合所有答案,我发现这是最好的、最简单的。

#!/bin/bash
# https://dev59.com/yHE85IYBdhLWcg3wShaf#64644990
exe(){
    set -x
    "$@"
    { set +x; } 2>/dev/null
}
# example
exe go generate ./...

{ set +x; } 2>/dev/null 来自 https://dev59.com/Q2Ys5IYBdhLWcg3wBvf7#19226038

如果需要命令的退出状态,如 这里 所提到的

可以使用

{ STATUS=$?; set +x; } 2>/dev/null

在结束时可以像这样使用$STATUS,比如exit $STATUS

稍微有点用处的一个

#!/bin/bash
# https://dev59.com/yHE85IYBdhLWcg3wShaf#64644990
_exe(){
    [ $1 == on  ] && { set -x; return; } 2>/dev/null
    [ $1 == off ] && { set +x; return; } 2>/dev/null
    echo + "$@"
    "$@"
}
exe(){
    { _exe "$@"; } 2>/dev/null
}

# examples
exe on # turn on same as set -x
echo This command prints with +
echo This too prints with +
exe off # same as set +x
echo This does not

# can also be used for individual commands
exe echo what up!

1
很好的例子。我会在两个脚本的顶部添加 #!/bin/bash - mhck

22

在Bash脚本名称前,在命令行上键入“bash -x”。例如,要执行foo.sh,请键入:

bash -x foo.sh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接