Linuxでコマンドの出力の特定の行を表示する方法

テキストの N 行目を表示する基本は sed -n 'Np' です。-n で自動出力を止め、アドレス N に一致した行だけを p で出します。行番号は1から始まり、空行も一行、最後が改行で終わらないレコードも一行として扱われます。ただし N が存在しなくても sed は通常エラーにせず、何も出さず終了値0です。存在確認が必要な自動処理では、出力の空・非空でなく専用の終了状態を設計します。 確認ポイント:sedは指定行がなくても終了0になり得るため、出力またはfound flagを別に確認します。

目次

N行目が存在する場合としない場合を分ける

末尾改行のない五行目を含むファイルを作ります。echo -e は shell により挙動が違うため、入力は printf で固定します。

work=$(mktemp -d) || exit 1
trap 'rm -r -- "$work"' EXIT
printf 'alpha\nbeta\ngamma\ndelta\nepsilon' >"$work/-records.txt"
sed -n '3p' -- "$work/-records.txt"
printf '%s\n' '--- range 2..4 ---'
sed -n '2,4p' -- "$work/-records.txt"
printf '%s\n' '--- last record ---'
sed -n '5p' -- "$work/-records.txt"

出力は最初が gamma、範囲が beta、gamma、delta、最後が epsilon です。epsilon の後ろに入力由来の改行はありませんが、端末の次の prompt と接着すると読みにくいため、表示用途ではコマンド後に printf '\n' を置けます。ただしそれは出力を変更するので、バイト一致が必要なパイプには追加しません。

sed -n Np と範囲指定

一行だけなら sed -n '3p' file、連続範囲なら sed -n '2,4p' file です。複数の離れた行は sed -n -e '2p' -e '5p' と書けます。GNU sed は -- でオプション解釈を止められます。パス変数を引用し、ファイルが -n のような名前でも命令に誤解されないようにします。

sed -n -e '2p' -e '5p' -- "$work/-records.txt"
sed -n '2,4{=;p}' -- "$work/-records.txt"

最初は beta と epsilon です。次は sed の = が行番号、p が内容を別々の行へ出すため、2、beta、3、gamma、4、delta の順です。nl -ba file や cat -n は全行へ番号を付ける目視用途ですが、その出力を再度 N 行目の原文として使うと番号やタブが混ざります。抽出は原本に対して行います。

巨大ファイルでは N 行目で q する

sed -n '500000p' は表示後も EOF まで入力を読み続けます。通常ファイルの大きな N では、同じアドレスで q を実行して早く閉じます。固定値なら sed -n '500000p;500000q' です。小さなサンプルでは次のように確認できます。

sed -n '3p;3q' -- "$work/-records.txt"
printf 'sed rc=%d\n' "$?"

出力は gamma、sed rc=0 です。3行目を表示して直ちに終了するため4、5行目を読みません。一般形を文字で sed -n 'Np;Nq' と表し、実行時は N を同じ正整数へ置換します。shell 変数を sed program に入れるなら、先に [[ $n =~ ^[1-9][0-9]*$ ]] で検証します。不正な n をそのまま "${n}p;${n}q" に連結すると別の sed 命令を注入できます。

標準入力が pipe、FIFO、終わらないログの場合、N 行目に達すれば q で上流を早く閉じられます。上流は SIGPIPE で非0になる可能性があり、Bash の pipefail 下では全体失敗に見えることがあります。入力を途中で閉じることが意図なら、上流の終了状態をどう扱うか決めます。N 行目に達しない無限待ちを避けるには、入力生成側の終了条件または timeout を別途設計します。

awk NR==N は存在確認と条件追加に向く

awk 'NR==3 {print; exit}' も三行目を表示して早期終了します。NR は複数入力全体の通算番号、FNR はファイルごとに1へ戻ります。複数ファイルの各3行目なら FNR==3、全体の3行目なら NR==3 です。

awk 'NR==3 {print; exit}' "$work/-records.txt"
printf '%s\n' '--- numbered range ---'
awk 'NR>=2 && NR<=4 {printf "%d:%s\n", NR, $0} NR==4 {exit}' "$work/-records.txt"

出力は gamma、範囲が 2:beta、3:gamma、4:delta です。awk は行を field 分割できますが、print だけなら元の $0 を出します。既定の print は出力末尾に改行を付けるため、入力最終行の改行有無をバイト単位で保存する抽出には向きません。原文の区切り状態まで必要ならバイトを扱う専用処理にします。

head | tail は読みやすいが終了状態に注意

head -n 3 -- "$work/-records.txt" | tail -n 1
printf 'pipeline rc=%d\n' "$?"
printf '%s\n' '--- range 2..4 ---'
head -n 4 -- "$work/-records.txt" | tail -n 3

出力は gamma と pipeline rc=0、範囲が beta、gamma、delta です。意味は「先頭 N 行を取り、その最後の一行」なので対話では読みやすい一方、二プロセスを使います。Bash の既定では終了状態が tail のものだけなので、head が入力を開けなくても tail が空入力で成功すれば0になり得ます。スクリプトでは set -o pipefail を使うか、sed/awk 一個で処理して入力エラーを見逃さないようにします。

最後から N 行目は別要件です。末尾から2行目なら tail -n 2 file | head -n 1 ですが、tail は末尾を知るため通常ファイル全体の位置を調べ、stream では EOF を待ちます。「先頭から N 行目」と混同せず、巨大な追記ログで EOF が来ない入力には使いません。

N 行目が存在しないのに sed は成功する

out=$(sed -n '99p' -- "$work/-records.txt")
rc=$?
printf 'sed missing rc=%d bytes=%d\n' "$rc" "${#out}"
awk -v n=99 'NR==n {print; found=1; exit} END {if (!found) exit 4}' "$work/-records.txt"
printf 'awk missing rc=%d\n' "$?"

正確な出力は sed missing rc=0 bytes=0、awk missing rc=4 です。sed の0は構文と読取りが成功した意味で、指定行の存在を保証しません。さらに N 行目が空行なら command substitution の値も空なので、bytes=0 では absent と空行を区別できません。awk の found flag と専用終了値なら区別できます。

printf 'first\n\nthird\n' >"$work/with-empty.txt"
awk -v n=2 'NR==n {printf "found line %d length=%d\n", NR, length($0); found=1; exit} END {if (!found) exit 4}' "$work/with-empty.txt"
printf 'rc=%d\n' "$?"

出力は found line 2 length=0 と rc=0 です。空だが存在する二行目を明示できます。ファイルが読めない場合も awk は非0ですが、存在しない行に割り当てた4と実装の入出力エラー値が衝突しないよう、実運用では stderr も記録し、必要なら wrapper で分類します。

末尾改行と wc -l の見え方

wc -l -- "$work/-records.txt"
awk 'END {print NR}' "$work/-records.txt"
tail -n 1 -- "$work/-records.txt" | od -An -t x1

wc -l は newline を4個と数えるので4、awk の NR は末尾改行なしの epsilon も record として読み5です。tail は epsilon のバイト 65 70 73 69 6c 6f 6e を出し、末尾の 0a はありません。表示上はどちらも「五行」に見えるため、行数要件と newline 数要件を分けます。sed、awk、head、tail は末尾改行なしの最終 record も表示できますが、再出力時に改行を加える実装・コマンドがあるので byte-for-byte 保存を仮定しません。

ハイフンで始まるファイル名

この例の basename は -records.txt です。絶対パス "$work/-records.txt" は引数全体が / で始まるためオプションには見えません。カレントディレクトリで basename を直接使う場合、GNU sed/head/tail は -- を付け、移植的には ./-records.txt とパス化します。

( cd "$work" || exit 1
  sed -n '3p' -- -records.txt
  awk 'NR==3 {print; exit}' ./-records.txt
  head -n 3 -- -records.txt | tail -n 1
)

三つとも gamma を出します。-- の対応はコマンド実装により異なるため、POSIX 寄りの shell script では ./ または絶対パスを全コマンドへ渡す方法が単純です。パス変数は引用し、空白・glob 文字・改行を shell に分割させません。

選択と検証

  • 固定 N 行目: sed -n 'Np;Nq'。N は正整数検証後に同じ値を p と q へ入れる。
  • 範囲: sed -n 'M,Np'。巨大入力なら終点で q する。
  • 存在しない行を非0にする: awk の found flag と END で専用終了値を返す。
  • 対話で分かりやすく表示: head -n N | tail -n 1。pipefail または前段失敗の確認を追加する。
  • 行番号、空行、末尾改行、先頭からか末尾からかを要件として固定し、既知サンプルの exact output と照合する。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次