Хобрук: Ваш путь к мастерству в программировании

Как записать строку в STDIN другого процесса в FreePascal

Мои наиболее распространенные варианты использования требуют, чтобы отчеты об ошибках и выполнении отправлялись системному администратору по электронной почте. В Bash вы должны использовать оператор Pipe | следующим образом:

echo "test mail" | mail -s "Mail Command Test" [email protected]

Теперь я пытаюсь применить эту концепцию к FreePascal с классом TProcess.

Как записать строку отчета в подпроцесс mail?

Я нашел информацию по адресу
https://wiki.freepascal.org/Executing_External_Programs
Но я не описываю точно свой вариант использования, так как нет первого процесса


Чтобы объяснить проблему операторов | < > не работает и вариант shell=true (из Python subprocess.Popen) не существует, как описано в:
Замена операторов оболочки

Я написал небольшой Perl скрипт как манекен Mailer:

#!/usr/bin/perl -w


use strict;



my $ipid = $$;
my $smessage = '';
my $log = '';


print "prms (cnt: '" . scalar(@ARGV) . "'):\n";

$log .= "prms (cnt: '" . scalar(@ARGV) . "'):\n";

foreach(@ARGV)
{
  print "prm: '$_'\n";

  $log .= "prm: '$_'\n";
}

while(<STDIN>)
{
  $smessage  .= $_ ;
}

print "message: '$smessage'\n";

$log .= "message: '$smessage'\n";


print "log: writing ...\n";

$log =~ s#"#\"#g;

print "log msg:\n'$log'\n";

system("echo -e \"${log}\" > ./mailer-perl_${ipid}.log");


exit 0;

Это покажет параметры и содержимое STDIN, переданного дочернему процессу.

  1. Теперь, когда я создаю свой объект TProcess следующим образом:
  mailcommand := TProcess.Create(nil);

  try
    try
      mailcommand.Options    := [poUsePipes, poStderrToOutPut];
      mailcommand.Executable := 'mailer.pl';
      mailcommand.Parameters.Add('-s "Mail Command Test" user_login@localhost < mail_message.txt');

      mailcommand.Execute;

      // Close the input on the SecondProcess
      // so it finishes processing it's data
      mailcommand.CloseInput;

      // and wait for it to complete

      bwait := mailcommand.WaitOnExit;

      WriteLn('Mail Command WaitOnExit: ', chr(39), bwait, chr(39));

   except
      on e : Exception do
      begin
        WriteLn('Mail Command - failed with Exception [', e.HelpContext, ']: '
          , chr(39), e.Message, chr(39));

        //e.Free;
      end //on E : Exception do
      else
      begin
        WriteLn('Mail Command - failed with Unknown Exception: '
          , chr(39), 'unknown error', chr(39));
      end;  //on e : Exception do
    end;
  finally
    // free our process objects
    mailcommand.Free;
  end;

Я могу наблюдать эту активность в дочернем процессе:

prms (cnt: '1'):
prm: '-s "Mail Command Test" user_login@localhost < mail_message.txt'
message: ''

На STDIN нет сообщения НЕТ.
Все было отправлено как 1 единственный параметр дочернему процессу!

  1. Я создаю объект TProcess следующим образом:
  mailcommand := TProcess.Create(nil);

//[...]
      mailcommand.Options    := [poUsePipes, poStderrToOutPut];
      mailcommand.Executable := 'mailer.pl';
      mailcommand.Parameters.Add('-s');
      mailcommand.Parameters.Add('Mail Command Test');
      mailcommand.Parameters.Add('user_login@localhost');
      mailcommand.Parameters.Add('<');
      mailcommand.Parameters.Add('mail_message.txt');
//[...]

Затем я наблюдаю эту активность в дочернем процессе:

prms (cnt: '5'):
prm: '-s'
prm: 'Mail Command Test'
prm: 'user_login@localhost'
prm: '<'
prm: 'mail_message.txt'
message: ''

Снова появляется сообщение NO STDIN
Символ был только что передан в качестве другого параметра дочернему процессу.

Класс TProcess не имеет и shell=true Option, как можно предположить из Python из одноименного модуля.


  • Если я понимаю ваш вопрос, вы можете использовать ReadLn и передавать данные в свою программу. 30.03.2020

Ответы:


1

Изучая архитектуру FreePascal, я понял, что функциональность | реализована с помощью свойства TProcess.Input, которое на самом деле является TOutputPipeStream.

