考虑这样一个例程:
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$$.$((_$$)) &"
}
...所以...我终于开始做_p
我p证明可行。这些[-+]
选项可以采取任何ioe
选项的意思std(in|out|err)
(尽管stdout
在命令子中几乎没用)或者[0-9]
引用您可能已经设置的任何描述符,或任何其他被解释为任意文件名的内容。当使用参数调用时,+
被理解为输出流 - 因为输入应该来自您调用它的命令 - 并且-
参数被用作输入。
因此,sed
上面的最后一个可能会写成:
echo hi sed >"$(_pp +2 sed s/sed/mikeserv/)"
hi mikeserv