ORing con verdadero en un comando sobre ssh

15

Cuando intento ejecutar de pkill -fforma remota a través de ssh, y trato de descartar el posible código de error (para continuar con el resto de mi script, incluso si no se encuentra ningún proceso), || trueno se comporta como esperaba.

$ pkill asdf || true
$ echo $?
0
$ pkill -f asdf || true
$ echo $?
0
$ ssh pi@10.20.0.10 "pkill asdf || true"
$ echo $?
0
$ ssh pi@10.20.0.10 "pkill -f asdf || true"
255

Supongo que es ssh que devuelve 255, no el comando entre comillas, pero ¿por qué?

Gauthier
fuente

Respuestas:

29

Su suposición de que es en sshsí misma la que devuelve el estado de salida 255 es correcta. La sshpágina del manual dice que:

ssh sale con el estado de salida del comando remoto o con 255 si se produjo un error.

Si simplemente ejecutara ssh [email protected] "pkill -f asdf", lo más probable es que obtenga un estado de salida de 1, correspondiente al pkillestado de " No hay procesos coincidentes ".

La parte difícil es entender por qué ocurre un error con SSH cuando ejecuta

ssh pi@10.20.0.10 "pkill -f asdf || true"

Comandos remotos SSH

El servidor SSH lanza un shell para ejecutar comandos remotos. Aquí hay un ejemplo de esto en acción:

$ 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

Tenga en cuenta que el shell predeterminado es bashy que el comando remoto no es un comando simple sino una tubería , "una secuencia de uno o más comandos separados por el operador de control |".

El shell Bash es lo suficientemente inteligente como para darse cuenta de que si el comando que le pasa la -copción es un comando simple , puede optimizar al no bifurcar un nuevo proceso, es decir, es directamente execel comando simple en lugar de pasar por el paso adicional de forking antes de execs. Aquí hay un ejemplo de lo que sucede cuando ejecuta un comando simple remoto ( ps -elfen este caso):

$ 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

Me he encontrado con este comportamiento antes, pero no pude encontrar una referencia mejor que esta respuesta de AskUbuntu .

comportamiento de pkill

Como pkill -f asdf || trueno es un comando simple (es una lista de comandos ), la optimización anterior no puede ocurrir, por lo tanto, cuando ejecuta ssh [email protected] "pkill -f asdf || true", el sshdproceso se bifurca y ejecuta bash -c "pkill -f asdf || true".

Como señala la respuesta de ctx, pkillno matará su propio proceso. Sin embargo, será matar a cualquier otro proceso cuya línea de comandos coincide con el -fpatrón. El bash -ccomando coincide con este patrón, por lo que mata este proceso, su propio padre (como sucede).

El servidor SSH luego ve que el proceso de shell que inició para ejecutar los comandos remotos se cerró inesperadamente, por lo que informa un error al cliente SSH.

Anthony G - justicia para Monica
fuente
1
Si bien la respuesta identifica correctamente la fuente de un problema, ya que pkillmata su proceso de shell principal porque su lista de argumentos coincide con la expresión regular, plantearé una objeción de terminología: nox || y es un comando compuesto , es una lista de comandos .
Stéphane Chazelas
@ StéphaneChazelas Gracias por los comentarios. Había estado siguiendo la inclusión de Bash de construcciones condicionales como comandos compuestos, pero estoy de acuerdo en que es lógicamente más coherente considerarlo x||ycomo una lista de comandos. Ahora he editado mi respuesta para incluir enlaces a las diversas definiciones POSIX.
Anthony G - justicia para Monica
1
Tenga en cuenta que en el caso general, no es tanto que no pueda optimizar porque es una lista de comandos que todavía tiene otro comando para ejecutar (potencialmente). En zsh/ ksh93/ FreeBSD sh, false || pkill -f asdfse habría pkillejecutado en el proceso de shell. bashsolo realiza la optimización cuando solo hay un comando simple. true; pkill -f asdfTambién sería un problema.
Stéphane Chazelas
9

Su comando remoto se suicida:

$ 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 y pkill ignorarán su propio proceso, pero con el indicador -f, encontrarán el shell principal:

$ 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
ctx
fuente
¡Eso tiene sentido! bash -c 'pgrep -af asdf'(sin el || true) no se encuentra. Por qué no? Tiene -f.
Gauthier
2
@Gauthier En realidad, creo que en este caso, Bash es lo suficientemente inteligente como para darse cuenta de que el comando es simple (no un comando compuesto), por lo que se optimiza al no bifurcar un nuevo proceso. Recuerdo haber encontrado un comportamiento similar antes (debo actualizar mi respuesta).
Anthony G - justicia para Monica
3

Le pides a pkill que mate cualquier cosa que coincida con "asdf". Debería decirle que coincida con [a] sdf, de esa manera seguirá buscando algo llamado "asdf", pero no se verá a sí mismo (si alinea asdf con [a] sdf, observe que la s está alineada con] y no s.)

ssh 10.0.3.70 'pgrep -af "[a]sdf" || true'

Es un truco común que también se usa con 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

Este truco es antiguo, y lo vi hace décadas en las preguntas frecuentes de Unix (¡que sigue siendo una buena lectura!)

Para "automatizarlo", no es fácil, pero generalmente cada vez que necesita grep para una cadena variable regexp = "algo", puede intentar hacer:

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" ...
Olivier Dulac
fuente
nota: Soy consciente de que mi ejemplo de "error" grep, uno podría haber mantenido la expresión regular tal como está, ya que ya no coincidirá (a, b o c no coincidirá con el ']' de la línea de comandos) . Pero no es trivial proponer una prueba de expresión regular. En general, el truco funciona. el que automatiza funcionará la mayor parte del tiempo. Cuando no, se necesitarán algunos trucos inteligentes (o intervención manual).
Olivier Dulac
Además, (abc)?(def)?tendrá que ser ([a]bc)?([d]ef)?... ¡¿No puedes analizar regex con regex ?! > :-)
wizzwizz4
@ wizzwizz4 Lo sé. pero su ejemplo ya no coincidirá solo. esto es algo complejo, solo proporcioné una solución simple para casos más simples
Olivier Dulac, el
@ wizzwizz4 Ya lo digo en mi primer comentario ...
Olivier Dulac