AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 507061
Accepted
katosh
katosh
Asked: 2019-03-19 12:57:40 +0800 CST2019-03-19 12:57:40 +0800 CST 2019-03-19 12:57:40 +0800 CST

进入不同的变量

  • 772

从 bash 代码

command1 | tee >(command2) | command3

我想捕获in的输出和command2invar2的输出。command3var3

command1是 I/O 绑定的,其他命令成本很高,但可以在command1完成之前开始工作。

command2和的输出顺序command3不固定。所以我尝试在

read -r var2 <<< var3=(command1 | tee >(command2 >&3) | command3) 3>&1

或者

{read -u 3 -r var2; read -r var3} <<< command1 | tee >(command2 >&3) | command3

但没有成功。

有没有办法让三个命令并行运行,将结果存储在不同的变量中而不是制作临时文件?

bash variable
  • 4 4 个回答
  • 2387 Views

4 个回答

  • Voted
  1. Stéphane Chazelas
    2019-03-19T14:08:13+08:002019-03-19T14:08:13+08:00

    因此,您想将 cmd1 的输出通过管道传输到 cmd2 和 cmd3 中,并将 cmd2 和 cmd3 的输出都输入到不同的变量中?

    然后看起来你需要来自 shell 的两个管道,一个连接到 cmd2 的输出,一个连接到 cmd3 的输出,并且 shell 使用select()/poll()从这两个管道中读取。

    bash不会这样做,你需要一个更高级的 shell,比如zsh. zsh没有原始接口,但如果在 Linux 上,您可以使用在常规管道上的行为类似于命名管道pipe()的事实,并使用与在使用 shell 重定向读取/写入相同文件描述符时使用的方法类似的方法/dev/fd/x

    #! /bin/zsh -
    
    cmd1() seq 20
    cmd2() sed 's/1/<&>/g'
    cmd3() tr 0-9 A-J
    
    zmodload zsh/zselect
    zmodload zsh/system
    typeset -A done out
    {
      cmd1 > >(cmd2 >&3 3>&-) > >(cmd3 >&5 5>&-) 3>&- 5>&- &
      exec 4< /dev/fd/3 6< /dev/fd/5 3>&- 5>&-
      while ((! (done[4] && done[6]))) && zselect -A ready 4 6; do
        for fd (${(k)ready[(R)*r*]}) {
          sysread -i $fd && out[$fd]+=$REPLY || done[$fd]=1
        }
      done
    } 3> >(:) 5> >(:)
    
    printf '%s output: <%s>\n' cmd2 "$out[4]" cmd3 "$out[6]"
    
    • 4
  2. Best Answer
    LL3
    2019-03-20T18:20:15+08:002019-03-20T18:20:15+08:00

    如果我很好地理解了您的所有要求,您可以bash通过为每个命令创建一个未命名管道,然后将每个命令的输出重定向到其各自的未命名管道,最后将每个输出从其管道检索到一个单独的变量中来实现。

    因此,解决方案可能如下:

    : {pipe2}<> <(:)
    : {pipe3}<> <(:)
    
    command1 | tee >({ command2 ; echo EOF ; } >&${pipe2}) >({ command3 ; echo EOF ; } >&${pipe3}) > /dev/null &
    var2=$(while read -ru ${pipe2} line ; do [ "${line}" = EOF ] && break ; echo "${line}" ; done)
    var3=$(while read -ru ${pipe3} line ; do [ "${line}" = EOF ] && break ; echo "${line}" ; done)
    
    exec {pipe2}<&- {pipe3}<&-
    

    这里特别注意:

    • 结构的使用<(:);这是一个未记录的 Bash 打开“未命名”管道的技巧
    • 使用简单echo EOF的方法来通知while循环不再有输出。这是必要的,因为只关闭未命名的管道(通常会结束任何while read循环)是没有用的,因为这些管道是双向的,即用于写入和读取。我不知道如何将它们打开(或转换)成通常的一对文件描述符,一个是读取端,另一个是写入端。

    在此示例中,我使用了纯 bash 方法(除了使用tee)来更好地阐明使用这些未命名管道所需的基本算法,但是您可以使用几个sed代替while循环来完成这两个分配,就像var2="$(sed -ne '/^EOF$/q;p' <&${pipe2})"变量 2 和它各自的变量 3 一样,产生相同的结果,输入更少。也就是说,整个事情将是:

    少量数据的精益解决方案

    : {pipe2}<> <(:)
    : {pipe3}<> <(:)
    
    command1 | tee >({ command2 ; echo EOF ; } >&${pipe2}) >({ command3 ; echo EOF ; } >&${pipe3}) > /dev/null &
    var2="$(sed -ne '/^EOF$/q;p' <&${pipe2})"
    var3="$(sed -ne '/^EOF$/q;p' <&${pipe3})"
    
    exec {pipe2}<&- {pipe3}<&-
    

    为了显示目标变量,请记住通过清除 IFS 来禁用分词,如下所示:

    IFS=
    echo "${var2}"
    echo "${var3}"
    

    否则你会在输出中丢失换行符。

    以上看起来确实是一个非常干净的解决方案。不幸的是,它仅适用于不太大的输出,并且您的里程可能会有所不同:在我的测试中,我遇到了大约 530k 输出的问题。如果你在 4k 的(非常保守的)限制之内,你应该没问题。

    该限制的原因在于这样的两个赋值,即命令替换语法,是同步操作,这意味着第二个赋值仅在第一个完成后运行,而相反,同时tee馈送两个命令并阻塞所有如果碰巧填满了它的接收缓冲区。一个僵局。

    解决这个问题需要一个稍微复杂的脚本,以便同时清空两个缓冲区。为此,while两个管道上的环路会派上用场。

    任何数据量的更标准的解决方案

    更标准的 Bashism 是这样的:

    declare -a var2 var3
    while read -r line ; do
       case "${line}" in
       cmd2:*) var2+=("${line#cmd2:}") ;;
       cmd3:*) var3+=("${line#cmd3:}") ;;
       esac
    done < <(
       command1 | tee >(command2 | stdbuf -oL sed -re 's/^/cmd2:/') >(command3 | stdbuf -oL sed -re 's/^/cmd3:/') > /dev/null
    )
    

    在这里,您将来自两个命令的行多路复用到单个标准“stdout”文件描述符,然后随后将合并的输出多路分解到每个相应的变量上。

    特别注意:

    • 使用索引数组作为目标变量:这是因为在存在大量输出的情况下,仅附加到普通变量会变得非常慢
    • 使用sed命令在每个输出行前面加上字符串“cmd2:”或“cmd3:”(分别),以便脚本知道每行属于哪个变量
    • 为命令输出设置行缓冲的必要用途stdbuf -oL:这是因为这里的两个命令共享相同的输出文件描述符,因此如果它们碰巧流式传输,它们很容易在最典型的竞争条件下覆盖彼此的输出同时输出数据;行缓冲输出有助于避免这种情况
    • 另请注意,只有每个链的最后一个命令才需要使用 stdbuf,即直接输出到共享文件描述符的命令,在这种情况下是 sed 命令,它在每个 commandX 的输出前加上其区别前缀

    正确显示此类索引数组的一种安全方法是:

    for ((i = 0; i < ${#var2[*]}; i++)) ; do
       echo "${var2[$i]}"
    done
    

    当然你也可以只使用"${var2[*]}"如下:

    echo "${var2[*]}"
    

    但是当有很多行时,这不是很有效。

    • 2
  3. katosh
    2019-03-21T02:18:19+08:002019-03-21T02:18:19+08:00

    我发现了一些似乎很好用的东西:

    exec 3<> <(:)
    var3=$(command1 | tee >(command2 >&3) | command3)
    var2=$(while IFS= read -t .01 -r -u 3 line; do printf '%s\n' "$line"; done)
    

    <(:)它的工作原理是为文件描述符 3设置一个匿名管道并将输出管道command2传输到它。var3捕获文件描述符 3 的输出command3并从文件描述符 3 读取最后一行,直到它在 0.01 秒内没有收到任何新数据。

    它仅适用于最多 65536 字节的输出,command2其中似乎由匿名管道缓冲。

    我不喜欢解决方案的最后一行。我宁愿一次读取所有内容,而不是等待 0.01 秒,而是在缓冲区为空时立即停止。但我不知道有什么更好的方法。

    • 0
  4. rayiik
    2022-03-24T15:03:51+08:002022-03-24T15:03:51+08:00

    这是从 4.​​0 开始可以实现的。bash 添加了一个shell 保留字coproc。它将跟随在后台的命令作为子进程分叉(通常不允许传递变量。)但是它创建一个数组(默认为 COPROC,但可以命名)。${COPROC[0]} 连接到子流程的标准输入 ${COPROC 1 } 它的标准输出。这些流程可以作为作业进行操作,是异步的,因此您只需使用 tee 管道连接到两个协同流程并拥有它们分别输出到一个单独的文件,然后通过调用 "${COPROCFIRST 1 } ${COPROCSECOND 1 }"将每个返回值的输出组合起来,这很棒,因为它甚至不需要在管道中。

    bash coproc command_1 { command1 arg1 arg2 arg3 >> command_1_output.txt } coproc command_2 { command2 arg1 arg2 arg3 >> command_2_output.txt } 其他命令 | tee >^${command_1 1 }" >&"${command_2 1 }" 读取 -r results1 <&"${command_1[0]" 读取 -r resluts2 <&"${command_2[0]" echo "$result1 $result2" | command3 >> combineresult.txt

    如前所述,该解决方案目前不完整,因此我很震惊,但是原理是合理的,并且与上面的答案相似。但是,我将指导您阅读有关该主题的一些好文章,并在工作允许时返回此答案。

    在协同进程中设置变量的示例

    深入了解协同进程和命名管道

    用途和陷阱

    • 0

相关问题

  • 从文本文件传递变量的奇怪问题

  • 虽然行读取保持转义空间?

  • 如何将带有〜的路径保存到变量中?

  • `tee` 和 `bash` 进程替换顺序

  • 运行一个非常慢的脚本直到它成功

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve