⚡ Node-RED Execノード ガイド

このガイドでは、Node-REDのExecノードについて、初心者の方でも理解できるように詳しく説明します。

📚 1. Execノードとは?

🤔 「Exec」って何?

Execノードは、外部のコマンドやプログラムを実行するノードです。 日常生活で例えると、秘書に電話で指示を出して、報告を受け取るようなものです。

📞 秘書への電話指示に例えると:

📦 基本的な動作

Inject Exec Debug
(stdout)


Debug
(stderr)


Debug
(rc)

Execノードは外部コマンドを実行し、3つの出力ポートからそれぞれ結果を出力します。 LinuxやWindowsのターミナルコマンドをNode-REDから実行できます。

🔌 3つの出力ポート

1 標準出力

コマンドの実行結果
(stdout)

2 標準エラー

エラーメッセージ
(stderr)

3 終了コード

処理結果コード
(return code)

入力
msg.payload = "Hello World!"
↓ echo コマンド実行 ↓
出力(stdout)
msg.payload = "Hello World!\n"

⚙️ 2. Execノードの2つのモード

exec Execモード

コマンドが完了するまで待機し、全ての出力をまとめて返す

完了後に一度だけ 結果を出力

spawn Spawnモード

コマンド実行中にリアルタイムで出力を返す

出力があるたびに メッセージを送信

📋 設定項目一覧

設定項目 説明
コマンド 実行するコマンドやプログラム echo, ls, python
引数 コマンドの後に追加する固定引数 -la, --version
msg.payloadを追加 入力メッセージのpayloadをコマンド引数に追加 チェックON/OFF
出力 exec(完了後)またはspawn(リアルタイム) exec / spawn
タイムアウト コマンド実行の最大待ち時間(秒) 30(秒)
旧式の出力 古い形式の終了コード出力を使用 チェックON/OFF

🎯 入力メッセージで制御できるプロパティ

プロパティ 説明
msg.payload 「msg.payloadを追加」がONの場合、コマンド引数に追加 "Hello World!"
msg.kill 実行中のプロセスを終了(spawnモードで有効) 任意の値
msg.pid 特定のプロセスIDを指定して終了 12345

📤 出力メッセージの形式

// 第1出力(stdout) msg.payload = "コマンドの標準出力" // 第2出力(stderr) msg.payload = "エラーメッセージ" // 第3出力(終了コード) msg.payload = { code: 0, // 終了コード(0=成功) signal: null // シグナル(あれば) }

🎯 3. 実用的な使用パターン

📥 サンプルフローのインポート方法:

  1. 下のサンプルフローJSONをコピー
  2. Node-REDエディタで メニュー → 読み込み を選択
  3. JSONをペーストして「読み込み」をクリック

このサンプルフローには、以下で説明する全パターンの実例が含まれています。

📋 サンプルフロー(クリックで展開)

参照元:Node-REDエディター内サンプルフロー

