当我尝试pkill -f
通过 ssh 远程运行,并尝试丢弃可能的错误代码(即使未找到进程,仍继续执行脚本的其余部分)时,|| true
其行为并不符合我的预期。
$ pkill asdf || true
$ echo $?
0
$ pkill -f asdf || true
$ echo $?
0
$ ssh [email protected] "pkill asdf || true"
$ echo $?
0
$ ssh [email protected] "pkill -f asdf || true"
255
我想是这样的SSH返回 255,而不是引号之间的命令,但为什么呢?
答案1
您认为它ssh
本身返回 255 退出状态的假设是正确的。手册ssh
页指出:
ssh 以远程命令的退出状态退出,如果发生错误,则以 255 退出。
如果您只是运行,您很可能会得到退出状态,对应于“的状态ssh [email protected] "pkill -f asdf"
1
pkill
没有匹配的进程”。
具有挑战性的部分是理解为什么运行时 SSH 发生错误
ssh [email protected] "pkill -f asdf || true"
SSH 远程命令
SSH 服务器启动 shell 来运行远程命令。这是一个实际的例子:
$ ssh server "ps -elf | tail -5"
4 S root 35323 1024 12 80 0 - 43170 poll_s 12:01 ? 00:00:00 sshd: anthony [priv]
5 S anthony 35329 35323 0 80 0 - 43170 poll_s 12:01 ? 00:00:00 sshd: anthony@notty
0 S anthony 35330 35329 0 80 0 - 28283 do_wai 12:01 ? 00:00:00 bash -c ps -elf | tail -5
0 R anthony 35341 35330 0 80 0 - 40340 - 12:01 ? 00:00:00 ps -elf
0 S anthony 35342 35330 0 80 0 - 26985 pipe_w 12:01 ? 00:00:00 tail -5
请注意,默认 shell 是bash
,并且远程命令不是一个简单的命令,而是一个管道,“由控制操作符分隔的一个或多个命令的序列|
”。
Bash shell 足够聪明,能够意识到如果选项传递给它的命令-c
是简单的命令,它可以通过不实际分叉一个新进程来进行优化,即,它直接执行简单命令,而不是在执行之前exec
执行额外的 ing 步骤。以下是运行远程简单命令时发生的情况的示例(在本例中):fork
exec
ps -elf
$ ssh server "ps -elf" | tail -5
1 S root 34740 2 0 80 0 - 0 worker 11:49 ? 00:00:00 [kworker/0:1]
1 S root 34762 2 0 80 0 - 0 worker 11:50 ? 00:00:00 [kworker/0:3]
4 S root 34824 1024 31 80 0 - 43170 poll_s 11:51 ? 00:00:00 sshd: anthony [priv]
5 S anthony 34829 34824 0 80 0 - 43170 poll_s 11:51 ? 00:00:00 sshd: anthony@notty
0 R anthony 34830 34829 0 80 0 - 40340 - 11:51 ? 00:00:00 ps -elf
我以前遇到过这种行为,但除了这个 AskUbuntu 答案。
删除行为
因为pkill -f asdf || true
这不是一个简单的命令(它是一个命令列表),上述优化不会发生,因此当您运行时,进程会分叉并执行。ssh [email protected] "pkill -f asdf || true"
sshd
bash -c "pkill -f asdf || true"
正如 ctx 的回答所指出的,pkill
不会杀死自己的进程。然而,它将要杀死命令行与该-f
模式匹配的任何其他进程。该bash -c
命令与此模式匹配,因此它会杀死此进程 - 它自己的父进程(正如它所发生的那样)。
然后,SSH 服务器发现它为运行远程命令而启动的 shell 进程被意外终止,因此它向 SSH 客户端报告错误。
答案2
您的远程命令会自行终止:
$ ssh 10.0.3.70 'pgrep -af asdf'
$ ssh 10.0.3.70 'pgrep -af asdf || true'
1018 bash -c pgrep -af asdf || true
pgrep 和 pkill 将忽略它们自己的进程,但使用 -f 标志,它们将找到父 shell:
$ pgrep -af asdf
$ pgrep -af asdf || true
$ bash -c 'pgrep -af asdf'
$ bash -c 'pgrep -af asdf || true'
9803 bash -c pgrep -af asdf || true
答案3
您要求 pkill 杀死任何与“asdf”匹配的内容。您应该告诉它匹配 [a]sdf,这样它仍然会查找名为“asdf”的任何内容,但不会看到自身(如果将 asdf 与 [a]sdf 对齐,请注意 s 与 ] 对齐并且不是。)
ssh 10.0.3.70 'pgrep -af "[a]sdf" || true'
这是 grep/egrep/awk/etc 中也使用的常见技巧:
ps -ef | grep "something" # will sometimes match itself too
ps -ef | grep "[s]omething" # will not match itself
# why it works:
# the commandline contains: ps -ef | grep [s]omething
# and grep tries to find: something
这个技巧很老了,我几十年前在 Unix 常见问题解答中看到过它(仍然值得一读!)
要“自动化”它并不容易,但通常每次需要 grep 查找变量字符串 regexp="something" 时,您可以尝试执行以下操作:
grep "$(echo "${regexp}" | LC_ALL='C' sed -e 's/[a-zA-Z0-9_-]/[&]/')"
# if regexp="something", it does: grep "[s]omething"
# if regexp="otherthing", it does: grep "[o]therthing"
# if regexp="^thirdthing", it does: grep "^[t]hirdthing" #ok, kept the "^"
#BUT fails on : regexp="[abc]def", as it does: grep "[[a]bc]def" instead of grep "[abc][d]ef" ...