如何在运行时仅从bash脚本中打印某些命令?


19

我有一个bash脚本,其中包含基于调用时传递的命令行参数的各种if语句。关于正在运行的命令有某种输出有助于确认所有if语句的流程,但是我当前的解决方案给了我太多信息。

set -v在脚本中使用有助于查看在脚本中运行时打印到屏幕上的命令,但是我得到的命令太多。它几乎就像是脚本的完整副本。

我希望输出显示正在运行的命令,但是我不想看到注释,换行符,if语句中的表达式等。

有没有一种方法可以在打印之前先使-v选项生成的所有可能的输出通过正则表达式?还是其他一些使bash仅输出特定“类型”命令的解决方案(例如,使用可执行文件而不仅仅是bash特定的语句,注释等)?

[1] /programming/257616/sudo-changes-path-why在此方面非常有帮助,也是我得到使用建议的地方set -v

编辑

与我正在运行的脚本类似(但不完全相同)的脚本:

#!/bin/bash

#get verbose command output
set -v

env=$1

if [ "$env" == "dev" ]; then
    python ascript.py
fi

if [ "$env" == "prod" ]; then

    #launching in prod will most likely fail if not run as root. Warn user if not running as root.
    if [ $EUID -ne 0 ]; then
        echo "It doesn't look like you're running me as root. This probably won't work. Press any key to continue." > /dev/stderr
        read input
    fi

    #"stop" any existing nginx processes
    pkill -f nginx

    nginx -c `pwd`/conf/artfndr_nginx.conf

fi

我只需要该脚本的2组可能的输出线。首先:

python ascript.py

第二:

pkill -f nginx
nginx -c /some/current/directory/conf/artfndr_nginx.conf

1
当然,您可以解析它,但是除非您向我们展示脚本并解释所需的set -v输出部分以及不需要的部分,否则我们无济于事。
terdon

Answers:


12

当我编写更复杂的bash脚本时,我使用一个小函数来运行命令,该命令还会将运行的命令打印到日志文件中:

runthis(){
    ## print the command to the logfile
    echo "$@" >> $LOG
    ## run the command and redirect it's error output
    ## to the logfile
    eval "$@" 2>> $LOG 
}

然后,在我的脚本中,运行以下命令:

runthis "cp /foo/bar /baz/"

如果您不想打印命令,则只需正常运行即可。

您可以将设置$LOG为文件名,也可以将其删除并打印到stdout或stderr。


+1另外,我还可以通过在函数中使用简短的名称开头的“重要”命令来在脚本中运行此代码,因此这些行看起来就像v python ascript.py不必用引号引起来,也不会丢失我的vim代码突出显示
Trindaz

@Trindaz的引号是您需要在命令中传递变量的地方,如果变量包含空格,否则可能会出现问题。
terdon

eval ..... || ok=1:仅当“ eval ...”失败时才将ok设置为“ 1” 也许您是说“ &&”?如果您要这样做,请在评估行之前添加“ ok = 0”,以便每次都“重置”。还是简单地将“确定”重命名为“错误”?看来这就是这里的意思。因此,最后:eval "$@" 2>> "$LOG" && error=0 || error=1
Olivier Dulac 2013年

@OlivierDulac,在我使用的版本中,我有一个ok变量,如果任何命令失败,该变量将停止脚本。由于此处无关紧要,因此我删除了它,但忘记删除了|| ok=1。谢谢,现在修复。
terdon

优秀的解决方案!我必须删除"周围的eval语句,因为该命令已经被"s 包围了
gromit190 '17

11

使用一个子shell,即:

( set -x; cmd1; cmd2 )

例如:

( set -x; echo "hi there" )

版画

+ echo 'hi there'
hi there

set -x; cmd; set +x出于某些原因而不喜欢这个。首先,set -x万一之前已打开,它不会重置。其次,内部脚本的终止不会导致在启用了详细设置的情况下执行陷阱。
奥利弗·冈萨

2

我见过使用类似于@terdon的方法。这是高级编程语言称为记录器的开始,并提供完整的库,例如log4J(Java),log4Perl(Perl)等。

set -x如前所述,您可以在Bash中获得类似的使用,但是您可以使用它来包装代码块,从而仅调试部分命令即可调试。

$ set -x; cmd1; cmd2; set +x

例子

这是您可以使用的一种衬纸图案。

