【问题标题】:Killing a specific process in a pipeline, from later in the pipeline从管道的后期终止管道中的特定进程
【发布时间】:2021-07-24 17:06:49
【问题描述】:

我正在编写一个 shell 脚本,它使用 telnet 连接到服务器,发送查询,然后获取单行响应以进行进一步处理。经典的解决方案是设置一个子shell,其中echos 查询然后运行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。”不幸的是,我的目标是嵌入式系统,添加expect 和编写它的TcL 脚本引擎会增加一个兆字节到我的图像大小。不行。)

经过多次尝试和错误,我想出了以下内容:

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

解释

  • 设置一个响应查询的子shell,然后休眠足够长的时间以获得最长的响应时间
  • 通过管道连接到telnet 以建立连接
  • 将服务器响应传送到一个循环,一次一行
  • 捕获并回显第一行后,按名称终止 sleep 命令,这将结束子 shell 和 telnet 连接
  • 2>/dev/null 使用 sleep 命令在终止时打印的“已终止”消息

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

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

【问题讨论】:

    标签: shell process telnet


    【解决方案1】:

    如果您有 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 的数组,其中包含两个文件描述符,即进程的标准输入和标准输出。 然后,您可以随意书写和阅读它们。 如果您的 telnet 在一个命令后没有结束,您可能需要发送第二个命令(例如 echo 'exit' &gt;&amp;${COPROC[1]})来断开连接。

    【讨论】:

    • 非常酷;我以前没见过“coproc”。不幸的是,我没有 bash(它是busybox 的 ash 版本),也没有 coproc。
    【解决方案2】:

    您可以尝试在运行 telnet 时在任意文件上使用 flock 命令替换睡眠。然后第二个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
    

    查看其他question 以了解flock -u 的简单测试。

    【讨论】:

    • 我仍然无法让这个版本工作。但是,您对flock 与文件描述符的使用的描述将是我在此之后询问的"What does flock -u actually do?" 的一个很好的答案;很高兴支持/接受它作为答案。
    • 为了完整起见,我已经恢复并修改了这个答案,现在我们有一个工作示例,说明如何通过名称和 fd 使用锁定文件 -u
    • Primo:现在也可以了。在您的两个与 coproc 无关的问题中,您认为哪个是最佳答案?
    • 最好的答案总是最容易编写、理解和维护的答案,所以对我来说,fifo 是最值得保留的答案。
    • 太棒了。感谢所有帮助。
    【解决方案3】:

    您可以尝试使用 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&gt;myfifo(无空格)打开一个新的输出文件描述号5,写入fifo。 echo &gt;&amp;5 写入此 fd。 exec 5&gt;&amp;- 关闭此 fd。此语法应该在ash 中有效。

    【讨论】:

    • 我无法让它为我工作。仍在努力,但任何提示都会有所帮助。 (Yowza:每个答案都教会了我一些东西。谢谢。)
    • 我在错误的地方留下了),抱歉。 Telnet 是一个棘手的协议。并非所有服务器在开始读取输入、断开连接或注意到断开连接时的反应都相同。
    • 有效!太棒了。
    猜你喜欢
    • 1970-01-01
    • 2018-07-18
    • 1970-01-01
    • 1970-01-01
    • 2019-12-21
    • 2014-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多