Могу ли я отправить какой-либо текст на STDIN активного процесса, запущенного в сеансе экрана?

У меня есть длительный процесс сервера внутри сеанса экрана на моем сервере Linux. Это немного нестабильно (и, к сожалению, не мое программное обеспечение, поэтому я не могу это исправить!), Поэтому я хочу написать сценарий перезапуска процесса каждую ночь, чтобы помочь стабильности. Единственный способ заставить его выполнить корректное завершение работы - перейти к экранному процессу, переключиться в окно, в котором он запущен, и ввести строку "стоп" на его консоли управления.

Есть ли какие-нибудь умные перенаправления, которые я могу сделать, чтобы cronjob отправлял эту команду остановки в определенное время каждый день?

9 ответов

Этот ответ не решает проблему, но он оставлен здесь, потому что более 30 человек нашли его полезным, в противном случае я бы удалил его давным-давно.

Написать в /proc/*pid of the program*/fd/0, fd подкаталог содержит дескрипторы всех открытых файлов и дескриптор файла 0 стандартный ввод (1 - стандартный вывод, 2 - стандартный вывод).

Вы можете использовать это для вывода сообщений на tty, где запущена программа, хотя это не позволяет вам писать в самой программе.

пример

Терминал 1:

[ciupicri@hermes ~]$ cat
shows on the tty but bypasses cat

Терминал 2:

[ciupicri@hermes ~]$ pidof cat
7417
[ciupicri@hermes ~]$ echo "shows on the tty but bypasses cat" > /proc/7417/fd/0

Экранное решение

Запустите сервер так:

# screen -d -m -S ServerFault tr a-z A-Z # replace with your server

Экран запустится в отдельном режиме, поэтому, если вы хотите увидеть, что происходит, запустите:

# screen -r ServerFault

Управляйте сервером так:

# screen -S ServerFault -p 0 -X stuff "stop^M"
# screen -S ServerFault -p 0 -X stuff "start^M"
# screen -S ServerFault -p 0 -X stuff "^D" # send EOF

(этот ответ основан на отправке текстового ввода на отдельный экран с сайта- партнера Unix & Linux)

Объяснение параметров:

-d -m
   Start screen in "detached" mode. This creates a new session but doesn't
   attach to it.  This is useful for system startup scripts.
-S sessionname
   When creating a new session, this option can be used to specify a meaningful
   name for the session.
-r [pid.tty.host]
-r sessionowner/[pid.tty.host]
   resumes a detached screen session.
-p number_or_name|-|=|+
   Preselect a window. This is useful when you want to reattach to a specific
   window or you want to send a command via the "-X" option to a specific
   window.
-X
   Send the specified command to a running screen session e.g. stuff.

вещи [строка]

   Stuff the string string in the input  buffer of the current window.
   This is like the "paste" command but with much less overhead.  Without
   a parameter, screen will prompt for a string to stuff.

решение на основе tmux

Запустите сервер так:

# tmux new-session -d -s ServerFault 'tr a-z A-Z' # replace with your server

tmux запустится в автономном режиме, поэтому, если вы хотите увидеть, что происходит, запустите:

# tmux attach-session -t ServerFault

Управляйте сервером так:

# tmux send-keys -t ServerFault -l stop
# tmux send-keys -t ServerFault Enter
# tmux send-keys -t ServerFault -l start
# tmux send-keys -t ServerFault Enter
# tmux send-keys -t ServerFault C-d # send EOF

