ORing z prawdą w poleceniu nad ssh

15

Kiedy próbuję uruchomić pkill -fzdalnie przez ssh i próbuję odrzucić możliwy kod błędu (aby kontynuować z resztą skryptu, nawet jeśli nie zostanie znaleziony żaden proces), || truenie działa tak, jak się spodziewam.

$ 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

Przypuszczam, że to ssh zwraca 255, a nie polecenie między cudzysłowami, ale dlaczego?

Gauthier
źródło

Odpowiedzi:

29

Twoje przypuszczenie, że to on sshsam zwraca kod wyjścia 255, jest prawidłowe. ThesshStrona mężczyzna stwierdza, że:

ssh kończy pracę ze statusem wyjścia polecenia zdalnego lub z 255, jeśli wystąpił błąd.

Jeśli po prostu ssh [email protected] "pkill -f asdf"chcesz uruchomić , najprawdopodobniej uzyskasz status wyjścia 1odpowiadający pkillstatusowi „ Brak dopasowanych procesów ”.

Wyzwaniem jest zrozumienie, dlaczego podczas uruchamiania występuje błąd SSH

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

Zdalne polecenia SSH

Serwer SSH uruchamia powłokę w celu uruchomienia zdalnych poleceń. Oto przykład tego w akcji:

$ 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

Zauważ, że domyślną powłoką jest bashi że zdalne polecenie nie jest prostym poleceniem, lecz potokiem , „sekwencją jednego lub więcej poleceń oddzielonych przez operatora sterującego| ”.

Powłoka Bash jest na tyle sprytna, aby zdać sobie sprawę, że jeśli polecenie przekazane do niej przez -copcję jest prostym poleceniem , można je zoptymalizować, nie wykonując nowego procesu, tj. Bezpośrednio execjest to proste polecenie zamiast przejść przez dodatkowy krok z forking zanim execs. Oto przykład tego, co dzieje się po uruchomieniu zdalnej prostej komendy ( ps -elfw tym przypadku):

$ 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

Zetknąłem się z tym już wcześniej, ale nie mogłem znaleźć lepszego źródła niż ta odpowiedź AskUbuntu .

zachowanie pkill

Ponieważ pkill -f asdf || truenie jest to proste polecenia (jest to lista poleceń ), powyższe optymalizacja nie może nastąpić po uruchomieniu tak ssh [email protected] "pkill -f asdf || true", te sshdwidły procesowych i execsbash -c "pkill -f asdf || true" .

Jak wskazuje odpowiedź ctx, pkillnie zabije własnego procesu. Jednak to będzie zabić jakiegokolwiek innego procesu, którego linia poleceń pasuje do -fwzorca. bash -cKomenda pasuje ten wzorzec tak zabija ten proces - własnego rodzica (jak to się dzieje).

Serwer SSH następnie widzi, że proces powłoki, który uruchomił w celu uruchomienia poleceń zdalnych, został nieoczekiwanie zabity, więc zgłasza błąd klientowi SSH.

Anthony G - sprawiedliwość dla Moniki
źródło
1
Podczas gdy odpowiedź poprawnie identyfikuje źródło problemu jako pkillzabijającego proces powłoki nadrzędnej, ponieważ jego lista argumentów jest zgodna z wyrażeniem regularnym, podniosę zastrzeżenie terminologiczne: x || yto nie jest złożone polecenie , to lista poleceń .
Stéphane Chazelas
@ StéphaneChazelas Dzięki za opinie. Zastanawiałem się nad włączeniem przez Basha konstrukcji warunkowych jako poleceń złożonych, ale zgadzam się, że bardziej logicznie spójne jest rozpatrywanie ich x||yjako listy poleceń. Zmodyfikowałem teraz moją odpowiedź, aby uwzględnić łącza do różnych definicji POSIX.
Anthony G - sprawiedliwość dla Moniki
1
Zauważ, że w ogólnym przypadku nie jest to tak dużo, że nie można go zoptymalizować, ponieważ jest to lista poleceń, która ma jeszcze inne polecenie do uruchomienia (potencjalnie). W zsh/ ksh93/ FreeBSD sh, false || pkill -f asdfbyłby pkillwykonany w procesie powłoki. bashwykonuje optymalizację tylko wtedy, gdy jest tylko jedno proste polecenie. true; pkill -f asdfbyłby również problem.
Stéphane Chazelas,
9

Twoje zdalne polecenie zabija się:

$ 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 i pkill zignorują własny proces, ale przy opcji -f znajdą powłokę nadrzędną:

$ 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
źródło
To ma sens! bash -c 'pgrep -af asdf'(bez || true) nie znajdzie się. Dlaczego nie? Ma -f.
Gauthier
2
@Gauthier Właściwie myślę, że w tym przypadku Bash jest na tyle sprytny, by zdać sobie sprawę, że polecenie jest proste (nie polecenie złożone), więc optymalizuje się, nie rozprawiając się z nowym procesem. Pamiętam, że wcześniej spotkałem podobne zachowanie (muszę zaktualizować swoją odpowiedź).
Anthony G - sprawiedliwość dla Moniki
3

Pytasz pkill, aby zabił wszystko, co pasowało do „asdf”. Powinieneś powiedzieć mu, aby pasował do [a] sdf, w ten sposób będzie nadal szukał czegoś o nazwie „asdf”, ale nie zobaczy siebie (jeśli wyrównasz asdf z [a] sdf, zwróć uwagę, że s jest wyrównany z] i nie s.)

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

Jest to powszechna sztuczka stosowana również w 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

Ta sztuczka jest stara i widziałem ją kilkadziesiąt lat temu w Uniksowym FAQ (który wciąż jest dobrą lekturą!)

Aby go „zautomatyzować”, nie jest to łatwe, ale zwykle za każdym razem, gdy potrzebujesz grep dla zmiennej zmiennej regexp = "coś", możesz spróbować:

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
źródło
Uwaga: Zdaję sobie sprawę z tego, że mój przykład `` fail '' grep mógł zachować wyrażenie regularne takie, jakie jest, ponieważ już się nie zgadza (a, b lub c nie będą pasować do ']' wiersza poleceń) . Ale nie jest trywialne wymyślenie testu wyrażenia regularnego. Ogólnie rzecz biorąc, sztuczka działa. ten, który automatyzuje, będzie działał przez większość czasu. W przeciwnym razie potrzebne będą sprytne hacki (lub ręczna interwencja).
Olivier Dulac
Poza (abc)?(def)?tym musisz być ([a]bc)?([d]ef)?... Nie możesz parsować wyrażeń regularnych z wyrażeniami regularnymi ?! > :-)
wizzwizz4
@ wizzwizz4 Wiem. ale twój przykład już się nie zgadza. to skomplikowana sprawa, właśnie podałem proste rozwiązanie dla prostszych przypadków
Olivier Dulac
@ wizzwizz4 Już tak mówię w moim pierwszym komentarzu ...
Olivier Dulac,