【问题标题】:echo that outputs to stderr输出到 stderr 的 echo
【发布时间】:2011-02-28 17:51:59
【问题描述】:

是否有标准的 Bash 工具,其作用类似于 echo,但输出到 stderr 而不是 stdout?

我知道我可以做到echo foo 1>&2,但它有点难看,而且我怀疑容易出错(例如,当事情发生变化时更有可能被编辑错误)。

【问题讨论】:

标签: bash


【解决方案1】:

你可以这样做,方便阅读:

>&2 echo "error"

>&2 将文件描述符#2 复制到文件描述符#1。因此,执行此重定向后,两个文件描述符将引用同一个文件:一个文件描述符#2 最初引用。如需更多信息,请参阅Bash Hackers Illustrated Redirection Tutorial

【讨论】:

  • 我很久以前就学会了这个技巧。这个页面有一些很好的信息。 tldp.org/LDP/abs/html/io-redirection.html
  • @BCS 我不知道在 shell 脚本中使用 alias。使用errcho(){ >&2 echo $@; } 可能会更安全
  • >&2 通常放在最后。这会起作用,但它的使用频率较低
  • 在我使用类 Unix 系统的近 40 年里,我从来没有想过你可以将重定向放在任何地方,但最后。像这样把它放在前面使它更加明显(或者像@MarcoAurelio 所说的那样“促进阅读”)。 +1 教我一些新东西。
  • 我一直很难理解如何阅读 bash 重定向,但我想我终于找到了正确的阅读方式:重要的是要注意 1 和 2 文件描述符已经指向某个地方,所以当你写2>&1它意味着point 2 where 1 is _currently_ pointing to所以现在如果你写错了2>&1 >file,它读为point 2 where 1 is already going and point 1 to a file所以2现在去1过去的地方但1不再去那里......相反>file 2>&1point 1 to this file and point 2 to where 1 is currently pointing to& 因此可以读作where
【解决方案2】:

你可以定义一个函数:

echoerr() { echo "$@" 1>&2; }
echoerr hello world

这将比脚本更快,并且没有依赖关系。

Camilo Martin 的 bash 特定建议使用“here 字符串”,并将打印您传递给它的任何内容,包括 echo 通常会吞下的参数 (-n):

echoerr() { cat <<< "$@" 1>&2; }

Glenn Jackman 的解决方案也避免了参数吞咽问题:

echoerr() { printf "%s\n" "$*" >&2; }

【讨论】:

  • 我必须说 echo 有点不可靠。 echoerr -ne xt 不会打印“-ne xt”。最好使用printf
  • 哦,其实你也可以用 cat:echoerr() { cat &lt;&lt;&lt; "$@" 1&gt;&amp;2; }
  • 我没有意识到这一点。已添加。
  • 或者,printf "%s\n" "$*" &gt;&amp;2
  • @GKFX 当然,它只有在引用时才能正常工作。为什么人们不引用他们的字符串超出了我的理解。 (当您不引用时,由一个或多个 $IFS 空格分隔的所有内容都作为单独的参数发送,在 echo 的情况下,这意味着将它们与 0x20s 连接起来,但不引用的危险远远超过减少 2 个字符的便利性)。
【解决方案3】:

由于1 是标准输出,您不必在像&gt; 这样的输出重定向前显式命名它。相反,您可以简单地输入:

echo This message goes to stderr >&2

由于您似乎担心1&gt;&amp;2 会难以可靠地键入,因此消除多余的1 可能会对您有所鼓励!

【讨论】:

  • 请注意 - &gt; &amp;2 不起作用,不允许有空格:)
【解决方案4】:

另一种选择

echo foo >>/dev/stderr

【讨论】:

  • 这个选项可移植吗?有人知道这是否不适用于某些 unix 风格吗?
  • 在某些无法访问 /dev/stderr 的 chroot 中不起作用。
  • 如果执行这一行的脚本——我们称之为foo——有自己的stderr重定向——例如foo &gt;foo.log 2&gt;&amp;1 - 然后echo foo &gt;/dev/stderr 将破坏它之前的所有输出。 &gt;&gt; 应改为:echo foo &gt;&gt;/dev/stderr
  • 同样,你有/dev/fd/2
  • @Dacav 这肯定是可移植的:/proc/self/fd/2。请参阅下面的答案:)