Объяснение параметров:

 new-session [-AdDP] [-c start-directory] [-F format] [-n window-name] [-s
         session-name] [-t target-session] [-x width] [-y height]
         [shell-command]
         Create a new session with name session-name.

         The new session is attached to the current terminal unless -d is
         given.  window-name and shell-command are the name of and shell
         command to execute in the initial window.  If -d is used, -x and
         -y specify the size of the initial window (80 by 24 if not
         given).

 send-keys [-lR] [-t target-pane] key ...
               (alias: send)
         Send a key or keys to a window.  Each argument key is the name of
         the key (such as `C-a' or `npage' ) to send; if the string is not
         recognised as a key, it is sent as a series of characters.  The
         -l flag disables key name lookup and sends the keys literally.

Можно отправить входной текст запущенному процессу, не запуская screen утилита, или любая другая необычная утилита. И это можно сделать, отправив этот входной текст в стандартный "файл" ввода процесса /proc/PID#/fd/0,

Тем не менее, входной текст должен быть отправлен специальным способом для чтения процессом. Отправка введенного текста через обычный файл write Метод не приведет к тому, что процесс получит текст. Это потому, что это добавит только к этому "файлу", но не запустит процесс для чтения байтов.

Чтобы запустить процесс для чтения байтов, необходимо сделать IOCTL операция типа TIOCSTI за каждый отправляемый байт. Это поместит байт в стандартную очередь ввода процесса.

Это обсуждается здесь с некоторыми примерами на C, Perl и Python:

https://unix.stackexchange.com/questions/48103/construct-a-command-by-putting-a-string-into-a-tty/48221

-

Таким образом, чтобы ответить на первоначальный вопрос, заданный почти 9 лет назад, заданию cron потребуется запустить небольшой служебный скрипт / программу, аналогичную примерам, которые люди написали для этого другого вопроса, который отправит строку "stop \ n" этому процессу сервера. в вопросе, отправив каждый из 5 байтов через IOCTL операция типа TIOCSTI,

Конечно, это будет работать только на системах, которые поддерживают TIOCSTIIOCTL тип операции (например, Linux), и только из root учетная запись пользователя, так как эти "файлы" под /proc/ "принадлежат" root,

Существует более элегантное решение, позволяющее избежать использованияtail -fи вечные циклы, которые тратят системные ресурсы.

  • Сначала создайте именованный канал для маршрутизации STDIN:mkfifo /data/in.

  • Затем заблокируйте его для записи, чтобы он не закрывался, когда ваш процесс прочитает все текущее содержимое:sleep infinity > /data/in &. Спать вечно лучше, чемtailf -f /dev/nullпотому что Tailf использует ресурсы inotify и будет запускаться каждый раз, когда какое-либо приложение отправляет данные в /dev/null. Вы можете увидеть это, запустивstraceв теме. Это также лучше, чемcat > /dev/null &потому чтоcatсам будет отключен от STDIN, который, в свою очередь, закроется.

  • Запустите свой процесс в фоновом режиме с помощью/data/inпредоставление STDIN:application < /data/in &. Это работает лучше, чем использование трубопровода из хвоста.tail -f /data/in | application &потому что канал будет завершен только в том случае, если хвост остановится, но если ваше приложение выйдет из строя, канал продолжит работать.

  • Остановите ожидание завершения работы приложения.wait $(pidof application). При этом не используются ресурсы, и если приложение приведет к сбою, ваш код после ожидания будет выполнен. При желании вы можете добавить вокруг него цикл перезапуска приложения.

  • Чтобы завершить работу приложения, корректно перехватывать и передавать ему системные сигналы с помощьюtrap 'kill -SIGTERM $(pidof app)' SIGTERM

На случай, если это кому-нибудь поможет:
У меня была похожая проблема, и поскольку процесс, который я использовал, не был под screen или же tmuxЯ должен был принять другой подход.

Я приложил gdb к xterm что мой процесс запущен и используется call write(5, "stop\n", 5) от gdb записать в дескриптор основного файла pty.
Я выяснил, к какому дескриптору файла отправлять данные, посмотрев на /proc/<pid>/fd для ссылки на /dev/ptmx а затем метод проб и ошибок между двумя вариантами (отправка моей строки в оба соответствующих файловых дескриптора, казалось, не принесла вреда).

РЕДАКТИРОВАТЬ

Оказалось, что xterm процесс, к которому я присоединился, был порожден spawn-new-terminal()xterm действие от связывания клавиш, а второй ptmx открытый дескриптор файла был просто ptmx родителя xterm процесс, который не был закрыт.
Следовательно, вызовы проб и ошибок отправили вывод на этот другой терминал.
Наиболее xterm процессы не имеют двух ptmx файловые дескрипторы.

КОНЕЦ РЕДАКТИРОВАНИЯ

Это эффективно ввело эту строку в терминал и, следовательно, отправило ее процессу, запущенному под ним.

nb вам может понадобиться разрешить присоединение к запущенному процессу с чем-то вроде
sudo bash -c "echo 0 > /proc/sys/kernel/yama/ptrace_scope"

Попробуйте это начать:

# screen
# cd /path/to/wd
# mkfifo cmd
# my_cmd <cmd
C-A d

И это убить

# cd /path/to/wd
# echo "stop" > cmd
# rm cmd

Поскольку я не могу прокомментировать наиболее приемлемый ответ Кристиана Чиупиту (2010 г.), я должен изложить это в отдельном ответе:

Этот вопрос уже был решен в этой теме: https://stackoverflow.com/questions/5374255/how-to-write-data-to-existing-processs-stdin-from-external-process

Короче:

Вы должны начать свой процесс с канала для стандартного ввода, который не блокирует и не закрывает, когда текущий ввод был записан. Это может быть реализовано простым бесконечным циклом, который будет передан в рассматриваемый процесс:

$ (while [ 1 ]; do sleep 1; done) | yourProgramToStart

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

Затем вы можете записать в файл.../fd/0 процесса и отправить ему инструкции. Единственный недостаток заключается в том, что вам необходимо также завершить процесс bash, который выполняет бесконечный цикл после того, как сервер завершил работу.

Если вы хотите избежать остановки цикла while в этом ответе, вы можете использовать хвост, который выйдет при завершении процесса.

Вместо записи в /prod//fd/0 необходим канал.

      $ mkfifo input_pipe
$ tail -f input_pipe | your_program

Команда хрон

      echo "some message" > input_pipe

Большое предостережение: на этом этапе вы не можете вводить команды на самом экране и не видите никаких вводимых данных.

Большинство решений не помогают, если программа уже запущена, то есть в другой оболочке, возможно, с другого компьютера, над которым вы теряете контроль.

В этом случае можно использовать код, показанный в https://unix.stackexchange.com/questions/48103/construct-a-command-by-putting-a-string-into-a-tty , с некоторыми изменениями.

Вариант использования:

  • запустите apt dist-upgrade на своем компьютере на работе и забудьте об этом
  • иди домой (да, такое случается)
  • apt теперь может зависнуть в ожидании ввода

В домашних условиях можно узнать ПТС апп процесса

      root@linux:~# w
16:50:27 up 148 days, 18:16,  5 users,  load average: 0.77, 0.42, 0.32
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
root     pts/1    10.0.0.1 14:37    3:39   5.07s  5.03s apt dist-upgrade

Теперь дома скомпилируйте этот код (адаптировано по ссылке выше)

      #include <sys/ioctl.h>
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>

void stackchar(char c)
{
  if (ioctl(0, TIOCSTI, &c) < 0) {
    perror("ioctl");
    exit(1);
  }
}
int main(int argc, char *argv[])
{
  int i, j;
  char c;

  for (i = 1; i < argc; i++) {
    if (i > 1) stackchar(' ');
    for (j=0; (c = argv[i][j]); j++) {
      stackchar(c);
    }
  }
  stackchar('\r');
  stackchar('\n');
  exit(0);
}

Давайте скомпилируем его

      root@linux:~# gcc pushTty.c -o pushTty

Затем сделайте (возможно, несколько раз), чтобы отправить Y в качестве входных данных.

      root@linux:~# ./pushTty Y </dev/pts/1

или просто отправить новую строку (используя подходящий ответ по умолчанию)

      root@linux:~# ./pushTty </dev/pts/1

и apt в конечном итоге завершит работу «чисто».

Другие вопросы по тегам