$ set -x; echo  "hi" ;set +x
+ echo hi
hi
+ set +x

您可以像这样将它们包装为脚本中的多个命令。

set -x
cmd1
cmd2
set +x

cmd3

Log4Bash

大多数人都没有注意到,但是Bash也有一个log4 *,Log4Bash。如果您的需求比较适中,那么值得花时间进行设置。

log4bash试图更好地记录Bash脚本(即,减少Bash中的记录)。

例子

以下是一些使用log4bash的示例。

#!/usr/bin/env bash
source log4bash.sh

log "This is regular log message... log and log_info do the same thing";

log_warning "Luke ... you turned off your targeting computer";
log_info "I have you now!";
log_success "You're all clear kid, now let's blow this thing and go home.";
log_error "One thing's for sure, we're all gonna be a lot thinner.";

# If you have figlet installed -- you'll see some big letters on the screen!
log_captains "What was in the captain's toilet?";

# If you have the "say" command (e.g. on a Mac)
log_speak "Resistance is futile";

Log4sh

如果您希望我将其归类为log4 *框架的全部功能,请尝试使用Log4sh

摘抄

log4sh最初是为解决我在某些生产环境中遇到的日志记录问题而开发的,在这些环境中,我的日志过多或不足。尤其是Cron的工作使我最头疼的是,它们不断发出令人讨厌的电子邮件,告诉我一切正常,或者没有任何作用,但没有详细的原因。现在,我在从shell脚本进行日志记录非常重要的环境中使用log4sh,但我需要的不仅仅是一个简单的“您好,请修复我!” 日志消息的类型。如果您喜欢所看到的内容,或对改进有任何建议,请随时给我发送电子邮件。如果对该项目有足够的兴趣,我将进一步开发它。

log4sh是在Linux上的Bourne Again Shell(/ bin / bash)下开发的,但是要小心确保它在Solaris的默认Bourne Shell(/ bin / sh)下工作,因为它恰好是主要产品。我自己使用的平台。

Log4sh支持多个Shell,而不仅仅是Bash。

  • 伯恩壳牌(sh)
  • 重击-GNU Bourne Again Shell(重击)
  • 破折号(破折号)
  • Korn Shell(ksh)
  • pdksh-Public Domain Korn Shell(pdksh)

它还已经在多个操作系统上进行了测试,而不仅仅是Linux。

  • Cygwin(在Windows下)
  • FreeBSD(受用户支持)
  • Linux(Gentoo,RedHat,Ubuntu)
  • Mac OS X
  • Solaris 8、9、10

使用log4 *框架需要花费一些时间来学习,但是如果您对日志记录有更高的要求,则值得这样做。Log4sh利用配置文件,您可以在其中定义附加程序并控制将出现的输出的格式。

#! /bin/sh
#
# log4sh example: Hello, world
#

# load log4sh (disabling properties file warning) and clear the default
# configuration
LOG4SH_CONFIGURATION='none' . ./log4sh
log4sh_resetConfiguration

# set the global logging level to INFO
logger_setLevel INFO

# add and configure a FileAppender that outputs to STDERR, and activate the
# configuration
logger_addAppender stderr
appender_setType stderr FileAppender
appender_file_setFile stderr STDERR
appender_activateOptions stderr

# say Hello to the world
logger_info 'Hello, world'

现在,当我运行它时:

$ ./log4sh.bash 
INFO - Hello, world

注意:以上将追加程序配置为代码的一部分。如果您愿意,可以将其提取到自己的文件中,log4sh.properties等中。

如果需要更多详细信息,请查阅Log4sh优秀文档


感谢您添加的注释,但是我set遇到的主要问题是我需要引入的所有命令,在注释周围交替等等,因此,只需在脚本顶部添加一个函数,并在前面添加一个字符函数调用即可目前,脚本中的所有“重要”行对我来说都显得更加整洁。(因为函数具有单个字符名称,所以使用单个字符)
Trindaz 2013年

@Trindaz-对不起,我还没有完成我的回答。如果您对terdon提供的功能有更多的需求,请看一下log4bash。
slm

1
@Trindaz-我有时会做类似的事情,我使用的另一种方法是包装echo自己的函数,mecho然后-v在我要关闭电源时将开关传递到名为verbose 的程序中。我还可以使用第二个参数开关来控制它,该开关指定了函数的名称,因此我有2条轴可以控制日志记录。不过,这通常是通向log4bash的门户。
slm

