I'm writing a shell script that uses telnet
to connect to a server, send a query, and then grab the one-line response for further processing. The classic solution is to set up a subshell which echo
s the query and then runs sleep
to keep the connection open long enough for the response to be returned:
#!/bin/sh
SERVER_ADD=localhost
SERVER_PORT=9995
result=$( ( echo '{"op":"get","path":"access"}'; sleep 30 ) | telnet "$SERVER_ADD" "$SERVER_PORT" )
echo "$result"
This works, except that the sleep
has to be longer than the longest the server might take to respond. This means that every invocation will take that long, which pushes the minimum time out from tens of milliseconds to tens of seconds. I need my script to wait for that first line of response, and then terminate the process so it can go on to do other things.
(Yes: I know the obvious answer is "use expect
." Unfortunately, I'm targeting an embedded system, and adding expect
and the TcL
script engine that it is written in would add about a megabyte to my image size. No can do.)
After much trial and error, I came up with the following:
#!/bin/sh
SERVER_ADD=localhost
SERVER_PORT=9995
result=$( ( echo '{"op":"get","path":"access"}'; sleep 30 ) |
telnet "$SERVER_ADD" "$SERVER_PORT" |
while read -r line; do
echo "$line"
killall sleep
done ) 2>/dev/null
echo "$result"
Explanation
telnet
to make the connection sleep
command by name, which ends the subshell and thus the telnet connection2>/dev/null
consumes the "Terminated" message that the sleep
command prints when it is terminated This works well, EXCEPT for that killall sleep
. This will kill EVERY instance of sleep
under this user's control. Most times this won't be a problem, but the other times it will be a seriously confusing source of bugs.
How can I kill that sleep
(or the entire subshell) when needed, without collateral damage?
If you have bash
you could try a coprocess instead:
coproc telnet "$SERVER_ADD" "$SERVER_PORT"
echo '{"op":"get","path":"access"}' >&${COPROC[1]}
result=
while IFS= read -r line
do result+="$line
"
done <&${COPROC[0]}
The coproc
bash command launches a process, and creates an array named COPROC
holding two file descriptors, stdin and stdout of the process. You can then write and read them at will. If your telnet doesn't end after one command you may need to send a second command (eg echo 'exit' >&${COPROC[1]}
) to break the connection.
You could try replacing the sleep by a flock
command on an arbitrary file whilst running telnet. A second flock
in place of the sleep will then hang. A third flock -u
can release the lock at any time.
Though typical usage is flock lockfile command
, this script exploits the usage flock filedescriptor
. This is because flock -u
can only be used on a file descriptor, and without a command (adding a command forces the file descriptor arg to be taken as a filename).
By opening the lockfile just once, and assigning it file descriptor 5 (arbitrarily), flock 5
will lock it, and flock -u 5
will unlock it.
result=$(
exec 5>mylock
flock 5
( echo '{"op":"get","path":"access"}'; flock mylock true ) |
telnet "$SERVER_ADD" "$SERVER_PORT" |
while read line; do
echo "$line"
flock -u 5
done
) 2>/dev/null
See this other question for a simple test of flock -u
.
You could try using a fifo to send data to the telnet, so you can close it at the wanted time.
rm -f myfifo
mkfifo myfifo
result=$(
telnet "$SERVER_ADD" "$SERVER_PORT" <myfifo |
( echo '{"op":"get","path":"access"}' >&5
while read -r line; do
echo "$line"
exec 5>&-
done
) 5>myfifo
)
The syntax 5>myfifo
(no space) opens a new output file description number 5 writing to the fifo. The echo >&5
writes to this fd. The exec 5>&-
closes this fd. This syntax should work in ash
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.