私たちは皆、mkfifo
とパイプラインを知っています。最初のものはnamedパイプを作成するため、名前を選択する必要があります。おそらくmktemp
を使用し、後でリンクを解除することを忘れないでください。もう1つは匿名パイプを作成し、名前と削除の手間はありませんが、パイプの両端はパイプラインのコマンドに関連付けられているため、ファイル記述子を把握して残りの部分で使用することは本当に便利ではありませんスクリプトの。コンパイルされたプログラムでは、単にret=pipe(filedes)
を実行します。バッシュにはexec 5<>file
したがって、"exec 5<> -"
または"pipe <5 >6"
-バッシュにそのようなものはありますか?
名前付きパイプを現在のプロセスにアタッチした直後に、名前付きパイプのリンクを解除できます。これにより、実質的には匿名パイプになります。
# 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
内に配置することと同等ですが、より読みやすくなっています。
2012年10月現在、この機能はまだBashには存在しないようですが、名前のない/匿名のパイプが必要なのが子プロセスと通信することだけである場合は、coprocを使用できます。この時点でのcoprocの問題は、明らかに一度に1つしかサポートされないことです。 coprocがこの制限を受けた理由を理解できません。それらは既存のタスクのバックグラウンドコード(&op)の拡張であるはずですが、それはbashの作者にとっての問題です。
@DavidAndersonの answer はすべてのベースをカバーし、いくつかの優れた保護手段を提供しますが、明らかにする最も重要なことは、匿名パイプを手に入れるのは<(:)
と同じくらい簡単で、 Linuxを使い続けるとき。
だからあなたの質問への最も短くて簡単な答えは:
exec 5<> <(:)
MacOSでは機能しません。リダイレクトするまで、名前付きのfifoを格納する一時ディレクトリを作成する必要があります。他のBSDについては知りません。
次の関数は、GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)
を使用してテストされました。オペレーティングシステムはUbuntu 18でした。この関数は、匿名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が匿名になります。
_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からの素晴らしく明るい答えを使用して、1つのライナーで使用するように少し変更しました。
# 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<&-