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-execute | execute()(終了を待って結果をまとめて受け取る) |
shell:allow-spawn | spawn()(出力を逐次受け取る。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 の書き方
| キー | 意味 |
|---|---|
name | JS の 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 の方法を使います。
