我们都知道mkfifo
管道。第一个创建一个命名管道,因此必须选择一个名称,最有可能使用mktemp
,后来记得取消链接。另一个创建了一个匿名管道,没有麻烦的名称和删除操作,但是管道的末端与管道中的命令绑定在一起,以某种方式掌握文件描述符并在其余部分中使用它们并不十分方便的脚本。在编译的程序中,我只想做ret=pipe(filedes)
;在Bash中有exec 5<>file
这样的期望- "exec 5<> -"
或者"pipe <5 >6"
Bash中有类似的东西吗?
我们都知道mkfifo
管道。第一个创建一个命名管道,因此必须选择一个名称,最有可能使用mktemp
,后来记得取消链接。另一个创建了一个匿名管道,没有麻烦的名称和删除操作,但是管道的末端与管道中的命令绑定在一起,以某种方式掌握文件描述符并在其余部分中使用它们并不十分方便的脚本。在编译的程序中,我只想做ret=pipe(filedes)
;在Bash中有exec 5<>file
这样的期望- "exec 5<> -"
或者"pipe <5 >6"
Bash中有类似的东西吗?
Answers:
您可以在将命名管道附加到当前进程后立即取消链接,这实际上导致了匿名管道:
# create a temporary named pipe
PIPE=$(mktemp -u)
mkfifo $PIPE
# attach it to file descriptor 3
exec 3<>$PIPE
# unlink the named pipe
rm $PIPE
...
# anything we write to fd 3 can be read back from it
echo 'Hello world!' >&3
head -n1 <&3
...
# close the file descriptor when we are finished (optional)
exec 3>&-
如果您确实要避免使用命名管道(例如,文件系统是只读的),那么“掌握文件描述符”的想法也可以。请注意,由于使用了procfs,因此这是特定于Linux的。
# start a background pipeline with two processes running forever
tail -f /dev/null | tail -f /dev/null &
# save the process ids
PID2=$!
PID1=$(jobs -p %+)
# hijack the pipe's file descriptors using procfs
exec 3>/proc/$PID1/fd/1 4</proc/$PID2/fd/0
# kill the background processes we no longer need
# (using disown suppresses the 'Terminated' message)
disown $PID2
kill $PID1 $PID2
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptors when we are finished (optional)
exec 3>&- 4<&-
虽然我所知的任何一个外壳都无法制造没有分叉的管道,但有些外壳确实比基本的外壳管道更好。
在bash,ksh和zsh中,假设系统支持/dev/fd
(当今大多数情况下),则可以将命令的输入或输出绑定到文件名:<(command)
扩展为一个文件名,该文件名指定从到输出连接的管道command
,然后>(command)
扩展到指定连接到的输入的管道的文件名command
。此功能称为流程替换。它的主要目的是将一个以上的命令通过管道传递到另一个命令中,例如,
diff <(transform <file1) <(transform <file2)
tee >(transform1 >out1) >(transform2 >out2)
这对于消除基本壳管的某些缺点也很有用。例如,command2 < <(command1)
等于command1 | command2
,但其状态为command2
。另一个用例是exec > >(postprocessing)
,与将整个脚本的其余部分放到里面等效,但比它更易读{ ... } | postprocessing
。
pipe:[123456]
)。Emacs认为symlink的目标不是现有的文件名,并且使它很混乱,以至于它无法读取文件(尽管Emacs不喜欢将管道作为档案)。
重击4具有协同处理。
协同处理在子shell中异步执行,就好像命令已由'&'控制运算符终止,并且在执行外壳和协同处理之间建立了双向管道。
协同处理的格式为:
coproc [NAME] command [redirections]
截至2012年10月,该功能似乎在Bash中仍然不存在,但是如果您需要与子进程进行通讯的所有未命名/匿名管道,都可以使用coproc。此时coproc的问题在于,显然一次仅支持一个。我不知道为什么coproc有这个限制。它们本来应该是现有任务背景代码(&op)的增强,但这是bash作者的一个问题。
coproc THING { dothing; }
现在,您的FD进入了${THING[*]}
,您可以运行coproc OTHERTHING { dothing; }
并向彼此发送和接收事物。
man bash
BUGS标题下这样说:一次可能只有一个活动的协同进程。如果您再次启动coproc,则会收到警告。它似乎可以正常工作,但我不知道后台会发生什么爆炸。
@DavidAnderson的答案涵盖了所有基础并提供了一些不错的保护措施,但它揭示的最重要的一点是,<(:)
只要您继续使用Linux,就可以轻松地访问匿名管道。
因此,最简单的答案是:
exec 5<> <(:)
在macOS上,它将无法正常工作,然后您需要创建一个临时目录来存放命名的fifo,直到您将其重定向到该目录为止。我不了解其他BSD。
exec
已通过,并且匿名fifo仅打开以供读取,则exec
不应允许使用自定义文件描述符打开此匿名fifo以进行读取和写入。您应该期望得到一条-bash: /dev/fd/5: Permission denied
消息,这就是macOS出现的问题。我相信错误是Ubuntu不会产生相同的消息。如果有人可以出示exec 5<> <(:)
允许的文件说明,我愿意改变主意。
open(..., O_RDWR)
在替换提供的一个单向管道末端执行操作,并将其转换为一个FD中的双向管道。您可能是对的,不应依赖此。:-D使用execline的piperw创建管道,然后使用bash重新设置管道的输出<>
:libranet.de/display/0b6b25a8-195c-84af-6ac7-ee6696661765
exec 5<>
,请输入fun() { ls -l $1; ls -lH $1; }; fun <(:)
。
使用进行了以下功能的测试GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)
。操作系统是Ubuntu18。此函数采用单个参数,该参数是匿名FIFO所需的文件描述符。
MakeFIFO() {
local "MakeFIFO_upper=$(ulimit -n)"
if [[ $# -ne 1 || ${#1} -gt ${#MakeFIFO_upper} || -n ${1%%[0-9]*} || 10#$1 -le 2
|| 10#$1 -ge MakeFIFO_upper ]] || eval ! exec "$1<> " <(:) 2>"/dev/null"; then
echo "$FUNCNAME: $1: Could not create FIFO" >&2
return "1"
fi
}
使用进行了以下功能的测试GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
。操作系统为macOS High Sierra。此功能首先在仅创建它的进程知道的临时目录中创建一个命名FIFO 。接下来,将文件描述符重定向到FIFO。最后,通过删除临时目录将FIFO与文件名断开链接。这使FIFO为匿名。
MakeFIFO() {
MakeFIFO.SetStatus() {
return "${1:-$?}"
}
MakeFIFO.CleanUp() {
local "MakeFIFO_status=$?"
rm -rf "${MakeFIFO_directory:-}"
unset "MakeFIFO_directory"
MakeFIFO.SetStatus "$MakeFIFO_status" && true
eval eval "${MakeFIFO_handler:-:}'; true'"
}
local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" "MakeFIFO_file="
MakeFIFO_handler="$(trap -p EXIT)"
MakeFIFO_handler="${MakeFIFO_handler#trap -- }"
MakeFIFO_handler="${MakeFIFO_handler% *}"
trap -- 'MakeFIFO.CleanUp' EXIT
until "$MakeFIFO_success"; do
[[ $# -eq 1 && ${#1} -le ${#MakeFIFO_upper} && -z ${1%%[0-9]*}
&& 10#$1 -gt 2 && 10#$1 -lt MakeFIFO_upper ]] || break
MakeFIFO_directory=$(mktemp -d) 2>"/dev/null" || break
MakeFIFO_file="$MakeFIFO_directory/pipe"
mkfifo -m 600 $MakeFIFO_file 2>"/dev/null" || break
! eval ! exec "$1<> $MakeFIFO_file" 2>"/dev/null" || break
MakeFIFO_success="true"
done
rm -rf "${MakeFIFO_directory:-}"
unset "MakeFIFO_directory"
eval trap -- "$MakeFIFO_handler" EXIT
unset "MakeFIFO_handler"
"$MakeFIFO_success" || { echo "$FUNCNAME: $1: Could not create FIFO" >&2; return "1"; }
}
可以将以上功能组合为在两个操作系统上都可以使用的单个功能。以下是此类功能的示例。在这里,尝试创建一个真正的匿名FIFO。如果未成功,那么将创建一个命名的FIFO并将其转换为匿名FIFO。
MakeFIFO() {
MakeFIFO.SetStatus() {
return "${1:-$?}"
}
MakeFIFO.CleanUp() {
local "MakeFIFO_status=$?"
rm -rf "${MakeFIFO_directory:-}"
unset "MakeFIFO_directory"
MakeFIFO.SetStatus "$MakeFIFO_status" && true
eval eval "${MakeFIFO_handler:-:}'; true'"
}
local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" "MakeFIFO_file="
MakeFIFO_handler="$(trap -p EXIT)"
MakeFIFO_handler="${MakeFIFO_handler#trap -- }"
MakeFIFO_handler="${MakeFIFO_handler% *}"
trap -- 'MakeFIFO.CleanUp' EXIT
until "$MakeFIFO_success"; do
[[ $# -eq 1 && ${#1} -le ${#MakeFIFO_upper} && -z ${1%%[0-9]*}
&& 10#$1 -gt 2 && 10#$1 -lt MakeFIFO_upper ]] || break
if eval ! exec "$1<> " <(:) 2>"/dev/null"; then
MakeFIFO_directory=$(mktemp -d) 2>"/dev/null" || break
MakeFIFO_file="$MakeFIFO_directory/pipe"
mkfifo -m 600 $MakeFIFO_file 2>"/dev/null" || break
! eval ! exec "$1<> $MakeFIFO_file" 2>"/dev/null" || break
fi
MakeFIFO_success="true"
done
rm -rf "${MakeFIFO_directory:-}"
unset "MakeFIFO_directory"
eval trap -- "$MakeFIFO_handler" EXIT
unset "MakeFIFO_handler"
"$MakeFIFO_success" || { echo "$FUNCNAME: $1: Could not create FIFO" >&2; return "1"; }
}
这是一个创建匿名FIFO,然后将一些文本写入同一FIFO的示例。
fd="6"
MakeFIFO "$fd"
echo "Now is the" >&"$fd"
echo "time for all" >&"$fd"
echo "good men" >&"$fd"
以下是读取匿名FIFO的全部内容的示例。
echo "EOF" >&"$fd"
while read -u "$fd" message; do
[[ $message != *EOF ]] || break
echo "$message"
done
这将产生以下输出。
Now is the
time for all
good men
下面的命令关闭匿名FIFO。
eval exec "$fd>&-"
使用htamas的出色回答,我对其进行了一些修改,以便在一个衬套中使用它,这里是:
# create a temporary named pipe
PIPE=(`(exec 0</dev/null 1</dev/null; (( read -d \ e < /proc/self/stat ; echo $e >&2 ; exec tail -f /dev/null 2> /dev/null ) | ( read -d \ e < /proc/self/stat ; echo $e >&2 ; exec tail -f /dev/null 2> /dev/null )) &) 2>&1 | for ((i=0; i<2; i++)); do read e; printf "$e "; done`)
# attach it to file descriptors 3 and 4
exec 3>/proc/${PIPE[0]}/fd/1 4</proc/${PIPE[1]}/fd/0
...
# kill the temporary pids
kill ${PIPE[@]}
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptor when we are finished (optional)
exec 3>&- 4<&-