ShellJS の使い方 — Node.js でUnixシェルコマンドをクロスプラットフォームに実行
一言でいうと
ShellJS は、Unix シェルコマンド(cp, rm, mkdir, grep など)を Node.js 上でクロスプラットフォーム(Windows/Linux/macOS)に実行できるライブラリです。Bash スクリプトの代わりに JavaScript/TypeScript でビルドスクリプトやデプロイスクリプトを書くことができます。
どんな時に使う?
- ビルド・デプロイスクリプトの作成 — Makefile や Bash スクリプトの代わりに、Node.js でファイル操作やコマンド実行を行いたいとき
- クロスプラットフォーム対応のCLIツール開発 — Windows でも Linux でも同じコードでファイル操作を行いたいとき
- CI/CD パイプラインのスクリプト — テストの前後処理やリリース自動化で、ファイルのコピー・置換・外部コマンド実行を同期的に行いたいとき
インストール
# npm
npm install shelljs
# yarn
yarn add shelljs
# pnpm
pnpm add shelljs
TypeScript で使う場合は型定義もインストールします。
npm install -D @types/shelljs
基本的な使い方
import shell from 'shelljs';
// git がインストールされているか確認
if (!shell.which('git')) {
shell.echo('Error: git is required');
shell.exit(1);
}
// ビルド成果物のディレクトリを準備
shell.rm('-rf', 'dist');
shell.mkdir('-p', 'dist');
shell.cp('-R', 'src/*', 'dist/');
// 各 .js ファイル内のバージョン文字列を置換
shell.cd('dist');
shell.ls('*.js').forEach((file) => {
shell.sed('-i', '__VERSION__', '1.0.0', file);
});
shell.cd('..');
// 外部コマンドを同期実行
const result = shell.exec('git commit -am "Release v1.0.0"');
if (result.code !== 0) {
shell.echo('Error: Git commit failed');
shell.exit(1);
}
すべてのコマンドはデフォルトで同期実行されるため、await や コールバックなしで直感的に書けます。
よく使うAPI — ShellJS の主要コマンド
1. cp() — ファイル・ディレクトリのコピー
import shell from 'shelljs';
// 単一ファイルのコピー
shell.cp('src/config.json', 'dist/config.json');
// ディレクトリごと再帰的にコピー
shell.cp('-R', 'assets/', 'dist/assets/');
// 複数ファイルを一括コピー(グロブ対応)
shell.cp('src/*.ts', 'backup/');
主なオプション:
-R/-r: ディレクトリを再帰的にコピー-f: 既存ファイルを強制上書き(デフォルト)-n: 既存ファイルを上書きしない
2. exec() — 外部コマンドの実行
import shell from 'shelljs';
// 同期実行(デフォルト)
const result = shell.exec('npm run build');
console.log('Exit code:', result.code);
console.log('stdout:', result.stdout);
console.log('stderr:', result.stderr);
// サイレントモード(標準出力を抑制)
const version = shell.exec('node --version', { silent: true }).stdout.trim();
console.log(`Node.js version: ${version}`);
// 非同期実行(コールバック)
shell.exec('npm test', { async: true }, (code, stdout, stderr) => {
console.log('Test finished with code:', code);
});
exec() はシェルを介してコマンドを実行するため、パイプ(|)やリダイレクト(>)も使えます。ただし、コマンドインジェクションのリスクがある点に注意してください。
3. cmd() — より安全な外部コマンド実行
import shell from 'shelljs';
// 引数を個別に渡すため、コマンドインジェクションのリスクが低い
const version = shell.cmd('node', '--version').stdout.trim();
// 特殊文字もリテラルとして扱われる
shell.cmd('echo', 'Hello | World & Goodbye ; End');
// => "Hello | World & Goodbye ; End" がそのまま出力される
// オプション指定
shell.cmd('ls', '-la', { cwd: '/tmp' });
exec() との最大の違いは、シェルを介さず直接コマンドを実行するため、|, &, ; などの特殊文字がリテラルとして扱われる点です。セキュリティ面で cmd() の方が推奨されます。
注意:
cmd()は v0.10.0 で追加された比較的新しい API です。
4. grep() — テキスト検索
import shell from 'shelljs';
// ファイル内の文字列検索
const matches = shell.grep('TODO', 'src/*.ts');
console.log(matches.stdout);
// 正規表現で検索
const errors = shell.grep(/Error:.*/, 'logs/app.log');
// -l: マッチしたファイル名のみ表示
const files = shell.grep('-l', 'deprecated', 'src/*.ts');
// パイプチェーン(ShellString のメソッドチェーン)
const count = shell.cat('data.txt').grep('important').head({ '-n': 5 });
5. sed() — テキスト置換
import shell from 'shelljs';
// ファイル内の文字列を置換(インプレース)
shell.sed('-i', 'old_version', 'new_version', 'package.json');
// 正規表現で置換
shell.sed('-i', /console\.log\(.*\);?/g, '', 'dist/app.js');
// 置換結果を変数に取得(-i なし)
const result = shell.sed(/v\d+\.\d+\.\d+/, 'v2.0.0', 'README.md');
console.log(result.stdout);
その他の頻出コマンド
import shell from 'shelljs';
// ディレクトリ作成(-p で中間ディレクトリも作成)
shell.mkdir('-p', 'dist/assets/images');
// ファイル・ディレクトリの削除
shell.rm('-rf', 'dist', 'tmp');
// ファイル一覧の取得
const files = shell.ls('src/**/*.ts');
files.forEach((f) => console.log(f));
// ファイルの存在確認
if (shell.test('-f', 'config.json')) {
console.log('config.json exists');
}
// -d: ディレクトリ存在確認, -L: シンボリックリンク確認
// コマンドの存在確認
const gitPath = shell.which('git');
// カレントディレクトリ
console.log(shell.pwd().toString());
// 環境変数の設定
shell.env['NODE_ENV'] = 'production';
類似パッケージとの比較
| 特徴 | ShellJS | execa | zx (Google) | Node.js child_process |
|---|---|---|---|---|
| 主な用途 | Unix コマンドの再実装 | 外部コマンド実行 | シェルスクリプト代替 | 外部コマンド実行 |
| 同期実行 | ✅ デフォルト | ✅ execaSync | ❌ async のみ | ✅ execSync |
ファイル操作 (cp, rm 等) | ✅ 組み込み | ❌ | ❌ | ❌ |
| クロスプラットフォーム | ✅ 強い | ✅ | △ | △ |
| グロブ対応 | ✅ 組み込み | ❌ | ❌ | ❌ |
| コマンドインジェクション対策 | ✅ cmd() | ✅ | △ | △ |
| TypeScript サポート | @types/shelljs | 組み込み | 組み込み | 組み込み |
| テンプレートリテラル構文 | ❌ | ❌ | ✅ $\cmd`` | ❌ |
使い分けの目安:
- ファイル操作中心のスクリプト → ShellJS
- 外部コマンド実行中心で async が前提 → execa または zx
- 依存を増やしたくない → Node.js 標準の
child_process+fs
注意点・Tips
1. グローバルインポートは非推奨
// ❌ グローバル名前空間を汚染するため非推奨
require('shelljs/global');
cp('-R', 'src/', 'dist/'); // グローバルに関数が生える
// ✅ ローカルインポートを使う
import shell from 'shelljs';
shell.cp('-R', 'src/', 'dist/');
2. エラーハンドリングを忘れずに
ShellJS のコマンドは失敗しても例外を投げません。code プロパティや shell.error() で明示的にチェックする必要があります。
import shell from 'shelljs';
// set('-e') で、コマンド失敗時にスクリプトを即座に終了させる
shell.set('-e');
// または個別にチェック
shell.cp('nonexistent.txt', 'dest/');
if (shell.error()) {
console.error('Copy failed:', shell.error());
shell.exit(1);
}
3. exec() vs cmd() の使い分け
// ユーザー入力を含む場合は cmd() を使う(コマンドインジェクション対策)
const userInput = 'some; rm -rf /'; // 悪意のある入力
shell.cmd('echo', userInput); // ✅ 安全:リテラルとして扱われる
shell.exec(`echo ${userInput}`); // ❌ 危険:シェルが解釈する
4. ShellString のパイプチェーン
ShellJS のコマンドは ShellString オブジェクトを返し、メソッドチェーンでパイプのように繋げられます。
import shell from 'shelljs';
// Unix の `cat file.txt | grep "error" | head -n 5` に相当
const result = shell.cat('server.log')
.grep('error')
.head({ '-n': 5 });
console.log(result.toString());
5. サイレントモードの活用
デフォルトではコマンドの出力がそのまま標準出力に流れます。スクリプト内で出力を制御したい場合はサイレントモードを使いましょう。
import shell from 'shelljs';
// グローバルにサイレントモードを設定
shell.config.silent = true;
// または個別に
const result = shell.exec('npm ls', { silent: true });
6. Node.js バージョン要件
v0.10.0 時点では Node.js v18 以上が必要です。古い Node.js 環境では旧バージョンの ShellJS を使用してください。
まとめ
ShellJS は、Unix シェルコマンドを Node.js 上でクロスプラットフォームに同期実行できるライブラリです。cp, rm, mkdir, grep, sed といった馴染みのあるコマンドをそのまま JavaScript/TypeScript で書けるため、Bash スクリプトからの移行が非常にスムーズです。外部コマンド実行には、セキュリティ面で優れた cmd() を優先的に使い、exec() はパイプやリダイレクトが必要な場合に限定するのがベストプラクティスです。