🎯 この章で学ぶこと

  • open()with 文によるファイルの読み書き
  • CSVファイルの扱い方
  • try / except による例外処理
  • 「安全に失敗する」プログラムの考え方

6.1 ファイルを読む — with open()

まず、練習用のログファイル access.log を作りましょう(エディタで以下を保存)。

2026-07-08 09:00:01 LOGIN OK user=tanaka ip=192.168.1.20
2026-07-08 09:00:15 LOGIN FAILED user=admin ip=203.0.113.9
2026-07-08 09:00:18 LOGIN FAILED user=admin ip=203.0.113.9
2026-07-08 09:01:02 LOGIN OK user=suzuki ip=192.168.1.31
2026-07-08 09:01:30 LOGIN FAILED user=root ip=203.0.113.9

読み込みの基本形はこれです。

# read_log.py
with open("access.log", encoding="utf-8") as f:
    for line in f:
        line = line.strip()      # 行末の改行を除去
        if "FAILED" in line:
            print(line)

ポイントを整理します。

6.2 ファイルに書く

# write_report.py
findings = ["adminへの失敗3回", "root試行1回"]

with open("report.txt", "w", encoding="utf-8") as f:
    f.write("== 点検レポート ==\n")
    for item in findings:
        f.write(f"- {item}\n")

第2引数がモードです:"r" 読み込み(省略時)、"w" 書き込み(既存内容を消して上書き)、"a" 追記。

⚠️ "w" は破壊的

"w" で開いた瞬間、既存のファイル内容は消えます。「上書きして良いファイルか」を確認してから書く癖をつけましょう。監査ログのような改ざんされては困るファイルへの追記は "a" を使います。

6.3 CSVファイルを扱う

IT部門で最も出会うデータ形式がCSV(カンマ区切り)です。標準ライブラリの csv モジュールを使います。

# accounts.csv
username,department,last_login
tanaka,情シス,2026-07-01
suzuki,営業,2025-11-20
sato,経理,2026-06-28
import csv

with open("accounts.csv", encoding="utf-8") as f:
    reader = csv.DictReader(f)      # 1行目をヘッダとして辞書で読む
    for row in reader:
        if row["last_login"] < "2026-01-01":
            print(f"長期未ログイン: {row['username']} ({row['department']})")
長期未ログイン: suzuki (営業)

DictReader を使うと各行が辞書になり、row["username"] のように列名でアクセスできます(第4章の辞書の知識がそのまま活きます)。

6.4 例外処理 — try / except

存在しないファイルを開こうとすると、プログラムはエラーで停止します。

>>> open("nai.log")
Traceback (most recent call last):
  ...
FileNotFoundError: [Errno 2] No such file or directory: 'nai.log'

この「実行中に起きるエラー」を例外(exception)と呼びます。try / except を使うと、例外が起きたときの対応をプログラム自身に用意させられます。

try:
    with open("access.log", encoding="utf-8") as f:
        lines = f.readlines()
except FileNotFoundError:
    print("ログファイルが見つかりません。パスを確認してください")
    lines = []

print(f"{len(lines)} 行読み込みました")

第2章の宿題だった「数値変換の安全な受け取り」もこれで解決できます。

def read_int(prompt):
    """数値が入力されるまで聞き直す安全な入力関数"""
    while True:
        text = input(prompt)
        try:
            return int(text)
        except ValueError:
            print("数値を入力してください")

age = read_int("年齢: ")
print(f"{age} 歳ですね")

except の書き分けと finally

try:
    with open("data.csv", encoding="utf-8") as f:
        value = int(f.readline())
except FileNotFoundError:
    print("ファイルがありません")
except ValueError:
    print("1行目が数値ではありません")
except Exception as e:
    # 想定外の例外。内容を記録して調査できるようにする
    print(f"想定外のエラー: {e}")
finally:
    print("処理を終了します")   # 成功でも失敗でも必ず実行される
