bash 如何在bash中制作管道循环

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/40244/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-17 20:28:11  来源:igfitidea点击:

How to make a pipe loop in bash

bash

提问by mweerden

Assume that I have programs P0, P1, ...P(n-1)for some n > 0. How can I easily redirect the output of program Pito program P(i+1 mod n)for all i(0 <= i < n)?

假设我有计划P0P1...P(n-1)一些n > 0。如何轻松地将程序的输出重定向PiP(i+1 mod n)所有i( 0 <= i < n) 的程序?

For example, let's say I have a program square, which repeatedly reads a number and than prints the square of that number, and a program calc, which sometimes prints a number after which it expects to be able to read the square of it. How do I connect these programs such that whenever calcprints a number, squaresquares it returns it to calc?

例如,假设我有一个程序square,它重复读取一个数字,然后打印该数字的平方,还有一个程序calc,它有时会打印一个数字,然后它希望能够读取它的平方。我如何连接这些程序,以便无论何时calc打印一个数字,square它都会将其返回到平方calc

Edit: I should probably clarify what I mean with "easily". The named pipe/fifo solution is one that indeed works (and I have used in the past), but it actually requires quite a bit of work to do properly if you compare it with using a bash pipe. (You need to get a not yet existing filename, make a pipe with that name, run the "pipe loop", clean up the named pipe.) Imagine you could no longer write prog1 | prog2and would always have to use named pipes to connect programs.

编辑:我可能应该用“轻松”来澄清我的意思。命名管道/fifo 解决方案确实有效(我过去曾使用过),但如果将其与使用 bash 管道进行比较,它实际上需要大量工作才能正确执行。(您需要获得一个尚不存在的文件名,使用该名称创建管道,运行“管道循环”,清除命名管道。)想象一下,您无法再编写prog1 | prog2并且始终必须使用命名管道来连接程序。

I'm looking for something that is almost as easy as writing a "normal" pipe. For instance something like { prog1 | prog2 } >&0would be great.

我正在寻找几乎和编写“普通”管道一样简单的东西。例如,像{ prog1 | prog2 } >&0这样的东西会很棒。

回答by mweerden

After spending quite some time yesterday trying to redirect stdoutto stdin, I ended up with the following method. It isn't really nice, but I think I prefer it over the named pipe/fifo solution.

昨天花了很长时间尝试重定向stdout到 之后stdin,我最终采用了以下方法。它不是很好,但我认为我更喜欢它而不是命名管道/fifo 解决方案。

read | { P0 | ... | P(n-1); } >/dev/fd/0

The { ... } >/dev/fd/0is to redirect stdout to stdin for the pipe sequence as a whole (i.e. it redirects the output of P(n-1) to the input of P0). Using >&0or something similar does not work; this is probably because bash assumes 0is read-only while it doesn't mind writing to /dev/fd/0.

{ ... } >/dev/fd/0是将整个管道序列的 stdout 重定向到 stdin(即,它将 P(n-1) 的输出重定向到 P0 的输入)。使用>&0或类似的东西不起作用;这可能是因为 bash 假定它0是只读的,而它不介意写入/dev/fd/0.

