Дескрипторы файлов и PHP
На днях подошёл на работе один из программистов и спросил не знаю ли я способа как передать из ПХП во входной поток утилиты не один файл, а два. Когда мы разобрались, что он имеет ввиду, оказалось, что задача следующая: имеется утилита командной строки, которая делает PDF из двух файлов — тела и заголовочной части. Нужно работать с ней из ПХП, но создавать промежуточные файлы не хочется.
Один файл передаётся во входной поток, с этим многие знакомы и проблемы нет, но как передать два файла? В командной строке такой способ есть — создаём ещё один файловый дескриптор, связываем его с файлом или вводом/выводом какой-то команды и радуемся. Например:
#!/bin/bash
# связываем третий дескриптор с чтением из указанного файла
exec 3< /etc/passwd
# связываем четвёртый со входом команды, которая будет писать в другой файл
exec 4> >(cat > /tmp/passwd)
# читаем из третьего дескриптора, пишем в четвёртый
cat <&3 >&4
А можно ли так в ПХП? Документация к proc_open говорит, что да:
The file descriptor numbers are not limited to 0, 1 and 2 — you may specify any valid file descriptor number and it will be passed to the child process. This allows your script to interoperate with other scripts that run as «co-processes».
Ну что же, давайте попробуем:
<?php
// заполняем первые три файловых дескриптора как обычно и,
// кое-что новое, — создаём ещё один
$ds = [
'stdin' => ['file', '/dev/null', 'r',],
'stdout' => ['pipe', 'w',],
'stderr' => ['file', '/dev/null', 'w',],
'stdnew' => ['pipe', 'r',],
];
// передаём наши дескрипторы команде 𝑐𝑎𝑡, специальный файл
// /𝑑𝑒𝑣/𝑓𝑑/3 связан с третьим (с нуля) дескриптором
$process = proc_open("cat /dev/fd/3", array_values($ds), $pipes);
// связываем дескрипторы с переменными, указанными в массиве $𝑑𝑠
// (пожалуйста не используйте 𝑒𝑥𝑡𝑟𝑎𝑐𝑡 без 𝑝ℎ𝑝𝑑𝑜𝑐 в своём коде)
extract(array_combine(array_intersect_key(array_keys($ds), $pipes), $pipes));
// пишем в наш новый дескриптор
fwrite($stdnew, "Hello world!\n");
fclose($stdnew);
// читаем из вывода переданное
fpassthru($stdout);
fclose($stdout);
proc_close($process);
Всё работает! Фраза, переданная на такой не совсем стандартный вход, благополучно выводится на экран.
Должен сразу сказать — не все утилиты командной строки умеют работать таким образом, некоторые требуют указания настоящего файла, не знаю из каких соображений. Но с большинством это сделать получится.
Эротично, спасибо!