Python 逆引き集 | 文字列操作:改行コードを処理する

Python Python
スポンサーリンク
スポンサーリンク

Pythonで改行コードを処理する方法

テキストファイルを読み込んだら想定と違う改行で表示が崩れた、CSVデータの中に改行が混入してレコードが壊れた、Windowsで作ったファイルをLinuxで処理したら余計な空白が出た――プログラミングの現場では、改行コードの扱いに悩まされる場面が非常に多くあります。実はこの「改行コード」、Windows・Mac・Linuxで歴史的に異なる形式が使われており、OSを跨いでデータをやり取りするときに問題が発生しやすいのです。この記事では、改行コードの種類と問題点、Pythonでの正しい読み書き方法、そして実務でよくあるトラブルとその対策まで、プログラミング初心者の方でも確実に使いこなせるように順を追って解説します。

はじめに:改行コードの種類と問題点

まず、改行コードには主に3つの形式があります。

  • LF(Line Feed):\n(1文字)。Unix/Linux/macOS(OS X以降)の標準
  • CR(Carriage Return):\r(1文字)。古いMac OS(OS 9以前)の標準。現在は単独ではほぼ使われない
  • CRLF(Carriage Return + Line Feed):\r\n(2文字)。Windowsの標準

問題は、同じ「改行」でもOSによってバイト数が違うことです。Windowsで作成したテキストファイル(CRLF)をLinuxのツールで処理すると、\rが「余計な文字」として残ってしまい、表示が崩れたり、文字列比較が失敗したりします。

# 改行コードの違いを実際に確認する例
# Windowsスタイル(CRLF)の文字列
win_text = "Hello\r\nWorld\r\n"

# Linuxスタイル(LF)の文字列
unix_text = "Hello\nWorld\n"

# 文字数の違い(\rも1文字としてカウントされる)
print(len(win_text))   # 14(Hello=5 + \r\n=2 + World=5 + \r\n=2)
print(len(unix_text))  # 12(Hello=5 + \n=1 + World=5 + \n=1)

# \rが残ると表示が崩れる例(printの出力)
print("Windows:", repr(win_text))  # 'Hello\r\nWorld\r\n'(\rが見える)
print("Unix:  ", repr(unix_text))  # 'Hello\nWorld\n'
Python

この記事では以下の流れで解説します。

  • 基本編:改行コードの表現方法(\n、\r\n)、文字列の分割と結合
  • 実務編:ファイルの読み書きでの改行処理、正規表現による置換
  • 注意点:CSVでの改行問題、セキュリティ上の考慮(ログインジェクション対策)

まずは基本から確実に押さえていきましょう。

基本編:改行コードの表現と操作

改行コードの基本表現

Pythonでは、改行コードはエスケープシーケンスで表現します。文字列リテラルの中で、\nはLF、\rはCR、\r\nはCRLFを意味します。

# 改行コードの基本表現
lf = "1行目\n2行目"      # LF(Unix/Linux/macOSスタイル)
crlf = "1行目\r\n2行目"  # CRLF(Windowsスタイル)
cr = "1行目\r2行目"      # CR(古いMacスタイル)

# repr()で実際の文字列を確認(\nや\rが見える)
print(repr(lf))    # '1行目\n2行目'
print(repr(crlf))  # '1行目\r\n2行目'
print(repr(cr))    # '1行目\r2行目'

# 実際にprintすると改行として表示される
print(lf)
# 1行目
# 2行目
Python

splitlines()で改行で分割する

文字列を改行で分割するには、splitlines()メソッドを使うのが最も推奨されます。これはLF、CRLF、CRのいずれの改行コードにも対応し、かつ末尾の改行を適切に処理します。

# splitlines()で改行で分割する例(推奨)
text = "1行目\n2行目\r\n3行目\r4行目"

lines = text.splitlines()
print(lines)  # ['1行目', '2行目', '3行目', '4行目']

# split("\n")との違い:\rが残ってしまう
print(text.split("\n"))  # ['1行目', '2行目\r', '3行目\r', '4行目'](\rが残る!)

# 空行も正しく処理される
text2 = "1行目\n\n3行目"
print(text2.splitlines())  # ['1行目', '', '3行目'](空行が保持される)
Python