[ { "id": "fcde1833fb43f65c", "type": "tab", "label": "exec", "disabled": false, "info": "", "env": [] }, { "id": "6a5f26a9.0cc2d8", "type": "inject", "z": "fcde1833fb43f65c", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "Hello World!", "payloadType": "str", "x": 290, "y": 200, "wires": [ [ "fc2b343c.bbe2f8" ] ] }, { "id": "fc2b343c.bbe2f8", "type": "exec", "z": "fcde1833fb43f65c", "command": "echo", "addpay": true, "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "name": "", "x": 430, "y": 200, "wires": [ [ "2f3bcd73.6fedf2" ], [], [ "3280586e.4e3d28" ] ] }, { "id": "2f3bcd73.6fedf2", "type": "debug", "z": "fcde1833fb43f65c", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 610, "y": 200, "wires": [] }, { "id": "2b7bc9f1.ab36a6", "type": "comment", "z": "fcde1833fb43f65c", "name": "Example: Execute external command appending additional args", "info": "", "x": 380, "y": 100, "wires": [] }, { "id": "3280586e.4e3d28", "type": "debug", "z": "fcde1833fb43f65c", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 610, "y": 260, "wires": [] }, { "id": "feba83da.8f227", "type": "comment", "z": "fcde1833fb43f65c", "name": "↓ execute echo command", "info": "", "x": 490, "y": 155, "wires": [] }, { "id": "f507b27c.fff1", "type": "inject", "z": "fcde1833fb43f65c", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 260, "y": 460, "wires": [ [ "28b4f75a.eae548" ] ] }, { "id": "28b4f75a.eae548", "type": "exec", "z": "fcde1833fb43f65c", "command": "/non/existing/command", "addpay": false, "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "name": "", "x": 470, "y": 460, "wires": [ [], [ "27992c1f.a1c964" ], [ "6e7ff001.2412d" ] ] }, { "id": "6e7ff001.2412d", "type": "debug", "z": "fcde1833fb43f65c", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 690, "y": 520, "wires": [] }, { "id": "27992c1f.a1c964", "type": "debug", "z": "fcde1833fb43f65c", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 690, "y": 460, "wires": [] }, { "id": "77e85c7c.a560d4", "type": "comment", "z": "fcde1833fb43f65c", "name": "Example: Execute external command and get error output", "info": "", "x": 360, "y": 360, "wires": [] }, { "id": "c188c28c.f7d34", "type": "comment", "z": "fcde1833fb43f65c", "name": "↓ try to execute non-existing command", "info": "", "x": 510, "y": 414, "wires": [] }, { "id": "2e7be550.92ddfa", "type": "inject", "z": "fcde1833fb43f65c", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 280, "y": 740, "wires": [ [ "53a714a8.1214dc" ] ] }, { "id": "53a714a8.1214dc", "type": "exec", "z": "fcde1833fb43f65c", "command": "/bin/sh -c \"while true; do echo Hello; sleep 2; done\"", "addpay": false, "append": "", "useSpawn": "true", "timer": "", "winHide": false, "oldrc": false, "name": "Repeat message output", "x": 510, "y": 740, "wires": [ [ "d1815f06.463f3" ], [ "322d9b72.fc9194" ], [ "2dbf8e03.2d6a52" ] ] }, { "id": "7f351806.547908", "type": "comment", "z": "fcde1833fb43f65c", "name": "Example: Execute external command in spawn mode", "info": "", "x": 350, "y": 640, "wires": [] }, { "id": "2dbf8e03.2d6a52", "type": "debug", "z": "fcde1833fb43f65c", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 790, "y": 780, "wires": [] }, { "id": "d1815f06.463f3", "type": "debug", "z": "fcde1833fb43f65c", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 790, "y": 700, "wires": [] }, { "id": "28467ba9.c96284", "type": "comment", "z": "fcde1833fb43f65c", "name": "↓ spawn mode: repeat message output", "info": "", "x": 550, "y": 694, "wires": [] }, { "id": "322d9b72.fc9194", "type": "debug", "z": "fcde1833fb43f65c", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 790, "y": 740, "wires": [] }, { "id": "d5a990ce.c660b", "type": "inject", "z": "fcde1833fb43f65c", "name": "Kill process", "props": [ { "p": "kill", "v": "", "vt": "date" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "x": 290, "y": 800, "wires": [ [ "53a714a8.1214dc" ] ] } ]

パターン1: 基本的なコマンド実行

用途: シンプルなコマンドを実行して結果を取得

Inject
("Hello World!")
Exec
(echo)
Debug
(stdout)

📌 動作の流れ:

  1. Injectノードから "Hello World!" を送信
  2. Execノードが echo Hello World! を実行
  3. 標準出力から "Hello World!\n" を受け取る
  4. 終了コード 0(成功)が第3出力から出力

設定例:

コマンド: echo msg.payloadを追加: チェックON 出力形式: exec(待機) 入力: msg.payload = "Hello World!" 実行: echo Hello World! 出力(stdout): "Hello World!\n" 出力(rc): { code: 0 }

パターン2: エラー出力の取得

用途: コマンド実行時のエラーを検出・処理

Inject Exec
(存在しない
コマンド)
Debug
(stderr)


Debug
(rc)

📌 動作の流れ:

  1. 存在しないコマンドを実行しようとする
  2. stderrにエラーメッセージが出力される
  3. 終了コードは 0 以外(エラー)になる

設定例:

コマンド: /non/existing/command msg.payloadを追加: チェックOFF 実行: /non/existing/command 出力(stderr): "spawn /non/existing/command ENOENT" 出力(rc): { code: null, signal: null } または エラーコード

パターン3: Spawnモード(リアルタイム出力)

用途: 長時間実行するコマンドの出力をリアルタイムで取得

Inject
(開始)
Exec
(spawn)
→→→ Debug
(リアルタイム)


Inject
(Kill)

📌 動作の流れ:

  1. Spawnモードでコマンドを開始
  2. コマンドが出力するたびにメッセージを送信
  3. msg.killを送信してプロセスを終了可能

設定例:

コマンド: /bin/sh -c "while true; do echo Hello; sleep 2; done" 出力形式: spawn(リアルタイム) 動作: - 2秒ごとに "Hello\n" を出力 - msg.kill を受信するとプロセス終了 - 終了時に第3出力から終了コードを出力

パターン4: システム情報の取得

用途: ホスト名、日付、ディスク容量などのシステム情報を取得

Inject Exec
(hostname)
Change
(trim)
Debug

設定例:

コマンド: hostname(または date, df -h, uname -a など) msg.payloadを追加: チェックOFF 出力例(hostname): "raspberrypi\n" ※ 末尾の改行を除去するにはChangeノードやFunctionノードでtrim()を使用

パターン5: Pythonスクリプトの実行

用途: Pythonスクリプトを実行してデータ処理

Inject
(データ)
Exec
(python3)
Function
(JSON解析)
Debug

設定例:

コマンド: python3 /home/pi/scripts/process.py msg.payloadを追加: チェックON(スクリプトへの引数として) // Pythonスクリプト側でsys.argvで引数を受け取り // 結果をprint()でJSON形式で出力 // Node-RED側でJSON.parse()して利用

🏋️ 4. 実践演習

演習1: echoコマンドの実行初級

📝 課題:

echoコマンドを使って、任意のメッセージを出力してください。

🎯 要求仕様:

📊 期待される動作:

✅ 成功の条件:

💡 ヒント

Injectノードの設定:

  • payload: 文字列型で "Node-REDからこんにちは!"

Execノードの設定:

  • コマンド: echo
  • msg.payloadを追加: チェックON
  • 出力: exec(待機)

Debugノードの設定:

  • 1つ目: Execの第1出力(stdout)に接続
  • 2つ目: Execの第3出力(rc)に接続
✅ 解答例フロー
[ { "id": "ex1_inject", "type": "inject", "name": "メッセージ入力", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "Node-REDからこんにちは!", "payloadType": "str", "x": 140, "y": 100, "wires": [["ex1_exec"]] }, { "id": "ex1_exec", "type": "exec", "name": "echoコマンド", "command": "echo", "addpay": true, "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "x": 330, "y": 100, "wires": [["ex1_debug_stdout"], [], ["ex1_debug_rc"]] }, { "id": "ex1_debug_stdout", "type": "debug", "name": "標準出力", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 520, "y": 80, "wires": [] }, { "id": "ex1_debug_rc", "type": "debug", "name": "終了コード", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 520, "y": 140, "wires": [] } ]

演習2: システム日時の取得中級

📝 課題:

dateコマンドを使ってシステムの現在日時を取得し、末尾の改行を除去して表示してください。

🎯 要求仕様:

📊 期待される動作:

✅ 成功の条件:

💡 ヒント

Injectノードの設定:

  • payload: タイムスタンプ(デフォルト)でOK

Execノードの設定:

  • コマンド: date
  • msg.payloadを追加: チェックOFF

Changeノードの設定:

  • ルール: msg.payload に代入
  • 値: JSONata式 $trim(payload) で末尾の改行を除去
✅ 解答例フロー
[ { "id": "ex2_inject", "type": "inject", "name": "実行", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 110, "y": 100, "wires": [["ex2_exec"]] }, { "id": "ex2_exec", "type": "exec", "name": "dateコマンド", "command": "date", "addpay": false, "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "x": 260, "y": 100, "wires": [["ex2_change"], [], []] }, { "id": "ex2_change", "type": "change", "name": "改行除去", "rules": [{"t": "set", "p": "payload", "pt": "msg", "to": "$trim(payload)", "tot": "jsonata"}], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 420, "y": 100, "wires": [["ex2_debug"]] }, { "id": "ex2_debug", "type": "debug", "name": "結果", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 570, "y": 100, "wires": [] } ]

演習3: エラーハンドリング中級

📝 課題:

コマンド実行結果の成功/失敗を判定し、適切なメッセージを出力してください。

🎯 要求仕様:

📊 期待される動作:

✅ 成功の条件:

💡 ヒント

Execノードの第3出力(終了コード)を使用

Switchノードの設定:

  • プロパティ: msg.payload.code
  • 条件1: == 0 → 成功
  • 条件2: その他 → 失敗

テスト用コマンド:

  • 成功: echo test
  • 失敗: /non/existing/command
✅ 解答例フロー
[ { "id": "ex3_inject_ok", "type": "inject", "name": "成功テスト", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "test", "payloadType": "str", "x": 120, "y": 80, "wires": [["ex3_exec"]] }, { "id": "ex3_inject_ng", "type": "inject", "name": "失敗テスト", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 120, "y": 140, "wires": [["ex3_exec_ng"]] }, { "id": "ex3_exec", "type": "exec", "name": "echoコマンド", "command": "echo", "addpay": true, "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "x": 290, "y": 80, "wires": [[], [], ["ex3_switch"]] }, { "id": "ex3_exec_ng", "type": "exec", "name": "存在しないコマンド", "command": "/non/existing/command", "addpay": false, "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "x": 320, "y": 140, "wires": [[], [], ["ex3_switch"]] }, { "id": "ex3_switch", "type": "switch", "name": "成功/失敗判定", "property": "payload.code", "propertyType": "msg", "rules": [ {"t": "eq", "v": "0", "vt": "num"}, {"t": "else"} ], "checkall": "true", "repair": false, "outputs": 2, "x": 520, "y": 100, "wires": [["ex3_debug_ok"], ["ex3_debug_ng"]] }, { "id": "ex3_debug_ok", "type": "debug", "name": "成功", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 690, "y": 80, "wires": [] }, { "id": "ex3_debug_ng", "type": "debug", "name": "失敗", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 690, "y": 140, "wires": [] } ]

演習4: Spawnモードでログ監視上級

📝 課題:

Spawnモードを使って、定期的にメッセージを出力するプロセスを実行し、途中で停止できるようにしてください。

🎯 要求仕様:

📊 期待される動作:

✅ 成功の条件:

💡 ヒント

Execノードの設定:

  • コマンド: /bin/sh -c "while true; do date; sleep 2; done"
  • 出力: spawn(リアルタイム)にチェック

停止用Injectノードの設定:

  • msg.payload ではなく、msg.kill プロパティを設定
  • プロパティ追加: kill = タイムスタンプ(または任意の値)

注意:

  • このフローはLinux/Mac環境で動作します
  • Windowsの場合は別のコマンドが必要です
✅ 解答例フロー
[ { "id": "ex4_inject_start", "type": "inject", "name": "開始", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 110, "y": 80, "wires": [["ex4_exec"]] }, { "id": "ex4_inject_kill", "type": "inject", "name": "停止", "props": [{"p": "kill", "v": "", "vt": "date"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "x": 110, "y": 140, "wires": [["ex4_exec"]] }, { "id": "ex4_exec", "type": "exec", "name": "定期出力(spawn)", "command": "/bin/sh -c \"while true; do date; sleep 2; done\"", "addpay": false, "append": "", "useSpawn": "true", "timer": "", "winHide": false, "oldrc": false, "x": 320, "y": 100, "wires": [["ex4_debug_stdout"], ["ex4_debug_stderr"], ["ex4_debug_rc"]] }, { "id": "ex4_debug_stdout", "type": "debug", "name": "標準出力", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 540, "y": 60, "wires": [] }, { "id": "ex4_debug_stderr", "type": "debug", "name": "標準エラー", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 550, "y": 100, "wires": [] }, { "id": "ex4_debug_rc", "type": "debug", "name": "終了コード", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 550, "y": 140, "wires": [] } ]

🎓 5. まとめ

Execノードの重要ポイント

⚠️ よくある間違い

📚 次のステップ

Execノードをマスターしたら、以下のノードも学んでみましょう:

🔧 6. トラブルシューティング

よくある問題と解決方法

問題 原因 解決方法
コマンドが見つからない PATHが通っていない 絶対パスで指定(例: /usr/bin/python3)
Permission denied 実行権限がない chmod +x でスクリプトに実行権限を付与
出力が空 標準エラーに出力されている 第2出力(stderr)を確認
文字化け 文字コードの不一致 LANGやLC_ALL環境変数を設定
タイムアウトする 処理時間が長すぎる タイムアウト値を増やす or spawnモードを使用
プロセスが終了しない spawnモードで永続プロセス msg.killを送信してプロセスを終了

💡 7. 実務での活用例

ケース1: センサーデータの取得(Raspberry Pi)

Injectノード(定期実行: 1分ごと) ↓ Execノード: python3 /home/pi/read_sensor.py ↓ Functionノード: JSON.parse(msg.payload) ↓ MQTTノード or ダッシュボード表示 用途: DHT22温湿度センサー、BME280気圧センサーなどからデータ取得

ケース2: システム監視

Injectノード(定期実行: 5分ごと) ↓ Execノード: df -h / free -m / uptime ↓ Functionノード: 出力をパース ↓ 閾値チェック → アラート通知 用途: ディスク使用率、メモリ使用量、システム稼働時間の監視

ケース3: バックアップの自動実行

Injectノード(定期実行: 毎日深夜2時) ↓ Execノード: /home/pi/backup.sh ↓ 終了コード確認(Switchノード) ↓ 成功: ログ記録 失敗: Slack/メール通知 用途: データベースバックアップ、ログローテーション

ケース4: GPIOピン制御(Raspberry Pi)

HTTPリクエスト(/led/on) ↓ Execノード: gpio write 0 1 ↓ レスポンス返却 用途: LED、リレー、モーターなどの制御 ※ wiringPiやpigpioコマンドを使用

🔗 8. セキュリティに関する注意

⚠️ セキュリティ上の注意事項

安全なコマンド実行の例:

// 危険な例(ユーザー入力を直接使用) コマンド: rm -rf msg.payloadを追加: ON ← ユーザーが "/ --no-preserve-root" を入力したら... // 安全な例(ホワイトリスト方式) Functionノードで入力値を検証してからExecノードに渡す 許可された値のみを使用する

🔗 9. 追加リソース


このガイドが役に立ちましたら、実際のプロジェクトで練習してみてください!
Execノードはシステム連携の強力なツールです。

参照元:Node-REDエディター内サンプルフロー

🏠