【问题标题】:How to use set -x without showing stdout?如何在不显示标准输出的情况下使用 set -x?
【发布时间】:2018-07-19 09:08:26
【问题描述】:

在 CI 中,我正在运行一个调用许多 bash 脚本的 bash 脚本。

./internals/declination/create "${RELEASE_VERSION}" "${CI_COMMIT_REF_NAME}" > /dev/null

不会禁用脚本返回的stdout

Gitlabi-CI 运行器在 100MB 日志后停止记录,上面写着 Job's log exceeded limit of 10240000 bytes

我知道日志脚本只能长大。

如何优化输出日志大小?

我不需要拥有所有的stdout,我可以拥有stderr,但这将是一个没有信息的长时间运行的脚本。

有没有办法像set -x一样显示正在运行的命令?

编辑

阅读答案后,我无法解决我的问题。我需要添加 我正在使用 nodejs 来运行运行长 bash 脚本的 bash 脚本。

这就是我在.gitlab-ci.yml 中调用节点脚本的方式:

scripts:
   - node my_script.js

my_script.js 内,我有:

exports.handler = () => {
  const ls = spawn('bash', [path.join(__dirname, 'release.sh')], { stdio: 'inherit' });
  ls.on('close', (code) => {
    if (code !== 0) {
      console.log(`ps process exited with code ${code}`);
      process.exitCode = code;
    }
  });
};

my_script.sh 内,我有:

./internals/declination/create "${RELEASE_VERSION}" "${CI_COMMIT_REF_NAME}" > /dev/null

【问题讨论】:

  • 也许你可以将标准输出重定向到/dev/null——就像cmd >/dev/null一样? (仍应打印到stderr 的输出。)
  • 您的命令非常肯定确实重定向标准输出。也许您打算编写它不会重定向标准错误,例如set -x 输出去了?
  • stderr 肯定会进入日志。没有 CI 系统会丢弃 stderr,因为这是所有有用的诊断和错误消息的所在。
  • 我在 CI 中执行的命令是一个 node 脚本,它将运行 bash 脚本,该脚本将执行这个长时间运行的命令。我曾尝试使用> dev/null 2>&1,但失败时我的 CI 没有显示任何错误。我没能解决这个问题。
  • 我有ps process exited with code 2。我已经用额外的信息添加了我的问题

标签: linux bash logging sh


【解决方案1】:

您可以使用exec 选择性地重定向文件句柄。

exec >stdout 2>stderr

然而,这会失去与终端的连接,因此在此之后没有简单的方法可以向终端输出任何内容。

您可以改为使用m>&n 复制文件句柄,其中m 是要复制的文件描述符的编号,n 是新文件描述符的编号(选择像 99 这样的大数字,以免意外破坏现有句柄)。

exec 98<&1  # stdout
exec 99<&2  # stderr
exec >/dev/null 2>&1
:

要重新启用输出,

exec 1<&98 2<&99

如果您重定向到一个临时文件而不是 /dev/null,您现在显然可以向调用者显示这些文件的 tail

tail -n 100 "$TMPDIR"/stdout "$TMPDIR"/stderr

(在共享服务器上,可能使用mktemp 在脚本开头创建一个唯一的临时目录;静态硬编码文件名使得无法同时运行两个构建。)

由于您通常无法预测下一个错误会发生在哪里,可能会将所有这些都放在一个包装脚本中,该脚本执行重定向、运行构建并最终显示临时日志文件的结尾。一些构建服务器可能希望每隔几分钟在日志文件中看到一些生命迹象,所以也许tail 每隔一段时间就会循环几行。

另一方面,如果只有一个构建命令,整个构建作业的 stdout 和 stderr 可以简单地重定向到一个日志文件,你不需要来回 exec 事情。如果您需要为部分脚本选择性地启用输出,请使用exec,如上;但是对于批发重定向,只需重定向一个命令。

总之,你的构建脚本可能看起来像这样。

#!/bin/sh

t=$(mktemp -t -d cibuild.XXXXXXXX) || exit

trap 'kill $buildpid; wait $buildpid; tail -n 500 "$t"/*; rm -rf "$t"' 0 1 2 3 5 15

# Your original commands here
${initial_process_wd}/internals/declination/create "${RELEASE_VERSION}" "${CI_COMMIT_REF_NAME}">"$t"/stdout 2>"$t"/stderr &

buildpid=$!
while kill -0 $buildpid; do
    sleep 180
    date
    tail -n 1 "$t"/*
done
wait

这种方法的一个缺陷是您会丢失时间信息。一个合适的解决方案可以让您看到每行的生成时间,并按照消息打印的顺序显示标准输出和标准错误,可能带有可见的时间戳,甚至带有着色提示(stderr 的红色时间戳?)

【讨论】:

  • 在您的第一个示例中,您的意思是 exec my_script.sh 2&amp;&gt;stderr 将立即仅向我的 CI 错误显示此脚本抛出的错误?
  • 我不知道如何理解复制文件句柄的例子,你能用my_script.sh更新你的sniper,这样我就可以看到应该如何使用exec?我是否将 exec 替换为脚本名称?
  • 不,没有额外的参数或任何东西。我们使用exec 的语法only 操作文件描述符。但我会用一个具体的例子来更新。
  • 感谢您的具体命令。我还更新了我的问题,因为&gt; dev/null 2&gt;&amp;1 没有产生标准输出或标准错误,您仍然认为您的具体示例可以将错误转发给 ci 进程吗?
  • 该示例将它们重定向到临时文件而不是/dev/null,并在最后对它们运行tail。我假设在失败的情况下日志文件的结尾将是最有趣的,尽管您必须使用tail 提取的行数显然是纯粹的推测。
【解决方案2】:

选项 1

如果您的脚本会将错误消息输出到 stderr,您可以使用 command &gt; /dev/null 忽略所有到 stdout 的输出,其中 /dev/null 是一个黑洞,它将带走任何输出。

选项 2

如果您的错误消息中有任何模式,您可以使用grep 过滤掉这些错误消息。


编辑 1:

要显示正在运行的命令,您可以向 bash 提供 -x 命令;因此,您的命令将是

bash -x ${initial_process_wd}/internals/declination/create "${RELEASE_VERSION}" "${CI_COMMIT_REF_NAME}" &gt; /dev/null

bash 将执行的命令打印到 stderr


编辑 2:

如果要减小输出文件的大小,可以使用${initial_process_wd}/internals/declination/create "${RELEASE_VERSION}" "${CI_COMMIT_REF_NAME}" | gzip &gt; logfile 将其传递给gzip

要读取日志文件的内容,可以使用zcat logfile

【讨论】:

  • 它们不是错误消息的模式。我无法在/dev/null 中发送所有内容,否则人们无法在构建日志发生时查看构建日志,有没有办法像在执行-x 时一样输出执行的命令,并且仍将标准输出转发到/dev/null
  • 你的意思是tee 吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-29
  • 2021-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-05
相关资源
最近更新 更多