Программирование PC FAQ программирования Linux: различные вопросы программирования Sun, April 28 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

FAQ программирования Linux: различные вопросы программирования Печать
Добавил(а) microsin   

Здесь приведен перевод главы 5 из FAQ по программированию Linux [1], посвященной дополнительным вопросам. Описание незнакомых терминов и аббревиатур см. в Словарике статьи [2].

[Список вопросов]

5. Разные вопросы программирования

  5.1 Как сравнивать строки с использованием wildcards?

    5.1.1 Как сравнивать строки, используя шаблоны имени файла?

    5.1.2 Как сравнивать строки с помощью регулярных выражений?

  5.2 Какой самый лучший способ послать email из программы?

    5.2.1 Простой метод: /bin/mail

    5.2.2 Прямое обращение к MTA: /usr/lib/sendmail

      5.2.2.1 Явное предоставление конверта

      5.2.2.2 Как разрешить sendmail определить получателей

5.1 Как сравнивать строки с использованием wildcards?
=====================================================

Ответ зависит от того, ЧТО ИМЕННО подразумевается под wildcards (подстановочные символы).

Существует два совершенно разных понятия, которые квалифицируются как 'wildcards':

Шаблоны имен файла (filename patterns). Это то, что оболочка использует для расширения имени файла (подстановка, 'globbing'). В этом случае см. ответ 5.1.1.

Регулярные выражения (regex). Это специальные строки для создания шаблонов совпадения текста, используемые утилитами grep, gawk, sed и другими. Однако они обычно не относятся к именам файлов. Для сравнения строк с помощью регулярных выражений см. ответ 5.1.2.

5.1.1 Как сравнивать строки, используя шаблоны имени файла?
-----------------------------------------------------------

За исключением случаев, когда не повезет, ваша система должна содержать функцию fnmatch() для сопоставления имени файла. Она в основном позволяет использовать wildcards для имен файлов стиля оболочки Bourne shell. Т. е. она распознает '*', '[...]' и '?', однако вероятно не будет распознавать более замысловатые шаблоны, доступные в оболочках Korn shell и Bourne-Again shell.

Если у все нет этой функции, то вместо того, чтобы изобретать велосипед, вам лучше прошерстить исходный код BSD или GNU.

Кроме того, для общих случаев сопоставления реальных имен файлов рассмотрите функцию glob(), которая найдет все существующие файлы, совпавшие с шаблоном.

5.1.2 Как сравнивать строки с помощью регулярных выражений?
-----------------------------------------------------------

Существует несколько незначительно отличающихся друг от друга вариантов синтаксиса регулярных выражений (regex). Большинство систем используют как минимум два: один распознается с помощью ed, и иногда его называют 'Basic Regular Expressions', и другой распознается с помощью egrep, 'Extended Regular Expressions'. У языка Perl есть свой собственный, несколько отличающийся принцип построения regex, как в Emacs.

Чтобы для поддержки этого множества форматов существует соответствующее множество реализаций. В системах чаще всего есть предоставленные функции для regex-совпадений (обычно regcomp() и regexec()), однако есть отличия. В некоторых системах есть больше одного варианта реализаций этих функций, с различными интерфейсами. Дополнительно существуют многие библиотечные реализации (кстати, обычно regex-выражения компилируются во внутреннюю форму перед использованием в предположении, что вы можете сравнить несколько отдельных строк с одним и тем же выражением regex).

Одна из доступных библиотек это rx, которая есть на GNU зеркалах. Эта библиотека выглядит как находящаяся в активной разработке, так что с вашей точки зрения она может быть как хорошим, так и плохим выбором.

5.2 Какой самый лучший способ послать email из программы?
=========================================================

Существует несколько способов отправки email из Unix-программы. Какой из них лучший - зависит от ситуации, далее представлены два из них. Третья возможность, которая здесь не рассматривается - подключение к локальному порту SMTP (или smarthost) и использование протокола SMTP напрямую, см. RFC 821.

5.2.1 Простой метод: /bin/mail
------------------------------

Для простых реализаций может быть достаточным запустить mail (обычно /bin/mail, но на некоторых системах это может быть /usr/bin/mail).

ПРЕДУПРЕЖДЕНИЕ: некоторые версии UCB Mail могут выполнять команды с префиксом '~!' или '~|', указанном в теле сообщения, даже в не интерактивном режиме. Это может представлять угрозу для безопасности.

Вызов mail -s 'subject' recipients... принимает тело сообщения из стандартного ввода, и предоставляет заголовок по умолчанию (включающий указанную тему сообщения, subject), и передает созданное сообщение в sendmail для доставки.

