外部コマンド(ls/dir等)を実行する

Command.create() と execute() で ls や git を実行する。shell:allow-execute の scope と validator の書き方、dir など組み込みコマンドの扱いも示す。

外部プロセス 対象: Tauri 2.x 更新日: 読了目安: 約8分 shell-001
目次
  1. 前提条件
  2. scope の書き方
  3. 1. フロントエンドから実装する (TypeScript)
  4. dir などの組み込みコマンドは cmd 経由で
  5. 2. バックエンドから実装する (Rust)
  6. 動作確認
  7. よくあるエラーと対処法
  8. OS ごとの違いと注意点
  9. 関連レシピ

git や ls のような OS のコマンドは、Shell プラグインの Command.create() で組み立て、execute() で終了まで待って結果を受け取ります。JS から実行するには、起動してよいプログラムと引数を capability の scope に列挙する必要があり、ここがいちばんつまずく所です。Windows の dir のような組み込みコマンドは、そのままでは起動できません。引数などの渡し方は shell-002、出力の読み方は shell-003 で扱います。

前提条件

npm run tauri add shell

tauri add はパッケージの追加、tauri_plugin_shell::init() の登録、shell:default の追加まで行います。ただし shell:default に含まれるのは URL を開く open() の権限だけで、コマンドは実行できません。使う API に合わせて次の権限を足します。

権限使う API
shell:allow-executeexecute()(終了を待って結果をまとめて受け取る)
shell:allow-spawnspawn()(出力を逐次受け取る。shell-009)
shell:allow-kill / shell:allow-stdin-write起動したプロセスの kill() / write()

execute と spawn の権限は、許可するコマンドの一覧(allow)を持つオブジェクトとして書きます。一覧は権限ごとに別なので、同じコマンドを spawn() でも使うなら shell:allow-spawn にも同じ内容を書きます。

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "shell:default",
    {
      "identifier": "shell:allow-execute",
      "allow": [
        {
          "name": "ls",
          "cmd": "ls",
          "args": ["-1", { "validator": "/[\\w/ .-]*" }]
        },
        {
          "name": "dir",
          "cmd": "cmd",
          "args": ["/U", "/C", "dir", "/B", { "validator": "[A-Za-z]:\\\\[\\w\\\\ .-]*" }]
        }
      ]
    }
  ]
}

scope の書き方

キー意味
nameJS の Command.create() に渡す呼び名。プログラム名と同じでなくてよい
cmd起動するプログラム。名前だけなら PATH から探す。$HOME などのフォルダ変数で始めてもよい
args許可する引数。true は何でも可、false または省略は引数なし、配列は位置ごとの指定
sidecarアプリに同梱したバイナリなら true(shell-012)

配列の要素は固定の文字列か { "validator": "正規表現" } で、JS から渡した引数と位置ごとに照合されます。固定値の位置には常に固定値が使われ、validator の位置は値が正規表現に一致するか調べられます。正規表現は前後に ^・$ が補われて値全体との一致になります("raw": true で補わなくなりますが、素通りしやすいので避けます)。JSON では \ を \\ と書くので、バックスラッシュ 1 文字に一致させる \\ は \\\\ になります。

見落としやすいのは、args を省略したエントリーに JS から引数を渡してもエラーにならず、引数なしで実行されることです。配列より多く渡した引数も黙って捨てられます。「オプションが効かない」ときはまず scope を確かめます。

1. フロントエンドから実装する (TypeScript)

Command.create() の第 1 引数は scope の name です(cmd の値ではありません)。

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

const isWindows = navigator.userAgent.includes('Windows');

// フォルダ内の名前を OS のコマンドで一覧する(dir は cmd.exe 経由)
export async function listDir(dir: string): Promise<string[]> {
  const command = isWindows
    ? Command.create('dir', ['/U', '/C', 'dir', '/B', dir], { encoding: 'utf-16le' })
    : Command.create('ls', ['-1', dir]);
  const output = await command.execute(); // 権限・scope・起動の失敗はここで reject
  if (output.code !== 0) {
    throw new Error(output.stderr.trim() || `exit code ${output.code}`);
  }
  return output.stdout.split(/\r?\n/).filter((line) => line !== '');
}

dir などの組み込みコマンドは cmd 経由で

dir・echo・type・copy は cmd.exe の組み込みコマンドで、実行ファイルが無いため "cmd": "dir" では起動できません(PowerShell の ls や dir も PowerShell 内の別名です)。cmd を起動して /C の後ろに渡します。例の /U は組み込みコマンドの出力を UTF-16 にするオプションで、encoding: 'utf-16le' と組み合わせると日本語のファイル名も化けません(文字コードは shell-003)。