1
@Trindaz set -x在执行命令时打印命令。它不打印注释。set -x对于调试set -v非常实用(与它不是很有用)不同。Zsh具有set -x比bash 更好的输出,例如,它显示了当前正在执行哪个功能以及源行号。
吉尔(Gilles)'所以

感谢@Gilles的说法是正确的,但这确实给了我if表达式的扩展,在这种情况下这是过分的了
Trindaz 2013年

1

你可以 trap DEBUG再测试BASH_COMMAND变量。将其添加到脚本顶部:

log() {
    case "$1" in
        python\ *)
            ;&
        pkill\ *)
            printf "%s\n" "$*"
            ;;
    esac
}

trap 'log "$BASH_COMMAND"' DEBUG

该代码可读。它只是测试第一个参数是否以pythonpkill,并且在这种情况下将其打印出来。陷阱使用BASH_COMMAND(包含将要执行的命令)作为第一个参数。

$ bash foo.sh dev
python ascript.py
python: can't open file 'ascript.py': [Errno 2] No such file or directory
$ bash foo.sh prod
It doesn't look like you're running me as root. This probably won't work. Press any key to continue.

pkill -f nginx
foo.sh: line 32: nginx: command not found

请注意,尽管case使用全局变量,您也可以轻松地做到这一点:

if [[ $1 =~ python|nginx ]]
then
    printf "%s" "$*"
fi

并使用正则表达式。


0

这是史蒂文·潘尼(Steven Penny)简洁功能的修订版。它以彩色打印其参数,并根据需要引用它们。使用它有选择地回显您要跟踪的命令。由于输出的是引号,因此您可以在调试脚本时复制打印的行并将其粘贴到终端以立即重新执行。阅读第一个评论以了解我的更改以及原因。

xc() # $@-args
{
  cecho "$@"
  "$@"
}
cecho() # $@-args
{
  awk '
  BEGIN {
    x = "\047"
    printf "\033[36m"
    while (++i < ARGC) {
      if (! (y = split(ARGV[i], z, x))) {
        printf (x x)
      } else {
        for (j = 1; j <= y; j++) {
          printf "%s", z[j] ~ /[^[:alnum:]%+,./:=@_-]/ ? (x z[j] x) : z[j]
          if (j < y) printf "\\" x
        }
      }
      printf i == ARGC - 1 ? "\033[m\n" : FS
    }
  }
  ' "$@"
}

输出示例用法:

# xc echo "a b" "c'd" "'" '"' "fg" '' " " "" \# this line prints in green

echo 'a b' c\'d \' '"' fg '' ' ' '' '#' this line prints in green

a b c'd ' " fg # this line prints in green

上面的第二行以绿色打印,可以复制粘贴以重现第三行。

进一步说明

@ Steven-Penny的原始xc很聪明,他值得得到所有荣誉。但是,我注意到了一些问题,但是由于我没有足够的声誉,所以无法直接评论他的帖子。因此,我对他的帖子进行了建议的修改,但审阅者拒绝了我的修改。因此,尽管我本来希望能够编辑Steve Penny自己的答案,但我还是将评论发表为该答案。

我用史蒂文·彭尼的答案改变了什么

修正:打印空字符串-未打印。修正:打印包含以下内容的字符串%-它们导致awk语法错误。替换for (j in ...)for (j = 0, ...)之所以是因为前者不能保证数组遍历的顺序(它与awk实现有关)。在八进制数字上增加了0,以便于移植。

更新资料

史蒂文·潘尼(Steven Penny)从那以后就在他的回答中解决了这些问题,因此这些评论仅作为我回答的历史记录。有关更多详细信息,请参见评论部分。


0

您可以在运行命令之前使用POSIX stdlib库中的“ sh_trace” shell函数以彩色打印命令。例:

预习

基本的Awk功能:

function sh_trace(ary,   b, d, k, q, w, z) {
  b = "\47"
  for (d in ary) {
    k = split(ary[d], q, b)
    q[1]
    if (d - 1)
      z = z " "
    for (w in q) {
      z = z (!k || q[w] ~ "[^[:alnum:]%+,./:=@_-]" ? b q[w] b : q[w]) \
      (w < k ? "\\" b : "")
    }
  }
  printf "\33[36m%s\33[m\n", z
  system(z)
}
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.