⚠️ 「握りつぶし」は最悪のアンチパターン

except Exception: pass のように例外を黙って無視するコードは絶対に書かないでください。エラーが起きた事実ごと消えるため、障害にも侵入の兆候にも気づけなくなります。例外は「捕まえたら、記録するか、ユーザーに伝えるか、対処する」のいずれかが必須です。

🛡️ セキュリティの視点 — エラーメッセージは情報漏えいの窓口

攻撃者はわざとエラーを起こして、返ってくるメッセージからシステムの内部情報(ファイルパス、SQL文、ライブラリのバージョンなど)を収集します。これをエラーベースの情報収集と呼びます。原則は「利用者には簡潔なエラーメッセージ、詳細はサーバー側のログにだけ記録」。Tracebackを画面にそのまま出すWebアプリは、攻撃者に設計図を配っているのと同じです。

6.5 実践 — 頑丈なログ集計スクリプト

この章の内容を組み合わせた、実務スタイルの小さなスクリプトです。

# failed_report.py — ログイン失敗をユーザー別に集計してファイル出力
import csv

def load_lines(path):
    """ログを読み込む。無ければ空リスト(プログラムは止めない)"""
    try:
        with open(path, encoding="utf-8") as f:
            return [line.strip() for line in f]
    except FileNotFoundError:
        print(f"[警告] {path} が見つかりません")
        return []

def count_failed_by_user(lines):
    counts = {}
    for line in lines:
        if "FAILED" not in line:
            continue
        for part in line.split(" "):
            if part.startswith("user="):
                user = part.replace("user=", "")
                counts[user] = counts.get(user, 0) + 1
    return counts

lines = load_lines("access.log")
counts = count_failed_by_user(lines)

with open("failed_report.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["user", "failed_count"])
    for user, n in sorted(counts.items(), key=lambda x: x[1], reverse=True):
        writer.writerow([user, n])

print(f"{len(counts)} ユーザー分のレポートを出力しました")

関数分割(第5章)、辞書での集計(第4章)、ファイルとCSV、例外処理──ここまでの全部が入っています。写経して動かしてみてください。

まとめ

練習問題

問題 6-1

6.1で作った access.log を読み込み、「FAILEDの行だけ」を failed_only.log という別ファイルに書き出すプログラムを書いてください。

解答を見る
with open("access.log", encoding="utf-8") as src:
    failed_lines = [line for line in src if "FAILED" in line]

with open("failed_only.log", "w", encoding="utf-8") as dst:
    dst.writelines(failed_lines)

print(f"{len(failed_lines)} 行を書き出しました")
問題 6-2

ユーザーに「1〜65535のポート番号」を入力させ、数値でない・範囲外の場合は聞き直す関数 read_port() を書いてください。

解答を見る
def read_port():
    """1〜65535のポート番号が入力されるまで聞き直す"""
    while True:
        text = input("ポート番号 (1-65535): ")
        try:
            port = int(text)
        except ValueError:
            print("数値を入力してください")
            continue
        if 1 <= port <= 65535:
            return port
        print("範囲外です")

print(read_port())

「型の検証」と「範囲の検証」の2段階チェックになっています。これが入力バリデーションの基本形です。

問題 6-3

次のコードのセキュリティ上の問題点を2つ指摘してください。

try:
    with open(filename) as f:
        data = f.read()
except Exception:
    pass
print(f"読み込み結果: {data}")
解答を見る
  1. 例外の握りつぶしexcept Exception: pass により、ファイルが無い・権限が無いなどの異常が起きても誰も気づけません。記録もされず、障害調査も不可能になります。
  2. 失敗時に未定義変数を参照 — 例外が起きた場合 data は定義されないまま print で使われるため、今度は NameError で落ちます。「エラー処理のつもりが別のエラーを生む」典型例です。

修正例:例外の種類を絞って捕捉し、メッセージを記録し、data に安全な初期値を与える(または処理を中断する)。