コマンドの出力をリアルタイムで受け取る

Recipe ID: shell-009

コマンドの出力をリアルタイムで(1行ずつ、あるいはデータが到着するたびに)受け取る方法を詳しく解説します。
tail -f のようなログ監視や、進捗率を表示する処理に必須のテクニックです。

前提条件

プラグインのインストールが必要です。

npm run tauri add shell

Permissions (権限) の設定

src-tauri/capabilities/default.json に以下の権限を追加します。

{
  "permissions": [
    ...,
    "shell:allow-open",
    {
      "identifier": "shell:allow-execute",
      "allow": [
        {
          "name": "ping",
          "cmd": "ping",
          "args": true
        }
      ]
    },
    {
      "identifier": "shell:allow-spawn",
      "allow": [
        {
          "name": "ping",
          "cmd": "ping",
          "args": true
        }
      ]
    }
  ]
}

1. フロントエンドから作成する (TypeScript)

Command インスタンスから spawn() する前に、stdout および stderr プロパティに対してイベントリスナー on('data', callback) を登録します。

ログ監視の例

import { Command } from '@tauri-apps/plugin-shell';

async function watchLog() {
  // ping コマンドで擬似的なリアルタイム出力を再現
  const cmd = Command.create('ping', ['127.0.0.1', '-c', '5']);

  cmd.stdout.on('data', line => {
    // データは行単位またはチャンク単位で来る可能性があります
    console.log(`RECIEVED: ${line}`);
    
    // UIを更新する処理などをここに記述
    // updateLogWindow(line);
  });

  const child = await cmd.spawn();
  console.log('Monitoring started...');
}

2. バックエンドから作成する (Rust)

バックエンドでプロセスを起動し、標準出力をイベントとしてフロントエンドに送信します。

use tauri::{AppHandle, Emitter};
use std::process::{Command, Stdio};
use std::io::{BufRead, BufReader};

#[tauri::command]
fn watch_log(app: AppHandle) {
    let mut command = Command::new("ping");
    command.args(["127.0.0.1", "-c", "5"]);

    let mut child = command
        .stdout(Stdio::piped())
        .spawn()
        .expect("failed to spawn");

    let stdout = child.stdout.take().unwrap();
    let reader = BufReader::new(stdout);

    // 別スレッドで読み取り
    std::thread::spawn(move || {
        for line in reader.lines() {
             if let Ok(l) = line {
                 // フロントエンドの listen('log-message', ...) に送信
                 let _ = app.emit("log-message", l);
             }
        }
    });
}

spawn 前にイベントリスナーを登録しないと、初期の出力を取り逃がす可能性があります。

イベントを受け取る (TypeScript)

バックエンドから送信されたイベントをフロントエンドで受信します。

import { listen } from '@tauri-apps/api/event';

// バックエンドの watch_log コマンドを実行する前にリスナーを設定
async function setupLogListener() {
  const unlisten = await listen<string>('log-message', (event) => {
    console.log(`Backend says: ${event.payload}`);
  });
  
  // 必要なくなったら unlisten() を呼び出して解除できます
}