Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Expect: диалог с терминальной программой и наследуемый API
  2. Зачем нужен диалог с процессом через PTY
  3. Требования, установка и честная граница PHP8
  4. Все настройки: время, буфер и журналирование
  5. Полностью представленный локальный собеседник
  6. Шаблоны, метки, выходные группы и все константы
  7. expect://: файловая оболочка вокруг процесса
  8. Ошибки диалога, безопасность и современные альтернативы
  9. Развить пример и выбрать подходящий интерфейс
  10. Функции и методы этой темы
  11. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 143 · Сопровождение терминальных интеграций

Expect: диалог с терминальной программой и наследуемый API

Полный раздел PECL Expect: две функции, все настройки и константы, PTY, локальный диалог и точная граница поддержки PHP.

Зачем нужен диалог с процессом через PTY

У старой локальной складской программы нет API: она печатает «SKU?», ждёт введённый артикул и отвечает «STOCK 7». Простая команда exec с готовой строкой не описывает весь такой диалог. Expect даёт программе псевдотерминал (PTY) и позволяет ждать определённый текст перед отправкой следующего ответа. Польза — автоматизация известного интерактивного протокола там, где действительно нет удобного программного интерфейса.

Учебный диалог без внешнего сервера
  1. PHP открыл PTY и запустил свой stock-prompt.php
  2. Дочерний процесс напечатал SKU?
  3. expect_expectl узнал приглашение
  4. PHP отправил A42 и перевод строки
  5. Дочерний процесс напечатал STOCK 7
  6. PHP извлёк 7 и завершил поток

PTY — пара концов виртуального терминала. Дочерняя программа воспринимает свою сторону как терминал; родитель читает вывод и отправляет ввод. Это отличается от HTTP запроса и обычного файла: возможны эхо ввода, переводы строк CRLF, управляющие последовательности и приглашение без завершающего . «Ждать две секунды через sleep» не означает дождаться приглашения; нужен условный переход по фактическому выводу.

Сначала понять переходы состояния без запуска процессаСамостоятельный пример
<?php
declare(strict_types=1);

$output = 'SKU? ';
$state = $output === 'SKU? ' ? 'send_sku' : 'unexpected';
if ($state === 'send_sku') echo "Следующий шаг: отправить A42 и перевод строки\n";
Ожидаемый результат
Следующий шаг: отправить A42 и перевод строки

Это маленькая модель выбора следующего действия, а не эмуляция Expect. В настоящем PTY текст приходит частями, и совпадение ищет библиотека. Подготовка: функции, ветвления, потоки.

Требования, установка и честная граница PHP8

Статус версии. Полный раздел сохраняет API из официального Manual, включая наследуемые расширения. На проверенной странице PECL последний опубликованный stable Expect — 0.4.0 от 13 января 2020 года; его metadata задаёт PHP не выше 7.99.99. Поэтому наличие страницы в Manual PHP8 не доказывает готовность этой версии расширения для PHP8.5.11. Здесь не предлагается понижать рабочий сайт до PHP7 и не заявляется, что пакет проверен на PHP8. Примеры служат для чтения/сопровождения существующих совместимых установок и изолированного стенда.

Расширение не входит в штатный PHP. Официальные требования: libexpect не ниже 5.43.0 и Tcl; при сборке нужны соответствующие development headers и совпадающие версии phpize/php-config/PHP ABI. Историческая configure опция — --with-expect[=DIR]. Установленный бинарник expect или libexpect.so сам по себе не добавляет PHP функцию expect_popen. PHP модуль загружается в ini конкретного SAPI. Официальная инструкция упоминает php_expect.dll, но готовый Windows DLL пакет там недоступен: это не рабочий шаг «включите отсутствующий файл».

Проверить именно PHP функции и wrapperПример для указанного окружения
<?php
declare(strict_types=1);

echo 'Модуль: ', extension_loaded('expect') ? 'есть' : 'нет', "\n";
echo 'Функция: ', function_exists('expect_popen') ? 'есть' : 'нет', "\n";
echo 'Wrapper: ', in_array('expect', stream_get_wrappers(), true) ? 'есть' : 'нет', "\n";
Ожидаемый результат
Три строки с фактической доступностью модуля, функции и wrapper.