split("\n")はLFのみで分割するため、CRLFやCRが混在していると\rが残ってしまいます。改行で分割したい場合はsplitlines()を使うのが鉄則です。

join()で改行を挿入して結合する

複数の文字列を改行で結合するには、join()メソッドを使います。改行コードは、"\n".join(リスト)のように指定します。

# join()で改行を挿入して結合する例
lines = ["1行目", "2行目", "3行目"]

# LFで結合(Unix/Linux/macOS向け)
lf_text = "\n".join(lines)
print(repr(lf_text))  # '1行目\n2行目\n3行目'

# CRLFで結合(Windows向け)
crlf_text = "\r\n".join(lines)
print(repr(crlf_text))  # '1行目\r\n2行目\r\n3行目'

# 末尾にも改行を付けたい場合は、+ "\n" を追加
text_with_end = "\n".join(lines) + "\n"
print(repr(text_with_end))  # '1行目\n2行目\n3行目\n'
Python

replace()で改行コードを統一する

文字列内の改行コードを別の形式に変換したい場合は、replace()メソッドを使います。ただし、CRLF→LFに変換する場合は順序に注意が必要です。

# replace()で改行コードを統一する例
mixed_text = "1行目\r\n2行目\n3行目\r4行目"

# CRLF→LF:\r\nを\nに置換
lf_text = mixed_text.replace("\r\n", "\n")
print(repr(lf_text))  # '1行目\n2行目\n3行目\r4行目'(\rだけが残る)

# 残ったCR(\r)もLFに変換
lf_text = lf_text.replace("\r", "\n")
print(repr(lf_text))  # '1行目\n2行目\n3行目\n4行目'(すべてLFに統一)

# シンプルにまとめると:まず\r\nを\nに、次に残りの\rを\nに
def normalize_newlines(text):
    """あらゆる改行コードをLFに統一する"""
    return text.replace("\r\n", "\n").replace("\r", "\n")

print(repr(normalize_newlines(mixed_text)))  # '1行目\n2行目\n3行目\n4行目'
Python

なお、splitlines()とjoin()を組み合わせても同じ結果が得られます。"\n".join(text.splitlines())とすれば、どんな改行コードでもLFに統一できます。

実務編:ファイルの読み書きと改行処理

ファイル読み込み時の改行処理

Pythonでテキストファイルを読み込む際、デフォルトではOSに応じた改行コードが自動的に変換されます(Universal Newlines)。つまり、Windowsで作成されたCRLFのファイルをLinuxで読んでも、Python内部ではLFに統一されて処理されます。

# ファイル読み込みの基本例(デフォルトの動作)
# 実際にはファイルが必要なため、StringIOでシミュレート
from io import StringIO

# Windowsスタイル(CRLF)のファイルを想定
win_file_content = "1行目\r\n2行目\r\n3行目\r\n"

# open()で読み込むと、内部ではLFに統一される(newline=""を指定しない場合)
with StringIO(win_file_content) as f:
    content = f.read()
    print(repr(content))  # '1行目\r\n2行目\r\n3行目\r\n'(StringIOは変換しない)

# 実際のファイルの場合(open()使用時)
# with open("file.txt", "r") as f:  # デフォルトでnewline=None(自動変換)
#     content = f.read()            # 内部ではLFに統一される
Python

重要なのは、ファイルを書き込む際の改行コードを制御したい場合です。open()関数のnewline引数で、書き込み時の改行コードを明示的に指定できます。

# ファイル書き込み時の改行コード制御の例
lines = ["1行目", "2行目", "3行目"]

# Windowsスタイル(CRLF)で書き出す
# with open("output_win.txt", "w", newline="\r\n") as f:
#     f.write("\r\n".join(lines))

# Unixスタイル(LF)で書き出す(デフォルト)
# with open("output_unix.txt", "w", newline="\n") as f:
#     f.write("\n".join(lines))

# newline=""を指定すると、OSのデフォルト改行が使われる
# with open("output_default.txt", "w", newline="") as f:
#     f.write("\n".join(lines))  # Windowsなら\r\n、Linuxなら\nに変換される
Python

実務の指針としては、「ファイルの読み込みはデフォルト(自動変換)でOK。書き込みは相手方のOSに合わせてnewlineを指定する」と覚えておくとよいでしょう。

