【问题标题】:Capture stdout to a variable but still display it in the console将标准输出捕获到变量,但仍将其显示在控制台中
【发布时间】:2012-09-09 04:45:26
【问题描述】:

我有一个 bash 脚本,它调用了几个长时间运行的进程。出于处理原因,我想将这些调用的输出捕获到变量中。但是,由于这些是长时间运行的进程,我希望 rsync 调用的输出实时显示在控制台中,而不是事后显示。

为此,我有found 一种方法,但它依赖于将文本输出到/dev/stderr。感觉输出到/dev/stderr并不是一个好办法。

VAR1=$(for i in {1..5}; do sleep 1; echo $i; done | tee /dev/stderr)

VAR2=$(rsync -r -t --out-format='%n%L' --delete -s /path/source1/ /path/target1 | tee /dev/stderr)

VAR3=$(rsync -r -t --out-format='%n%L' --delete -s /path/source2/ /path/target2 | tee /dev/stderr)

在上面的示例中,我调用了 rsync 几次,我想在处理文件名时查看它们,但最后我仍然希望在变量中输出,因为我稍后会解析它。

是否有一种“更清洁”的方式来完成此任务?

如果有什么不同,我使用的是 Ubuntu 12.04,bash 4.2.24。

【问题讨论】:

    标签: bash ubuntu tee process-substitution


    【解决方案1】:

    在您的 shell 中复制 &1(在我的示例中为 5)并在子 shell 中使用 &5(这样您将写入父 shell 的标准输出 (&1)):

    exec 5>&1
    FF=$(echo aaa|tee >(cat - >&5))
    echo $FF
    

    会打印两次aaa,一次是因为子shell中的echo,第二次打印变量的值。

    在您的代码中:

    exec 5>&1
    VAR1=$(for i in {1..5}; do sleep 1; echo $i; done | tee >(cat - >&5))
    # use the value of VAR1
    

    【讨论】:

    • 不应该在父shell中关闭描述符吗?
    • @akhan:我假设那是exec 5>&-
    • FF=$(echo aaa | tee /dev/tty) 怎么样? Source
    • 这不会保留输出颜色。
    • @user1011471,当你写tee >(inner_cmd...)时,shell打开一个到内部命令的管道,这样管道的写端也被外部命令(tee)继承,通常在描述符63下. 外部命令不知道外壳做了什么有趣的事情,它得到一个文件名参数/dev/fd/63en.wikipedia.org/wiki/Process_substitution 不知道为什么会出现这个错误,但这与写入描述符 5 的内部命令无关。
    【解决方案2】:

    Op De Cirkel 的回答是正确的。它可以进一步简化(避免使用cat):

    exec 5>&1
    FF=$(echo aaa|tee /dev/fd/5)
    echo $FF
    

    【讨论】:

    • /dev/fd/5 不是特定于操作系统的吗?
    • 我一直想知道你是否可以只使用tee /dev/fd/1,但这不起作用,因为输出仍然被$() 捕获。因此,如果其他人想知道同样的事情, 有必要使用额外的文件描述符(如 5)。
    • 我们可以进一步简化并使其成为一个没有exec 的单行器:{ FF=$(echo aaa|tee /dev/fd/5); } 5>&1 大括号允许在运行子shell 命令之前发生重定向,而$FF 仍然保留在当前 shell 的范围(不适用于普通括号 ( )。这样之后甚至不需要关闭 FD 5,这是一个被忽视的卫生习惯。
    • @akhan 不,它不会,据说 bash 正在模拟这条路径,如果它本身不存在于操作系统中。
    • 如果使用sudo -u <other non-root user> <script> 运行此操作,则 Op De Cirkel 的答案有效,但此答案无效。写入 /dev/fd/5 相当于直接写入终端。 /dev/fd/5 是终端的 /dev/pts/ 文件的符号链接,该文件归最初登录的用户所有,并且不能被 sudo 用户写入。但是,cat - >&5 写入由 bash 在进程中打开的文件描述符(这与写入 /dev/fd/5 不同)。此文件描述符通过每个父进程转发写入,避免任何权限问题。
    【解决方案3】:

    这是一个捕获stderr 和命令退出代码的示例。这是基于 Russell Davis 的回答。

    exec 5>&1
    FF=$(ls /taco/ 2>&1 |tee /dev/fd/5; exit ${PIPESTATUS[0]})
    exit_code=$?
    echo "$FF"
    echo "Exit Code: $exit_code"
    

    如果文件夹/taco/ 存在,这将捕获其内容。如果文件夹不存在,则会捕获错误消息,退出代码为 2。

    如果您省略2>&1,则只会捕获stdout

    【讨论】:

      【解决方案4】:

      如果您所说的“控制台”是指您当前的 TTY,请尝试

      variable=$(command with options | tee /dev/tty)
      

      这是一种有点可疑的做法,因为尝试使用它的人有时会在没有 TTY(cron 作业等)时输出到达意想不到的地方时感到惊讶。

      【讨论】:

      • 它在 Docker 容器中不起作用。 tee: /dev/tty: No such device or address
      • 我认为这通常不是真的。它在 Debian Docker 容器中对我有用。
      【解决方案5】:

      您可以使用三个以上的文件描述符。在这里试试:

      http://tldp.org/LDP/abs/html/io-redirection.html

      "每个打开的文件都被分配一个文件描述符。[2] stdin、stdout 和 stderr 的文件描述符分别是 0、1 和 2。对于打开其他文件,剩下的描述符是 3 到 9。它是有时将这些附加文件描述符之一分配给 stdin、stdout 或 stderr 作为临时重复链接很有用。"

      关键是为了达到这个结果是否值得让脚本更复杂。其实这并没有错,你这样做的方式。

      【讨论】:

        猜你喜欢
        • 2011-11-08
        • 2010-10-21
        • 2019-04-10
        • 2010-11-19
        • 1970-01-01
        • 1970-01-01
        • 2013-05-04
        • 1970-01-01
        相关资源
        最近更新 更多