Следующий пример отправит тестовое email-сообщение пользователю root на локальной системе:

   #include < stdio.h>
   
   #define MAILPROG "/bin/mail"
   
   int main()
   {
      FILE *mail = popen(MAILPROG " -s 'Test Message' root", "w");
      if (!mail)
      {
         perror("popen");
         exit(1);
      }
   
      fprintf(mail, "This is a test.\n");
   
      if (pclose(mail))
      {
         fprintf(stderr, "mail failed!\n");
         exit(1);
      }
   }

Если отправляемый текст уже представлен в файле, то можно сделать следующее:

         system(MAILPROG " -s 'file contents' root < /tmp/filename");

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

   * Если используются system() или popen(), то следует быть очень осторожным с заключением аргументов в скобки, чтобы защитить их от расширения имени файла (filename expansion) или разбиения слова (word splitting).
   * Составления строки команды из ввода пользователя это наиболее частый случай возникновения ошибок переполнения буфера и других дыр в безопасности.
   * Этот метод не позволяет указывать получателей CC: или BCC: (некоторые версии /bin/mail это позволяют, некоторые нет).

5.2.2 Прямое обращение к MTA: /usr/lib/sendmail
-----------------------------------------------

Программа mail это пример пользовательского почтового агента, "Mail User Agent", т. е. это программа, предназначенная для запуска пользователем с целью отправки и получения почты, но которая не обрабатывает фактический транспорт. Программа для транспорта почты называется "MTA", а наиболее часто встречающийся MTA в Unix-системах называется sendmail. Существуют другие используемые MTA, такие как MMDF, но они обычно включают в себя программу, которая эмулирует обычное поведение sendmail.

Исторически sendmail можно обычно найти в /usr/lib, однако существует текущая тенденция переносить библиотечные программы из /usr/lib в такие каталоги, как /usr/sbin или /usr/libexec. В результате обычно приходится вызвать sendmail с указанием полного пути, зависящего от системы.

Чтобы понять поведение sendmail, полезно иметь представление о концепции "envelope" (конверт). Это очень похоже на бумажное письмо; конверт envelope определяет, кому должно быть доставлено сообщение и от кого оно (с целью сообщения об ошибках). В конверте содержатся "заголовки" (headers) и "тело письма" (body), отделенные друг от друга пустой строкой. Формат заголовков определяется прежде всего RFC 822; см. также RFC MIME.

Существует два основных способа использования sendmail для создания сообщения: либо получатели конверта могут быть явно предоставлены, либо sendmail может быть проинструктирован сформировать их из заголовков сообщения. Оба метода имеют свои преимущества и недостатки.

5.2.2.1 Явное предоставление конверта
.....................................

Получатели сообщения могут быть просто указаны в командной строке. Недостаток этого метода заключается в том, что email-адреса могут содержать символы, которые заставляют огорчаться функции system() и popen(), такие как одинарные кавычки, строки в двойных кавычках и т. п. Успешное прохождение таких конструкций через интерпретацию шелла доставляет проблемы. Можно решить проблему, применяя символы экранирования в виде последовательности обратной косой черты (backslash) одиночной кавычки и еще одной одиночной кавычки, затем обрамить весь адрес еще одними одиночными кавычками... Некрасиво, правда?

Некоторые из подобных неприятностей можно преодолеть, отказавшись от использования system() или popen(), и прибегая непосредственно к fork() и exec(). Иногда это необходимо для любого случая, например установленные пользователем обработчики SIGCHLD обычно будут обрывать pclose() в большей или меньшей степени.

