OU avec true dans une commande sur ssh

15

Lorsque j'essaie de m'exécuter à pkill -fdistance via ssh et que j'essaie de supprimer le code d'erreur possible (pour continuer avec le reste de mon script même si aucun processus n'est trouvé), || truene se comporte pas comme prévu.

$ 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

Je suppose que c'est ssh qui retourne 255, pas la commande entre guillemets, mais pourquoi?

Gauthier
la source

Réponses:

29

Votre supposition que c'est sshlui - même qui renvoie le statut de sortie 255 est correcte. La sshpage de manuel indique que:

ssh se ferme avec l'état de sortie de la commande à distance ou avec 255 en cas d'erreur.

Si vous deviez simplement exécuter ssh [email protected] "pkill -f asdf", vous obtiendriez très probablement un statut de sortie de 1, correspondant au pkillstatut « Aucun processus correspondant ».

La partie difficile est de comprendre pourquoi une erreur se produit avec SSH lorsque vous exécutez

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

Commandes à distance SSH

Le serveur SSH lance un shell pour exécuter des commandes à distance. Voici un exemple de ceci en action:

$ 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

Notez que le shell par défaut est bashet que la commande distante n'est pas une simple commande mais un pipeline , "une séquence d'une ou plusieurs commandes séparées par l'opérateur de contrôle |".

Le shell Bash est suffisamment intelligent pour se rendre compte que si la commande qui lui est passée par l' -coption est une commande simple , il peut optimiser en ne forçant pas réellement un nouveau processus, c'est-à-dire qu'il est directement execla commande simple au lieu de passer par l'étape supplémentaire d’ forking avant lui exec. Voici un exemple de ce qui se passe lorsque vous exécutez une commande simple à distance ( ps -elfdans ce cas):

$ 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'ai rencontré ce comportement auparavant, mais je n'ai pas pu trouver une meilleure référence autre que cette réponse AskUbuntu .

comportement pkill

Étant donné qu'il pkill -f asdf || truene s'agit pas d'une simple commande (c'est une liste de commandes ), l'optimisation ci-dessus ne peut pas se produire, donc lorsque vous exécutez ssh [email protected] "pkill -f asdf || true", le sshdprocessus se déroule et s'exécute bash -c "pkill -f asdf || true".

Comme le souligne la réponse de ctx, pkillne tuera pas son propre processus. Cependant, il va tuer tout autre processus dont la ligne de commande correspond à la -fconfiguration. La bash -ccommande correspond à ce modèle, donc elle tue ce processus - son propre parent (en l'occurrence).

Le serveur SSH voit alors que le processus shell qu'il a démarré pour exécuter les commandes à distance a été tué de manière inattendue, il signale donc une erreur au client SSH.

Anthony G - justice pour Monica
la source
1
Alors que la réponse identifie correctement la source d'un problème car il pkilltue son processus shell parent car sa liste d'arguments correspond à l'expression rationnelle, je soulèverai une objection de terminologie: ce x || yn'est pas une commande composée , c'est une liste de commandes .
Stéphane Chazelas
@ StéphaneChazelas Merci pour la rétroaction. J'étais allé par l'inclusion de Bash de constructions conditionnelles en tant que commandes composées mais je conviens qu'il est plus logiquement cohérent de considérer x||ycomme une liste de commandes. J'ai maintenant modifié ma réponse pour inclure des liens vers les différentes définitions POSIX.
Anthony G - justice pour Monica
1
Notez que dans le cas général, ce n'est pas tant qu'il ne peut pas être optimisé car c'est une liste de commandes qu'il a toujours une autre commande à exécuter (potentiellement). Dans zsh/ ksh93/ FreeBSD sh, false || pkill -f asdfaurait été pkillexécuté dans le processus shell. bashne fait l'optimisation que lorsqu'il n'y a qu'une seule commande simple. true; pkill -f asdfserait également un problème.
Stéphane Chazelas
9

Votre commande à distance se tue:

$ 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 et pkill ignoreront leur propre processus, mais avec le drapeau -f, ils trouveront le shell parent:

$ 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
la source
Ça a du sens! bash -c 'pgrep -af asdf'(sans le || true) ne se retrouve pas . Pourquoi pas? Oui -f.
Gauthier
2
@Gauthier En fait, je pense que dans ce cas, Bash est assez intelligent pour se rendre compte que la commande est simple (pas une commande composée) donc elle optimise en ne forçant pas réellement un nouveau processus. Je me souviens d'avoir rencontré un comportement similaire auparavant (je dois mettre à jour ma réponse).
Anthony G - justice pour Monica
3

Vous demandez à pkill de tuer tout ce qui correspond à "asdf". Vous devez lui dire de correspondre à [a] sdf, de cette façon, il cherchera toujours tout ce qui s'appelle "asdf", mais ne se verra pas (si vous alignez asdf avec [a] sdf, notez que le s est aligné avec] et pas l'art.)

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

C'est une astuce courante également utilisée avec 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

Cette astuce est ancienne, et je l'ai vue il y a des décennies dans la FAQ Unix (qui est toujours une bonne lecture!)

Pour "l'automatiser", ce n'est pas facile, mais généralement à chaque fois que vous devez rechercher une chaîne variable regexp = "quelque chose", vous pouvez essayer de faire:

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
la source
note: je suis conscient que mon exemple de grep 'échec', on aurait pu garder l'expression régulière telle qu'elle ne correspond pas déjà (les a, b ou c ne correspondront pas avec les ']' de la ligne de commande) . Mais il n'est pas anodin de proposer un test de l'expression rationnelle. En général, l'astuce fonctionne. celui qui automatise fonctionnera la plupart du temps. Sinon, des hacks intelligents (ou une intervention manuelle) seront nécessaires.
Olivier Dulac
Aussi, (abc)?(def)? devra être ([a]bc)?([d]ef)?... Vous ne pouvez pas analyser regex avec regex?! > :-)
wizzwizz4
@ wizzwizz4 je sais. mais votre exemple ne correspondra pas déjà. c'est une chose complexe, je viens de fournir une solution simple pour les cas plus simples
Olivier Dulac
@ wizzwizz4 Je le dis déjà dans mon premier commentaire ...
Olivier Dulac