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.