更新:
我已将脚本中的部分更改grep $1
为grep '$1'
(当我试图表示时),这次我得到了grep "$1"
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
消息(而不是Terminated: 15
消息)。我不明白发生了什么事。
问题:
我写了一个简单的 shell 脚本,名为mykill
.
mykill
:
#!/bin/sh
kill `ps -A | grep $1 | grep -v 'grep' | grep -Eom 1 '^[0-9]+'`
然而,有一个奇怪的行为。当我写下这一行时:
kill `ps -A | grep process_name | grep -v 'grep' | grep -Eom 1 '^[0-9]+'`
手动在 bash 上,如果没有任何输出ps -A | grep process_name
,我会得到以下内容:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
如果出现问题,命令会正确执行并静默终止。
现在,如果我通过执行文件来运行脚本mykill
,如果 的输出出现某些内容ps -A | grep process_name
,则脚本会正确执行并静默终止,这与手动执行命令的行为相同。
但是,如果 的输出没有出现任何内容ps -A | grep process_name
,我就不会收到有关使用kill 命令的消息。相反,我得到:
Terminated: 15
我还检查了返回代码。在我尝试通过在 shell 上手动写入命令来终止不存在的进程后,echo $?
返回1
。但是,在我尝试通过调用脚本来终止不存在的进程后,echo $?
返回143
。
这里发生了什么?为什么我在通过手动在 shell 上编写的方式执行相同的命令时与在 shell 脚本中执行它时观察到不同的行为?
注意:sh
我的工作 shell 都是bash
.
奖励:我的 shell 脚本能否以更有效和/或更优雅的方式编写,仅使用POSIX 实用程序?如果是这样,怎么办?
答案1
可移植性说明。的输出格式ps -A
是POSIX 未指定对于不符合 Unix 的系统(如 FreeBSD)(您会注意到输出格式部分和选项的描述-f
都被标记为XSI在规范中),所以你不能真正可靠地对其进行后处理。
例如,对于Linux 上的ps
from procps
,它将输出PID TTY TIME CMD
columns(其中CMD
是进程名称,而不是命令参数),而在 FreeBSD 上它会输出PID TT STAT TIME COMMAND
(其中COMMAND
是参数)。
鉴于您的使用情况grep -v grep
,我想您期望后者或至少ps -A
输出进程执行的命令的参数,而不仅仅是进程名称(通常源自最后一个执行命令或第一个(第0个)参数)。
如果您grep
只想grep
使用命令参数,则应该使用:
ps -A -o pid= -o args=
其输出由 POSIX 指定。
现在,你的问题是它mykill
正在自杀,因为mykill foo
匹配foo
.
另一个问题是它mykill grep
不会杀死任何东西。
在这里,你可以这样做:
#! /bin/sh -
PATTERN=${1?} export PATTERN
trap '' TERM # ignore SIGTERM for the shell and its children
ps -A -o pid= -o args= | awk '$0 ~ ENVIRON["PATTERN"] {
system("kill " $1); exit}'
(请注意,POSIX 不指定 POSIX 实用程序的路径,sh
也不指定 she-bang 机制,因此/bin/sh
可能不是 POSIX shell。但实际上,大多数 POSIX 系统都支持 she-bang,并且它可以是/bin/sh
POSIXsh
或 Bournesh
并且上面的代码应该适用于两者)。
尽管这并不理想,因为即使没有找到进程,它也总是返回 true (0) 退出状态。更好的方法是:
#! /bin/sh -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
ps -A -o pid= -o args= | grep -e "$pattern" | {
read pid args && kill "$pid"
}
grep -m 1
在这两种情况下,我们只会按照您的方法建议的方式终止第一个匹配进程。
现在,trap '' SIGTERM
我们确保我们的进程没有被杀死,如果我们要杀死它就可以了全部匹配的进程,但因为在这里,我们只杀死第一个匹配的进程,问题是第一个很可能是正在运行的进程mykill pattern
或grep pattern
。
grep -ve grep -e mykill
您可以尝试比较匹配进程的进程 ID,而不是添加一些进程(这并不是万无一失的,因为它可能会排除比预期更多的进程)。
#! /bin/sh -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
# just in case
psoutput=$(exec ps -A -o pid= -o ppid= -o args=)
printf '%s\n' "$psoutput" | grep -e "$pattern" | {
while read -r pid ppid args; do
if [ "$pid" -ne "$$" ] && [ "$ppid" -ne "$$" ]; then
kill "$pid"
exit # with the exit status of kill above
fi
done
exit 1 # not found
}
(请注意,$(...)
和read -r
是 POSIX,但不是 Bourne)。
或者使用ksh93
, bash
, zsh
or yash
(都不是 POSIX 命令),这是一个具有内置正则表达式匹配的 shell:
#! /bin/bash -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
# just in case
psoutput=$(exec ps -A -o pid= -o ppid= -o args=)
printf '%s\n' "$psoutput" | {
while read -r pid ppid args; do
if ((pid != $$ && ppid != $$)) && [[ $args =~ $pattern ]]; then
kill "$pid"
exit # with the exit status of kill above
fi
done
exit 1 # not found
}