ReactPHP의 child-process 패키지를 사용하면 손쉽게 pipe를 사용할 수 있다. 아래 명령을 코드로 전환한다고 생각해보자.

$ cat app.php | wc -l

수작업으로 proc_open 열어서 pipe를 받아 fread, fwrite 해도 되지만 코드가 복잡해진다. ReactPHP를 사용하면 Child Process에 구현된 pipe로 데이터를 전달할 수 있다.

먼저 필요한 의존성을 추가한다.

$ composer require react/event-loop react/child-process react/stream

예시 코드는 다음과 같다.

<?php

use React\EventLoop\Factory;
use React\ChildProcess\Process;
use React\Stream\ReadableResourceStream;
use React\Stream\WritableResourceStream;

// 이벤트 루프를 먼저 생성
$loop = Factory::create();

$catProc = new Process('cat hello.txt');
$wcProc = new Process('wc -l');

// 최종 결과를 표준출력으로 표시하기 위해 스트림을 추가
$stdout = new WritableResourceStream(STDOUT, $loop);

$catProc->start($loop);
$wcProc->start($loop);

// cat의 출력을 wc의 입력으로, wc의 출력을 표준 출력으로 pipe함
$catProc->stdout->pipe($wcProc->stdin);
$wcProc->stdout->pipe($stdout);

$loop->run();
?>

여기서의 pipe 예시는 간단해서 오히려 명령어를 직접 적는게 낫겠다.

<?php
// ...
$countProc = new Process('cat hello.txt | wc -l');
// ...

보다시피 위 예시는 매우 단순하다. 하지만 xmodem 프로토콜 등으로 전송하는 경우에는 파일 전송 명령과 터미널의 입출력을 서로 연결하는 식으로 작성해야 한다. 예전에는 이런 프로토콜이 터미널 도구 자체에 내장되어 있어서 쉽게 전송할 수 있었다고 한다. 다음처럼 입출력 스트림을 양쪽으로 pipe하면 전송이 가능하다.

<?php
// ...

$hose = new Process('hose 192.168.0.10 2000');
$sx = new Process('sx -k -v /foo/bar.bin');

$hose->start($loop);
$sx->start($loop);

$hose->stdout->on('data', function($chunk) use ($hose, $sx) {
    // 복사 초기화 명령을 전송
    $hose->stdin->write("\n");
    $hose->stdin->write("copy xmodem: flash:bar.bin\n");

    // 두 프로세스의 입출력을 연결
    $hose->stdout->pipe($sx->stdin);
    $sx->stdout->pipe($hose->stdin);
});

// xmodem에서는 stdin/stdout으로 파일을 전송하기 때문에 stderr에 현재 전송 상태를 출력함
$sx->stderr->on('data', function($chunk) {
    echo $chunk;
});

// 전송이 완료되면 해당 프로세스가 종료되고 메시지를 출력
$sx->on('exit', function($exitCode) {
    echo "Process exited with code {$exitCode}." . PHP_EOL;
});

$loop->run();

xmodem은 1977년에 개발되었다고 한다. 프로젝트에서 41년 된 파일 전송 프로토콜을 사용해보게 될 줄은 생각도 못했다.

색상을 바꿔요

눈에 편한 색상을 골라보세요 :)

Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.