📚 連載:Ansibleが理解できない理由はLinuxにあった【Shell編】第2回:なぜ結果が消えるのか(Redirect / Pipe)
Shellの仕組みからAnsibleを理解するシリーズです。
① なぜShellを理解しないとAnsibleは使えないのか
② なぜAnsibleで出力が取得できないのか
③ なぜ結果が消えるのか
④ なぜgrepで見つからないのか
⑤ なぜ正規表現で壊れるのか
⑥ なぜ環境が違うのか
⑦ なぜ条件分岐が失敗するのか
⑧ なぜループがうまく動かないのか
⑨ なぜ途中で処理が止まるのか
⑩ なぜ変数が意図通りに展開されないのか
⑪ なぜAnsibleの挙動が読めるようになるのか
🗺️ 初めての方・シリーズの全体像を知りたい方はこちら
本記事は、OSの仕組みからAnsible設計までを繋ぐ連載シリーズの一部です。
「どこから読み始めればいいか」あるいは、「OS/Shell/Ansible編の関係性」 について把握されたい場合は、以下の統合ガイドで整理しています。
📑 【Shell編】全体のまとめはこちら
→ Ansibleが理解できない理由はLinuxにあった【Shell編】まとめ
📑 連載の移動
前の記事:【Shell編】第1回 | 次の記事:【Shell編】第3回
📋 目次
- 前回の振り返り(第1回)
- 逆引き辞典との連動
- Shellのストリーム構造:データと転送先の分離
- なぜ消えるのか:転送先決定の「2つの判定ステップ」
- 状態からの逆引き:転送先の特定
- 【実機検証】データの転送と消失:Ansibleに届かない「行先」の特定
- まとめ:出力消失を特定する「調査の原則」
- 次回予告
- 連載一覧:Ansibleが理解できない理由はLinuxにあった【Shell編】
※本記事の位置とシリーズ全体の関係を先に確認します。
シリーズ全体構造(学習 × 問題解決)
本シリーズは
「理解(各記事)」と「問題解決(逆引き辞典)」を組み合わせて
スキルを身につける構成になっています。
この図は、どこから学び、どこに進めばよいかを示した“ロードマップ”です。
📍 現在の位置
現在はこの図の「Shell編の第2回」になります
📍 はじめに:この記事のスタンス
第1回では、OSには「1番(stdout)」と「2番(stderr)」という2つの独立した出口があることを整理しました。しかし、出口の役割を把握していても、依然として「実行結果が空になる」現象は発生します。
-
パイプ(
|)を繋ぐと、本来表示されるはずのログが消える -
リダイレクト(
>)を記述すると、Ansibleのregister変数が空になる -
手元でのコマンド実行結果と、Ansibleで取得した変数の中身が一致しない
これらの原因は、出口の先で行われる 「データ転送(流路の切り替え)」 にあります。
⚠️ 注意事項
この記事の目的は、ファイル記述子の深層理論を解説することではありません。
Ansibleで出力が「消えた」と感じたとき、以下の状態を即座に特定することを目指します。
- 「OSがデータをどのプロセス、あるいはどのファイルへ転送したのか」
「出力はされたが、Ansibleへ届く手前で別の場所へ送られた」という物理的な挙動を把握することで、実務上の出力制御を理解します。
1. 前回の振り返り(第1回)
前回は、出力には2つの独立した出口があることを学びました。
-
1番(stdout):標準出力
-
2番(stderr):標準エラー出力
今回は、出口から出たデータが 「パイプ( | )」 や 「リダイレクト( > )」 によって、どのように転送先が上書きされるのか。その判定ロジックとAnsibleへの影響を解説します。
2. 逆引き辞典との連動
具体的なエラー名から原因を特定したい場合は、以下の逆引き辞典を活用してください。
【保存版】Ansibleよくあるエラー一覧と原因まとめ(Shell編)
- まず「逆引き辞典」で該当ステップを特定する
- 次に「本編(各連載記事)」で仕組みを理解する
このサイクルが、OS編から続くトラブル解決の最短ルートです。
3. Shellのストリーム構造:データと転送先の分離
OS(Shell)において、コマンドが出力したデータは内部的な「転送先設定(ファイル記述子)」によって制御されます。Ansibleはこの仕組みに依存してデータを取得しています。
| 転送先 | 標準の出力先 | 実務上の指定方法 |
|---|---|---|
| 呼び出し元(Ansible) | デフォルトの出力先 | 特になし(デフォルト) |
| 他プロセス(Pipe) | 後続のコマンドへ渡す | | |
| ファイル(Redirect) | 永続的に保存する | > >> /dev/null |
「出力が取得できない」事象は、実際には 「OSによって別の転送先にデータが送られている」 という状態を示しています。
4. なぜ消えるのか:転送先決定の「2つの判定ステップ」
OSは出力の転送先を以下の順序で判定します。途中のステップで転送先が確定した場合、そのデータはAnsible(呼び出し元)には到達しません。
転送先判定の仕様
| ステップ | 判定内容 | 到達しない理由 |
|---|---|---|
| Step 1: Pipe | 他プロセスに渡しているか | 出力が次段のコマンドの入力として消費されるため |
| Step 2: Redirect | ファイルに書き込んでいるか | 出力先がターミナルからファイルへ置き換わるため |
【ストリーム転送フロー】
結論:取得失敗の本質
データが消失しているのではなく、OSによる流路制御(転送)によって、Ansibleへの経路から外れていることが原因です。
5. 状態からの逆引き:転送先の特定
Ansibleの実行結果(register 変数)の状態を確認することで、OSがどのステップを通過したかを特定できます。
観測対象と判定の目安
| 観測対象 | 状態 | 判定されるStep |
|---|---|---|
result.stdout が空 |
出力が届いていない | Step1 または Step2 |
| 最終コマンドの結果のみ存在 | 中間処理のログが欠損 | Step1(Pipe) |
| ファイルに内容が存在 |
stdoutが空 |
Step2(Redirect) |
/dev/null を指定 |
完全に消失 | Step2(Redirect) |
OSがどの判定を優先したかを切り分けることで、期待したデータが取得できない原因を物理的に特定可能です。
6. 【実機検証】データの転送と消失:Ansibleに届かない「行先」の特定
セクション4で解説した「転送先判定(Pipe / Redirect)」が、実際のAnsible実行においてどのようにデータを遮断するのか。その物理的な挙動を確認します。
6-1. 【検証1】リダイレクトによる「stdout」の空化(Step 2)
コマンドの実行結果をファイルへ書き込んだ際、Ansibleの stdout 変数がどのような状態になるかを観測します。
① 検証手順(リモートノード側)
標準出力を持つコマンドを実行し、リダイレクトを用いてその行先を変更します。
# 現在時刻をファイルに書き込む
[ansibleuser@localhost ~]$ date > /tmp/date_result.txt
※この時、ターミナル上には何も表示されません。これをAnsible経由で実行し、register 変数の中身を確認します。
② Ansibleによる実行テスト
実行するPlaybook (case1_redirect.yml)
---
- name: 検証1 リダイレクトによるデータの行先
hosts: test_servers
gather_facts: false
tasks:
- name: ファイルへリダイレクトして実行
ansible.builtin.shell: "date > /tmp/date_result.txt"
register: res_redirect
- name: 変数の中身を確認
ansible.builtin.debug:
msg: "STDOUTの中身: {{ res_redirect.stdout }}"
実行コマンド
ansible-playbook -i inventory.ini case1_redirect.yml
③ 検証結果
(ansible) [root@localhost workspace]# ansible-playbook -i inventory.ini case1_redirect.yml
PLAY [検証1 リダイレクトによるデータの行先] *************************************************************************************************************************************************
TASK [ファイルへリダイレクトして実行] *******************************************************************************************************************************************************
changed: [192.168.1.21]
TASK [変数の中身を確認] *********************************************************************************************************************************************************************
ok: [192.168.1.21] => {
"msg": "STDOUTの中身: "
}
PLAY RECAP **********************************************************************************************************************************************************************************
192.168.1.21 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ログの読み解きと考察
-
現象: タスクは正常終了し、リモートノード側でファイルへの書き込みも完了していますが、
res_redirect.stdoutは完全に空となっています。 -
理由: OSレベルで
stdout(ファイル記述子 1)の出力先が「呼び出し元(Ansible)」から「ファイルシステム」へ切り替わったためです。Ansibleは標準出力から流れてくるデータをキャプチャしているため、出力先がファイルに固定された場合、その中身を保持することはできません。
テスト結果のまとめ:
リダイレクトを用いた際にAnsibleで内容を取得できない原因は、OSがデータの転送先をファイルへ固定し、Ansible(呼び出し元)への流路を遮断したためです。
6-2. 【検証2】パイプラインによる中間データの「消費」(Step 1)
複数のコマンドを繋いだ際、前段のコマンドが出力したデータがAnsibleに届かない現象を確認します。
① 検証手順(OS上での挙動確認)
echo で出力した文字列を sed で加工します。
[ansibleuser@localhost ~]$ echo "Raw_Data" | sed 's/Raw/Processed/'
Processed_Data
※この時、最終的な加工結果のみが表示されます。Ansibleが保持しているデータが「誰のものか」を検証します。
② Ansibleによる実行テスト
実行するPlaybook (case2_pipe_flow.yml)
---
- name: 検証2 パイプラインにおけるデータの消費
hosts: test_servers
gather_facts: false
tasks:
- name: パイプを含むコマンドの実行
ansible.builtin.shell: "echo 'Raw_Data' | sed 's/Raw/Processed/'"
register: res_pipe
- name: 変数の中身を確認
ansible.builtin.debug:
msg: "取得されたデータ: {{ res_pipe.stdout }}"
実行コマンド
ansible-playbook -i inventory.ini case2_pipe_flow.yml
③ 検証結果
(ansible) [root@localhost workspace]# ansible-playbook -i inventory.ini case2_pipe_flow.yml
PLAY [検証2 パイプラインにおけるデータの消費] ***********************************************************************************************************************************************
TASK [パイプを含むコマンドの実行] ***********************************************************************************************************************************************************
changed: [192.168.1.21]
TASK [変数の中身を確認] *********************************************************************************************************************************************************************
ok: [192.168.1.21] => {
"msg": "取得されたデータ: Processed_Data"
}
PLAY RECAP **********************************************************************************************************************************************************************************
192.168.1.21 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ログの読み解きと考察
-
現象:
res_pipe.stdoutに格納されたのはProcessed_Dataのみであり、前段のechoが出力したRaw_Dataは含まれていません。 -
理由:
Raw_Dataはパイプ(Step 1)によって次段のsedコマンドの標準入力へと転送され、そのプロセス内で消費されたためです。Ansibleのregister変数に届くのは、常にパイプラインの最終段が出力したストリームのみとなります。
テスト結果のまとめ:
途中の出力が欠落する原因は、前段の出力がOSによって次段プロセスの入力へと転送され、Ansibleへの流路から外れたためです。
7. まとめ:出力消失を特定する「調査の原則」
Ansibleで期待した出力が得られない場合は、以下のフローで確認を行います。
【調査手順】
1. Pipe(Step 1)の確認
→ 「|」が使われていないか。
→ 中間出力は後続プロセスに渡され、Ansibleの手前で消費される。
2. Redirect(Step 2)の確認
→ 「>」「>>」「2>」が使われていないか。
→ 出力先がターミナルからファイルへ切り替わる。
3. 最終到達点の特定
→ 最終的にデータはどこ(stdout / stderr / ファイル / /dev/null)へ送られたか。
調査の視点
出力の消失は、エラーやバグではなくOSの正常な挙動(転送)の結果です。
「どこに出ているか」ではなく、「OSがどこへ転送したか」という視点でコマンドラインを分解してください。
-
|や>の有無から通過したStepを特定する。 -
実行後の状態(変数の空き具合、ファイルの有無)からOSの判定結果を逆算する。
この原則に沿って確認すれば、データが途絶えた箇所を物理的に特定できます。
出力問題は、「出口(第1回)」と「転送(第2回)」 の組み合わせによって、その原因がすべて整理可能です。
8. 次回予告
ここまで、出力の「出口」と「転送」の仕組みを整理しました。
しかし、実務では次のような事象に直面することがあります。
「ファイルにデータは存在するのに、grepでヒットしない」
「プロンプトの入力待ち状態で、Ansibleのタスクが停止(ハングアップ)する」
これらの原因は、出力(stdout)ではなく「入力(stdin)」の扱いにあります。
次回:
【Shell編】第3回:なぜgrepで見つからないのか(stdin)
「入力」の仕組みを理解することで、Shellにおけるデータの流れを完結させます。
📑 連載の移動
前の記事:【Shell編】第1回 | 次の記事:【Shell編】第3回
📑 【Shell編】全体のまとめはこちら
→ Ansibleが理解できない理由はLinuxにあった【Shell編】まとめ
🗺️ 初めての方・シリーズの全体像を知りたい方はこちら
本記事は、OSの仕組みからAnsible設計までを繋ぐ連載シリーズの一部です。
「どこから読み始めればいいか」あるいは、「OS/Shell/Ansible編の関係性」 について把握されたい場合は、以下の統合ガイドで整理しています。
9. 連載一覧:Ansibleが理解できない理由はLinuxにあった【Shell編】
| 回数とタイトル | 内容(概要) |
|---|---|
| 【Shell編】第0回:なぜShellを理解しないとAnsibleは使えないのか | AnsibleはShellを通してコマンドを実行している。Ansible → SSH → Shell → Linux の構造を理解し、なぜShell理解が必須なのかを整理する。 |
| 【Shell編】第1回:なぜAnsibleで出力が取得できないのか | Ansibleでregisterが空になる・エラーが見えない原因はstdout / stderrの違いにある。Shellの出力構造を理解することで原因を特定できる。 |
| 【Shell編】第2回:なぜ結果が消えるのか | Ansibleで実行結果が見えなくなる原因はパイプ・リダイレクトによる出力先の変化にある。どこに出力が流れたのかを追うことで原因を特定できる。 |
| 【Shell編】第3回:なぜgrepで見つからないのか | Ansibleでgrepがヒットしない原因は検索対象ではなく入力(stdin)の問題にある。Shellの入力(ストリーム)構造を理解することで原因を特定できる。 |
| 【Shell編】第4回:なぜ正規表現で壊れるのか | Ansibleで検索や置換が壊れる原因は正規表現の評価ルールにある。文字列ではなくルールとして解釈される仕組みを理解し、リテラルの境界を学ぶ。 |
| 【Shell編】第5回:なぜ環境が違うのか | 手動では動くのにAnsibleで失敗する原因は実行環境(PATH・環境変数)の差分にある。Shellの実行コンテキストの違いを理解し、環境を制御する。 |
| 【Shell編】第6回:なぜ条件分岐が失敗するのか | Ansibleのwhen条件が意図通りに動かない原因はexit codeにある。Shellの終了ステータスの判定ロジックを理解することで、正しく条件を組める。 |
| 【Shell編】第7回:なぜループがうまく動かないのか | Ansibleで繰り返し処理が期待通り動かない原因はShellのループと入力・サブシェルの干渉にある。データの扱い方を理解し、ループを安定させる。 |
| 【Shell編】第8回:なぜ途中で処理が止まるのか | Ansible実行中に意図せず処理が止まる原因はShellのエラーハンドリング(set -e 等)にある。エラー伝播の仕組みを理解し、停止条件を制御する。 |
| 【Shell編】第9回:なぜ変数が意図通りに展開されないのか | Ansibleで変数が空になる・壊れる原因はShellのクォートと展開順序にある。値の保護と展開のルールを理解し、安定したコマンドを記述する。 |
| 【Shell編】第10回:なぜ値が分割されてしまうのか | スペースや改行で意図せず値が壊れる原因はShellの単語分割(word splitting)にある。IFSなどの内部処理の本質を理解し、データの整合性を守る。 |
| 【Shell編】第11回:なぜAnsibleの挙動が読めるようになるのか | これまでの知識を統合し、AnsibleのエラーをShellの実行プロセスから逆算して分解できるようになる。実務で使える“読み方”を完成させる。 |