Я хочу знать, как я могу взаимодействовать с программой, которую я запускаю в сценарии PHP командной строки. Сценарий таков:
Как я могу написать скрипт PHP, который делает это? Я думаю, что я, вероятно, хочу использовать proc_open()
но я не могу понять, как. Я думаю, что это будет что-то вроде этого, но это, конечно, не работает:
$descriptorspec = array(
0 => array('pipe', 'r'), //STDIN
1 => array('pipe', 'w'), //STDOUT
2 => array('pipe', 'r'), //STDERR
);
$process = proc_open('mycommand', $descriptorspec, $pipes, null, null);
if (is_resource($process)) {
// Get output until first question is asked
while ($buffer = fgets($pipes[1])) {
echo $buffer;
}
if (strpos($buffer, 'STEP 1:') !== false) {
fwrite($pipes[0], "My first answer\n"); //enter the answer
} else {
die('Unexpected last line before question');
}
// Get output until second question is asked
while ($buffer = fgets($pipes[1])) {
echo $buffer;
}
if (strpos($buffer, 'STEP 2:') !== false) {
fwrite($pipes[0], "My second answer\n"); //enter the answer
} else {
die('Unexpected last line before question');
}
// ...and so we continue...
} else {
echo 'Not a resource';
}
ОБНОВИТЬ: Я понял, что программа выводит вопросы в STDERR (потому что записывает STDOUT в файл).
Надеемся, что следующего примера, основанного на приведенном выше коде, будет достаточно для начала работы.
Я протестировал его на Linux, хотя вы не указали, какую операционную систему вы использовали,
так что ваш пробег может отличаться, если вы работаете с чем-то другим.
Поскольку команда передается другому процессу, выходные данные будут буферизованы.
Это означает, что мы не будем получать приглашения, потому что буфер всегда ждет
перевод строки перед отправкой текущей строки и подсказки не сопровождаются
переводы строки.
Спасибо Крису и ignomueller.net«s
ответы на Обманите приложение, заставив его думать, что его стандартный ввод интерактивный, а не труба
мы можем запутать команду в том, что она говорит с терминалом, передав
это как аргумент script
, который генерирует машинописный текст вывода команды.
Сохраняя машинописный текст в / dev / null, сбрасывая вывод после каждой записи (-f
) а также
исключая стартовые и готовые сообщения (-q
) это означает, что мы можем читать подсказки, как они
выводятся.
Вы указали STDOUT из команды должен быть отправлен в файл, тогда как
вопросы на STDERR. Это дополнительное осложнение, потому что
Кажется, что использованная логика не работает для чтения из STDERR. Однако, если вы перенаправите
STDOUT к файлу внутри -c
параметр для script
, а затем перенаправить script
собственный
STDERR для STDOUT, кажется, все работает нормально.
$descriptorspec = array(
0 => array('pipe', 'r'), //STDIN
1 => array('pipe', 'w'), //STDOUT
2 => array('pipe', 'r'), //STDERR
);
// Avoid buffering by passing the command through "script"$process = proc_open(
'script -qfc "mycommand >mycommand.out" /dev/null 2>&1',
$descriptorspec, $pipes, null, null);
if (is_resource($process)) {
$buffer = "";
// Read from the command's STDOUT until it's closed.
while (!feof($pipes[1])) {
$input = fread($pipes[1], 8192);
$buffer .= $input;
// Output what we've read for debugging. You'd want to add some logic here
// instead to handle the input and work out the answers to the questions.
echo $input;
// Answer the questions when appropriate.
// This won't work if your output ever includes "STEP 1:" other than when
// prompting for a question. You might need some more robust logic here.
if (preg_match("/\nSTEP 1:$/", $buffer)) {
fwrite($pipes[0], "My first answer\n");
} elseif (preg_match("/\nSTEP 2:$/", $buffer)) {
fwrite($pipes[0], "My second answer\n");
}
}
proc_close($process);
} else {
echo 'Not a resource';
}
Я написал код таким образом, потому что вы указали, что ответы на запросы требуют «чтения и интерпретации вывода». Если все ответы были известны до запуска программы, решение будет намного проще. В этом случае вы можете просто вывести все ответы сразу, прежде чем начать читать ответ. Команде не важно, чтобы вы не ждали приглашения, прежде чем вводить данные. Вам может понадобиться позвонить stream_set_blocking($pipes[0], false);
сначала в этом случае я не уверен на 100%.
Вы, безусловно, на правильном пути.
Лучше всего начать с вопиющей проблемы в вашем коде:
while ($buffer = fgets($pipes[1])) {
echo $buffer;
}
Этот цикл никогда не завершится. В какой-то момент программа задаст вам вопрос, но ваш код все еще выполняет (блокирующий) вызов fgets.
Что касается того, как писать код, который работает правильно ….
Наиболее очевидное решение — не ждать ответа, прежде чем дать ответ. Это будет работать до тех пор, пока:
Вам не нужно адаптировать свой ответ на основе предыдущего вывода программы
Программа читает со своего стандартного ввода и не очищает буфер в любой момент
На самом деле, вам даже не нужен процесс контроля для этого:
program <input.txt >output.txt 2>errors.txt
Но если предположить, что 1 и / или 2 не применимы, и учитывая, что он уже перенаправляет свой стандартный вывод (что говорит о том, что в истории есть нечто большее, чем мы знаем),
...
if (is_resource($process)) {
while ($buffer=fgets($pipes[2]) { // returns false at EOF/program termination
if (strpos($buffer, 'STEP 1:') !== false) {
fwrite($pipes[0], "My first answer\n");
} else if (strpos($buffer, 'STEP 2:') !== false) {
fwrite($pipes[0], "My second answer\n"); //enter the answer
}
}
}
Внедрение проверок для вопросов вне последовательности и ветвления в цикле запрос / ответ оставлено читателю в качестве упражнения.