【解决方案5】:

不,这是标准的做法。它不应该导致错误。

【讨论】:

  • 不应该导致错误,但我可能更有可能。 OTOH 这没什么大不了的。
  • @Mike DeSimone:如果其他人弄乱了代码,在输出中随机播放,并且实际上并不了解 bash,他们很容易放弃(或错误输入)1&gt;&amp;2。我们都希望这不会发生,但我敢肯定我们都经历过这种情况。
  • ( echo something 1&gt;&amp;2 ; something else ) &gt; log -> (echo something; cp some junk 1&gt;&amp;2 ; something else) &gt; log 哎呀。
  • 恕我直言,如果有人弄乱了代码并且不知道 bash,这可能是您遇到的最少问题。
  • 我认为如果这可能是一个问题,你应该开始使用不同的语言:试图让 bash 万无一失是愚蠢的冒险。
【解决方案6】:

如果您不介意将消息也记录到 syslog,not_so_ugly 方法是:

logger -s $msg

-s 选项的意思是:“将消息输出到标准错误以及系统日志。”

【讨论】:

  • 这太棒了!它的便携性如何?
  • @code_monk: logger 命令预计与 IEEE Std 1003.2 ("POSIX.2") 兼容,logger 命令是 util-linux 软件包的一部分,可从 Linux Kernel Archive ⟨@ 987654321@⟩.
【解决方案7】:

注意:我正在回答帖子,而不是误导/模糊的“输出到 stderr 的回声”问题(OP 已经回答)。

使用函数来显示意图并获取所需的实现。例如

#!/bin/bash

[ -x error_handling ] && . error_handling

filename="foobar.txt"
config_error $filename "invalid value!"

output_xml_error "No such account"

debug_output "Skipping cache"

log_error "Timeout downloading archive"

notify_admin "Out of disk space!"

fatal "failed to open logger!"

error_handling 是:

ADMIN_EMAIL=root@localhost

config_error() { filename="$1"; shift; echo "Config error in $filename: $*" 2>&1; }

output_xml_error() { echo "<error>$*</error>" 2>&1; }

debug_output() { [ "$DEBUG"=="1" ] && echo "DEBUG: $*"; }

log_error() { logger -s "$*"; }

fatal() { which logger >/dev/null && logger -s "FATAL: $*" || echo "FATAL: $*"; exit 100; }

notify_admin() { echo "$*" | mail -s "Error from script" "$ADMIN_EMAIL"; }

在 OP 中处理问题的原因:

  • 最好的语法(有意义的词而不是丑陋的符号)
  • 更难出错(尤其是如果您重复使用脚本)
  • 它不是标准的 Bash 工具,但它可以是您或您的公司/组织的标准 shell 库

其他原因:

  • 清晰 - 向其他维护者表明意图
  • 速度 - 函数比 shell 脚本更快
  • 可重用性 - 一个函数可以调用另一个函数
  • 可配置性 - 无需编辑原始脚本
  • 调试 - 更容易找到导致错误的行(尤其是当您因大量重定向/过滤输出而死机时)
  • 健壮性 - 如果缺少某个函数并且您无法编辑脚本,您可以回退到使用具有相同名称的外部工具(例如,在 Linux 上,log_error 可以别名为 logger)
  • 切换实现 - 您可以通过移除库的“x”属性来切换到外部工具
  • 输出不可知 - 您不再需要关心它是转到 STDERR 还是其他地方
  • 个性化 - 您可以使用环境变量配置行为

