BASH:将 stdin 重定向到可变数量的子进程

BASH: redirect stdin to variable number of subprocesses

提问人:Mega-X 提问时间:7/28/2023 更新时间:8/7/2023 访问量:64

问:

正如标题所说,我想重定向到可变数量的输出子进程。stdin

如果我必须重定向到输出文件,我可以做类似的事情

files=(file_1 file_2 ... file_n)
tee ${files[*]} >/dev/null

但是对于子流程(特别是使用流程替换),可以做类似的事情

programs=(">(exe_1 args_1)" ... ">(exe_n args_n)")
tee ${programs[@]} >/dev/null

不会将 as 进程替换作为文本文件名(出于安全原因,我假设);此外,具有替换的标志被解释为 的标志。>()tee

是否可以从中读取一行并将其重定向到所有这些进程(同样,这些进程在数量上是可变的:未知)?我是否在某个地方错过了什么?stdinn

提前致谢,对不起我的英语不好。

bash io-重定向

评论

2赞 Charles Duffy 7/28/2023
不幸的是,您需要使用它来执行此操作(这意味着需要非常小心才能使其安全)。eval
0赞 pjh 7/28/2023
一种选择是使用广泛可用的 moreutils 软件包中的命令。pee

答:

2赞 Charles Duffy 7/28/2023 #1

不幸的是,这是一份工作。给出类似的东西:eval

all_redirections=""

add_redirection() {
  local argv_q
  printf -v argv_q '%q ' "$@"       # generate an eval-safe escaping of "$@"
  all_redirections+=" ${argv_q} "   # append that to all_redirections string
}

to_all_redirections() { eval "tee ${all_redirections}" >/dev/null; }

...您可以运行:

add_redirection exe_1 arg_1_a arg_1_b
add_redirection exe_2 arg_2_a
# ...
add_redirection exe_n arg_n

...然后,当你有一个程序,其输出将被复制到这些可执行文件的输入中时:

yourprogram | to_all_redirections
4赞 Barmar 7/28/2023 #2

不要使用进程替换,而是在循环中创建一堆命名管道,然后运行每个进程,并将其 stdin 重定向到其中一个管道。然后用于写入所有管道。tee

progs=(exe_1 exe_2 ...)
args=(args_1 args2 ...)
pipes=()
arraylenth=${#progs[@]}

for (( i=0; i<${arraylength}; i++ ))
do
    pipe=/tmp/pipe.$$.$i
    mkfifo "$pipe" && pipes+=("$pipe") && "$progs[i]" "$args[i]" < "$pipe" &
done

tee "${pipes[@]" > /dev/null
# Clean up
rm -f "${pipes[@]"

此解决方案使每个程序都只使用 1 个参数运行。很难使它更通用,因为 bash 没有二维数组。

评论

0赞 Mega-X 7/28/2023
选择这个因为它避免了使用,感谢您的支持!:)eval
1赞 pjh 7/28/2023 #3

Shellcheck-clean 代码不使用,并且可以处理具有任意数量参数的任意数量的程序:eval

#! /bin/bash -p

prog_args=( ::: sed -e 's/^/[1] /'
            ::: sed -e 's/^/[2] /'
            ::: sed -e 's/^/[3] /'  )

exec 3>&1

function tee_to_progs
{
    (( $# < 2 )) && return 1

    local -r startstr=$1
    shift

    local pargs=()
    while [[ $# -gt 0 && $1 != "$startstr" ]]; do
        pargs+=( "$1" )
        shift
    done

    if (( $# == 0 )); then
        # Last program to run.  It can just read stdin and write stdout.
        "${pargs[@]}"
    else
        tee >("${pargs[@]}" >&3) | tee_to_progs "$@"
    fi
}

tee_to_progs "${prog_args[@]}"
  • 该数组包含要运行的程序和参数。由于 Bash 不支持嵌套数组,因此每个命令前面都有一个标记字符串,以便识别单独的程序和参数。我使用了这个字符串(因为它在 GNU Parallel 中用于类似的目的),但任何不用作程序名称或参数的字符串都可以改用。该代码假定数组中的第一个字符串(无论它碰巧是什么)是标记字符串,因此如果字符串发生更改,则不需要更改代码。我测试了使用而不是.prog_args:::_:::
  • 这些命令只是示例。我用它们进行测试,因为每个程序的输出都可以很容易地识别出来。sed
  • 请注意,此代码仅支持运行简单命令。如果您需要运行其他命令(例如使用重定向的命令),则需要使用不同的方法(可能涉及可怕的)。eval
  • exec 3>&1使文件描述符编号 3 与标准输出相关联。它在代码中用于确保输出进入“真实”标准输出。
  • 该函数运行其参数列表中的第一个程序,并将输入复制到该程序,并将输出通过管道传递给该函数的递归调用,该函数对第二个和后续程序执行相同的操作。tee_to_progsteetee
1赞 Ole Tange 8/7/2023 #4

GNU Parallel 为此提供了:--tee

cat input | parallel --tee --pipe my_program {} ::: arg1 arg2 arg3

在内部,它的工作方式与 Barmar 的解决方案非常相似,但您可以获得 GNU Parallel 的输出控制:

  • 输出是序列化的,因此两个作业的输出不会混合
  • 您可以保留订单--keep-order
  • 你可以每行--tag

临时文件的清理是在作业完成之前完成的,因此如果脚本被终止,则无需清理临时文件。

例如

seq 10000 | parallel --tee --pipe --tag --keep-order grep {} ::: {1..9}