简体   繁体   English

从管道的后期终止管道中的特定进程

[英]Killing a specific process in a pipeline, from later in the pipeline

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.我正在编写一个 shell 脚本,该脚本使用telnet连接到服务器,发送查询,然后获取单行响应以进行进一步处理。 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:经典的解决方案是设置一个子shell,它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"

This works, except that the sleep has to be longer than the longest the server might take to respond.这是可行的,只是sleep时间必须长于服务器响应所需的最长时间。 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.我需要我的脚本等待第一行响应,然后终止进程,以便它可以 go 继续执行其他操作。

(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.) (是的:我知道显而易见的答案是“使用expect 。”不幸的是,我的目标是嵌入式系统,添加expect和编写它的TcL脚本引擎会使我的图像大小增加大约一兆字节。不能.)

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解释

  • Set up a subshell that echoes the query and then sleeps long enough for the longest response time设置一个响应查询的子shell,然后休眠足够长的时间以获得最长的响应时间
  • Pipe to telnet to make the connection Pipe 到telnet建立连接
  • Pipe the server response to a loop, one line at a time Pipe 服务器响应一个循环,一次一行
  • Once the first line has been captured and echoed, kill the sleep command by name, which ends the subshell and thus the telnet connection捕获并回显第一行后,按名称终止sleep命令,这将结束子shell,从而结束 telnet 连接
  • The 2>/dev/null consumes the "Terminated" message that the sleep command prints when it is terminated 2>/dev/null使用sleep命令在终止时打印的“终止”消息

This works well, EXCEPT for that killall sleep .这很好用,除了那个killall sleep This will kill EVERY instance of sleep under this user's control.这将杀死该用户控制下的每个sleep实例。 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?我怎样才能在需要时杀死sleep (或整个子外壳),而不会造成附带损害?

If you have bash you could try a coprocess instead:如果您有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]}

The coproc bash command launches a process, and creates an array named COPROC holding two file descriptors, stdin and stdout of the process. coproc bash 命令启动一个进程,并创建一个名为COPROC的数组,其中包含两个文件描述符,即进程的标准输入和标准输出。 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.如果您的 telnet 在一个命令后没有结束,您可能需要发送第二个命令(例如echo 'exit' >&${COPROC[1]} )来断开连接。

You could try replacing the sleep by a flock command on an arbitrary file whilst running telnet.您可以尝试在运行 telnet 时用任意文件上的flock命令替换睡眠。 A second flock in place of the sleep will then hang.然后第二个flock代替睡眠将挂起。 A third flock -u can release the lock at any time.第三个flock -u可以随时释放锁。

Though typical usage is flock lockfile command , this script exploits the usage flock filedescriptor .虽然典型的用法是flock lockfile command ,但这个脚本利用了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).这是因为flock -u只能用于文件描述符,并且没有命令(添加命令会强制将文件描述符arg 用作文件名)。

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.通过只打开一次锁定文件,并为其分配文件描述符 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

See this other question for a simple test of flock -u .请参阅此其他问题以获取对flock -u的简单测试。

You could try using a fifo to send data to the telnet, so you can close it at the wanted time.您可以尝试使用 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
)

The syntax 5>myfifo (no space) opens a new output file description number 5 writing to the fifo.语法5>myfifo (无空格)打开一个新的 output 文件描述号 5 写入 fifo。 The echo >&5 writes to this fd. echo >&5写入这个 fd。 The exec 5>&- closes this fd. exec 5>&-关闭这个 fd。 This syntax should work in ash .这种语法应该在ash中工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM