catコマンドはプリントじゃない ― ストリームを連結する“ポンプ”
catとは「ファイルや標準入力のバイト列を連結して標準出力へ流すコマンド」である。
catの本質と語源(con"cat"enate)
- 本質: 読み取ったバイト列を解釈せず、入力→出力へそのまま転送する。
- 語源: “concatenate”(連結)から。**表示(print)**は副産物で、出力先が端末(TTY)だと見えるだけ。
- ユースケースの核: 「複数入力の連結」「ストリーム化してパイプに流す」「特殊ファイルの取り扱い(/dev/zero, FIFOなど)」。
よくある誤解とアンチパターン(比較表)
同じ階層(=解決したい課題が同じ)の書き方を表で比較します。
| やりがちな書き方 | 何が問題か | より良い書き方 | 備考 | |
|---|---|---|---|---|
| `cat file | grep kw` | 二重I/Oで無駄(UUOC: Useless Use of cat) | grep kw file |
例外: 複数プロセス出力の結合にはcatが有効(下記参照)。 |
| `cat file | sed 's/a/b/'` | 同上 | sed 's/a/b/' file |
-iで直接編集も可(バックアップ注意)。 |
| `cat file | awk '{...}'` | 同上 | awk '{...}' file |
awkは自前でファイルを読む。 |
| `cat file | wc -l` | パイプ不要 | wc -l < file |
入力リダイレクトで同等、速い。 |
| `cat file | tail -n 1` | 無駄な中継 | tail -n 1 file |
head/tailはファイルを直接読める。 |
| `cat large.gz | zcat` | 二重展開の混乱 | zcat large.gz |
圧縮は伸ばしてからstringsやgrepへ。 |
catで行番号付け |
用途ズレ |
nl file / cat -n file
|
目的が表示なら専用コマンドの方が表現力あり。 |
“良いcat”の使いどころ(実務に効く具体例)
1) 複数生成元の結合(プロセス置換)
cat <(jq -r '.items[] | .id' a.json) \
<(jq -r '.items[] | .id' b.json) \
| sort -u > ids.txt
2つのコマンド出力を順序を保って1本のストリームにまとめる。
2) 分割ファイルの連結
cat backup.part.* > backup.tar
分割アーカイブを連結して元に戻す。split/gsplitの逆操作。
3) **FIFO(名前付きパイプ)**の中継
mkfifo pipe
cat pipe | gzip -9 > out.gz & # 中継(圧縮担当)
producer > pipe # どこかのプロセスが供給
生データを別プロセスに受け渡すときの“ポンプ”役。
4) デバイスファイルの取り扱い
# 1MiBのゼロ埋めファイル
cat /dev/zero | head -c 1M > zeros.bin
# ランダムデータでテスト
cat /dev/urandom | head -c 256 > key.bin
ddでも可能だが、cat+headは直感的で簡潔。
5) ストリームを途中で枝分かれ(tee連携)
make build 2>&1 | tee build.log | grep -i 'error'
リアルタイム表示しつつログ保存&フィルタ。cat単体では分岐できないが、パイプラインの起点/前段として有用。
6) “中身をいじらず運ぶだけ”が欲しい時
cat config.yml | ssh host 'cat > /etc/myapp/config.yml'
余計な改行やエスケープを入れない“素の転送”。scp代替にならないが、一時的な書き込みに便利。
catと近縁コマンドの役割比較(表)
同列の目的を持つコマンドを具体例つきで比較します(同じ課題を解く“選択肢”として捉える)。
| コマンド | 一言で | 具体例(ひと目で用途がわかる) | 落とし穴 / 補足 | ||
|---|---|---|---|---|---|
cat |
連結/転送 |
cat a b > all`cat <(cmd1) <(cmd2) |
sort -u` | 加工はしない。ポンプ役に徹する。 | |
tac |
逆順cat | `tac access.log | grep -m1 'ERROR'`(最後のエラーを速攻で) | 行単位逆順。巨大ファイルはI/O多め。 | |
tee |
分岐 | `make 2>&1 | tee build.log | grep -i error` |
-aで追記。分岐はtee、連結はcat。 |
paste |
横連結 |
paste -d, a.csv b.csv(列を並べてCSV化) |
行数ズレは要注意。列操作はawk/cutも選択肢。 |
||
head |
先頭を見る |
head -n 20 file`zcat huge.gz |
head -n 1` | “確認/スニペット取得”に最適。 | |
tail |
末尾を見る |
tail -f app.log(追従)tail -n +2 file(1行目除去) |
ログローテは-Fが堅い。 |
||
less |
ページャ |
less -R +G build.log(色保持で末尾から閲覧) |
対話ツール。スクリプト用途は非推奨。 | ||
nl |
行番号付与 | `nl -ba source.c | sed -n '120,140p'` |
cat -nでも可。空行扱いはオプションで調整。 |
|
tr |
文字置換/削除 |
tr -d '\r' < win.txt > unix.txt(CR削除) |
正規表現は不可。単純変換のみ。 | ||
dd |
ブロックI/O | dd if=/dev/zero of=file bs=1M count=1 status=none |
サイズ/アライメント厳密に制御。進捗status=progress。 |
||
printf/echo
|
生成 | `printf 'GET / HTTP/1.0\r\n\r\n' | nc host 80` |
echoは実装差多い。正確さはprintf。 |
|
sed/awk
|
加工 |
sed -E 's/(foo)/\U\1/g' fileawk -F, '$3>0{sum+=$3} END{print sum}' data.csv
|
テキスト加工の主戦力。大規模集計はawkが楽。 |
||
split |
分割 |
split -b 100M big.bin part. → cat part.* > big.bin
|
再結合は順序に注意(接頭辞の並び)。 |
実務Tips(性能・可搬性・安全)
- UUOCは避ける: 直接ファイルを読めるコマンドにはそのまま渡す方が高速・明快。
-
ロケールで速度差: 大量テキストは
LC_ALL=Cでバイト比較に倒すと速いことがある(例:LC_ALL=C sort)。 -
改行とバイナリ: 改行無しの巨大レコードやNULを含むデータは、
catは通せるが後段が詰まることがある。grep -aやtr/odで前処理。 -
権限と上書き:
cat > /path/fileは上書き。>>で追記、set -o noclobberで事故防止も検討。 -
巨大ファイル: ネットワーク越しは逐次処理(ストリーミング)を活用。
cat big | gzip | ssh host 'cat > big.gz'のように保存せず転送。
パイプは1行ずつ渡すの? それともまとめて?
結論: パイプは「行」ではなく、ただのバイト列ストリームです。
cat data.txt | xxx のとき、catは読み取ったデータを**塊(ブロック)で write(2)し、受け手xxxは任意サイズで read(2)**します。行単位で“渡す”仕組みはありません。
-
行っぽく見える理由:
grepやawkのように“行で処理する”コマンドが、受け取ったバイト列を自前で改行で区切って扱うから。読み取りはまとめてでも、解釈は行ベース。 - バッファリングの性質: 多くの実装は標準ライブラリのバッファリングに従います。出力先が端末(TTY)なら行バッファ、パイプ/ファイルならブロックバッファが一般的。そのためパイプではまとめて流れやすい。
- パイプの器(カーネルバッファ): サイズはOS依存(Linuxの既定は数十KiBが典型)。アプリ側は“連続したバイト列”として扱えば十分。
-
典型的な挙動:
yes | head -n1は、headが1行読んだ瞬間パイプを閉じる→yesはSIGPIPEで終了。行で止まるように見えても、実体はバイトストリームです。
バッファリングの比較と制御(表)
| モード | いつ起きる | 利点 | 欠点 | 代表例 / 強制手段 |
|---|---|---|---|---|
| 行バッファ | 出力先がTTY(端末)のとき | 低遅延(行ごとに即時フラッシュ) | スループットが出にくい / 行末まで出ない |
grep --line-buffered、stdbuf -oL cmd、awk '{...; fflush()}'
|
| ブロックバッファ | 出力先がパイプ/ファイルのとき | 高スループット | バッファが溜まるまで遅延 | 既定動作が多い。調整: stdbuf -o 4096 cmd(ブロック/サイズ指定) |
| アンバッファ | 明示指定時 | ほぼ即時 | I/O回数増で重くなる |
stdbuf -o0 cmd、python -u、unbuffer cmd(expect) |
リアルタイムに見たいログは、次のように“行バッファ化”すると快適です:
tail -f app.log | grep --line-buffered -i 'error'
python producer.py | stdbuf -oL consumer
まとめ
-
catはプリントコマンドではない。本質はストリームの連結と転送。 - “表示したい”なら
less/nl/grep等、目的特化の道具を選ぶ。 - “データを運ぶ・束ねる”場面では、
catは最短で確実なポンプになる。