【讨论】:

    【解决方案8】:

    我最近偶然发现的另一个选项是:

        {
            echo "First error line"
            echo "Second error line"
            echo "Third error line"
        } >&2
    

    这仅使用 Bash 内置函数,同时使多行错误输出更不容易出错(因为您不必记住将 &amp;&gt;2 添加到每一行)。

    【讨论】:

    • 不敢相信,当我建议使用 bash-redirect 并且在您自己的答案中您使用的是 bash-redirect 时,您投票否决了我。
    • @return42 我对你的答案投了反对票,因为它所做的只是告诉 OP,没有比他们开始时更好的答案了。这并不是真正的答案。我也没有在您的回答中看到子外壳建议...您的回答实际上只是建议 OP 不要使用 cat 或任何其他实用程序,这与问题无关。
    【解决方案9】:

    我的建议:

    echo "my errz" >> /proc/self/fd/2
    

    echo "my errz" >> /dev/stderr
    

    echo "my errz" &gt; /proc/self/fd/2 将有效地输出到stderr,因为/proc/self 是指向当前进程的链接,而/proc/self/fd 持有进程打开的文件描述符,然后,01 和@987654330 @分别代表stdinstdoutstderr

    /proc/self 链接在 MacOS 上不起作用,但是,/proc/self/fd/* 在 Android 上的 Termux 上可用,但在 /dev/stderr 上不可用。 How to detect the OS from a Bash script? 可以帮助您确定要使用的变体,从而使您的脚本更具可移植性。

    【讨论】:

    • /proc/self 链接在 MacOS 上不起作用,所以我会坚持使用更直接的 /dev/stderr 方法。此外,正如其他答案/cmets 所述,使用&gt;&gt; 附加可能会更好。
    • /proc/self/fd/* 在 Android 上的 Termux 上可用,但在 /dev/stderr 上不可用。
    • 我用它在我当前的系统上进行测试,它可以被修改以在不同的上下文中进行测试,以完成除了回显之外的其他任务。使用这个:echo -n "has '/proc/self/fd/2': " ; [ -e /proc/self/fd/2 ] &amp;&amp; echo true || echo false 或使用那个:echo -n "has '/dev/stderr': " ; [ -e /dev/stderr ] &amp;&amp; echo true || echo false
    【解决方案10】:

    不要使用cat,正如这里有些人提到的那样。 cat 是一个程序echoprintf 是 bash (shell) 内置函数。启动一个程序 或另一个脚本(上面也提到过)意味着创建一个新进程,并承担所有成本。使用内置函数,编写函数非常便宜,因为不需要创建(执行)进程(-environment)。

    开瓶器问“是否有任何标准工具可以将(管道)输出到标准错误”,简短的回答是:不...为什么? ...重定向管道是 unix (Linux...) 和 bash (sh) 等系统中的一个基本概念。

    我同意用这样的符号重定向的开场白:&amp;2&gt;1 对于现代程序员来说不是很愉快,但那是 bash。 Bash 并非旨在编写庞大而强大的程序,它旨在帮助管理员以更少的按键完成工作;-)

    至少,您可以将重定向放置在行中的任何位置:

    $ echo This message >&2 goes to stderr 
    This message goes to stderr
    

    【讨论】:

    • 告诉开发人员不要仅仅因为性能原因而使用程序是过早的优化。优雅、易于理解的方法应该优先于性能更好(以毫秒为单位)的难以理解的代码。
    • @GuyPaddock 抱歉,您没有正确阅读此内容。冷杉;它关于重定向由 bash 处理的管道。如果一个人不喜欢 bash 重定向的(丑陋的)语法,他应该停止实现 bash 脚本或学习 bash 方式。第二;您应该知道与调用内置 bash 相比,启动一个新的 prozes 是多么昂贵。
    • 让某人知道 Bash 内置插件与 cat 的性能权衡与指示某人不要使用 cat 因为它很慢是有区别的。有无数用例表明 cat 是正确的选择,所以这就是我反对您的回答的原因。
    • @GuyPaddock 揭幕战要求更换echo。即使他使用cat,他也必须使用 bash 重定向。反正。所以,在这里使用cat 绝对没有意义。顺便说一句,我每天使用cat 100 次,但从来没有在开场白要求的上下文中......你明白了吗?
    【解决方案11】:

    这是一个简单的 STDERR 函数,它将管道输入重定向到 STDERR。

    #!/bin/bash
    # *************************************************************
    # This function redirect the pipe input to STDERR.
    #
    # @param stream
    # @return string
    #
    function STDERR () {
    
    cat - 1>&2
    
    }
    
    # remove the directory /bubu
    if rm /bubu 2>/dev/null; then
        echo "Bubu is gone."
    else
        echo "Has anyone seen Bubu?" | STDERR
    fi
    
    
    # run the bubu.sh and redirect you output
    tux@earth:~$ ./bubu.sh >/tmp/bubu.log 2>/tmp/bubu.err
    

    【讨论】:

    • 我认为你可以用 alias 做同样的事情并且更紧凑
    • 或者您可以直接通过管道传输到设备文件echo what | /dev/stderr ...
    【解决方案12】:

    read 是一个 shell 内置命令,可以打印到 stderr,并且可以像 echo 一样使用而无需执行重定向技巧:

    read -t 0.1 -p "This will be sent to stderr"
    

    -t 0.1 是一个超时,它禁用读取的主要功能,将一行标准输入存储到一个变量中。

    【讨论】:

    • Bash on OS X 不允许 "0.1"
    【解决方案13】:

    James RothGlenn Jackman 建议的组合解决方案

    • 添加ANSI颜色代码以红色显示错误消息:
    echoerr() { printf "\e[31;1m%s\e[0m\n" "$*" >&2; }
    
    # if somehow \e is not working on your terminal, use \u001b instead
    # echoerr() { printf "\u001b[31;1m%s\u001b[0m\n" "$*" >&2; }
    
    echoerr "This error message should be RED"
    

    【讨论】:

      【解决方案14】:

      编写脚本

      #!/bin/sh
      echo $* 1>&2
      

      这将是你的工具。

      如果您不想将脚本放在单独的文件中,也可以创建一个函数。

      【讨论】:

      • 最好是一个函数(就像 James Roth 的答案),最好传递所有参数,而不仅仅是第一个。
      • 为什么函数会更好? (或者,或者:“更好地解释为什么它会更好......”)
      • @OgrePsalm33 函数会更好的一个原因是,在调用脚本时,通常会创建一个新的 shell 实例来提供执行脚本的环境。另一方面,一个函数被放置在当前运行的 shell 环境中。在这种情况下,调用函数将是一种更有效的操作,因为可以避免创建另一个 shell 实例。
      【解决方案15】:

      这是一个检查最后一个命令的退出状态,显示错误并终止脚本的功能。

      or_exit() {
          local exit_status=$?
          local message=$*
      
          if [ "$exit_status" -gt 0 ]
          then
              echo "$(date '+%F %T') [$(basename "$0" .sh)] [ERROR] $message" >&2
              exit "$exit_status"
          fi
      }
      

      用法:

      gzip "$data_dir"
          or_exit "Cannot gzip $data_dir"
      
      rm -rf "$junk"
          or_exit Cannot remove $junk folder
      

      该函数打印出脚本名称和日期,以便在从crontab 调用脚本时有用并记录错误。

      59 23 * * * /my/backup.sh 2>> /my/error.log
      

      【讨论】:

        【解决方案16】:

        Mac OS X:我尝试了接受的答案和其他几个答案,所有这些都导致在我的 Mac 上写入 STDOUT 而不是 STDERR。

        这是一种使用 Perl 写入标准错误的可移植方式:

        echo WARNING! | perl -ne 'print STDERR'
        

        【讨论】:

        • 大声笑你想要的都投反对票,但这是我在代码中实际使用的解决方案!
        • 这个方法直接写入STDERR而不是重定向,所以输出可以用stderred着色。
        猜你喜欢
        • 1970-01-01
        • 2019-09-04
        • 2014-07-25
        • 1970-01-01
        • 2011-07-25
        • 2014-07-29
        • 2020-09-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多