Можно выполнить как диагностику в CLI и отдельно в используемом SAPI. php --ini и php -m помогают проверить CLI; они не описывают автоматически конфигурацию PHP-FPM. Пример не запускает процесс. Контекст: изолированный Unix стенд с совместимой исторической сборкой PECL Expect и libexpect. Здесь проверен только PHP синтаксис; PTY и PHP8 совместимость не заявляются.

expect_popen возвращает stream resource. Он передаётся в expect_expectl, fwrite и fclose; у этой книги нет новых классов PHP8 вместо него. Для проекта на современном PHP сначала ищите API, batch/JSON режим нужной программы, затем управляемый запуск через функции запуска процессов. Если интерфейс действительно только терминальный, выбирайте поддерживаемую реализацию PTY/Expect как отдельный проверяемый компонент, с ограниченным временем и правами.

Все настройки: время, буфер и журналирование

Директива, INI_ALLПоведение
expect.timeoutВ секундах; default10. -1 отключает timeout, 0 возвращает управление немедленно. Ограничивает ожидание expect_expectl, не весь жизненный цикл процесса.
expect.loguserDefault1: вывод дочернего процесса идёт в stdout. Терминальное эхо может включать введённые значения.
expect.logfileDefault пусто; иначе вывод пишется в файл независимо от loguser. Файл может создаться; в release0.4.0 открыт на добавление.
expect.match_maxРазмер буфера сопоставления. В Manual упомянут базовый libexpect размер2000 байт, но source релиза0.4.0 регистрирует default5000. Проверяйте ini_get своей сборки и задавайте обоснованный лимит явно.

В учебном диалоге ставим timeout2, loguser0, logfile пусто. Это предотвращает смешивание терминального вывода с нашим отчётом. Обработка timeout обязательна даже при ограничении в настройке. Для долгого job добавьте отдельный общий deadline и политику остановки дочернего процесса: новый expect вызов снова может ждать свой timeout. loguser0 не отключает logfile; не записывайте пароли/токены в терминальный transcript.

Назначить настройки до открытия учебного PTYПример для указанного окружения
<?php
declare(strict_types=1);

if (!extension_loaded('expect')) throw new RuntimeException('Модуль не загружен');
foreach (['expect.timeout' => '2', 'expect.loguser' => '0', 'expect.logfile' => '', 'expect.match_max' => '4096'] as $key => $value) {
    if (ini_set($key, $value) === false) throw new RuntimeException('Не изменена настройка ' . $key);
}
echo "Ожидание ограничено, transcript отключён\n";
Ожидаемый результат
Ожидание ограничено, transcript отключён

Проверяется результат изменения. В библиотеке сохраняйте предыдущие значения и восстанавливайте их в finally, чтобы настройки одного диалога не влияли на другой. Контекст: изолированный Unix стенд с совместимой исторической сборкой PECL Expect и libexpect. Здесь проверен только PHP синтаксис; PTY и PHP8 совместимость не заявляются.

Полностью представленный локальный собеседник

Для примеров создаём только собственную учебную программу. Она не авторизуется на сервере, не меняет файлы и не выполняет присланные команды. Через три секунды без ввода она завершится с NO_INPUT, чтобы ошибочный тест не удерживал процесс бесконечно. Сам Expect тоже ограничен timeout2.

Файл stock-prompt.php: понятный интерактивный собеседникПример для указанного окружения
<?php
fwrite(STDOUT, 'SKU? ');
fflush(STDOUT);
$read = [STDIN]; $write = []; $except = [];
$ready = stream_select($read, $write, $except, 3);
if ($ready !== 1) { fwrite(STDOUT, "NO_INPUT\n"); exit(2); }
$line = fgets(STDIN);
if ($line === false) { fwrite(STDOUT, "NO_INPUT\n"); exit(2); }
$sku = trim($line);
fwrite(STDOUT, $sku === 'A42' ? "STOCK 7\n" : "UNKNOWN\n");
Ожидаемый результат
После ввода A42: STOCK 7; другой артикул: UNKNOWN; без ввода до трёх секунд: NO_INPUT.

Программа выводит приглашение без и явно fflush. stream_select ожидает доступность входа; fgets получает строку после Enter. Этот файл приложен ко всем примерам диалога автоматически. Контекст: изолированный Unix стенд с совместимой исторической сборкой PECL Expect и libexpect. Здесь проверен только PHP синтаксис; PTY и PHP8 совместимость не заявляются.