cmd.exe は引数の中の & や | を次のコマンドの区切りと解釈するので、validator が緩いと C:\&calc のような値で別のプログラムを起動されます。例ではドライブ名の後を文字・数字・\・空白・.・- に限っています。cmd・powershell・sh・node を args: true で許可するのは、画面側に任意のコマンド実行を許すのと同じです。ファイル一覧が目的なら fs プラグインの readDir() の方が、OS の差も文字コードも気にせず済みます。

2. バックエンドから実装する (Rust)

Rust からはプラグインの ShellExt で同じようにコマンドを組み立てます。Rust 側の呼び出しには capability の scope が効かないので、JS から受け取った値をプログラム名や引数に使うときは自分で検査します。次の例は Git が使えるかを確かめます。

use tauri_plugin_shell::ShellExt;

/// インストール済みの Git のバージョンを返す
#[tauri::command]
async fn git_version(app: tauri::AppHandle) -> Result<String, String> {
    let output = app
        .shell()
        .command("git")
        .arg("--version")
        .output()
        .await
        .map_err(|e| format!("git を起動できません(未インストールか PATH にない): {e}"))?;
    if !output.status.success() {
        return Err(String::from_utf8_lossy(&output.stderr).trim().to_string());
    }
    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .invoke_handler(tauri::generate_handler![git_version])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';

const version = await invoke<string>('git_version'); // 例: "git version 2.52.0.windows.1"

std::process::Command でも実行できますが、Windows のリリースビルドではコマンドの実行中に黒いコンソール画面が一瞬表示されます。プラグインの command() はこれを表示せずに起動します。

動作確認

npm run tauri dev で起動し、ボタンのクリックなどで listDir() を呼んで結果を console.log() します。Windows で listDir('C:\\Users') とすると ['taro', 'Public'] のような配列が返ります。listDir('C:\\Users&calc') は validator に合わないので reject され、tauri dev のターミナルに理由が出ます(位置は 0 から数えます)。

Scoped command argument at position 4 was found, but failed regex validation ^[A-Za-z]:\\[\w\\ .-]*$

よくあるエラーと対処法

  • 「shell.execute not allowed. Permissions associated with this command: shell:allow-execute」: shell:default だけで、実行の権限がありません。リリースビルドでは「Command plugin:shell|execute not allowed by ACL」になります。capability を直したら tauri dev を起動し直します。
  • 「program not allowed on the configured shell scope: dir」: 名前が scope に無い(cmd の値を渡した)、validator に合わない、引数が足りない、のどれかです。開発ビルドではターミナルに「Scoped command … not found」など詳しい理由が出ます。
  • 「program not found」(Windows)/「No such file or directory (os error 2)」(macOS・Linux): プログラムが見つかりません。組み込みコマンドを直接指定した、未インストール、PATH が通っていない、のいずれかです。
  • Windows で npm や code が見つからない: 実体が npm.cmd のようなバッチファイルのコマンドは、名前だけでは探されません。"cmd": "npm.cmd" と拡張子まで書きます。

OS ごとの違いと注意点

  • Windows: 組み込みコマンドとバッチファイルは上記のとおりです。日本語を含む出力は、コマンドによって Shift_JIS 系や UTF-16 になります(shell-003)。
  • macOS / Linux: ls などは実行ファイルとして存在するので、そのまま cmd に書けます。
  • iOS / Android: Shell プラグインで使えるのは URL を開く open() だけで、コマンドは実行できません。
  • 共通: execute() は終了するまで返りません。ping -t のように終わらないコマンドや長い処理は 時間のかかるプロセスを管理・強制終了する を、進み具合を表示したいときは shell-009 の方法を使います。

関連レシピ

参考リンク(公式ドキュメント)

Web Ninja

この記事を書いた人

Web Ninja ウェブエンジニア (Web Engineer)

会社員ネットワークエンジニアから独立してかれこれ 25 年以上 Web エンジニアとして活動中。普段は JavaScript と Node.js を自在に操り、時には C++ や Perl といった古流の技も嗜みます。近年は Tauri × Rust という新たな武器を手に、デスクトップアプリ開発の最前線を駆け抜けています。「作りたい」を「作れる」に変えるための、実践的な「技」をお届けします。

お問い合わせ: tauri.ninja@gmail.com

内容の誤り・動かないコードを報告する