TOutputPipeStream — это объектно-ориентированная реализация хорошо известного pipe
https://www.freepascal.org/docs-html/fcl/pipes/toutputpipestream.html

TOutputPipeStream создается вызовом CreatePipeStreams для представления конца записи канала. Это потомок TStream, который не позволяет читать.

Таким образом, в TStream объектов вы можете писать с помощью функции TStream.Write().

Поскольку команда echo в bash просто выводит текст, я просто заменил его на TStringStream, который динамически растет и приводит к Pascal String. Это удобно для прогрессивной генерации отчетов. Получившийся Паскаль String можно использовать напрямую и записать в поток TProcess.Input.

Примечание. Вам нужно вызвать TProcess.Execute, прежде чем вы сможете писать во входной поток, потому что это момент, когда происходит системный вызов fork() и pipe открыт.
Но пока TProcess не работает. потому что он все еще ждет ввода STDIN.

program mail_pipe;

uses
Classes, sysutils, process;

var
  mailcommand: TProcess;
  messagestream: TStringStream;
  smessage: String;
  sbuffer: String;
  ReadSize: Integer;
  bwait: Boolean;
begin
  mailcommand := TProcess.Create(nil);
  messagestream := TStringStream.Create('');

  try
    try
      smessage := 'test mail';

      messagestream.WriteString(smessage);

      // this would be the same as 'mail -s "Mail Command Test" [email protected]'
      mailcommand.Options    := [poUsePipes, poStderrToOutPut];
      mailcommand.Executable := 'mail';
      mailcommand.Parameters.Add('-s');
      mailcommand.Parameters.Add('Mail Command Test');
      mailcommand.Parameters.Add('user_login@localhost');

      mailcommand.Execute;

      WriteLn('Mail Input: Writing ...');

      WriteLn('Mail Input (Length ', chr(39), messagestream.Size, chr(39), '):');
      WriteLn(chr(39), messagestream.DataString, chr(39));
      mailcommand.Input.Write(messagestream.DataString[1], messagestream.Size);
      //mailcommand.Input.Write(PChar(''), 0);

      // Close the input on the SecondProcess
      // so it finishes processing it's data
      mailcommand.CloseInput;

      // and wait for it to complete

      bwait := mailcommand.WaitOnExit;

      WriteLn('Mail Command WaitOnExit: ', chr(39), bwait, chr(39));

      // that's it! the rest of the program is just so the example
      // is a little 'useful'

      // we will reuse Buffer to output the SecondProcess's
      // output to *this* programs stdout
      WriteLn('Mail Output: Reading ...');

      sbuffer := '';

      ReadSize := mailcommand.Output.NumBytesAvailable;

      WriteLn('Mail Report (Length ', chr(39), ReadSize, chr(39), '):');

      SetLength(sbuffer, ReadSize);

      if ReadSize > 0 then
      begin
        mailcommand.Output.Read(sbuffer[1], ReadSize);

        WriteLn(chr(39), sbuffer, chr(39));
      end;

      WriteLn('Mail Command finished with [', mailcommand.ExitStatus, ']');
    except
      //------------------------
      //Report Exception

      on e : Exception do
      begin
        WriteLn('Mail Command - failed with Exception [', e.HelpContext, ']: '
          , chr(39), e.Message, chr(39));
      end //on E : Exception do
      else
      begin
        WriteLn('Mail Command - failed with Unknown Exception: '
          , chr(39), 'unknown error', chr(39));
      end;  //on e : Exception do
    end;

  finally
    // free our process objects
    messagestream.Free;
    mailcommand.Free;
  end;

end.

На самом деле вы можете наблюдать в журнале strace, что TProcess создает каналы, выполняет поиск команды mail и разветвляет ее из основного процесса:

