ORing com true em um comando sobre ssh

15

Quando tento executar pkill -fremotamente via ssh e tento descartar o possível código de erro (para continuar com o restante do meu script mesmo que nenhum processo seja encontrado), || truenão se comporta como o esperado.

$ 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

Suponho que é ssh que retorna 255, não o comando entre aspas, mas por quê?

Gauthier
fonte

Respostas:

29

Sua suposição de que é ela sshmesma que retorna o status de saída 255 está correta. A sshpágina do manual afirma que:

O ssh sai com o status de saída do comando remoto ou com 255 se ocorreu um erro.

Se você simplesmente executasse ssh [email protected] "pkill -f asdf", provavelmente obteria um status de saída 1correspondente ao pkillstatus de " Nenhum processo correspondido ".

A parte desafiadora é entender por que ocorre um erro no SSH quando você executa

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

Comandos remotos SSH

O servidor SSH inicia um shell para executar comandos remotos. Aqui está um exemplo disso em ação:

$ 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

Observe que o shell padrão é bashe que o comando remoto não é um comando simples, mas um pipeline , “uma sequência de um ou mais comandos separados pelo operador de controle |”.

O shell Bash é inteligente o suficiente para perceber que, se o comando que está sendo passado pela -copção for um comando simples , ele pode otimizar não sendo realmente bifurcado em um novo processo, ou seja, é diretamente execo comando simples em vez de passar pela etapa extra de forking antes que ele execé. Aqui está um exemplo do que acontece quando você executa um comando simples remoto ( ps -elfneste 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

Já deparei com esse comportamento antes, mas não consegui encontrar uma referência melhor além desta resposta do AskUbuntu .

comportamento pkill

Como pkill -f asdf || truenão é um comando simples (é uma lista de comandos ), a otimização acima pode não ocorrer; portanto, quando você executa ssh [email protected] "pkill -f asdf || true", o sshdprocesso bifurca e executa bash -c "pkill -f asdf || true".

Como a resposta da ctx aponta, pkillnão matará seu próprio processo. No entanto, ele vai matar qualquer outro processo cuja linha de comando corresponde ao -fpadrão. O bash -ccomando corresponde a esse padrão e mata esse processo - seu próprio pai (por acaso).

O servidor SSH vê, então, que o processo do shell iniciado para executar os comandos remotos foi interrompido inesperadamente e, portanto, relata um erro ao cliente SSH.

Anthony G - justiça para Monica
fonte
1
Enquanto a resposta identifica corretamente a origem de um problema, pois pkillmata seu processo de shell pai porque sua lista arg corresponde ao regexp, levantarei uma objeção terminológica: nãox || y é um comando composto , é uma lista de comandos .
Stéphane Chazelas
@ StéphaneChazelas Obrigado pelo feedback. Eu estava passando pela inclusão de construções condicionais de Bash como comandos compostos, mas concordo que é mais logicamente coerente considerar x||yuma lista de comandos. Agora editei minha resposta para incluir links para as várias definições de POSIX.
Anthony G - justiça para Monica
1
Observe que, no caso geral, não é muito difícil otimizar, porque é uma lista de comandos que ainda tem outro comando para executar (potencialmente). No zsh/ ksh93/ FreeBSD sh, false || pkill -f asdfteria sido pkillexecutado no processo do shell. bashsó faz a otimização quando há apenas um comando simples. true; pkill -f asdftambém seria um problema.
Stéphane Chazelas
9

Seu comando remoto se mata:

$ ssh 10.0.3.70 'pgrep -af asdf'
$ ssh 10.0.3.70 'pgrep -af asdf || true'
1018 bash -c pgrep -af asdf || true

O pgrep e o pkill ignoram seu próprio processo, mas com o sinalizador -f, eles encontrarão o shell pai:

$ 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
fonte
Isso faz sentido! bash -c 'pgrep -af asdf'(sem o || true) não se encontra. Por que não? Tem -f.
Gauthier
2
@ Gauthier Na verdade, acho que, neste caso, o Bash é inteligente o suficiente para perceber que o comando é simples (não um comando composto), então otimiza por não forjar um novo processo. Lembro-me de ter encontrado um comportamento semelhante antes (devo atualizar minha resposta).
Anthony G - justiça para Monica
3

Você pede ao pkill para matar qualquer coisa que corresponda a "asdf". Você deve dizer para ele corresponder a [a] sdf, para que ainda procure por qualquer coisa chamada "asdf", mas não se veja (se você alinhar asdf com [a] sdf, observe que s está alinhado com] e não s.)

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

É um truque comum também usado com 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

Esse truque é antigo, e eu o vi décadas atrás no FAQ do Unix (que ainda é uma boa leitura!)

Para "automatizá-lo", não é fácil, mas geralmente toda vez que você precisa grep para uma sequência variável regexp = "something", você pode tentar fazer:

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
fonte
note: Estou ciente de que meu exemplo de falha 'grep' pode ter mantido a regexp como está, pois ela já não corresponde a si mesma (o a, b ou c não corresponde ao ']' da linha de comando) . Mas não é trivial apresentar um teste do regexp. Em geral, o truque funciona. aquele que automatiza funcionará a maior parte do tempo. Caso contrário, serão necessários alguns hacks inteligentes (ou intervenção manual).
precisa
Além disso, (abc)?(def)?terá que ser ([a]bc)?([d]ef)?... Você não pode analisar regex com regex ?! > :
wizzwizz4
@ wizzwizz4 eu sei. mas seu exemplo já não será compatível. isso é uma coisa complexa, eu só forneceu uma solução simples para casos mais simples
Olivier Dulac
@ wizzwizz4 Eu já disse isso na minha primeira comentário ...
Olivier Dulac