从管道的后面杀死管道中的特定进程

Killing a specific process in a pipeline, from later in the pipeline

我正在编写一个 shell 脚本,它使用 telnet 连接到服务器,发送查询,然后获取单行响应以进行进一步处理。经典的解决方案是设置一个 subshell 来 echo 查询,然后运行 ​​sleep 以保持连接打开足够长的时间以便返回响应:

#!/bin/sh

SERVER_ADD=localhost
SERVER_PORT=9995

result=$( ( echo '{"op":"get","path":"access"}'; sleep 30 ) | telnet "$SERVER_ADD" "$SERVER_PORT" )
    
echo "$result"

这有效,除了 sleep 必须长于服务器可能响应的最长时间。这意味着每次调用都会花费那么长的时间,这将最短时间从几十毫秒推到几十秒。我需要我的脚本等待第一行响应,然后终止进程,以便它可以继续做其他事情。

(是的:我知道显而易见的答案是“使用 expect." Unfortunately, I'm targeting an embedded system, and adding expect and the TcL 编写的脚本引擎会使我的图像大小增加大约 1 兆字节。不行。)

经过反复试验,我得出以下结论:

#!/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"

说明

除了那个 killall sleep,这很好用。这将杀死该用户控制下的每个 sleep 实例。大多数时候这不会成为问题,但其他时候这将是一个 严重 令人困惑的错误来源。

我怎样才能在需要时杀死那个 sleep(或整个子shell)而不造成附带伤害?

如果您有 bash,您可以尝试使用协同处理:

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]}

coproc bash 命令启动一个进程,并创建一个名为 COPROC 的数组,其中包含进程的两个文件描述符,stdin 和 stdout。 然后您可以随意编写和阅读它们。 如果您的 telnet 在一个命令后没有结束,您可能需要发送第二个命令(例如 echo 'exit' >&${COPROC[1]})来断开连接。

您可以尝试在任意文件上用 flock 命令替换睡眠,同时 运行 telnet。代替睡眠的第二个 flock 将挂起。第三个 flock -u 可以随时释放锁。

虽然典型用法是 flock lockfile command,但此脚本利用了用法 flock filedescriptor。这是因为 flock -u 只能在文件描述符上使用,并且没有命令(添加命令会强制将文件描述符 arg 用作文件名)。

通过只打开一次锁定文件,并为其分配文件描述符 5(任意),flock 5 将锁定它,flock -u 5 将解锁它。

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

查看其他 以获得 flock -u 的简单测试。

您可以尝试使用 fifo 将数据发送到 telnet,这样您就可以在需要的时候关闭它。

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
)

语法5>myfifo(无space)打开一个新的输出文件描述编号5写入fifo。 echo >&5 写入此 fd。 exec 5>&- 关闭这个 fd。此语法应适用于 ash.