Как запустить системные команды Perl в фоновом режиме? - PullRequest
11 голосов
/ 26 апреля 2010
#!/usr/bin/env perl
use warnings; use strict;
use 5.012;
use IPC::System::Simple qw(system);

system( 'xterm', '-geometry', '80x25-5-5', '-bg', 'green', '&' );

say "Hello";
say "World";

Я попытался запустить команду xterm в фоновом режиме, но она не работает:

Абсолютный путь для оболочки не найден: &

Каким будет правильный способ заставить его работать?

Ответы [ 4 ]

19 голосов
/ 26 апреля 2010

Система Perl's имеет два режима:

  1. взятие одной строки и передача ее в командную оболочку для обработки специальных символов
  2. взятие списка строк, выполнение первой и передача оставшихся строк в качестве аргументов

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

В вашем случае вы, кажется, смешиваете две формы. Символ & имеет значение «запускать эту программу в фоновом режиме», только если он передан в оболочку. В вашей программе амперсанд передается в качестве 5-го аргумента команде xterm.

Как сказал Якоб Крузе, простой ответ заключается в использовании однострочной формы system. Если какой-либо из аргументов исходит из ненадежного источника, вам придется использовать кавычки или экранирование, чтобы сделать их безопасными.

Если вы предпочитаете использовать форму с несколькими аргументами, вам нужно вызвать fork () и затем, вероятно, использовать exec () вместо system().

12 голосов
/ 26 апреля 2010

Обратите внимание, что форма списка system специально предназначена для , а не для обработки таких символов, как &, как метасимволов оболочки.

Из ответа perlfaq8 на Как запустить процесс в фоновом режиме?


(предоставлено Брайаном Д. Фой)

Нет единого способа запуска кода в фоновом режиме, поэтому вам не нужно ждать его завершения, прежде чем ваша программа перейдет к другим задачам. Управление процессами зависит от вашей конкретной операционной системы, и многие из методов находятся в perlipc.

Может помочь несколько модулей CPAN, включая IPC :: Open2 или IPC :: Open3 , IPC :: Run , Parallel :: Jobs , Parallel :: ForkManager , POE , Proc :: Background и Win32 :: Process . Вы можете использовать множество других модулей, поэтому проверьте эти пространства имен и для других опций.

Если вы работаете в Unix-подобной системе, вам, возможно, удастся избежать системного вызова, когда вы ставите & в конце команды:

system("cmd &")

Вы также можете попробовать использовать fork, как описано в perlfunc (хотя это то же самое, что многие модули сделают для вас).

STDIN, STDOUT и STDERR совместно используются

И основной, и фоновый процессы ("дочерний" процесс) используют одни и те же файловые дескрипторы STDIN, STDOUT и STDERR. Если оба пытаются получить к ним доступ сразу, могут произойти странные вещи. Вы можете закрыть или снова открыть их для ребенка. Вы можете обойти это, открыв канал (см. Open в perlfunc), но в некоторых системах это означает, что дочерний процесс не может пережить родительский. сигналы Вам придется поймать сигнал SIGCHLD и, возможно, SIGPIPE. SIGCHLD отправляется после завершения фонового процесса. SIGPIPE отправляется, когда вы пишете в файловый дескриптор, чей дочерний процесс был закрыт (нераскрытый SIGPIPE может привести к молчаливой смерти вашей программы). Это не проблема с системой ("cmd &").

Zombies

Вы должны быть готовы "пожинать" дочерний процесс после его завершения.

$SIG{CHLD} = sub { wait };

$SIG{CHLD} = 'IGNORE';

Вы также можете использовать двойную вилку. Вы немедленно ждете () своего первого ребенка, а демон init будет ждать () вашего внука, как только он выйдет.

unless ($pid = fork) {
    unless (fork) {
        exec "what you really wanna do";
        die "exec failed!";
    }
    exit 0;
}
waitpid($pid, 0);

См. Сигналы в perlipc для других примеров кода для этого. Зомби не проблема с системой ("prog &").

4 голосов
/ 26 апреля 2010

Вы пробовали?

system('xterm -geometry 80x25-5-5 -bg green &');

http://www.rocketaware.com/perl/perlfaq8/How_do_I_start_a_process_in_the_.htm

0 голосов
/ 20 мая 2013

Это не просто объяснение Perl. Та же проблема в C и других языках.

Сначала поймите, что делает команда system :

  1. Вилка
  2. Под дочерним вызовом процесса exec
  3. Родительский процесс ожидает завершения дочернего процесса

Не имеет значения, если вы передаете несколько аргументов или один аргумент. Разница в том, что с несколькими аргументами команда выполняется напрямую. С одним аргументом команда оборачивается оболочкой и, наконец, выполняется как:

/bin/sh -c your_command_with_redirections_and_ambersand

Когда вы передаете команду как some_command par1 par2 & , тогда между интерпретатором Perl и командой идет процесс sh или bash , используемый в качестве оболочки , и он ожидает окончания some_command . Ваш скрипт ожидает интерпретатора оболочки, и никаких дополнительных waitpid не требуется, потому что функция Perl system делает это за вас.

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

  1. Используйте функцию fork . Смотрите пример: http://users.telenet.be/bartl/classicperl/fork/all.html
  2. В дочерних условиях (if) используйте функцию exec. Ваш пользователь похож на system , см. Руководство. Обратите внимание, что exec вызывает выполнение дочерней программы / содержимого / данных при выполнении команды.
  3. В родительском условии (если fork выходит с ненулевым значением), вы используете waitpid, используя pid, возвращаемый функцией fork.

Вот почему вы можете запустить процесс в фоновом режиме. Я надеюсь, что это просто.

Простейший пример:

if (my $pid = fork) { #exits 0 = false for child process, at this point is brain split
  # parent ($pid is process id of child)
  # Do something what you want, asynchronously with executed command
  waitpid($pid);  # Wait until child ends
  # If you don't want to, don't wait. Your process ends, and then the child process will be relinked
  # from your script to INIT process, and finally INIT will assume the child finishing.
  # Alternatively, you can handle the SIGCHLD signal in your script
}
else {
  # Child
  exec('some_command arg1 arg2'); #or exec('some_command','arg1','arg2');
  #exit is not needed, because exec completely overwrites the process content
}
...