正規表現で柔軟に改行を処理する

より複雑な改行処理が必要な場合は、reモジュール(正規表現)を使います。\r\n|\r|\nのパターンで、あらゆる改行コードにマッチさせることができます。

# 正規表現で改行コードを統一する例
import re

mixed_text = "1行目\r\n2行目\n3行目\r4行目"

# \r\n, \r, \n のいずれにもマッチして \n に置換
normalized = re.sub(r"\r\n|\r|\n", "\n", mixed_text)
print(repr(normalized))  # '1行目\n2行目\n3行目\n4行目'

# 連続する改行(空行)を1つにまとめる例
text_with_blank = "1行目\n\n\n2行目\n\n3行目"
collapsed = re.sub(r"\n+", "\n", text_with_blank)
print(repr(collapsed))  # '1行目\n2行目\n3行目'
Python

実務テンプレート:改行コードを統一してから処理する

外部からの入力(ファイル、API、フォーム)を処理する際は、まず改行コードを統一してから後続の処理に進むのが安全です。以下のテンプレートは、あらゆる改行コードをLFに統一し、前後の空白も除去します。

# 実務テンプレート:改行コードを統一して行リストに変換する
def read_lines_normalized(text):
    """
    あらゆる改行コード(LF/CR/CRLF)をLFに統一し、
    前後の空白を除去した行のリストを返す
    """
    if not isinstance(text, str):
        return []
    # ① 改行コードをLFに統一(\r\n → \n、\r → \n)
    normalized = text.replace("\r\n", "\n").replace("\r", "\n")
    # ② 行に分割し、各行の前後の空白(スペース、タブなど)を除去
    lines = [line.strip() for line in normalized.split("\n")]
    # ③ 空行を除去したい場合は、以下のコメントを外す
    # lines = [line for line in lines if line]
    return lines

# 使用例
mixed = "  1行目  \r\n\n  2行目\r3行目  \n"
print(read_lines_normalized(mixed))
# ['1行目', '', '2行目', '3行目'](改行が統一され、空白が除去されている)
Python

注意すべき重要なポイント

CSVファイルでの改行問題

CSVファイルでは、フィールドの中に改行が含まれることがあります(例:住所欄に「東京都千代田区\n1-2-3」と入力)。この場合、単純にsplitlines()で処理すると、1つのレコードが複数行に分断されてしまいます。

# 【問題】CSVの中身をsplitlines()で処理すると壊れる例
csv_content = "名前,住所\n太郎,\"東京都千代田区\n1-2-3\"\n花子,大阪府"
# 2レコードのはずが、splitlines()では3行になってしまう
print(csv_content.splitlines())
# ['名前,住所', '太郎,"東京都千代田区', '1-2-3"', '花子,大阪府']

# 【対策】csvモジュールを使って正しく処理する
import csv
import io

f = io.StringIO(csv_content)
reader = csv.reader(f)
for row in reader:
    print(row)
# ['名前', '住所']
# ['太郎', '東京都千代田区\n1-2-3'](改行を含むフィールドが正しく1つのセルとして処理される)
# ['花子', '大阪府']
Python

つまり、CSVデータを処理する場合は、必ずcsvモジュールを使い、行分割はcsv.readerに任せるのが鉄則です。手動でsplitlines()やsplit("\n")を使うと、データが壊れる原因になります。

print()の改行制御

print()関数はデフォルトで末尾に改行を出力しますが、end引数でこれを制御できます。また、\rを使うと、同じ行に上書き表示する「進行状況バー」なども実現できます。

# print()の改行制御の例
# デフォルト:末尾に\nが付く
print("1回目")
print("2回目")

# end=""で改行を抑制(連続して同じ行に出力)
print("進行中...", end="")
print("完了")  # 進行中...完了(同じ行に表示)

# \rで行頭に戻って上書き(プログレスバーの基本)
import time
for i in range(3):
    print(f"\r処理中: {i+1}/3", end="", flush=True)
    time.sleep(0.5)
print("\r完了しました    ")  # 最後に改行して上書き
Python

Noneや非文字列型への対策

