ORing con true in un comando su ssh


15

Quando provo a eseguire in pkill -fremoto tramite ssh e provo a scartare il possibile codice di errore (per continuare con il resto del mio script anche se non viene trovato alcun processo), || truenon si comporta come mi aspetto.

$ 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

Suppongo che sia ssh che restituisca 255, non il comando tra virgolette, ma perché?

Risposte:


29

La tua supposizione che sia esso sshstesso a restituire lo stato di uscita 255 è corretta. La sshpagina man afferma che:

ssh esce con lo stato di uscita del comando remoto o con 255 se si è verificato un errore.

Se dovessi semplicemente eseguire ssh pi@10.20.0.10 "pkill -f asdf", molto probabilmente otterrai uno stato di uscita 1, corrispondente allo pkillstato di " Nessun processo abbinato ".

La parte difficile è capire perché si verifica un errore con SSH durante l'esecuzione

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

Comandi remoti SSH

Il server SSH avvia una shell per eseguire comandi remoti. Ecco un esempio di questo in azione:

$ 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

Si noti che la shell predefinita è bashe che il comando remoto non è un semplice comando ma una pipeline , "una sequenza di uno o più comandi separati dall'operatore di controllo |".

La shell Bash è abbastanza intelligente da rendersi conto che se il comando che gli viene passato -cdall'opzione è un comando semplice , può essere ottimizzato non in realtà un fork di un nuovo processo, ovvero è direttamente execil comando semplice invece di passare attraverso il passaggio aggiuntivo di forking prima che execsia. Ecco un esempio di ciò che accade quando si esegue un comando remoto semplice ( ps -elfin questo 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

Mi sono imbattuto in questo comportamento prima ma non sono riuscito a trovare un riferimento migliore diverso da questa risposta AskUbuntu .

comportamento pkill

Dal momento che pkill -f asdf || truenon è un semplice comando (è un elenco di comandi ), l'ottimizzazione di cui sopra non può avvenire, quindi quando si esegue ssh pi@10.20.0.10 "pkill -f asdf || true", il sshdprocesso si biforca e si esegue bash -c "pkill -f asdf || true".

Come sottolinea la risposta di ctx, pkillnon ucciderà il suo stesso processo. Tuttavia, sarà uccidere qualsiasi altro processo la cui linea di comando corrisponde al -fmodello. Il bash -ccomando corrisponde a questo modello in modo da uccidere questo processo - il suo genitore (mentre succede).

Il server SSH vede quindi che il processo di shell avviato per eseguire i comandi remoti è stato interrotto in modo imprevisto, quindi segnala un errore al client SSH.


1
Mentre la risposta identifica correttamente l'origine di un problema in quanto pkilluccide il suo processo di shell genitore perché la sua lista arg corrisponde alla regexp, solleverò un'obiezione terminologica: nonx || y è un comando composto , è un elenco di comandi .
Stéphane Chazelas,

@ StéphaneChazelas Grazie per il feedback. Avevo seguito l'inclusione di costrutti condizionali da parte di Bash come comandi composti, ma sono d'accordo che è logicamente più coerente considerare x||ycome un elenco di comandi. Ora ho modificato la mia risposta per includere collegamenti alle varie definizioni POSIX.
Anthony G - giustizia per Monica il

1
Si noti che nel caso generale, non è così tanto che non può essere ottimizzato perché è un elenco di comandi che ha un altro comando da eseguire (potenzialmente). In zsh/ ksh93/ FreeBSD sh, false || pkill -f asdfsarebbe stato pkilleseguito nel processo di shell. bashesegue l'ottimizzazione solo quando c'è un solo comando semplice. true; pkill -f asdfsarebbe anche un problema.
Stéphane Chazelas,

9

Il tuo comando a distanza si uccide:

$ 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 e pkill ignoreranno il loro processo, ma con il flag -f, troveranno la shell genitore:

$ 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

Ha senso! bash -c 'pgrep -af asdf'(senza il || true) non si trova. Perchè no? Ha -f.
Gauthier,

2
@Gauthier In realtà, penso in questo caso, Bash è abbastanza intelligente da rendersi conto che il comando è semplice (non un comando composto), quindi si ottimizza non eseguendo effettivamente un nuovo processo. Ricordo di aver riscontrato comportamenti simili prima (devo aggiornare la mia risposta).
Anthony G - giustizia per Monica,

3

Chiedi a pkill di uccidere tutto ciò che corrisponde a "asdf". Dovresti dirlo per abbinare [a] sdf, in questo modo cercherà comunque qualsiasi cosa chiamata "asdf", ma non vedrà se stesso (se allinei asdf con [a] sdf, nota che s è allineato con] e non s.)

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

È un trucco comune usato anche 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

Questo trucco è vecchio e l'ho visto decenni fa nella FAQ di Unix (che è ancora una buona lettura!)

Per "automatizzarlo", non è facile, ma di solito ogni volta che hai bisogno di grep per una stringa variabile regexp = "qualcosa", puoi provare a fare:

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" ...

nota: sono consapevole che il mio esempio grep "fail", si sarebbe potuto mantenere la regexp così com'è, poiché non corrisponderà già a se stessa (a, b o c non corrisponderanno a "]" della riga di comando) . Ma non è banale elaborare un test della regexp. In generale, il trucco funziona. quello che automatizza funzionerà per la maggior parte del tempo. Altrimenti, saranno necessari alcuni hack intelligenti (o interventi manuali).
Olivier Dulac,

Inoltre, (abc)?(def)?dovrà essere ([a]bc)?([d]ef)?... Non puoi analizzare regex con regex ?! > :-)
wizzwizz4,

@ wizzwizz4 Lo so. ma il tuo esempio già non coinciderà con se stesso. questa è una cosa complessa, ho appena fornito una soluzione semplice per casi più semplici
Olivier Dulac,

@ wizzwizz4 Lo dico già nel mio primo commento ...
Olivier Dulac
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.