Команда строится из фиксированного PHP_BINARY и известного пути к этому файлу. На Unix каждый аргумент экранируется escapeshellarg, потому что expect_popen выполняет строку через Bourne shell. Не подставляйте целую команду из $_GET/$_POST. Даже корректное quoting не заменяет ограничение разрешённых действий: пользовательский выбор должен отображаться на заранее известную команду, а не на произвольный исполняемый файл.

Шаблоны, метки, выходные группы и все константы

Один вызов ожидания получает список вариантов. Каждый вариант — массив из шаблона, возвращаемой метки и необязательного вида шаблона. Массив cases делайте плотным списком 0,1,2…; если отфильтровали, примените array_values. Используйте уникальные строковые метки, которые не спутать с числовыми кодами EOF/timeout. Успех совпадения не означает успех всей бизнес-операции: «SKU?» только разрешает отправить артикул.

Все шесть константНазначение
EXP_GLOBШаблон glob; default: * — последовательность, ? — один символ, [] — класс. Это не PCRE.
EXP_EXACTБуквальное совпадение строки; * и ? не становятся шаблонами. Удобно для SKU?.
EXP_REGEXPРегулярное выражение libexpect, без PHP delimiters /.../. Не обещает все PCRE возможности.
EXP_EOFКонец потока до нужного совпадения. Это событие чтения, не проверенный exit code дочерней программы.
EXP_TIMEOUTЗа время expect.timeout нужное совпадение не найдено.
EXP_FULLBUFFERБуфер заполнен без нужного совпадения; нужно проверить протокол/границы/лимит, не бесконечно наращивать память.

Случаи завершения проверяются явно и сравниваются строго. Не пишите if ($result): метка может быть 0 или пустой строкой, а отрицательный служебный код тоже truthy. Перекрывающиеся шаблоны делают поведение хрупким; держите набор подходящим текущему состоянию. match[0] — совпавший фрагмент, не весь transcript; группы regex находятся в match[1]…match[9] с ограничением libexpect.

expect://: файловая оболочка вокруг процесса

Альтернативный вход — fopen("expect://command", "r"). Это всё равно запуск команды через PTY, а не чтение удалённого URL. Wrapper не включён без расширения и не ограничивается allow_url_fopen. Поэтому выключение одной ini директивы не делает непроверенный путь пользователя безопасным.

Возможность wrapper по официальной таблицеПоддержка
Чтение / запись / добавлениеДа. Запись — ввод процессу, не сохранение файла.
Simultaneous reading/writing как файловый режимВ таблице отмечено No; не выводите из этого отсутствие диалога: официальный способ выполняет ожидание и fwrite последовательно через PTY. Не предполагается семантика произвольного r+ файла.
stat/unlink/rename/mkdir/rmdirНет.
allow_url_fopenНе является ограничителем expect://.
Открыть короткую фиксированную команду через wrapperПример для указанного окружения
<?php
declare(strict_types=1);

if (!in_array('expect', stream_get_wrappers(), true)) throw new RuntimeException('Нет wrapper');
ini_set('expect.loguser', '0');
ini_set('expect.timeout', '2');
$stream = fopen("expect://printf 'READY\\n'", 'r');
if ($stream === false) throw new RuntimeException('Не открыт PTY');
try {
    $state = expect_expectl($stream, [['READY', 'ready', EXP_EXACT]]);
    if ($state !== 'ready') throw new RuntimeException('Не получен READY');
    echo "Учебная программа готова\n";
} finally { fclose($stream); }
Ожидаемый результат
Учебная программа готова

printf — фиксированная локальная Bourne shell команда с немедленным завершением. Даже строка с expect:// может исполнять код; не допускайте произвольные схемы в параметрах импорта файла. Контекст: изолированный Unix стенд с совместимой исторической сборкой PECL Expect и libexpect. Здесь проверен только PHP синтаксис; PTY и PHP8 совместимость не заявляются.

Ошибки диалога, безопасность и современные альтернативы

