关于trace:如何在执行shell命令时进行回显

How to echo shell commands as they are executed

在shell脚本中,如何回送调用的所有shell命令并展开任何变量名?

例如,给出以下行:

1
ls $DIRNAME

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

1
ls /full/path/to/some/dir

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


set -xset -o xtrace扩展变量并在行前打印一点+符号。

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

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

在脚本的第一行,可以将#!/bin/sh -x-v放在脚本的后面,使其具有与set -x-v相同的效果。

以上也适用于/bin/sh

set属性和调试上查看bash黑客的wiki。

1
2
3
4
5
6
7
8
9
10
$ cat shl
#!/bin/bash                                                                    

DIR=/tmp/so
ls $DIR

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


set -x会给你想要的。

下面是一个示例shell脚本,演示:

1
2
3
4
#!/bin/bash
set -x #echo on

ls $PWD

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

输出:

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


您还可以将脚本中的select行包装在set -xset +x中,从而为它们切换此选项。

1
2
3
4
5
6
7
8
9
#!/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

我使用一个函数来回音,然后运行命令

1
2
3
4
5
#!/bin/bash
#function to display commands
exe() { echo"\$ $@" ;"$@" ; }

exe echo hello world

哪些输出

1
2
$ echo hello world
hello world

编辑:

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

1
2
3
4
5
#!/bin/bash
#function to display commands
exe() { echo"\$ ${@/eval/}" ;"$@" ; }

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

哪些输出

1
2
$  echo 'Hello World' | cut -d ' ' -f1
Hello


Shuckc对回显select行的回答有一些缺点:您最终会得到如下的set +x命令被回显,并且您会失去使用$?测试退出代码的能力,因为它会被set +x覆盖。

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

1
2
3
4
5
6
7
echo"getting URL..."
( set -x ; curl -s --fail $URL -o $OUTFILE )

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

这将为您提供如下输出:

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

但是,这会导致为命令创建新的子shell的开销。


另一个选项是将"-x"放在脚本顶部,而不是命令行上:

1
2
3
4
5
6
7
8
$ cat ./server
#!/bin/bash -x
ssh user@server

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

(没有足够的代表对所选答案发表评论。)


根据TLDP的bash初学者指南:第2章。编写和调试脚本

2.3.1. Debugging on the entire script

1
$ 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

1
2
3
set -x            # activate debugging from here
w
set +x            # stop debugging from here

...

Table 2-1. Overview of set debugging options

1
2
3
4
5
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:

1
#!/bin/bash -xv

在命令行的bash脚本名称之前键入"bash-x"。例如,要执行foo.sh,请键入:

1
bash -x foo.sh

您可以使用-x选项在调试模式下执行bash脚本。这将回响所有命令。

1
2
3
4
5
bash -x example_script.sh

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
######## 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


上面有人发帖:

1
2
3
#!/bin/bash
#function to display commands
exe() { echo"\$ $@" ;"$@" ; }

这看起来很有希望,但我一辈子都搞不清楚它的作用。我在man bash页面上搜索并搜索了"$"和"$@",结果一无所获。

我知道正在创建一个名为"exec()"的函数。我理解花括号标记函数的开始和结束。我认为我理解分号在多行命令之间标记一个"硬返回",因此"echo"$@";"$@";"本质上变成:

1
2
3
4
5
{
echo"\$ $@"
"$@"

}

既然我的google fu显然让我失望了,有人能给我一个简短的解释,或者在哪里找到这个信息吗?

(不打算在旧线程上开始新问题,我的目标是将输出重新路由到文件。"set-x;[commands];set+x"方法对我来说可以很好地工作,但是我不知道如何将结果回送到文件而不是屏幕,所以我试图理解另一种方法,希望我对重定向/pipes/tee/等的理解非常差以完成相同的工作。)

谢谢!

后期编辑:

做了些小修补,我想我已经弄明白了。以下是我需要的等效代码:

1
2
3
4
5
6
7
8
9
SCRIPT_LOG="\home\buddy\logfile.txt"
exe () {
  params="$@"                       # Put all of the command-line into"params"
  printf"%s\t$params""$(date)">>"$SCRIPT_LOG"   # Print the command to the log file
  $params                           # Execute the command
}

exe rm -rf /Library/LaunchAgents/offendingfile
exe rm -rf /Library/LaunchAgents/secondoffendingfile

logfile.txt中的结果如下:

1
2
Tue Jun  7 16:59:57 CDT 2016  rm -rf /Library/LaunchAgents/offendingfile
Tue Jun  7 16:59:57 CDT 2016  rm -rf /Library/LaunchAgents/secondoffendingfile

只是我需要的。谢谢!


ZSH回波

1
 setopt VERBOSE

用于调试

1
 setopt XTRACE


对于cshtcsh,您可以设置set verboseset echo(或者您甚至可以设置两者,但大多数情况下可能会导致一些重复)。

verbose选项打印的shell表达式与您键入的几乎完全相同。

echo选项更能说明将通过产卵来执行什么。

http://www.tcsh.org/tcsh.html/special_shell_variables.html_verbose

http://www.tcsh.org/tcsh.html/special_shell_variables.html echo

Special shell variables

verbose
If set, causes the words of each command to be printed, after history substitution (if any). Set by the -v command line option.

echo
If set, each command with its arguments is echoed just before it is executed. For non-builtin commands all expansions occur before echoing. Builtin commands are echoed before command and filename substitution, because these substitutions are then done selectively. Set by the -x command line option.


1
2
3
4
5
6
$ cat exampleScript.sh
#!/bin/bash
name="karthik";
echo $name;

bash -x exampleScript.sh

输出如下:

enter image description here


为了允许复合命令被回送,我使用eval加上soth的exe函数来回送,然后运行该命令。这对于管道命令很有用,否则将只显示管道命令的无部分或仅显示管道命令的初始部分。

没有EVA:

1
2
exe() { echo"\$ $@" ;"$@" ; }
exe ls -F | grep *.txt

输出:

1
2
$
file.txt

用EVA:

1
2
exe() { echo"\$ $@" ;"$@" ; }
exe eval 'ls -F | grep *.txt'

哪些输出

1
2
$ exe eval 'ls -F | grep *.txt'
file.txt