🐧

Bash のコマンド置換における stdout / stderr の出し分け

2023/02/17に公開

契機

とあるプロジェクトで,C のヘッダファイルのテンプレートに配列の初期化リテラルを注入するシェルスクリプトが用いられていた [1]

このシェルスクリプトは,別の C ベースの実行ファイルにとあるシェル変数(改行を含まない ID ライクなもの)の中身を食わせ,変数の中身を逐次的に char * 型に置き換えた後,前述のテンプレートのクローンに sed で叩き込んで C のヘッダファイルとして解釈可能にした後,更に別のシェル変数に代入するというとんでもないことをやっていた.

要するに,これのデバッグを CI 環境で行う必要が生じた.

問題

実行ファイルの I/O が stdin / stdout 頼みだったため,シェルスクリプト上でシェル変数に代入する過程を汚さずに print デバッグする方法が欲しかった.

結論から言えば,至ってシンプルなソリューションで解決可能で,標準エラー出力 stderr を使えば何の問題も生じない.

実際,Bash のマニュアルを参照すると,下記のような記載がある:

Bash performs the expansion by executing command in a subshell environment and replacing the command substitution with the standard output of the command, with any trailing newlines deleted. Embedded newlines are not deleted, but they may be removed during word splitting. The command substitution $(cat file) can be replaced by the equivalent but faster $(< file).

すなわち,コマンド置換においては標準出力しか参照されないらしい.

したがって,変数の中身を tty 上でダンプしたければ初手で標準エラー出力を検討するのが良さそうである.

下記のプログラムを考える:

std_test.c
#include <stdio.h>

int main(void) {
 fprintf(stderr, "stderr\n");
 fprintf(stdout, "stdout\n");
 return 0;
}

こいつをコンパイルして実行すると,tty 上の結果は下記のようになる:

❯ ./a.out
stderr
stdout

一方で,これの実行結果をコマンド置換でシェル変数に代入すると,結果は下記のようになる:

❯ OUTPUT=$(./a.out)
stderr
❯ echo $OUTPUT
stdout

結び

Bash のコマンド置換が標準出力のみをフィルタリングする挙動は何らかの応用が可能.

脚注
  1. 新規プロジェクトではこういう慣習は辞めましょうね. ↩︎

Discussion