【问题标题】:How to repeatedly pipe input between commands until condition on output applies?如何在命令之间重复管道输入,直到输出条件适用?
【发布时间】:2014-06-25 09:31:44
【问题描述】:

我想创建一个 bash 脚本,在两个进程之间重复管道输出,直到输出包含一些特殊字符。

考虑以下两个 Python 脚本:

script1.py

from sys import stdin
for line in stdin.readline():
    if line.isdigit():
        print int(line) + 3

script2.py

from sys import stdin
for line in stdin.readline():
    if line.isdigit():
        print int(line) - 1

以及接下来的执行

$ echo 0|python script1.py|python script2.py|python script1.py|python script2.py 
4

能否创建一个 Bash 脚本,执行两个脚本并将其中一个的输出作为输入传递给另一个,直到 script1 的输出达到某个特定数量?

(我不想修改 Python 脚本,我实际上并没有使用它们,它们只是为了展示我想要实现的目标而创建的)。

【问题讨论】:

  • 你想要一个双向管道吗?

标签: bash shell


【解决方案1】:

一种可以防止分叉炸弹的更安全的方法:

function piper {
    read
    if [[ $REPLY == 4 ]]; then
        echo 4
    elif [[ $REPLY =~ ^[[:digit:]]+$ && $REPLY -lt 4 ]]; then
        echo "$REPLY" | python script1.py | python script2.py | piper
    fi
}

echo 0 | piper

【讨论】:

  • 在概念上与我的递归答案没有什么不同(并且在 stackoverflows 上会有相同的缺陷)。你说的更安全是什么意思?仅仅是因为您检查组合的输出是由数字组成的单词吗?还是我错过了其他东西?我的回答会产生什么叉子炸弹? (潜在的无限循环,是的,叉子炸弹,我没看到)。
  • 另外,正则表达式可能有点矫枉过正,一个 glob 就可以了;而您的测试[[ $REPLY -lt 4 ]] 要么错误,要么没用。
  • @gniourf_gniourf 我实际上发现[[ $REPLY -lt 4 ]] 有点低效,但我懒得计算它。你能证明它为什么没用吗?关于叉子炸弹,echo "$l" | apply_until4 可以产生无限的子壳。为什么正则表达式矫枉过正?什么确切的表达方式就足够了?哦,是的,经过更仔细的检查,$REPLY == [[:digit:]] 似乎就足够了,因为目标不会增加。
  • echo "$l" | apply_until4 可能在给出错误的初始条件时在OP给出的玩具脚本的情况下产生无限的子shell。我回答的目标是展示可用于实现 OP 的目标的概念,而不是围绕明显的演示案例制作 100% 防弹脚本我>。话虽如此,我们讨论的其他观点变得无关紧要。不过,有两件事:[[ $REPLY = +([[:digit:]]) ]] 避免使用正则表达式;此安全防护装置无法使用负数进行初始化:[[ $REPLY = ?(-)+([[:digit:]]) ]] 进行修复
  • 是的,这就是我这样做的明显原因。为了避免 - 可能的 - 分叉炸弹,以防 OP 错误地发送错误的初始输入。如果您要检查全局模式,您还需要== 而不仅仅是=。而且由于您还使用扩展模式,您需要初始化extglob。我总是喜欢分机。 glob 并对其进行了第一次模式检查,但我只是选择使用正则表达式来减少行数并防止多余的 shopt 调用。
【解决方案2】:

假设你的python脚本script2.py只在一行输出一个数字,而你想执行python script1.py | python script2.py直到输出数字为4,这里是递归的方式。它使用递归,因此如果调用过多,您可能会遇到堆栈溢出:

#!/bin/bash

apply_until4() {
   local l
   read l < <(python script1.py | python script2.py)
   if [[ $l = 4 ]]; then
      echo "$l"
      return
   fi
   echo "$l" | apply_until4
}

echo "0" | apply_until4

或者(逻辑相同但实现略有不同——不再是纯 Bash,因为 cat,尽管这可以更改):

#!/bin/bash

apply_until4() {
   local l next=apply_until4
   read l < <(python script1.py | python script2.py)
   [[ $l = 4 ]] && next=cat
   echo "$l" | $next
}

echo "0" | apply_until4

没有递归的另一种可能性:

#!/bin/bash

apply_until4() {
   local l
   read l
   until [[ $l = 4 ]]; do
      read l < <(echo "$l" | python script1.py | python script2.py)
   done
   echo "$l"
}

echo "0" | apply_until4

  • 在这两种方法中,我们的想法是读取组合 python script1.py | python script2.py 的输出,如果输出不是 4,则再次将其提供给组合。
  • read l < <(python script.py | python script2.py)
    

    语句可以改成

    l=$(python script.py | python script2.py)
    

    同样,

    read l < <(echo "$l" | python script1.py | python script2.py)
    

    语句可以改成

    l=$(echo "$l" | python script1.py | python script2.py)
    

    不同之处在于read 将丢弃所有前导和尾随空格(如果有)。

【讨论】:

    猜你喜欢
    • 2015-12-07
    • 1970-01-01
    • 1970-01-01
    • 2013-12-21
    • 1970-01-01
    • 2012-10-17
    • 1970-01-01
    • 2013-01-12
    • 1970-01-01
    相关资源
    最近更新 更多