终止 bash 命令当找到指定行。

huangapple go评论60阅读模式
英文:

Kill bash command when line is found

问题

我希望在输出中找到某个字符串时杀死一个bash命令。

为了澄清,我希望解决方案类似于timeout命令:

timeout 10s looping_program.sh

这将执行脚本:looping_program.sh并在执行10秒后终止脚本。

相反,我想要类似于:

regexout "^Success$" looping_program.sh

这将执行脚本,直到在程序的stdout中匹配到只有Success的一行。请注意,我假设这个looping_program.sh不会因为某种原因在输出Success的同时退出,所以如果我不关心之后发生的事情,等待程序退出将浪费时间。

因此,类似于:

bash -e looping_program.sh > /tmp/output &
PID="$(ps aux | grep looping_program.sh | head -1 | tr -s ' ' | cut -f 2 -d ' ')"
echo $PID
while :; do
  echo "$(tail -1 /tmp/output)"
  if [[ "$(tail -1 /tmp/output)" == "Success" ]]; then
    kill $PID
    exit 0
  fi
  sleep 1
done

其中looping_program.sh是这样的:

echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
echo "Success"
sleep 1;
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;

但这不够健壮(使用了一个临时文件...可能会杀死其他程序...),我希望它只是一个命令。是否存在类似的东西?如果没有,我可能会编写一个C程序来完成它。

附言:我提供了我的代码作为我希望程序执行的示例。它并没有采用良好的编程实践。其他评论者的注意事项:

@KamilCuk 不要使用临时文件。使用一个fifo。

@pjh 请注意,任何涉及在shell代码中使用PID和kill的方法都有可能杀死错误的进程。只在绝对必要的情况下在shell程序中使用kill。

其他用户还提出了更多建议,我只是想确保没有人看到这个问题,然后认为模仿他们的代码是个好主意。

英文:

I want to kill a bash command when I found some string in the output.

To clarify, I want the solution to be similar to a timeout command:

timeout 10s looping_program.sh

Which will execute the script: looping_program.sh and kill the script after 10 seconds of execute.

Instead I want something like:

regexout "^Success$" looping_program.sh

Which will execute the script until it matches a line that just says Success in the stdout of the program.
Note that I'm assuming that this looping_program.sh does not exit at the same time it outputs Success for whatever reason, so simply waiting for the program to exit would waste time if I don't care about what happens after that.

So something like:

bash -e looping_program.sh > /tmp/output &
PID="$(ps aux | grep looping_program.sh | head -1 | tr -s ' ' | cut -f 2 -d ' ')"
echo $PID
while :; do
  echo "$(tail -1 /tmp/output)"
  if [[ "$(tail -1 /tmp/output)" == "Success" ]]; then
    kill $PID
    exit 0
  fi
  sleep 1
done

Where looping_program.sh is something like:

echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
echo "Success"
sleep 1;
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;

But that is not very robust (uses a single tmp file... might kill other programs...) and I want it to just be one command. Does something like this exist? I may just write a c program to do it if not.

P.S.: I provided my code as an example of what I wanted the program to do. It does not use good programming practices. Notes from other commenters:

@KamilCuk Do not use temporary file. Use a fifo.

@pjh Note that any approach that involves using kill with a PID in shell code runs the risk of killing the wrong process. Use kill in shell programs only when it is absolutely necessary.

There are more suggestions below from other users, I just wanted to make sure no one came across this and thought it would be good to model their code after.

答案1

得分: 2

looping_program() {
    for i in 1 2 3; do echo $i; sleep 1; done
    echo Success
    yes
}
coproc looping_program
while IFS= read -r line; do
    if [[ "$line" =~ Success ]]; then
        break
    fi
done <&${COPROC[0]}
exec {COPROC[0]}>&- {COPROC[1]}>&-
kill ${COPROC_PID}
wait ${COPROC_PID}
英文:
looping_program() {
   for i in 1 2 3; do echo $i; sleep 1; done
   echo Success
   yes
}
coproc looping_program
while IFS= read -r line; do
   if [[ &quot;$line&quot; =~ Success ]]; then
      break
   fi
done &lt;&amp;${COPROC[0]}
exec {COPROC[0]}&gt;&amp;- {COPROC[1]}&gt;&amp;-
kill ${COPROC_PID}
wait ${COPROC_PID}

Notes:

  • Do not use temporary file. Use a fifo.
  • Do not use tail -n1 to read last line. Read from the stream in a loop.
  • Do not repeat tail -1 twice. Cache the result.
  • Wait for pid after killing to synchronize.
  • When you're using a coprocess, use COPROC_PID to get the PID
  • When you're not using a coprocess, use $! to get the PID of a background process started from the current shell.
  • When you can't use $! (because the process you're trying to get a PID of was not spawned in the background as a direct child of the current shell), do not use ps aux | grep to get the pid. Use pgrep.
  • Do not use echo $(stuff). Just run the stuff, no echo.

答案2

得分: 1

#!/usr/bin/env -S expect -f
set timeout -1
spawn ./looping_program.sh
expect "Success"
send -- "\x03"
expect eof

Call it looping_killer:

$ ./looping_killer
spawn ./looping_program.sh
Fail
Fail
Fail
Success
^C

To pass the program and pattern:

./looping_killer some_program "some pattern"

You'd change the expect script to

#!/usr/bin/env -S expect -f
set timeout -1
spawn [lindex $argv 0]
expect -- [lindex $argv 1]
send -- "\x03"
expect eof
英文:

With [tag:expect]

#!/usr/bin/env -S expect -f
set timeout -1
spawn ./looping_program.sh
expect &quot;Success&quot;
send -- &quot;\x03&quot;
expect eof

Call it looping_killer:

$ ./looping_killer
spawn ./looping_program.sh
Fail
Fail
Fail
Success
^C

To pass the program and pattern:

./looping_killer some_program &quot;some pattern&quot;

You'd change the expect script to

#!/usr/bin/env -S expect -f
set timeout -1
spawn [lindex $argv 0]
expect -- [lindex $argv 1]
send -- &quot;\x03&quot;
expect eof

答案3

得分: 0

Assuming that your looping program exists when it tries to write to a broken pipe, this will print all output up to and including the 'Success' line and then exit:

./looping_program | sed '/^Success$/q';

You may need to disable buffering of the looping program output. See Force line-buffering of stdout in a pipeline and How to make output of any shell command unbuffered? for ways to do it.
See Should I save my scripts with the .sh extension? and Erlkonig: Commandname Extensions Considered Harmful for reasons why I dropped the '.sh' suffix.

Note that any approach that involves using kill with a PID in shell code runs the risk of killing the wrong process. Use kill in shell programs only when it is absolutely necessary.

英文:

Assuming that your looping program exists when it tries to write to a broken pipe, this will print all output up to and including the 'Success' line and then exit:

./looping_program | sed &#39;/^Success$/q&#39;

Note that any approach that involves using kill with a PID in shell code runs the risk of killing the wrong process. Use kill in shell programs only when it is absolutely necessary.

huangapple
  • 本文由 发表于 2023年2月16日 02:11:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/75463871.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定