ORing с истиной в команде над SSH

15

Когда я пытаюсь запустить pkill -fудаленно через ssh, и пытаюсь отбросить код возможной ошибки (чтобы продолжить работу с остальным моим сценарием, даже если процесс не найден), || trueведет себя не так, как я ожидаю.

$ 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

Я полагаю, что ssh возвращает 255, а не команда между кавычками, но почему?

Готье
источник

Ответы:

29

Ваше предположение, что оно sshсамо возвращает статус 255, верно. sshСтраница людей заявляет , что:

ssh завершает работу с состоянием выхода удаленной команды или с 255, если произошла ошибка.

Если бы вы просто запустились ssh pi@10.20.0.10 "pkill -f asdf", вы, скорее всего, получили бы статус выхода 1, соответствующий pkillстатусу « Нет соответствующих процессов ».

Задача состоит в том, чтобы понять, почему при запуске SSH возникает ошибка

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

SSH удаленные команды

Сервер SSH запускает оболочку для запуска удаленных команд. Вот пример этого в действии:

$ 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

Обратите внимание, что оболочкой по умолчанию является bashи что удаленная команда - это не простая команда, а конвейер , «последовательность из одной или нескольких команд, разделенных оператором управления |».

Оболочка Bash достаточно умна, чтобы понять, что если команда, передаваемая ей с помощью -cопции, является простой командой , она может оптимизироваться, фактически не разветвляя новый процесс, т. Е. Она напрямую execвыполняет простую команду вместо выполнения дополнительного шага. из forkING до того , как execс. Вот пример того, что происходит, когда вы запускаете простую удаленную команду ( ps -elfв данном случае):

$ 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

Я сталкивался с таким поведением раньше, но я не мог найти лучшую ссылку, кроме этого ответа AskUbuntu .

поведение pkill

Так как pkill -f asdf || trueэто не простая команда (это список команд ), выше оптимизации не могут произойти так , при запуске ssh pi@10.20.0.10 "pkill -f asdf || true", в sshdпроцессе вилки и Execs bash -c "pkill -f asdf || true".

Как указывает ответ ctx, pkillне будет убивать свой собственный процесс. Тем не менее, он будет убивать любой другой процесс , командная строка которого соответствует -fшаблону. Команда bash -cсоответствует этому шаблону, поэтому убивает этот процесс - своего родителя (как это происходит).

Затем SSH-сервер видит, что процесс оболочки, который он запустил для запуска удаленных команд, был неожиданно завершен, поэтому он сообщает об ошибке клиенту SSH.

Энтони Дж - справедливость для Моники
источник
1
Хотя ответ правильно идентифицирует источник проблема , как в pkillубивает его процесс оболочки родительского , потому что его агд список совпадает с регулярным выражением, я подниму терминологическую возражение: x || yэто не команда соединения , это список команд .
Стефан
@ StéphaneChazelas Спасибо за отзыв. Я собирался включить в Bash условные конструкции как составные команды, но я согласен, что логически более логично рассматривать их x||yкак список команд. Теперь я отредактировал свой ответ, включив ссылки на различные определения POSIX.
Энтони Дж - справедливость для Моники
1
Обратите внимание, что в общем случае, это не так уж много, что он не может оптимизировать, потому что это список команд, который, по-видимому, имеет еще одну команду для запуска (потенциально). В zsh/ ksh93/ FreeBSD sh, был false || pkill -f asdfбы pkillвыполнен в процессе оболочки. bashОптимизация выполняется только тогда, когда есть только одна простая команда. true; pkill -f asdfтакже будет проблемой.
Стефан
9

Ваша удаленная команда убивает себя:

$ 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 и pkill игнорируют свои собственные процессы, но с флагом -f они найдут родительскую оболочку:

$ 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
источник
Это имеет смысл! bash -c 'pgrep -af asdf'(без || true) не находит себя. Почему нет? Это имеет -f.
Готье
2
@ Gauthier На самом деле, я думаю, что в этом случае Bash достаточно умен, чтобы понять, что команда простая (а не составная), поэтому она оптимизирует, фактически не создавая новый процесс. Я помню, как сталкивался с подобным поведением раньше (я должен обновить свой ответ).
Энтони Дж - правосудие для Моники
3

Вы просите pkill убить все, что соответствует "asdf". Вы должны указать, чтобы он совпадал с [a] sdf, таким образом, он все равно будет искать что-нибудь с именем «asdf», но не будет видеть себя сам (если вы выровняете asdf с [a] sdf, обратите внимание, что s выровнен с] и не с.)

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

Это обычная уловка, также используемая с 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

Этот трюк старый, и я видел его десятилетия назад в FAQ по Unix (который до сих пор хорошо читается!)

«Автоматизировать» это нелегко, но обычно каждый раз, когда вам нужно grep для переменной string, regexp = «что-то», вы можете попробовать сделать:

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" ...
Оливье Дюлак
источник
примечание: я знаю, что мой пример grep 'fail', можно было бы сохранить регулярное выражение как есть, так как оно уже не будет совпадать (a, b или c не будут совпадать с ']' командной строки) , Но это не просто придумать тест регулярного выражения. В общем, хитрость работает. тот, который автоматизирует, будет работать большую часть времени. Если нет, понадобятся некоторые умные хаки (или ручное вмешательство).
Оливье Дюлак
Кроме того, (abc)?(def)?должно быть ([a]bc)?([d]ef)?... Вы не можете разобрать регулярное выражение с регулярным выражением ?! > :-)
wizzwizz4
@ wizzwizz4 Я знаю. но ваш пример уже не будет соответствовать самому себе. это сложная вещь, я просто предоставил простое решение для более простых случаев
Оливье Дюлак
@ wizzwizz4 Я уже сказал это в своем первом комментарии ...
Оливье Дюлак