Вот пример:

   #include < sys/types.h>
   #include < sys/wait.h>
   #include < unistd.h>
   #include < stdlib.h>
   #include < fcntl.h>
   #include < sysexits.h>
   /* #include < paths.h> if you have it */
   
   #ifndef _PATH_SENDMAIL
   #define _PATH_SENDMAIL "/usr/lib/sendmail"
   #endif
   
   /* -oi означает "не обрабатывать . как терминатор сообщения"
    * удалите ,"--" если используете pre-V8 sendmail (и есть надежда,
    * что никогда не используется адрес получателя, начинающийся
    * с дефиса). Сюда также можно добавить опцию -oem (сообщать
    * об ошибках через email)
    */
   #define SENDMAIL_OPTS "-oi","--"
   
   /* Макрос для возврата количества элементов в массиве: */
   #define countof(a) ((sizeof(a))/sizeof((a)[0]))
   
   /* Функция посылает содержимое открытого файла по дескриптору fd указанным
    * получателям recipients; подразумевается, что файл содержит заголовки RFC822
    * и тело сообщения, а список получателей завершается указателем NULL.
    * Будет возвращена -1 в случае ошибки, иначе будет возвращено значение
    * из sendmail (использующее < sysexits.h> для предоставления смысла кода
    * выхода)
    */
   int send_message(int fd, const char **recipients)
   {
      static const char *argv_init[] = { _PATH_SENDMAIL, SENDMAIL_OPTS };
      const char **argvec = NULL;
      int num_recip = 0;
      pid_t pid;
      int rc;
      int status;
   
      /* Подсчет количества получателей: */
      while (recipients[num_recip])
         ++num_recip;
   
      if (!num_recip)
         return 0;    /* если нет получателей, то отправка считается успешной */
   
      /* Выделение памяти для вектора аргумента */
      argvec = malloc((sizeof char*) * (num_recip+countof(argv_init)+1));
      if (!argvec)
         return -1;
   
      /* Инициализация вектора аргумента */
      memcpy(argvec, argv_init, sizeof(argv_init));
      memcpy(argvec+countof(argv_init),
             recipients, num_recip*sizeof(char*));
      argvec[num_recip + countof(argv_init)] = NULL;
   
      /* Здесь может понадобиться добавление сигнала блокировки. */
      switch (pid = fork())
      {
      case 0:   /* дочерний процесс */
         /* Дополнительная проверка */
         if (fd != STDIN_FILENO)
            dup2(fd, STDIN_FILENO);
   
         /* Определенная где-нибудь функция, закрывающая все FD, которые
            больше или равны аргументу */
         closeall(3);
   
         /* Запуск действия по отправке: */
         execv(_PATH_SENDMAIL, argvec);
         _exit(EX_OSFILE);
   
      default:  /* родительский процесс */
         free(argvec);
         rc = waitpid(pid, &status, 0);
         if (rc < 0)
            return -1;
         if (WIFEXITED(status))
            return WEXITSTATUS(status);
         return -1;
   
      case -1:  /* ошибка */
         free(argvec);
         return -1;
      }
   }

5.2.2.2 Как разрешить sendmail определить получателей
.....................................................

Опция -t для sendmail дает для неё указание выполнить парсинг заголовков в сообщении, и использовать все заголовки recipient-типа (т. е. 'To:', 'Cc:' и 'Bcc:') для конструирования списка получателей конверта. Достоинство этого метода в том, что упрощается командная строка для sendmail, однако делает невозможным указание получателей, которые не перечислены в заголовках (хотя обычно это не является проблемой).

В качестве примера ниже показана программа, отправляющая файл по email из стандартного ввода указанным получателям как вложение (MIME attachment). Для краткости некоторые проверки на ошибки в приведенном примере опущены. Этот код требует наличия программы mimencode из дистрибутива metamail.

   #include < stdio.h>
   #include < unistd.h>
   #include < fcntl.h>
   /* #include < paths.h>, если он у вас есть */
   
   #ifndef _PATH_SENDMAIL
   #define _PATH_SENDMAIL "/usr/lib/sendmail"
   #endif
   
   #define SENDMAIL_OPTS "-oi"
   #define countof(a) ((sizeof(a))/sizeof((a)[0]))
   
   char tfilename[L_tmpnam];
   char command[128+L_tmpnam];
   
   void cleanup(void)
   {
      unlink(tfilename);
   }
   
   int main(int argc, char **argv)
   {
      FILE *msg;
      int i;
   
      if (argc < 2)
      {
         fprintf(stderr, "usage: %s recipients...\n", argv[0]);
         exit(2);
      }
   
      if (tmpnam(tfilename) == NULL
          || (msg = fopen(tfilename,"w")) == NULL)
         exit(2);
   
      atexit(cleanup);
   
      fclose(msg);
      msg = fopen(tfilename,"a");
      if (!msg)
         exit(2);
   
      /* Конструирование списка получателей */
      fprintf(msg, "To: %s", argv[1]);
      for (i = 2; i < argc; i++)
         fprintf(msg, ",\n\t%s", argv[i]);
      fputc('\n',msg);
   
      /* Тема письма */
      fprintf(msg, "Subject: file sent by mail\n");
   
      /* sendmail может добавить свои собственные From:, Date:, Message-ID: и т. п. */
   
      /* Элемент MIME */
      fprintf(msg, "MIME-Version: 1.0\n");
      fprintf(msg, "Content-Type: application/octet-stream\n");
      fprintf(msg, "Content-Transfer-Encoding: base64\n");
   
      /* Конец заголовков, вставка пустой строки */
      fputc('\n',msg);
      fclose(msg);
   
      /* Запуск программы кодирования */
      sprintf(command, "mimencode -b >>%s", tfilename);
      if (system(command))
         exit(1);
   
      /* Запуск почтовика */
      sprintf(command, "%s %s -t < %s",
              _PATH_SENDMAIL, SENDMAIL_OPTS, tfilename);
      if (system(command))
         exit(1);
   
      return 0;
   }

[Ссылки]

1. Unix Programming FAQ (v1.37) site:opennet.ru.
2. FAQ программирования Linux: управление процессами.
3Опции GCC для поддержки отладки.

 

Добавить комментарий


Защитный код
Обновить

Top of Page