ORing dengan true dalam perintah over ssh

15

Ketika saya mencoba menjalankan pkill -fdari jarak jauh melalui ssh, dan mencoba untuk membuang kode kesalahan yang mungkin (untuk melanjutkan dengan sisa skrip saya bahkan jika tidak ada proses ditemukan), || truetidak berperilaku seperti yang saya harapkan.

$ 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

Saya kira itu adalah ssh yang mengembalikan 255, bukan perintah di antara tanda kutip, tapi mengapa?

Gauthier
sumber

Jawaban:

29

Anggapan Anda bahwa itu sshsendiri yang mengembalikan status keluar 255 adalah benar. The sshman page menyatakan bahwa:

ssh keluar dengan status keluar dari perintah jarak jauh atau dengan 255 jika terjadi kesalahan.

Jika Anda hanya menjalankan ssh [email protected] "pkill -f asdf", kemungkinan besar Anda akan mendapatkan status keluar 1, sesuai dengan pkillstatus untuk " Tidak ada proses yang cocok ".

Bagian yang menantang adalah untuk memahami mengapa kesalahan terjadi dengan SSH saat Anda menjalankan

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

Perintah jarak jauh SSH

Server SSH meluncurkan shell untuk menjalankan perintah jarak jauh. Berikut ini contoh tindakan ini:

$ 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

Perhatikan bahwa shell default adalah bashdan bahwa perintah jarak jauh bukan perintah sederhana tetapi sebuah pipa , "urutan satu atau lebih perintah yang dipisahkan oleh operator kontrol |".

Bash shell cukup pintar untuk menyadari bahwa jika perintah yang diteruskan oleh -copsi tersebut adalah perintah sederhana , ia dapat mengoptimalkan dengan tidak benar-benar memalsukan proses baru, yaitu, ia secara langsung execmerupakan perintah sederhana alih-alih melalui langkah tambahan dari forksebelum itu execs. Berikut adalah contoh dari apa yang terjadi ketika Anda menjalankan perintah sederhana jarak jauh ( ps -elfdalam kasus ini):

$ 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

Saya telah menemukan perilaku ini sebelumnya tetapi saya tidak dapat menemukan referensi yang lebih baik selain jawaban AskUbuntu ini .

perilaku pkill

Karena pkill -f asdf || trueini bukan perintah sederhana (ini daftar perintah ), optimasi di atas tidak dapat terjadi sehingga ketika Anda menjalankan ssh [email protected] "pkill -f asdf || true", sshdproses bercabang dan mengeksekusi bash -c "pkill -f asdf || true".

Seperti yang ditunjukkan oleh jawaban ctx, pkilltidak akan mematikan prosesnya sendiri. Namun, itu akan membunuh proses lain yang baris perintahnya sesuai dengan -fpola. The bash -cperintah sesuai dengan pola ini sehingga membunuh proses ini - induk sendiri (seperti yang terjadi).

Server SSH kemudian melihat bahwa proses shell itu dimulai untuk menjalankan perintah jarak jauh terbunuh secara tak terduga sehingga melaporkan kesalahan ke klien SSH.

Anthony G - keadilan untuk Monica
sumber
1
Sementara jawabannya benar mengidentifikasi sumber masalah seperti dalam pkillmembunuh proses shell induknya karena daftar arg yang cocok regexp, saya akan mengangkat keberatan terminologi: x || yadalah tidak sebuah perintah senyawa , itu adalah daftar perintah .
Stéphane Chazelas
@ StéphaneChazelas Terima kasih atas umpan baliknya. Saya telah pergi dengan memasukkan Bash dari konstruksi kondisional sebagai perintah majemuk tetapi saya setuju bahwa lebih logis logis untuk dipertimbangkan x||ysebagai daftar perintah. Sekarang saya telah mengedit jawaban saya untuk memasukkan tautan ke berbagai definisi POSIX.
Anthony G - keadilan untuk Monica
1
Perhatikan bahwa dalam kasus umum, ini tidak terlalu banyak sehingga tidak dapat dioptimalkan karena ini adalah daftar perintah yang masih memiliki perintah lain untuk dijalankan (berpotensi). Di zsh/ ksh93/ FreeBSD sh, false || pkill -f asdfakan pkilldieksekusi dalam proses shell. bashhanya melakukan optimasi ketika hanya ada satu perintah sederhana. true; pkill -f asdfjuga akan menjadi masalah.
Stéphane Chazelas
9

Perintah jarak jauh Anda bunuh diri:

$ 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 dan pkill akan mengabaikan proses mereka sendiri, tetapi dengan flag -f, mereka akan menemukan shell induk:

$ 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
sumber
Itu masuk akal! bash -c 'pgrep -af asdf'(tanpa || true) tidak menemukan dirinya sendiri. Kenapa tidak? Sudah -f.
Gauthier
2
@ Gauthier Sebenarnya, saya pikir dalam kasus ini, Bash cukup pintar untuk menyadari bahwa perintah itu sederhana (bukan perintah majemuk) sehingga dioptimalkan dengan tidak benar-benar mencari proses baru. Saya ingat pernah menjumpai perilaku serupa sebelumnya (saya harus memperbarui jawaban saya).
Anthony G - keadilan untuk Monica
3

Anda meminta pkill untuk membunuh apa pun yang cocok dengan "asdf". Anda harus mengatakannya untuk mencocokkan [a] sdf, dengan cara itu ia akan tetap mencari apa pun yang bernama "asdf", tetapi tidak akan melihat dirinya sendiri (jika Anda menyelaraskan asdf dengan [a] sdf, perhatikan bahwa s selaras dengan] dan bukan s.)

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

Ini adalah trik umum yang juga digunakan dengan 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

Trik ini sudah tua, dan saya melihatnya beberapa dekade yang lalu di faq Unix (yang masih bagus dibaca!)

Untuk "mengotomatisasi" itu, itu tidak mudah, tetapi biasanya setiap kali Anda perlu grep untuk string variabel regexp = "sesuatu", Anda dapat mencoba melakukan:

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
sumber
catatan: Saya sadar bahwa contoh grep 'gagal' saya, seseorang dapat mempertahankan regexp seperti apa adanya, karena sudah tidak akan cocok dengan dirinya sendiri (a, b atau c tidak akan cocok dengan ']' pada commandline) . Tapi itu tidak sepele dengan tes regexp. Secara umum, triknya berhasil. yang mengotomatiskan akan bekerja sebagian besar waktu. Jika tidak, beberapa peretas cerdas (atau intervensi manual) akan diperlukan.
Olivier Dulac
Juga, (abc)?(def)?harus menjadi ([a]bc)?([d]ef)?... Anda tidak dapat mengurai regex dengan regex ?! > :-)
wizzwizz4
@ wizzwizz4 Saya tahu. tetapi teladan Anda sudah tidak akan cocok dengan dirinya sendiri. ini adalah hal yang kompleks, saya hanya memberikan solusi sederhana untuk kasus yang lebih sederhana
Olivier Dulac
@ wizzwizz4 Saya sudah mengatakannya dalam komentar pertama saya ...
Olivier Dulac