Для старого SSH/SCP диалога меняются приглашения, локаль, баннер, prompt, host verification и права пользователя сервера. Надёжный код моделирует ограниченное число переходов, обрабатывает отказ аутентификации/неизвестное состояние и прекращает действие. Нельзя автоматически отвечать yes на неизвестный SSH host key ради прохождения примера: доверие проверяется заранее. Для передачи файлов предпочтителен программный SFTP/SSH клиент с проверкой сервера; для новой утилиты — аргументы или JSON stdin вместо автоматизации меню.

СимптомПроверка и решение
Работает в терминале, зависает из вебаДругой пользователь, HOME, known_hosts, PATH, ini, доступ к PTY; новое приглашение не входит в cases. Не отключать timeout.
EOF до ожидаемого ответаПроцесс мог отказаться/не запуститься/завершиться; не объявлять бизнес-успех только по EOF.
stdout испортил JSON ответ сайтаloguser и терминальное эхо; libexpect пишет в stdout, ob_start не гарантирует перехват чужого низкоуровневого вывода.
FULLBUFFERВывод не соответствует ожидаемому протоколу, слишком широкий/неверный шаблон или обоснованно длинный ответ; задайте предел и политику отказа.
Undefined functionНаличие системного expect не означает PHP module; проверить ABI и SAPI ini.
Процесс остался после timeoutTimeout ожидания не является kill. fclose может ждать завершения; нужен общий supervisor/deadline и политика остановки своих дочерних процессов.

Стоимость определяется блокирующим ожиданием, числом переходов, объёмом вывода и сложностью шаблонов. Не держите HTTP worker минутами ради интерактивного меню. Для фоновой задачи храните job ID/состояние отдельно и запускайте управляемый процесс под минимальными правами. Для больших выгрузок терминальный интерфейс особенно неудобен: структурированный API обычно легче ограничить по объёму, протестировать и обработать после отказа.

Развить пример и выбрать подходящий интерфейс

Задание 1: отправьте B17 вместо A42. Решение: обрабатывайте UNKNOWN отдельной меткой, а не как timeout. Задание 2: замените приглашение на «Артикул?». Решение: обновить согласованный протокол или точный шаблон; ожидание старого SKU? будет ошибкой. Задание 3: дочерний процесс выводит STOCK 12. Решение: EXP_REGEXP извлечёт группу 12; проверка бизнес-диапазона остаётся в приложении. Задание 4: прекратите ввод. Решение: EXP_TIMEOUT и NO_INPUT означают разные события родителя и ребёнка; общего успешного результата нет.

Полный официальный раздел включён, но исторический пакет не становится современным от переписывания примера. Для нового проекта сначала оцените exec/proc_open и передачу данных другой программе, HTTP API и sockets. Для чтения терминального кода пригодятся состояния и ветвления, различие синтаксисов шаблонов и владение потоками.

Функции и методы этой темы

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

expect_popenЗапустить управляемый терминальный диалог через Bourne shell.

PECL expect 0.1.0+ · PECL expect (legacy)

expect_popen(string $command): resource|false

Запустить управляемый терминальный диалог через Bourne shell.

API относится к версии PECL и требованиям среды. Сначала разберите учебный диалог SKU? → A42 → STOCK 7. Скобки вызова передают параметры; возвращённая метка выбирает следующий шаг программы, а не исполняет его автоматически.

ДоговорЗначение
commandСтрока shell команды. Исполняемый файл/действие фиксируются приложением; отдельные аргументы экранируются для Unix shell.
РезультатPTY stream resource либо false при отказе открытия. Успешный stream не доказывает успешный exit команды.
ОперацииОжидание expect_expectl, ввод fwrite, освобождение fclose; seek не предполагается.
ГраницаСамостоятельный deadline/остановка дочернего процесса и версия расширения требуют отдельного договора.
expect_popen: шаги диалога
  1. Фиксированный PHP_BINARY + stock-prompt.php
  2. popen → PTY stream
  3. wait SKU?
  4. write A42 и перевод строки
  5. wait STOCK7 → результат

Утилита запрашивает данные через терминал. expect_popen запускает указанную строку через Bourne shell и возвращает PTY stream для ввода и общего терминального вывода. Это не обычный однонаправленный popen: можно дождаться приглашения и ответить. stdout/stderr здесь не являются двумя независимыми PHP pipes; если вам нужно разделить каналы, выбирайте подходящий process API.

