【问题标题】:Redirect stdout and stderr separately over a socket connection通过套接字连接分别重定向 stdout 和 stderr
【发布时间】:2021-10-18 02:07:40
【问题描述】:

我正在尝试在 unix-socket 连接的另一端运行脚本。为此,我尝试使用socat。脚本是

#!/bin/bash
read MESSAGE1
echo "PID: $$"
echo "$MESSAGE1"
sleep 2
read MESSAGE2
echo "$MESSAGE2" 1>&2

作为 socat 的听众,我有

socat unix-listen:my_socket,fork exec:./getmsg.sh,stderr

作为我使用的客户:

echo $'message 1\nmessage 2\n' | socat -,ignoreeof unix:my_socket 2> stderr.txt

我得到了输出

PID: 57248
message 1
message 2

而文件stderr.txt 是空的。

然而我的期望是这样的

  • 来自侦听器端脚本的标准输出将通过管道传输到客户端上的标准输出,然后
  • 侦听器端的stderr 到客户端的stderr。

即文件stderr.txt 应该有内容message 2 而不是空的。

关于如何实现 stdout 和 stderr 单独传输而不是合并的任何想法?

谢谢

【问题讨论】:

    标签: bash sockets stdout stderr socat


    【解决方案1】:

    不过我的期望

    只有一个套接字,通过一个套接字可以发送一个数据流,而不是两个。您不能通过一个句柄发送 stdout 和 stderr (两个流)(我的意思是,不喜欢将它们相互混合,即不会丢失数据来自哪个流的信息)。另请参阅man socat 中对stderr 标志和exec 的解释,并参阅man dup。脚本的 stderr 和 stdout 都重定向到相同的输出。

    期望stderr.txt 为空,因为socat 不会向stderr 写入任何内容。

    如何实现 stdout 和 stderr 分开传输而不是合并?

    为每个流分别使用两个套接字。

    使用可以区分两个流的协议传输消息。例如一个简单的基于行的协议,为消息添加前缀:

    # script.sh
    echo "stdout: this is stdout"
    echo "stderr: this is stderr"
    
    # client
    ... | socat ... | tee >(sed -n 's/stderr: //p' >&2) | sed -n 's/stdout: //p'
    

    【讨论】:

    • 感谢您的回答。这就是我的怀疑。是否有一个简单的 CLI 命令可以进行复用和解复用,可能不仅仅是基于行的?
    • Is there a **simple** 我不知道。这是基于意见的 - 对我来说sed -z 很简单。无论如何en.wikipedia.org/wiki/Inter-process_communication。我想现在 dbus ,我知道 ZMQ 和 POSIX 和 XSI 消息队列。
    【解决方案2】:

    如果输入和输出只是行长有限的文本,那么您可以在纯 Bash 中轻松编写复用和解复用命令。

    唯一的问题是socat(错误)如何处理stderr;它基本上要么强制它与stdout 相同,要么完全忽略它。此时最好在处理程序脚本中使用自己的文件描述符约定,使用不与012 冲突的异常文件描述符。

    例如,让我们为stdout 选择11,为stderr 选择12。对于stdin,我们可以照常使用0

    getmsg.sh

    #!/bin/bash
    set -e -o pipefail
    
    read message
    echo "PID: $$"  1>&11   # to stdout
    echo "$message" 1>&11   # to stdout
    sleep 2
    read message
    echo "$message" 1>&12   # to stderr
    

    mux.sh

    #!/bin/bash
    
    "$@" \
    11> >(while read line; do printf '%s\n' "stdout: ${line}"; done) \
    12> >(while read line; do printf '%s\n' "stderr: ${line}"; done)
    

    demux.sh

    #!/bin/bash
    set -e -o pipefail
    
    declare -ri stdout="${1:-1}"
    declare -ri stderr="${2:-2}"
    while IFS= read -r line; do
      if [[ "$line" = 'stderr: '* ]]; then
        printf '%s\n' "${line#stderr: }" 1>&"$((stderr))"
      elif [[ "$line" = 'stdout: '* ]]; then
        printf '%s\n' "${line#stdout: }" 1>&"$((stdout))"
      else
        exit 3  # report malformed stream
      fi
    done
    

    几个例子

    #!/bin/bash
    set -e -o pipefail
    
    socat unix-listen:my_socket,fork exec:'./mux.sh ./getmsg.sh' &
    
    declare -ir server_pid="$!"
    trap 'kill "$((server_pid))"
          wait -n "$((server_pid))" || :' EXIT
    
    until [[ -S my_socket ]]; do :; done  # ugly
    
    echo '================= raw data from the socket ================='
    echo $'message 1\nmessage 2\n' | socat -,ignoreeof unix:my_socket
    
    echo '================= normal mode of operation ================='
    echo $'message 1\nmessage 2\n' | socat -,ignoreeof unix:my_socket \
    | ./demux.sh
    
    echo '================= demux / mux test for fun ================='
    echo $'message 1\nmessage 2\n' | socat -,ignoreeof unix:my_socket \
    | ./mux.sh ./demux.sh 11 12
    

    【讨论】:

    • "$((stderr))" 为什么是算术展开式?只需"$stderr"。使用while IFS= read -r line 而不是while read line 并且更喜欢printf "%s\n" 而不是echo。 ;)
    • 算术扩展(在这种情况下)只是一种记录/强调扩展变量是整数的方式。我已经应用了while IFS= read -r 的建议;这肯定使它更普遍。我看不出printf '%s\n' 在性能或功能方面比echo 有什么好处(但如果有的话,我很乐意更新它)。
    • I don’t see a benefit unix.stackexchange.com/questions/65803/… ;) 例如printf "%s\n" "-e" | socat .... | ./demux.sh 不会打印-e,因为echo -e
    • 你是对的。那是个很好的观点。我已将printf 放入mux.shdemux.sh
    猜你喜欢
    • 2011-12-27
    • 1970-01-01
    • 2011-12-26
    • 1970-01-01
    • 2014-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多