Linuxのテキストファイルから特定の行だけを取り出すには、sed、awk、headとtailを使えます。最も大切なのは、抽出と編集を分けることです。調査では原本を書き換えず、結果を標準出力へ表示し、正しい範囲だと確認してから新しいファイルへ保存します。また「45行目」は、その時点のファイルで改行文字により区切られた物理行を指します。表計算のレコード、JSONオブジェクト、画面の折り返し、アプリケーションのイベント単位と同じとは限りません。
作業前にファイルを確認する
対象パスを推測せず、所有者、サイズ、更新時刻、ファイル種別を確認します。ログが追記中なら、確認中にも行番号が変わり得ます。機密情報を含む可能性があるため、全体を画面へ表示したりチケットへ貼ったりせず、必要な範囲だけを扱います。文字コード、改行形式、圧縮の有無、1行が非常に長くないかも重要です。バイナリや圧縮ファイルに通常のテキストコマンドを直接使うと、端末表示が乱れたり意味のない結果になったりします。
stat -- sample.log
wc -l -- sample.log
wc -lは改行文字の数を数えます。最終行が改行で終わっていない場合、人が数える行数と1だけ違って見えることがあります。巨大ファイルでは全体を最後まで読み取るため時間とI/Oを使います。行番号が既に分かっているなら、まず少量の範囲を読み取る方が負荷を抑えられます。
sedで1行だけ表示する
sed -nは既定の自動出力を抑え、アドレスに一致した行へpコマンドを適用して表示します。45行目だけなら次の形です。-nを付け忘れると全行の自動出力に加えて対象行も表示されるため、「2回出た」と混乱します。これは原本を変更しない読み取りです。
sed -n '45p' -- sample.log
対象行が存在しない場合、GNU sedは何も表示せず正常終了することがあります。そのため、出力が空だからといってコマンド失敗とは限りません。空行が対象だった可能性もあります。前後の行番号を付けて確認する、全行数を確認する、抽出結果のバイト数を見るなど、目的に応じて判定します。入力ファイルが開けない場合は標準エラーと終了状態を確認してください。
sedで範囲を表示する
20行目から30行目までなら20,30pとします。始点と終点を含みます。範囲が逆、0や負数、非常に大きい値にならないよう、スクリプトでは整数と上限を検証します。ユーザー入力をsed式へそのまま連結すると、意図しないコマンドとして解釈される危険があるため、数値だけを許可します。
sed -n '20,30p' -- sample.log
複数の離れた行を表示する
3行目、8行目、12行目のように離れた行は、複数の-e式で明示できます。範囲が増えるならawkの条件式の方が読みやすいこともあります。運用スクリプトでは、何を抽出したかレビューできる記述を優先します。
sed -n -e '3p' -e '8p' -e '12p' -- sample.log
awkのNRで行番号を条件にする
awkのNRはこれまでに読み取った入力レコード数です。既定では改行区切りなので、通常のテキストでは行番号として使えます。45行目だけならNR == 45、20〜30行目なら範囲条件にします。終点を越えたらexitすることで、巨大ファイルの残りを読まずに終えられます。
awk 'NR == 45 { print; exit }' sample.log
awk 'NR >= 20 && NR <= 30 { print } NR > 30 { exit }' sample.log
awkプログラムはシングルクォートで囲み、シェルによる$や空白の展開を防ぎます。行番号をシェル変数で渡すなら、プログラム文字列へ連結せず-vで値を渡し、事前に正の整数か検証します。ファイル名はオプションと誤認されないよう--対応の有無を確認し、必要なら./付きの安全なパスを使います。GNU awkと他実装では細部が異なるため、移植性が必要ならPOSIX範囲の機能へ限定します。
line_number=45
case $line_number in (*[!0-9]*|'') printf '%s\n' 'invalid line number' >&2; exit 2;; esac
awk -v target="$line_number" 'NR == target { print; found=1; exit } END { if (!found) exit 1 }' sample.log
headとtailを組み合わせる
head -n 45で先頭45行を取り出し、その結果の最後1行をtail -n 1で表示すれば45行目を得られます。直感的ですが、対象行までを一度パイプへ流すため、sedや早期exitするawkの方が分かりやすい場合があります。またパイプラインの終了状態は既定で末尾のtailに由来するため、headが入力エラーになった場合を見落とさないようBashのpipefailや個別確認を使います。
set -o pipefail
head -n 45 -- sample.log | tail -n 1
末尾から数える場合はtailが適しています。最後の10行ならtail -n 10、最後から20行目だけなら末尾20行を取得してその先頭1行を見る方法があります。ただしログが追記中なら、実行時点ごとに「末尾」が変わります。厳密な再現が必要なら、原本を止めずに編集するのではなく、承認された固定コピーやスナップショットを使います。
tail -n 10 -- sample.log
tail -n 20 -- sample.log | head -n 1
抽出結果を新しいファイルへ保存する
抽出内容が画面で正しいと確認できたら、存在しない新規ファイルへリダイレクトします。>は保存先が既にあると切り詰めるため、案件固有のファイル名を用意し、同名がないことを確かめます。原本と同じパスを保存先にしてはいけません。シェルはコマンドが入力を読み終える前に出力ファイルを開くため、同じファイルを指定すると原本が空になる可能性があります。
test ! -e ./sample-lines-20-30.txt || { printf '%s\n' 'destination already exists' >&2; exit 2; }
sed -n '20,30p' -- sample.log > ./sample-lines-20-30.txt
stat -- ./sample-lines-20-30.txt
保存後は終了状態、ファイルサイズ、行数、先頭と末尾の内容を確認します。秘密情報を含む場合は保存先のアクセス権、同期設定、共有先、保持期限を確認します。抽出結果を加工するなら、元のファイル名、取得日時、範囲、タイムゾーン、使用したコマンドを別の作業記録に残すと再現できます。
行番号付きで前後関係を見る
障害行だけでは文脈が足りない場合、前後数行を表示します。たとえば45行目の前後5行なら40〜50行です。行番号も付けたい場合はawkでNRを一緒に出力するか、GNU sedの=を使います。ただしログ行にタブや制御文字が含まれると整形が崩れることがあります。解析ツールへ入力するために内容を改変せず、表示用の番号は原本の一部ではないと明示します。
awk 'NR >= 40 && NR <= 50 { printf "%d\t%s\n", NR, $0 } NR > 50 { exit }' sample.log
文字コードと改行の落とし穴
Windows由来のCRLFファイルでは、行末に復帰文字が残り、比較や正規表現が想定外になることがあります。UTF-16などマルチバイトの形式をバイト列として読むと、sedやawkが正常なテキストとして扱えない場合があります。まずfileや作成元仕様で形式を確認し、変換が必要なら原本を保持したまま新しい作業コピーへ変換します。ロケールも文字クラスや並び順へ影響します。行番号抽出だけなら比較的影響は小さいものの、内容条件を組み合わせるときは実行ロケールを記録します。
1行が1レコードとは限らない
CSVは引用符内に改行を含められ、JSONは一つのオブジェクトが複数行にまたがり、スタックトレースは一件のイベントが多数行になります。特定の「レコード」を取り出したいのに物理行番号を使うと、途中だけを切り出して意味を失います。その場合はCSV、JSON、XML、ジャーナルなど形式専用のパーサーを使い、構造を保って抽出します。ログのイベントIDや時刻範囲があるなら、固定行番号より安定した条件になります。
巨大ファイルで負荷を抑える
- 先頭に近い範囲はsedやawkで終点後に終了し、ファイル全体を不要に読まない。
- 末尾付近はtailを使い、全行数を先に数える二重読みを避ける。
- 圧縮ログは形式に対応した読み取り方法を使い、展開先容量を事前に見積もる。
- 本番ストレージで並列に多数の全走査を行わず、I/O監視と担当者の承認を得る。
- 追記中のログで再現性が必要なら、ローテーション済みの固定ファイルや保全コピーを使う。
- 抽出範囲を画面に大量表示せず、新規の保護されたファイルへ保存して必要部分だけ確認する。
結果が空のときの確認順序
- 入力パス、ファイル名の大文字小文字、読み取り権限を確認する。
- 標準エラーと終了状態を確認し、ファイルを開けたかを判断する。
- 対象行が総行数を超えていないか、対象が空行ではないか確認する。
- ログローテーションや別ノードへの出力で対象ファイルが変わっていないか確認する。
- 改行形式、文字コード、圧縮、バイナリ、複数行レコードではないか確認する。
- 時刻やイベントIDなど、行番号より適切な抽出条件へ切り替える。
一行だけならsed -n 45p、条件と早期終了を組み合わせるならawk、先頭や末尾ならheadとtailが分かりやすい選択です。どの方法でも原本を編集せず、抽出対象の意味、ファイルの変化、終了状態、保存先の上書きを確認することが基本です。

コメント