连接管道

连接管道

考虑这样一个例程:

alpha() { echo a b c |tr ' ' '\n'; }

它输出一个流,我想获取输出流,对其进行转换,然后将paste其与原始输出流一起使用。

如果我将 use upcasing 作为示例转换,我可以通过以下方式实现我想要的:

$ mkfifo p1 p2
$ alpha | tee p1 >( tr a-z A-Z > p2) >/dev/null &
$ paste p1 p2
a       A
b       B
c       C

我的问题是,有没有更好的方法来做到这一点,最好是不涉及命名管道的方法?

答案1

在大多数 unice 中,您可以通过设备链接引用文件描述符。

alpha(){ echo a b c | tr \  \\n; }
alpha | { alpha |
    tr \[:lower:] \[:upper:] |
    paste - /dev/fd/9
}   9<&0

A   a
B   b
C   c

命名管道


好吧,我确实已经有了一个运行良好的版本。在我的编辑器中打开的版本(尚未完成)目前不可行 - 我有点把整个选项解析得粉碎,只模糊地记得我的意图。但是这个...

_p(){   : "$((_$$=0))"
    cd -P -- "${TMPDIR:-/tmp}"          &&
    while   [ -h "$$.$((_$$+=$$))" ]    ||
            [ -e "$$.$((_$$))" ]
    do :;   done                        &&
    mkfifo  -m a-w,u=rwx "$$.$((_$$))"  &&
    printf  %s "$PWD/$$.$((_$$))"       &&
    exec    >&- >&0                     &&
    case    $1 in
    (+)     shift
            eval "  rm $$.$((_$$))
                    cd - >/dev/null
            $@" < "$$.$((_$$))" &;;
    (-)     shift
            eval "  rm $$.$((_$$))
                    cd - >/dev/null
            $@" >   "$$.$((_$$))"   &;;
    (*) set --
        : "${1:?"USAGE: [-+] [cmd args...]"}"
    esac
}   <&- <>/dev/null

您可能会注意到 . 发生了一些非常奇怪的事情$((_$$))。这是一种尝试尽可能少地踏入命令环境的方式。我在这里非常努力地保持全部可能的环境值与我可能的一样原始,因为该函数的目的是执行与进程替换相同的操作 - 运行写入管道的任意命令。

cat "$(_p - echo a b c)"

a b c

这是工作流程的简化版本:

mkfifo pipe                         # make a pipe
printf %s\\n "$PWD/pipe"            # substitute its file name
exec <&- </dev/null >&- >/dev/null  # break cmd sub i/o
eval "rm pipe;$@" >pipe &           # eval rm; args

因为该eval命令将其所有参数包装在一个简单的命令中,eval所以 的 stdout 是整个eval'd 命令组的标准输出。这是它拥有该管道描述符,而不是其任何仅将其继承为标准输出的子进程。所以外壳做了open()- 因为我们做了阻塞 open() (喜欢>而不是<>shell 会挂起,直到该管道获得读取器。

这意味着rm不会执行 - 无法执行 - 直到其他进程为该管道建立读取文件描述符。当某个进程打开它进行读取时(如上所述cat外壳停止悬挂,并且采取的第一个操作是管道被rm“d”——然后再进行其他操作。这是不是问题——管道已经有一个读取器进程和一个写入器进程。一切都很好。

这有点麻烦。例如:

echo "$(_p -)"

/tmp/6023.6023

^管道没有得到rm'd。echo不读它。所以eval还在等待。杀死它:

: </tmp/6023.6023

……不过,足以杀死它。

我用它的目的是这样的:

exec 9<> "$(_p -)"
sed -ue's/sed/mikeserv/' <&9 &
echo hi sed >&9

hi mikeserv

顺便说一句,我只展示了<()等效的内容,但>()可以像这样模拟......

echo hi sed >"$(_p + sed -e's/sed/mikeserv/' '>&2')"

hi mikeserv

其他管道


你不需要我的 hacky 函数来做这个。流程替代相当普遍。没有什么可以阻止你保持当你需要一个管道时,你不需要立即分配它并丢弃它。

(偏执笔记:我有点讨厌它,因为我永远不知道这些文件在哪里来自- 很可能您是一个适应能力更强的人,因此您可以接受以下内容)

eval "exec 9<> " <(:)

有一根管子 - 都是你的。无需清理。你可以走了。


_pp- 呵呵


_pp(){  :   "$((_$$=0))"
        _e()    case    ${1:-0} in
                (i|0)   exec >&- 1<>/dev/null;;
                (o|1) ! exec <&-  <>/dev/null;;
                (e|[2-9])
                        exec <&-  <>/dev/null >&- >&0
                        set     "${1#e}"
                        return  "${1:-2}";;
                (*[\<\>]*)
                        eval "
                        exec <&-  <>/dev/null >&- >&0 $1"
                esac
        cd -P   -- "${TMPDIR:-/tmp}"            &&
        while   [ -h "$$.$((_$$+=1))" ]         ||
                [ -e "$$.$((_$$))"    ]
        do :;   done                            && 
        mkfifo  -m a-w,u=rwx   "$$.$((_$$))"    &&
        printf  "%s/%s" "$PWD" "$$.$((_$$))"    &&
        case    $1  in
        (+*)    set \> \< "$@"  ;;
        (-*)    set \< \> "$@"  ;;
        (*)     rm  "$$.$((_$$))"
                set ''  USAGE:  '<-+>[+-[fd][file]]'
                ${1:?"$(printf "\n%s\t%s [cmd...]$@")"};;
        esac    &&
        case    $3  in
        (?|?[ioe0-9])
                _e  "${3#?}"
                eval "exec $1&$?";;
        (?/*)   _e "$1"'"$2"' "${3#?}";;
        (*)     _e "$1"'"$2"' "$OLDPWD/${3#?}"
        esac    &&
        eval "  shift 3
                eval \" rm $$.$((_$$))
                        cd - >/dev/null
        \$@\"   $2$$.$((_$$)) &"
}

...所以...我终于开始做_pp证明可行。这些[-+]选项可以采取任何ioe选项的意思std(in|out|err) (尽管stdout在命令子中几乎没用)或者[0-9]引用您可能已经设置的任何描述符,或任何其他被解释为任意文件名的内容。当使用参数调用时,+被理解为输出流 - 因为输入应该来自您调用它的命令 - 并且-参数被用作输入。

因此,sed上面的最后一个可能会写成:

echo hi sed >"$(_pp +2 sed s/sed/mikeserv/)"

hi mikeserv

相关内容