【问题标题】:How to evaluate results of pipes in a bash script如何在 bash 脚本中评估管道的结果
【发布时间】:2020-12-30 12:45:28
【问题描述】:

我需要帮助解决以下问题: 我想杀死一个程序的所有实例,比如说 xpdf。 在提示符下,以下工作按预期工作:

$ ps -e | grep xpdf | sed -n -e "s/^[^0-9]*\([0-9]*\)[^0-9]\{1,\}.*$/\1/p" | xargs kill -SIGTERM

(需要 sed 步骤来提取 PID)。

但是,可能存在没有运行 xpdf 进程的情况。然后很难将该行嵌入到脚本中,因为它会在它之后立即中止,并带有来自 kill 的消息。我该怎么办?

我尝试过(在脚本中)

#!/bin/bash
#
set -x
test=""
echo "test = < $test >"
test=`ps -e | grep xpdf | sed -n -e "s/^[^0-9]*\([0-9]*\)[^0-9]\{1,\}.*$/\1/p"`
echo "test = < $test >"
if [ -z "$test" ]; then echo "xpdf läuft nicht";
else echo "$test" | xargs -d" " kill -SIGTERM
fi

运行上面的脚本时,我得到了

$ Kill_ps
+ test=
+ echo 'test = <  >'
test = <  >
++ ps -e
++ grep xpdf
++ sed -n -e 's/^[^0-9]*\([0-9]*\)[^0-9]\{1,\}.*$/\1/p'
+ test='21538
24654
24804
24805'
+ echo 'test = < 21538
24654
24804
24805 >'
test = < 21538
24654
24804
24805 >
+ '[' -z '21538
24654
24804
24805' ']'
+ xargs '-d ' kill -SIGTERM
+ echo '21538
24654
24804
24805'
kill: failed to parse argument: '21538
24654
24804
24805

一些意外发生:在测试中,PID 比进程多

在提示符处:

$ ps -e | grep xpd
21538 pts/3    00:00:00 xpdf.real
24654 pts/2    00:00:00 xpdf.real

再次运行脚本时,24* PID 发生变化。

所以这是我的问题:

  1. 额外的 PID 来自哪里?
  2. 如果没有我想杀死的进程正在运行(或者为什么 xargs 不接受 echo "$test" 作为输入),我可以做些什么来处理这种情况? (我希望我的脚本不会被中止)

提前致谢。

【问题讨论】:

  • xargs -r kill -SIGTERM? xargs -r--no-run-if-empty。顺便说一句,你为什么不改用killall/pkill?有什么并发症吗?
  • 另外一个 pid 来自您的 grep 命令,它的术语为 xpdf。如果您使用xargs -n 1 kill -TERM,您的脚本将起作用(不是最佳的)。

标签: bash pipe kill


【解决方案1】:

您可以使用pkill(1)killall(1) 而不是解析ps 输出。 (不建议为脚本解析人类可读的输出。This 就是一个例子。)

用法:

 pkill xpdf
 killall xpdf # Note that on solaris, it kills all the processes that you can kill. https://unix.stackexchange.com/q/252349#comment435182_252356
 pgrep xpdf # Lists pids
 ps -C xpdf -o pid=  # Lists pids

请注意,pkillkillallpgrep 等工具的性能类似于 ps -e | grep。因此,如果您执行pkill sh,它将尝试杀死shbashssh 等,因为它们都匹配模式。

但要回答有关在输入没有任何内容时跳过 xargs 运行的问题,您可以使用 (GNU) xargs-r 选项。

   -r, --no-run-if-empty
          If the standard input does not contain any nonblanks, do not run the command.
          Normally, the command is run once even if there is no input.
          This option is a GNU extension.

【讨论】:

  • 当我们将ls 放在一边时,不建议使用for i in $(cmd) 循环遍历可能包含空格的命令输出。但是cmd|while read -rawk 非常好,并且针对基于列的解析或CSV 输出进行了优化。我在这里写过:stackoverflow.com/a/19607361/2900196
  • 顺便说一句,我同意在这种情况下使用killallpkill
  • 而 POSIX xargs 不提供 --no-run-if-empty 扩展,所以我会保留自己的答案以防万一:)
  • 正如我在回答中所说,-r 是一个 GNU 扩展。 FWIW,我没有否决您的回答。所以,请不要删除它。
  • 在直接工具可用时避免解析。此外,cmd 的输出必须可靠且不可破解。例如对于ps,我不知道进程名称中的不可打印字符是否总是打印为?,或者它只是安装在我的系统上的ps。此外,我已经看到 ps 输出列在某些“非标准”类 unix 发行版上有所不同(咳嗽 vmware 咳嗽)。所以,我尽量不依赖这样的解析。
【解决方案2】:

此答案适用于 killallpkill(在 this answer 中建议)对您来说不够用的情况。例如,如果您真的想打印 "xpdf läuft nicht"(如果没有要杀死的 pid)或应用 kill -SIGTERM,因为您想确定发送给您的 pid 的信号或其他什么。

您可以使用 bash 循环来代替 xargssed。迭代 CSV/列输出非常简单:

count=0
while read -r uid pid ppid trash; do
  kill -SIGTERM $pid
  (( count++ ))
done < <(ps -ef|grep xpdf)
[[ $count -le 0 ]] && echo "xpdf läuft nicht"

使用pgrep 有一种更快的方法(上一个是为了说明如何使用 bash 迭代基于列的命令输出):

count=0
while read -r; do
  kill -SIGTERM $REPLY
  (( count++ ))
done < <(pgrep xpdf)
[[ $count -le 0 ]] && echo "xpdf läuft nicht"

如果您是xargs 的版本,可以为您提供--no-run-if-empty,您仍然可以将它与pgrep 一起使用(如this answer 中的建议),但它在BSD 或POSIX 版本上不可用。例如,我在 macOS 上的情况...

awk 也可以做到这一点(管道后只有一个命令):

ps -ef|awk 'BEGIN {c="0"} {if($0 ~ "xpdf" && !($0 ~ "awk")){c++; system("kill -SIGTERM "$2)}} END {if(c <= 0){print "xpdf läuft nicht"}}'

【讨论】:

  • @GIC 很高兴我能提供帮助。如果您的问题已解决,请毫不犹豫地选择您想要的答案并标记为已解决。
  • 一个小小的更正:while 打开一个子shell,所以在while循环之后 count =0(见link。所以,那部分不起作用
  • @GIC 是的,你完全正确。所以我用另一种方式更新了我的答案,在不打开子shell的情况下循环命令输出(使用while read)。如果您不需要保留计数,可以保留cmd|while read
猜你喜欢
  • 1970-01-01
  • 2018-05-07
  • 2017-03-25
  • 2012-08-08
  • 2016-01-28
  • 2011-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多