Дождаться известного вывода короткой программыПример для указанного окружения
<?php
declare(strict_types=1);

ini_set('expect.loguser', '0');
ini_set('expect.timeout', '2');
$stream = expect_popen("printf 'READY\\n'");
if ($stream === false) throw new RuntimeException('Не создан PTY');
try {
    $state = expect_expectl($stream, [['READY', 'ready', EXP_EXACT]]);
    if ($state !== 'ready') throw new RuntimeException('Нет ожидаемого вывода');
    echo "READY получен\n";
} finally { fclose($stream); }
Ожидаемый результат
READY получен

Переменная $stream — ресурс связи, не текст результата. expect_expectl читает его и выбирает метку ready. Команда фиксирована, завершается сама, sleep не нужен. Контекст: изолированный Unix стенд с совместимой исторической сборкой PECL Expect и libexpect. Здесь проверен только PHP синтаксис; PTY и PHP8 совместимость не заявляются.

Отправить артикул только после приглашенияПример для указанного окружения
<?php
declare(strict_types=1);

if (!extension_loaded('expect')) throw new RuntimeException('Нужна совместимая сборка Expect');
ini_set('expect.timeout', '2');
ini_set('expect.loguser', '0');
ini_set('expect.logfile', '');
$command = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(__DIR__ . '/stock-prompt.php');
$stream = expect_popen($command);
if ($stream === false) throw new RuntimeException('Не создан PTY');
try {
    $prompt = expect_expectl($stream, [['SKU? ', 'prompt', EXP_EXACT]]);
    if ($prompt !== 'prompt') throw new RuntimeException('Не получено приглашение');
    $input = "A42\n";
    if (fwrite($stream, $input) !== strlen($input)) throw new RuntimeException('Ввод отправлен не целиком');
    $result = expect_expectl($stream, [['STOCK 7', 'stock', EXP_EXACT], ['UNKNOWN', 'unknown', EXP_EXACT]]);
    if ($result === 'stock') echo "A42: осталось 7\n";
    elseif ($result === 'unknown') echo "Артикул неизвестен\n";
    else throw new RuntimeException('Диалог не завершён ожидаемым ответом');
} finally { fclose($stream); }
Ожидаемый результат
A42: осталось 7

Передача завершает ввод строки. Если приглашение не найдено, код не отправляет артикул вслепую. Дочерняя учебная программа имеет собственный предел ожидания; это существенно, потому что fclose не является универсальным принудительным kill. Контекст: изолированный Unix стенд с совместимой исторической сборкой PECL Expect и libexpect. Здесь проверен только PHP синтаксис; PTY и PHP8 совместимость не заявляются.

Связь: expect_expectl.

expect_expectlЖдать нужный текст и получить метку события либо код завершения ожидания.

PECL expect 0.1.0+ · PECL expect (legacy)

expect_expectl(resource $expect, array $cases, array &$match = []): mixed

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

API относится к версии PECL и требованиям среды. Сначала разберите учебный диалог SKU? → A42 → STOCK 7. Скобки вызова передают параметры; возвращённая метка выбирает следующий шаг программы, а не исполняет его автоматически.

ДоговорЗначение
expectPTY stream, открытый Expect. Обычный HTTP/file ресурс не заменяет его.
casesПлотный список индексных массивов [pattern, returnValue, optionalType].
matchНеобязательная выходная переменная; заполняется совпадением/группами. Сбрасывать до новой операции.
РезультатAssociated value; иначе EXP_EOF/EXP_TIMEOUT/EXP_FULLBUFFER. Не проверять одним if(result).
Ожиданиеexpect.timeout ограничивает отдельный вызов; не останавливает весь дочерний процесс.
expect_expectl: шаги диалога
  1. Получены cases для текущего шага
  2. Вывод копится до совпадения/отказа
  3. STOCK7 даёт label stock и match[1]=7
  4. PHP if выбирает следующее действие

Это ожидание одного из событий диалога, а не выполнение команды. Вы передаёте поток и варианты того, что ожидаете сейчас. Функция возвращает выбранную вами метку; ваш if/switch после неё отправляет ответ или прекращает операцию. В краткой странице Manual return указан int, но cases допускает mixed, и source0.4.0 возвращает associated value без принудительного int: строковые метки работают по этому договору.