strace: Process 11860 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
mmap(NULL, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7e8e3c0000
pipe([3, 4])                            = 0
pipe([5, 6])                            = 0
access("mail", F_OK)                    = -1 ENOENT (No such file or directory)
mmap(NULL, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7e8e3b8000
access("/usr/lib64/qt-3.3/bin/mail", F_OK) = -1 ENOENT (No such file or directory)
access("/usr/local/bin/mail", F_OK)     = -1 ENOENT (No such file or directory)
access("/usr/local/sbin/mail", F_OK)    = -1 ENOENT (No such file or directory)
access("/usr/bin/mail", F_OK)           = 0
fork()                                  = 13921
close(4)                                = 0
close(5)                                = 0
write(1, "Mail Input: Writing ...\n", 24) = 24
write(1, "Mail Input (Length '9'):\n", 25) = 25
write(1, "'test mail'\n", 12)           = 12
write(6, "test mail", 9)                = 9
close(6)                                = 0
wait4(13921, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 13921
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=13921, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
write(1, "Mail Command WaitOnExit: 'TRUE'\n", 32) = 32
write(1, "Mail Output: Reading ...\n", 25) = 25
ioctl(3, FIONREAD, [0])                 = 0
write(1, "Mail Report (Length '0'):\n", 26) = 26
write(1, "Mail Command finished with [0]\n", 31) = 31
close(3)                                = 0
munmap(0x7f7e8e3e0000, 32768)           = 0
munmap(0x7f7e8e3d8000, 32768)           = 0
munmap(0x7f7e8e3d0000, 32768)           = 0
munmap(0x7f7e8e3e8000, 32768)           = 0
munmap(0x7f7e8e3c8000, 32768)           = 0
munmap(0x7f7e8e3c0000, 32768)           = 0
munmap(0x7f7e8e3b8000, 32768)           = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Вывод программы mail_pipe:

$ ./mail_pipe
Mail Input: Writing ...
Mail Input (Length '9'):
'test mail'
Mail Command WaitOnExit: 'TRUE'
Mail Output: Reading ...
Mail Report (Length '0'):
Mail Command finished with [0]

Это нормальный и ожидаемый результат, так как mail даст результат только в случае сбоя. Дополнительную информацию об обработке электронной почты вы можете найти в вашем местном maillog.

В результате вы получите электронное письмо в свой Локальный почтовый ящик:

Delivered-To: user_login@localhost
Received: by user-workstation (Postfix, from userid 1000)
    id E977140439D4C; Tue, 31 Mar 2020 08:31:42 +0100 (WEST)
Date: Tue, 31 Mar 2020 08:31:42 +0100
To: user_login@localhost
Subject: Mail Command Test
User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20200331073142.E977140439D4C@user-workstation>
From: user_login (User Name)

test mail=

При записи Pascal Strings в поток важно то, что из-за затрат памяти Pascal Strings данные начинаются с позиции 1, как в:

SecondProcess.Input.Write(messagestream.DataString[1], messagestream.Size);

Возможные ошибки:

  • Если TProcess.Executable не существует, будет выбрано Exception:
$ ./mail_pipe
Mail Command - failed with Exception [0]: 'Executable not found: "no_script.pl"'

Это может произойти, если в системе не установлена ​​команда mail.

  • Если TProcess.Executable не является исполняемым, будет сгенерирован сигнал SIGCHLD:
    (и если приложение продолжит запись в поврежденный pipe, будет сгенерирован сигнал SIGPIPE.)
strace: Process 24911 attached restart_syscall(<...
resuming interrupted nanosleep ...>) = 0 mmap(NULL, 32768,
PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0x7ff970770000 pipe([3, 4])                            = 0 pipe([5,
6])                            = 0 access("noexec_script.pl", F_OK)  
= 0 fork()                                  = 27068 close(4)                                = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=27068, si_uid=1000, si_status=127, si_utime=0, si_stime=0} --- close(5)     
= 0 write(1, "Mail Input: Writing ...\n", 24) = 24 write(1, "Mail Input (Length '9'):\n", 25) = 25 write(1, "'test mail'\n", 12)       
= 12 write(6, "test mail", 9)                = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=24911, si_uid=1000} ---
+++ killed by SIGPIPE +++ 

Это может произойти, если Приложение не имеет Разрешения на взаимодействие с этой Командой (возможно, SELinux может его заблокировать).

31.03.2020

2

Возможно, вы можете избежать прямой записи в канал, но решите свою проблему, записав свои данные в msgfile, а затем запустив exec с перенаправлением ввода - send <msgfile...

Насколько я понимаю, каналы создаются вне вашего приложения, и они перенаправляют вывод вашей программы на ввод другой программы a | b должен быть таким же, как a >file && b <file. Если вы действительно не хотите ждать завершения отправки, вы можете создать отдельный поток, который записывает в файл, а затем запускает send <msgfile и завершает работу по завершении.

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

14.06.2020
  • Спасибо за ваш вклад. Да, когда программист Linux думает о задаче, первой мыслью является перенаправление ввода и вывода, потому что это в основном работает на всех основных языках сценариев Bash, Python и < я>Перл. Но в официальной документации wiki.freepascal.org/ ясно объяснено, что класс TProcess НЕ является оболочкой bash и, следовательно, не будет работать так. Но я все еще заинтригован, чтобы увидеть ваш прототип, как вы добиваетесь, чтобы заставить эту работу. 22.06.2020
  • Таким образом, инструкция Shell command & означает выполнение команды отдельно, чтобы команда не блокировала терминал. Пример $ echo "hello world" & результатом является PID отсоединенного процесса: [1] 29152 22.06.2020
  • Извините, этот амперсанд был моей попыткой написать ‹ и › для перенаправления ввода-вывода в интерфейсе stackoverflow. Я хотел сказать, что если вы записываете свое почтовое содержимое в файл с именем «sendfile», то вы можете запустить что-то вроде «mail -s Mail Command Test [email protected] ‹sendfile», чтобы отправить его... 23.06.2020
  • может быть, вы можете просто использовать что-то вроде exec ('send <msgfile') для запуска внешней программы? 17.07.2020
  • Пожалуйста, посмотрите мои примеры, которые я добавил к вопросу, чтобы продемонстрировать проблему, чтобы полностью понять проблему. 17.07.2020
  • Уважаемый Бодо, проверьте, чтобы между < и именем файла не было пробелов. ура Робби 18.07.2020

  • 3

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

    program pr;
    var s : string;
    begin
      readln  (s);
      writeln (s)
    end.
    
    program test;
    uses process;
    var f : text; s : ansistring;
    begin
      assign (f,'text');
      rewrite (f);
      writeln (f,'content');
      close (f);
      runcommand ('/bin/bash',['./pr <text'],s)
    end.
    

    работая на os x, должно работать на linux, а для windows можно использовать unit dos и exec...

    18.07.2020
  • Спасибо за ваше время и ваши усилия, которые вы вложили в этот вопрос. Ваше решение может скомпилироваться и также работать. Но помимо того, что это не идиоматическое решение, а скорее bash, написанное на Pascal, у него есть несколько проблем. 1) Он не может обрабатывать более 256 байт 2) Он вводит накладные расходы на запуск другого Процесса, который является bash shell 3) Размер буфера также ограничен 256 байтами. 15.08.2020
  • Привет, Бодо, это был просто образец для демонстрации концепции. Я предполагаю, что ввод в почтовую команду находится в диапазоне 256 символов в строке, если вам нужно больше, вы можете создать файл с пользовательским типом данных и читать/записывать его с помощью всего одного чтения/записи. Идоматически говоря - файл эмулирует канал, вы можете запускать почту вместо bash, и вы можете писать все, что хотите в файл (не ограничиваясь 256 байтами). Что касается накладных расходов, если вы хотите использовать каналы (soluton работает только с unix-es, а не с Windoze), у вас уже будет почтовый процесс, работающий все время на другом конце канала. 17.08.2020
  • Новые материалы

    Учебные заметки JavaScript Object Oriented Labs
    Вот моя седьмая неделя обучения программированию. После ruby ​​и его фреймворка rails я начал изучать самый популярный язык интерфейса — javascript. В отличие от ruby, javascript — это более..

    Разбор строк запроса в vue.js
    Иногда вам нужно получить данные из строк запроса, в этой статье показано, как это сделать. В жизни каждого дизайнера/разработчика наступает момент, когда им необходимо беспрепятственно..

    Предсказание моей следующей любимой книги 📚 Благодаря данным Goodreads и машинному обучению 👨‍💻
    «Если вы не любите читать, значит, вы не нашли нужную книгу». - J.K. Роулинг Эта статья сильно отличается от тех, к которым вы, возможно, привыкли . Мне очень понравилось поработать над..

    Основы принципов S.O.L.I.D, Javascript, Git и NoSQL
    каковы принципы S.O.L.I.D? Принципы SOLID призваны помочь разработчикам создавать надежные, удобные в сопровождении приложения. мы видим пять ключевых принципов. Принципы SOLID были разработаны..

    Как настроить Selenium в проекте Angular
    Угловой | Селен Как настроить Selenium в проекте Angular Держите свое приложение Angular и тесты Selenium в одной рабочей области и запускайте их с помощью Mocha. В этой статье мы..

    Аргументы прогрессивного улучшения почти всегда упускают суть
    В наши дни в кругах веб-разработчиков много болтают о Progressive Enhancement — PE, но на самом деле почти все аргументы с обеих сторон упускают самую фундаментальную причину, по которой PE..

    Введение в Джанго Фреймворк
    Схема «работать умно, а не усердно» В этой и последующих статьях я познакомлю вас с тем, что такое фреймворк Django и как создать свое первое приложение с помощью простых и понятных шагов, а..