【问题标题】:Log command line plus its output in a bash script在 bash 脚本中记录命令行及其输出
【发布时间】:2021-08-06 08:11:17
【问题描述】:

有没有办法让脚本同时记录命令行运行(包括管道运行)及其输出,而无需复制命令的行?

目的是脚本应该有一个干净的输出,但应该详细地记录到一个日志文件中(所以没有set -x)。除了输出之外,它还应该记录导致输出的命令行,这可能是一个管道命令行。

最基本的方法是复制脚本中的命令行,然后将其转储到日志中,然后捕获正在运行的实际命令的输出:

echo "command argument1 \"quoted argument2\" | grep -oE \"some output\"" >> file.log
output="$(command argument1 "quoted argument2" 2>&1 | grep -oE "some output")"
echo "${output}" >> file.log

这有一个副作用,即需要为日志转义引用的部分,这可能会导致错误,从而导致混乱。

如果没有任何命令通过管道传输,则可以将命令行存储在一个数组中,然后“运行”该数组。

command=(command argument1 "quoted argument2")
echo "${command[@]}" >> file.log
output="$("${command[@]}" 2>&1)"
echo "${output}" >> file.log

虽然使用这种方法"quoted argument2" 会在日志中变为quoted argument2

有没有办法(在 bash 中)实现这一点而不必复制命令?

【问题讨论】:

  • 你试过eval吗?类似cmd="..."; echo "$cmd" &>> file.log; eval "$cmd" &>> file.log
  • eval 对我来说仍然是个谜。所以,我通常很少使用它,因为我更喜欢在广泛使用它之前了解一个工具。我当然可以修补它并试一试。如果这解决了我打算做的事情,将会报告。谢谢!
  • 不过,请注意eval。在这种情况下使用它可能不是最好的主意。顺便说一句,为什么不能按需打开和关闭 -x 选项?
  • 如果我启用了-x,据我所知,它会将所有内容都输出到 STDOUT。这将不再为用户提供干净的输出。虽然我还没有尝试在命令替换中运行它,其中输出被捕获在变量中。但是,除了输出我真正想要的命令和结果之外,它还会在每行的开头引入新字符(即“+”)以及它本身(+ set +x)再次禁用它时。如果可能的话,我想避免这种情况。
  • 我的建议是在你感兴趣的命令之前打开-x,然后关闭。取消设置PS4 会抑制+。所以PS4=; set -x; <your command>; set+x 应该做几乎你想要的(除了set +x 也会被记录)。当然,您必须使用一些 exec <redirection> 命令将其他所有内容重定向到其他地方。

标签: linux bash


【解决方案1】:

您可以使用重定向,按需打开和关闭x 选项,取消设置PS4 以摆脱领先的+ ,并定义log_onlog_off 函数以便于编码。像这样的:

$ cat script.sh
#!/usr/bin/env bash

function log_on {
  exec 3>&1 4>&2
  exec &> >( sed -E '/^(set \+x|log_off)$/d' >> file.log )
  ps4=$PS4
  PS4=
  set -x
}

function log_off {
  set +x
  exec 1>&3 2>&4
  PS4=$ps4
}

echo something not logged
log_on
echo something logged
log_off
echo something else not logged
$ rm -f file.log
$ ./script.sh
something not logged
something else not logged
$ cat file.log
echo something logged
something logged

exec <redirection> 命令看起来有点神秘(与大多数重定向一样),但它们相当简单:

  • exec 3>&1 4>&2 复制文件描述符fd1fd2(默认为stdoutstderr)以便能够在log_off 中恢复它们。在此之后,fd3fd4 分别是 fd1fd2 的副本。如果您已经使用它们,请选择除 3 或 4 之外的其他 fd

  • exec &> >( sed ... )fd1fd2 重定向到sed 命令的标准输入。

  • sed 命令sed -E '/^(set \+x|log_off)$/d' >> file.log 删除仅包含set +log_off 的行并将其输出附加到file.log。如果没有此 sed 命令,您将始终看到以下两行:

      log_off
      set +x
    

    在您的日志中,在一组记录的命令之后。

  • exec 1>&3 2>&4fd3fd4 中的副本中恢复fd1fd2

其余的很简单:将PS4 保存在ps4 中以便可以恢复,启用/禁用x 选项。如果需要,这应该很容易适应或扩展。

x 选项单独显示简单命令。例如,它会破坏管道。如果您更喜欢看起来更像您编写的命令的命令日志,可以将set -/+x 替换为set -/+v

【讨论】:

  • 谢谢教授!我试过你的例子,它通常有效。但是,管道命令是断行的,而不是使用管道。阅读 bash 手册页,这似乎是 set +x 的预期行为,因为它扩展了每个“简单命令”。所以echo something logged | sed "s/logged/locked/" 会变成两行。现在想弄清楚如何才能扭转这种局面。
  • 哦,只需使用-v/+v 而不是-x/+x。我相应地编辑我的答案。
  • 我不想让您失望,但是使用-v 它不再将命令行中的任何变量扩展为它们的值。 ://
  • 我什至尝试过-vx,但这让事情变得更糟。 ;-)
  • 嗯。您要求的接缝非常困难和/或有点未指定。例如,当有命令替换时,您想记录什么?说echo $(seq 3)seq 本身就是一个命令。也应该这样记录吗?还是您只想查看echo $(seq 3)?还是echo 后跟$(seq 3) 的扩展?但是你想怎么看呢?一个连续3行?那么函数调用呢?除了简单的扩展命令 (-x) 或原始 shell 输入行 (-v) 之外,我没有看到太多其他选择。你还有什么想法?
【解决方案2】:

恕我直言,这已经回答了here

为简单起见,set linux 命令就是您所需要的。 set -xset -v

【讨论】:

  • 这与我理解的“脚本应该有一个干净的输出”相矛盾,因为它将所有内容输出到 STDOUT。因此我说set -x 不是一个好的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-17
  • 1970-01-01
  • 2021-03-23
  • 2020-04-30
  • 1970-01-01
相关资源
最近更新 更多