外部データや辞書の値など、Noneや整数が混入する可能性がある場合、そのままreplace()やsplitlines()を使おうとするとエラーになります。型チェックのガードを入れるのが安全です。

# 安全に改行処理するためのガード例
def safe_normalize(text):
    # 文字列型でなければNoneを返す(または空文字列など、プロジェクトの方針に応じる)
    if not isinstance(text, str):
        return None
    return text.replace("\r\n", "\n").replace("\r", "\n")

print(repr(safe_normalize("a\r\nb\rc\nd")))  # 'a\nb\nc\nd'
print(safe_normalize(None))                    # None(エラーにならない)
print(safe_normalize(123))                     # None(数値型も安全に処理)
Python

セキュリティ上の考慮:ログインジェクション対策

情報セキュリティの観点では、改行コードの扱いはログインジェクション(Log Injection)対策とも深く関わります。攻撃者が入力に改行コード(\nや\r\n)を混入させることで、ログの改ざんや偽装を行う攻撃手法です。

# ログインジェクションの例(概念的な説明)
user_input = "正常な入力\n[2026-10-10] 管理者による操作: 成功"  # 悪意のある改行を含む

# そのままログに書き込むと、2行に分かれて偽のログが混入する
log_line = f"[{datetime.now()}] ユーザー入力: {user_input}"
# 出力例:
# [2026-10-10 09:00:00] ユーザー入力: 正常な入力
# [2026-10-10] 管理者による操作: 成功(←偽のログとして解釈される可能性)
Python

対策としては、ログに出力する前に改行コードをエスケープ(削除または可視化)することが有効です。

# ログ出力前の改行サニタイズ例
import datetime

def sanitize_for_log(text):
    """ログ出力用に改行コードをエスケープする"""
    if not isinstance(text, str):
        return str(text)
    # 改行コードを可視化(\n → \\n、\r → \\r)
    return text.replace("\\", "\\\\").replace("\r", "\\r").replace("\n", "\\n")

user_input = "正常な入力\n偽のログ行"
safe_input = sanitize_for_log(user_input)
log_line = f"[{datetime.datetime.now()}] 入力: {safe_input}"
print(log_line)
# [2026-10-10 09:00:00.000000] 入力: 正常な入力\n偽のログ行(1行に収まり、改行が可視化される)
Python

また、CSVインジェクション(セルの先頭に=や+、-、@を付けることで、スプレッドシートで数式として実行させる攻撃)対策としても、改行や特殊文字の処理は重要です。CSV出力時は、csvモジュールのQUOTE_ALLオプションなどで適切にエスケープしてください。

文字列比較時の改行コードの影響

改行コードの違いは、文字列の等価比較(==)に影響します。"hello\n"と"hello\r\n"は、見た目は同じでも別物として判定されます。

# 改行コードの違いによる比較の失敗例
a = "OK\n"
b = "OK\r\n"

print(a == b)           # False(\nと\r\nは別物)
print(a.strip() == b.strip())  # True(strip()で改行も除去すれば一致)

# 改行コードを統一してから比較するのが安全
def normalize_and_compare(text1, text2):
    n1 = text1.replace("\r\n", "\n").replace("\r", "\n")
    n2 = text2.replace("\r\n", "\n").replace("\r", "\n")
    return n1 == n2

print(normalize_and_compare(a, b))  # True(改行を統一すれば一致)
Python

ファイルから読み込んだデータや、外部APIからのレスポンスを比較する場合は、比較前に改行コードを統一するか、strip()で前後の空白・改行を除去することで、意図しない不一致を防げます。

まとめ

改行コードを処理するには、splitlines()で改行で分割、join()で改行を挿入して結合、replace()で改行コードを統一する、という3つの基本を押さえてください。ファイルの読み書きでは、open()のnewline引数で書き込み時の改行コードを制御でき、CSVデータは必ずcsvモジュールで処理するのが鉄則です。

注意すべきは、改行コードの違い(LF/CR/CRLF)による文字列比較の失敗、CSVフィールド内の改行によるレコード分断、そしてログインジェクション対策としての改行のサニタイズです。これらのポイントを押さえ、紹介したテンプレートを実際に動かしてみながら、改行コードを自在に操れるスキルを身につけてください。

タイトルとURLをコピーしました