The initial read-pipe is necessary because without it both the input and output file descriptor are the same pts device (at least on my system) and the redirect has no effect. (The pts device doesn't work as a pipe; writing to it puts things on your screen.) By making the input of the { ... }a normal pipe, the redirect has the desired effect.

初始read-pipe 是必要的,因为没有它,输入和输出文件描述符都是相同的 pts 设备(至少在我的系统上)并且重定向无效。(pts 设备不能用作管道;写入它会将内容放在屏幕上。)通过使{ ... }普通管道的输入,重定向具有所需的效果。

To illustrate with my calc/squareexample:

用我的calc/square例子来说明:

function calc() {
  # calculate sum of squares of numbers 0,..,10

  sum=0
  for ((i=0; i<10; i++)); do
    echo $i                   # "request" the square of i

    read ii                   # read the square of i
    echo "got $ii" >&2          # debug message

    let sum=$sum+$ii
  done

  echo "sum $sum" >&2           # output result to stderr
}

function square() {
  # square numbers

  read j                         # receive first "request"
  while [ "$j" != "" ]; do
    let jj=$j*$j
    echo "square($j) = $jj" >&2  # debug message

    echo $jj                     # send square

    read j                       # receive next "request"
  done
}

read | { calc | square; } >/dev/fd/0

Running the above code gives the following output:

运行上面的代码会得到以下输出:

square(0) = 0
got 0
square(1) = 1
got 1
square(2) = 4
got 4
square(3) = 9
got 9
square(4) = 16
got 16
square(5) = 25
got 25
square(6) = 36
got 36
square(7) = 49
got 49
square(8) = 64
got 64
square(9) = 81
got 81
sum 285

Of course, this method is quite a bit of a hack. Especially the readpart has an undesired side-effect: termination of the "real" pipe loop does not lead to termination of the whole. I couldn't think of anything better than readas it seems that you can only determine that the pipe loop has terminated by try to writing write something to it.

当然,这种方法有点小技巧。尤其是该read部分具有不希望的副作用:“真实”管道回路的终止不会导致整体的终止。我想不出比read这更好的方法了,因为您似乎只能通过尝试向其写入一些内容来确定管道循环已终止。

回答by Douglas Leeder

A named pipe might do it:

命名管道可能会这样做:

$ mkfifo outside
$ <outside calc | square >outside &
$ echo "1" >outside ## Trigger the loop to start

回答by Mark Witczak

This is a very interesting question. I (vaguely) remember an assignment very similar in college 17 years ago. We had to create an array of pipes, where our code would get filehandles for the input/output of each pipe. Then the code would fork and close the unused filehandles.

这是一个非常有趣的问题。我(模糊地)记得 17 年前在大学里的一项非常相似的作业。我们必须创建一个管道数组,我们的代码将在其中获取每个管道的输入/输出的文件句柄。然后代码将派生并关闭未使用的文件句柄。

I'm thinking you could do something similar with named pipes in bash. Use mknod or mkfifo to create a set of pipes with unique names you can reference then fork your program.

我想你可以用 bash 中的命名管道做类似的事情。使用 mknod 或 mkfifo 创建一组具有唯一名称的管道,您可以引用然后分叉您的程序。

回答by Andreas Florath

My solutions uses pipexec(Most of the function implementation comes from your answer):

我的解决方案使用pipexec(大部分功能实现来自您的回答):

square.sh

方.sh

function square() {
  # square numbers

  read j                         # receive first "request"
  while [ "$j" != "" ]; do
    let jj=$j*$j
    echo "square($j) = $jj" >&2  # debug message

    echo $jj                     # send square

    read j                       # receive next "request"
  done
}

square $@

calc.sh

计算文件

function calc() {
  # calculate sum of squares of numbers 0,..,10

  sum=0
  for ((i=0; i<10; i++)); do
    echo $i                   # "request" the square of i

    read ii                   # read the square of i
    echo "got $ii" >&2          # debug message

    let sum=$sum+$ii
 done

 echo "sum $sum" >&2           # output result to stderr
}

calc $@

The command

命令

pipexec [ CALC /bin/bash calc.sh ] [ SQUARE /bin/bash square.sh ] \
    "{CALC:1>SQUARE:0}" "{SQUARE:1>CALC:0}"

The output (same as in your answer)

输出(与您的答案相同)

square(0) = 0
got 0
square(1) = 1
got 1
square(2) = 4
got 4
square(3) = 9
got 9
square(4) = 16
got 16
square(5) = 25
got 25
square(6) = 36
got 36
square(7) = 49
got 49
square(8) = 64
got 64
square(9) = 81
got 81
sum 285

Comment: pipexec was designed to start processes and build arbitrary pipes in between. Because bash functions cannot be handled as processes, there is the need to have the functions in separate files and use a separate bash.

评论:pipexec 旨在启动进程并在它们之间构建任意管道。因为 bash 函数不能作为进程处理,所以需要将这些函数放在单独的文件中并使用单独的 bash。

回答by 1729

Named pipes.

命名管道。

Create a series of fifos, using mkfifo

使用 mkfifo 创建一系列 fifo

i.e fifo0, fifo1

即 fifo0, fifo1

Then attach each process in term to the pipes you want:

然后将每个进程附加到您想要的管道上:

processn < fifo(n-1) > fifon

processn < fifo(n-1) > fifo

回答by Penz

I doubt sh/bash can do it. ZSH would be a better bet, with its MULTIOS and coproc features.

我怀疑 sh/bash 可以做到。ZSH 将是一个更好的选择,因为它具有 MULTIOS 和 coproc 功能。

回答by Fritz G. Mehner

A command stack can be composed as string from an array of arbitrary commands and evaluated with eval. The following example gives the result 65536.

命令堆栈可以由任意命令数组中的字符串组成,并使用 eval 进行评估。以下示例给出了结果 65536。

function square ()
{
  read n
  echo $((n*n))
}    # ----------  end of function square  ----------

declare -a  commands=( 'echo 4' 'square' 'square' 'square' )

#-------------------------------------------------------------------------------
#   build the command stack using pipes
#-------------------------------------------------------------------------------
declare     stack=${commands[0]}

for (( COUNTER=1; COUNTER<${#commands[@]}; COUNTER++ )); do
  stack="${stack} | ${commands[${COUNTER}]}"
done

#-------------------------------------------------------------------------------
#   run the command stack
#-------------------------------------------------------------------------------
eval "$stack"