Извлечь остаток регулярным выражением libexpectПример для указанного окружения
<?php
declare(strict_types=1);

if (!extension_loaded('expect')) throw new RuntimeException('Нужна совместимая сборка Expect');
ini_set('expect.timeout', '2');
ini_set('expect.loguser', '0');
ini_set('expect.logfile', '');
$command = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(__DIR__ . '/stock-prompt.php');
$stream = expect_popen($command);
if ($stream === false) throw new RuntimeException('Не создан PTY');
try {
    if (expect_expectl($stream, [['SKU? ', 'prompt', EXP_EXACT]]) !== 'prompt') throw new RuntimeException('Нет prompt');
    if (fwrite($stream, "A42\n") !== 4) throw new RuntimeException('Короткий ввод');
    $matches = [];
    $state = expect_expectl($stream, [
        ['STOCK ([0-9]+)', 'stock', EXP_REGEXP],
        ['UNKNOWN', 'unknown', EXP_EXACT]
    ], $matches);
    if ($state !== 'stock' || !isset($matches[1])) throw new RuntimeException('Нет значения остатка');
    echo 'Совпало: ', $matches[0], "\n";
    echo 'Остаток: ', $matches[1], "\n";
} finally { fclose($stream); }
Ожидаемый результат
Совпало: STOCK 7
Остаток: 7

Шаблон без /.../: это libexpect regex, не preg_match. Скобки выделили цифры в группу1. Match массив передан по ссылке и заполняется библиотекой; перед каждым независимым вызовом его сбрасывают, чтобы не прочитать старое совпадение после отказа. Контекст: изолированный Unix стенд с совместимой исторической сборкой PECL Expect и libexpect. Здесь проверен только PHP синтаксис; PTY и PHP8 совместимость не заявляются.

Разобрать EOF, timeout и полный буфер раздельноПример для указанного окружения
<?php
declare(strict_types=1);

ini_set('expect.loguser', '0');
ini_set('expect.timeout', '2');
$stream = expect_popen("printf 'UNKNOWN\\n'");
if ($stream === false) throw new RuntimeException('Не создан PTY');
try {
    $matches = [];
    $state = expect_expectl($stream, [['STOCK *', 'stock', EXP_GLOB]], $matches);
    if ($state === 'stock') echo "Найден ответ по шаблону\n";
    elseif ($state === EXP_EOF) echo "Программа завершила вывод без STOCK\n";
    elseif ($state === EXP_TIMEOUT) echo "Ответ не пришёл вовремя\n";
    elseif ($state === EXP_FULLBUFFER) echo "Вывод превысил буфер без совпадения\n";
    else throw new RuntimeException('Непредусмотренное состояние Expect');
} finally { fclose($stream); }
Ожидаемый результат
Программа завершила вывод без STOCK

Эта короткая программа выводит UNKNOWN и сразу завершается, поэтому ожидается EOF, не timeout. Все остальные отказы всё равно перечислены. EOF не содержит exit code и не подтверждает правильность результата. Контекст: изолированный Unix стенд с совместимой исторической сборкой PECL Expect и libexpect. Здесь проверен только PHP синтаксис; PTY и PHP8 совместимость не заявляются.

Элемент одного caseСинтаксис и смысл
0: patternОбязательная строка; например SKU? либо STOCK ([0-9]+).
1: valueОбязательная возвращаемая метка; желательно уникальная строка.
2: typeНеобязательно: EXP_GLOB default, EXP_EXACT или EXP_REGEXP.
Третий аргумент функции: &matchВыходной массив. [0] совпавший текст, [1]…[9] группы. До PECL0.2.1 возвращалась строка, не массив групп.

Ошибочный cases формат в историческом расширении может привести к PHP ошибке, а не удобному TypeError современного API: не создавайте шаблоны и type из непроверенного пользовательского ввода. FULLBUFFER означает отсутствие совпадения при заполненном буфере, а не любое временное отсутствие текста. Совпадение могло потребить часть вывода; чтение тем же stream через fgets конкурирует с Expect за байты. Не вставляйте вторую независимую читалку «для логов»: используйте согласованное журналирование без секретов.

Связь: expect_popen.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.