Why can't I use exec 3>myfifo in the same manner in a bash script as I can in my terminal?
I'm using named pipes to turn an awk filter into a simple "server", that should be able to take text input from clients, filter it, and flush on NUL.
In terminal 1, the server is running like this:
$ mkfifo to_server from_server;
$ while true; do
# Really, this awk script BEGIN's with reading in a huge file,
# thus the client-server model
awk '{sub("wrong", "correct");print;} /\0/ {fflush();}' <to_server >from_server;
echo "restarting...";
done
And I've got a simple script that should put input, ending with a NUL, into the input-pipe, and read from the output pipe, and then exit, without sending an EOF to the server (we don't want the server to restart):
#!/bin/bash
# According to http://mywiki.wooledge.org/BashFAQ/085 , using
# `exec 3>mypipe; echo foo >&3;` instead of `echo foo >mypipe`
# should ensure that the pipe does not get the EOF which closes it:
exec 3>to_server;
exec 4开发者_运维百科<from_server;
cat >&3;
echo -e '\0' >&3;
while read -rd '' <&4; do echo -n "$REPLY"; break; done;
Now, if I in terminal 2 do $ echo This is wrong | bash client.sh
, I get This is correct
back, but terminal 1 shows me that the server restarts! If I run the commands from client.sh from within terminal 2, however, it does not restart.
It seems to be related to the exec commands, since I can also do
$ exec 3>to_server; exec 4<from_server;
$ echo "This is wrong" | sh client.sh
and it does not restart. If I then
$ exec 3>&-; exec 4<&-
(which of course restarts it once) and do
$ echo "This is wrong" | sh client.sh
it restarts every time. So it seems the exec commands in the script have no effect. However, putting ls /proc/$$/fd/
after the exec commands in the script shows that they do in fact point to the correct pipes.
What am I missing here?
I think I figured it out!
The exec commands do work, but bash itself closes all open file descriptors on exiting from the script. Adding sleep 5 to the end of the client shows that it takes 5 seconds for the server to finally shut down.
So the solution is just to open some file descriptor to my named pipes from some other terminal, and just keep them open, e.g. in terminal 3:
$ exec 3>to_server; exec 4<from_server
$ # keep open for as long as server is open
or, in the server terminal/script itself:
while true; do
# Really, this awk script BEGIN's with reading in a huge file,
# thus the client-server model
awk '{sub("wrong", "correct");print;} /\0/ {fflush();}' <to_server >from_server &
AWKPID=$!
exec 3>to_server; exec 4<from_server
wait "$AWKPID"
echo "restarting...";
done
Here's a somewhat different version that processes (client-side) input in five-line blocks.
# using gawk for Mac OS X from: http://rudix.org/packages-ghi.html#gawk
# server
rm -v to_server from_server
mkfifo to_server from_server
(
while true; do
(exec gawk '{sub("wrong", "correct");print;} /\0/ {fflush();}' <to_server >from_server) &
bgpid=$!
exec 3>to_server; exec 4<from_server
wait "$bgpid"
echo "restarting..."
done
) &
# client.sh
#!/bin/bash
exec 3>to_server
exec 4<from_server
n=0
clientserver() {
n=0
(
while read -rd '' <&4; do echo -n "$REPLY"; break; done;
IFS="" read -r -d $'\n' <&4 lines && printf 'found a trailing newline in from_server fifo \n' "$lines"
) &
bgpid=$!
printf '%s\n' "${lines[@]}" >&3
printf '%b' '\000\n' >&3
wait $bgpid
unset -v lines
return 0
}
while IFS="" read -r -d $'\n' line; do
n=$((n+=1))
lines[$((n-1))]="$line"
if [[ $n -eq 5 ]]; then
clientserver
fi
done
if [[ ${#lines[@]} -gt 0 ]]; then
clientserver
fi
exit 0
# test
serverpid=$!
echo This is wrong | bash client.sh
printf '%s\n' {1..1007} | bash client.sh
kill -TERM $serverpid
精彩评论