如何在执行 shell 命令时回显它们

发布于 2024-09-01 17:46:15 字数 235 浏览 11 评论 0原文

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

例如,给定以下行:

ls $DIRNAME

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

ls /full/path/to/some/dir

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

In a shell script, how do I echo all shell commands called and expand any variable names?

For example, given the following line:

ls $DIRNAME

I would like the script to run the command and display the following

ls /full/path/to/some/dir

The purpose is to save a log of all shell commands called and their arguments. Is there perhaps a better way of generating such a log?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(17

ぃ弥猫深巷。 2024-09-08 17:46:15

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

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

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

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

以上也适用于 /bin/sh

请参阅 bash-hackers 的 wiki,了解设置属性,以及调试

$ cat shl
#!/bin/bash                                                                     

DIR=/tmp/so
ls $DIR

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

set -x or set -o xtrace expands variables and prints a little + sign before the line.

set -v or set -o verbose does not expand the variables before printing.

Use set +x and set +v to turn off the above settings.

On the first line of the script, one can put #!/bin/sh -x (or -v) to have the same effect as set -x (or -v) later in the script.

The above also works with /bin/sh.

See the bash-hackers' wiki on set attributes, and on debugging.

$ cat shl
#!/bin/bash                                                                     

DIR=/tmp/so
ls $DIR

$ bash -x shl 
+ DIR=/tmp/so
+ ls /tmp/so
$
活泼老夫 2024-09-08 17:46:15

set -x 会给你你想要的。

下面是一个用于演示的 shell 脚本示例:

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

ls $PWD

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

输出:

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

set -x will give you what you want.

Here is an example shell script to demonstrate:

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

ls $PWD

This expands all variables and prints the full commands before output of the command.

Output:

+ ls /home/user/
file1.txt file2.txt
情泪▽动烟 2024-09-08 17:46:15

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

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

exe echo hello world

whichoutputs

$ echo hello world
hello world

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

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

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

whichoutputs

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

I use a function to echo and run the command:

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

exe echo hello world

Which outputs

$ echo hello world
hello world

For more complicated commands pipes, etc., you can use eval:

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

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

Which outputs

$  echo 'Hello, World!' | cut -d ' ' -f1
Hello
素食主义者 2024-09-08 17:46:15

您还可以通过将脚本中的选定行包装在 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

You can also toggle this for select lines in your script by wrapping them in set -x and set +x, for example,

#!/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
心不设防 2024-09-08 17:46:15

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 的开销。

shuckc's answer for echoing select lines has a few downsides: you end up with the following set +x command being echoed as well, and you lose the ability to test the exit code with $? since it gets overwritten by the set +x.

Another option is to run the command in a subshell:

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

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

which will give you output like:

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

This does incur the overhead of creating a new subshell for the command, though.

游魂 2024-09-08 17:46:15

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

2.3.1。对整个脚本进行调试

<前><代码>$ bash -x script1.sh

...

现在有一个成熟的 Bash 调试器,可从 SourceForge 获取。从 3.x 开始,大多数现代 Bash 版本都提供这些调试功能。

2.3.2。对脚本的部分内容进行调试

set -x # 从这里激活调试
w
set +x # 从这里停止调试

...

表 2-1。设置调试选项概述

    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.

...

或者,这些模式可以在脚本本身中指定,方法是
将所需的选项添加到第一行 shell 声明中。
选项可以组合,就像 UNIX 命令的通常情况一样:

<前><代码>#!/bin/bash -xv

According to TLDP's Bash Guide for Beginners: Chapter 2. Writing and debugging scripts:

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
筱武穆 2024-09-08 17:46:15

您可以使用-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

You can execute a Bash script in debug mode with the -x option.

This will echo all the commands.

bash -x example_script.sh

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

You can also save the -x option in the script. Just specify the -x option in the shebang.

######## 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
染年凉城似染瑾 2024-09-08 17:46:15

另一种选择是将“-x”放在脚本顶部而不是命令行上:

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

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

Another option is to put "-x" at the top of your script instead of on the command line:

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

$ ./server
+ ssh user@server
user@server's password: ^C
$
烟若柳尘 2024-09-08 17:46:15

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

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

{ set +x; 2>/dev/null 来自 https://stackoverflow.com/a/19226038/8608146

如果需要命令的退出状态,如此处

使用

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

稍后使用 $STATUS 就像最后的 exit $STATUS

稍微有用的一个

#!/bin/bash
# https://stackoverflow.com/a/64644990/8608146
_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!

Combining all the answers I found this to be the best, simplest

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

{ set +x; } 2>/dev/null from https://stackoverflow.com/a/19226038/8608146

If the exit status of the command is needed, as mentioned here

Use

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

And use the $STATUS later like exit $STATUS at the end

A slightly more useful one

#!/bin/bash
# https://stackoverflow.com/a/64644990/8608146
_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!
再见回来 2024-09-08 17:46:15

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

bash -x foo.sh

Type "bash -x" on the command line before the name of the Bash script. For instance, to execute foo.sh, type:

bash -x foo.sh
撩人痒 2024-09-08 17:46:15

对于 zsh、echo

setopt VERBOSE

以及调试,

setopt XTRACE

For zsh, echo

setopt VERBOSE

And for debugging,

setopt XTRACE
宣告ˉ结束 2024-09-08 17:46:15

为了允许回显复合命令,我使用 eval 加上 Soth 的 exe 函数来回显并运行命令。这对于管道命令很有用,否则这些命令将只显示任何内容或仅显示管道命令的初始部分。

不带 eval:

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

输出:

$
file.txt

带 eval:

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

输出

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

To allow for compound commands to be echoed, I use eval plus Soth's exe function to echo and run the command. This is useful for piped commands that would otherwise only show none or just the initial part of the piped command.

Without eval:

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

Outputs:

$
file.txt

With eval:

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

Which outputs

$ exe eval 'ls -F | grep *.txt'
file.txt
短叹 2024-09-08 17:46:15

按照Soth的答案。

可以创建不突出显示的 Markdown 输出(未给出语言)

set -x
exe() { echo "\`\$\$  ${@/eval/} \`" ; "$@" ; }

脚本

set -x
exe() { echo "\`\$\$  ${@/eval/} \`" ; "$@" ; }
echo
echo -----------------------------------------------------------
echo # Setup
echo Lets take a random keyframe from in.mp4:
echo
exe eval "kfn=20"
echo
echo "kf=\$(ffprobe -v error -select_streams v -show_frames -print_format csv in.mp4 | grep 'frame,video,0,1' | head -$kfn | tail -1 | perl -pe 's|frame,video,0,1,.*?,(.*?),.*|\1|') "
exe eval "kf=$(ffprobe -v error -select_streams v -show_frames -print_format csv in.mp4 | grep 'frame,video,0,1' | head -$kfn | tail -1 | perl -pe 's|frame,video,0,1,.*?,(.*?),.*|\1|') "
echo
echo Lets select keyframe at $kf. Here are the timestamps of the all the frames from in.mp4 around this keyframe.
echo
exe eval "ffprobe -v error -select_streams v -show_frames -print_format csv in.mp4  | perl -pe 's|frame,video,0,(.*?),.*?,(.*?),.*|\2  \1|' | perl -pe 's|(.*?)  1|\1\tKF|' |  perl -pe 's|(.*?)  0|\1|' |grep -A 5 -B 5 --color $kf"
echo
echo Lets compare 2 methods of split: actual losslesscut 3.53.0 and another one
echo

输出


让我们从 in.mp4 中获取随机关键帧:

$$ kfn=20

kf=$(ffprobe -v error -select_streams v -show_frames -print_format csv in.mp4 | grep 'frame,video,0,1' | head -20 | tail -1 | perl -pe 's|frame,video,0,1,. ?),.*|\1|')

$$ kf=3.803792

让我们选择 3.803792 处的关键帧。以下是 in.mp4 中该关键帧周围所有帧的时间戳。

$$ ffprobe -v 错误 -select_streams v -show_frames -print_format csv in.mp4 | perl -pe 's|帧,视频,0,(.*?),.*?,(.*?),.*|\2 \1|' | perl -pe 's|(.*?) 1|\1\tKF|' | perl -pe 's|(.*?) 0|\1|' |grep -A 5 -B 5 --颜色 3.803792

3.720375
3.737083
3.753750
3.770417
3.787125
**3.803792**   KF
3.820500
3.837167
3.853833
3.870542
3.887208

Following the answer of Soth.

It is possible to create a markdown output without highlight (no language given)

set -x
exe() { echo "\`\$\$  ${@/eval/} \`" ; "$@" ; }

script

set -x
exe() { echo "\`\$\$  ${@/eval/} \`" ; "$@" ; }
echo
echo -----------------------------------------------------------
echo # Setup
echo Lets take a random keyframe from in.mp4:
echo
exe eval "kfn=20"
echo
echo "kf=\$(ffprobe -v error -select_streams v -show_frames -print_format csv in.mp4 | grep 'frame,video,0,1' | head -$kfn | tail -1 | perl -pe 's|frame,video,0,1,.*?,(.*?),.*|\1|') "
exe eval "kf=$(ffprobe -v error -select_streams v -show_frames -print_format csv in.mp4 | grep 'frame,video,0,1' | head -$kfn | tail -1 | perl -pe 's|frame,video,0,1,.*?,(.*?),.*|\1|') "
echo
echo Lets select keyframe at $kf. Here are the timestamps of the all the frames from in.mp4 around this keyframe.
echo
exe eval "ffprobe -v error -select_streams v -show_frames -print_format csv in.mp4  | perl -pe 's|frame,video,0,(.*?),.*?,(.*?),.*|\2  \1|' | perl -pe 's|(.*?)  1|\1\tKF|' |  perl -pe 's|(.*?)  0|\1|' |grep -A 5 -B 5 --color $kf"
echo
echo Lets compare 2 methods of split: actual losslesscut 3.53.0 and another one
echo

Output


Lets take a random keyframe from in.mp4:

$$ kfn=20

kf=$(ffprobe -v error -select_streams v -show_frames -print_format csv in.mp4 | grep 'frame,video,0,1' | head -20 | tail -1 | perl -pe 's|frame,video,0,1,.?,(.?),.*|\1|')

$$ kf=3.803792

Lets select keyframe at 3.803792. Here are the timestamps of the all the frames from in.mp4 around this keyframe.

$$ ffprobe -v error -select_streams v -show_frames -print_format csv in.mp4 | perl -pe 's|frame,video,0,(.*?),.*?,(.*?),.*|\2 \1|' | perl -pe 's|(.*?) 1|\1\tKF|' | perl -pe 's|(.*?) 0|\1|' |grep -A 5 -B 5 --color 3.803792

3.720375
3.737083
3.753750
3.770417
3.787125
**3.803792**   KF
3.820500
3.837167
3.853833
3.870542
3.887208
挽容 2024-09-08 17:46:15

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

verbose 选项几乎可以打印您键入的确切 shell 表达式。

echo 选项更能说明通过生成将执行的内容。


http://www.tcsh.org/tcsh.html/Special_shell_variables.html#详细

http://www.tcsh.org/tcsh.html /Special_shell_variables.html#echo


特殊 shell 变量

详细
如果设置,则在历史替换(如果有)之后打印每个命令的单词。通过-v命令行选项设置。

echo
如果设置,则每个命令及其参数在执行之前都会被回显。对于非内置命令,所有扩展都在回显之前发生。在命令和文件名替换之前会回显内置命令,因为这些替换是有选择地完成的。通过 -x 命令行选项设置。

For csh and tcsh, you can set verbose or set echo (or you can even set both, but it may result in some duplication most of the time).

The verbose option prints pretty much the exact shell expression that you type.

The echo option is more indicative of what will be executed through spawning.


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.

撩心不撩汉 2024-09-08 17:46:15

如果您只想记录特定命令,而不是每一行,set -x 有点难以使用。但是您可以使用此函数来打印任何命令,前缀为 $

function run() {
    echo -n '

用法:

run ls /full/path/to/some/dir

printf%q 格式说明符一起使用可确保输出的打印方式适合复制并粘贴到 shell 中,即使存在空格或其他特殊字符也是如此。它似乎使用 \ 转义空格而不是添加引号,但输出确实正确地表示了实际正在运行的内容。

# Print $ without a newline. for arg in "$@"; do printf " %q" "$arg" # Print $arg, properly escaped. done echo # Print a newline. "$@" }

用法:

printf%q 格式说明符一起使用可确保输出的打印方式适合复制并粘贴到 shell 中,即使存在空格或其他特殊字符也是如此。它似乎使用 \ 转义空格而不是添加引号,但输出确实正确地表示了实际正在运行的内容。

If you want to log only specific commands, rather than every single line, set -x is somewhat difficult to use. But you can use this function to print any command, prefixed with $:

function run() {
    echo -n '

Usage:

run ls /full/path/to/some/dir

Using printf with the %q format specifier makes sure that the output is printed in a way suitable for copying and pasting into a shell, even in the presence of spaces or other special characters. It seems to escape spaces using \ rather than adding quotes, but the output does correctly represent what's actually being run.

# Print $ without a newline. for arg in "$@"; do printf " %q" "$arg" # Print $arg, properly escaped. done echo # Print a newline. "$@" }

Usage:

Using printf with the %q format specifier makes sure that the output is printed in a way suitable for copying and pasting into a shell, even in the presence of spaces or other special characters. It seems to escape spaces using \ rather than adding quotes, but the output does correctly represent what's actually being run.

我使用以下内容来准确控制我想要看到的内容:

#!/bin/bash

cmd="ls $PWD"
echo $cmd; eval $cmd

cmd="cat $PWD/whatfille.txt"
echo "-> $cmd"; eval $cmd      #this prefixes to command for better reading

扩展变量的关键是使用双引号。单引号不会扩展。

我有时使用“logger”而不是“echo”发送到 /var/log/syslog,因为它们通常以非交互方式运行。

I use the following to control exactly what I want to see:

#!/bin/bash

cmd="ls $PWD"
echo $cmd; eval $cmd

cmd="cat $PWD/whatfille.txt"
echo "-> $cmd"; eval $cmd      #this prefixes to command for better reading

The key to expand a variable is to use double quote. Single quote would not expand.

I sometimes use "logger" instead of "echo" to send to /var/log/syslog since they are often run non-interactively.

忆沫 2024-09-08 17:46:15
$ cat exampleScript.sh
#!/bin/bash
name="karthik";
echo $name;

bash -x exampleScript.sh

输出如下:

在此处输入图像描述

$ cat exampleScript.sh
#!/bin/bash
name="karthik";
echo $name;

bash -x exampleScript.sh

Output is as follows:

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文