本文目录导读:

Python 脚本(最灵活)
#!/usr/bin/env python3
"""
批量修改文件末尾换行符
支持: LF (Unix), CRLF (Windows), CR (Old Mac)
"""
import os
import sys
import argparse
from pathlib import Path
def fix_line_endings(file_path, target_ending='LF'):
"""修改单个文件的换行符"""
# 读取文件内容(二进制模式以确保准确读取)
with open(file_path, 'rb') as f:
content = f.read()
# 检测当前换行符
if b'\r\n' in content:
current = 'CRLF'
has_crlf = True
elif b'\r' in content and b'\n' not in content:
current = 'CR'
has_cr = True
else:
current = 'LF'
has_lf = True
# 转换为目标换行符
if target_ending == 'LF':
# 先将 CRLF 和 CR 都转为 LF
content = content.replace(b'\r\n', b'\n')
content = content.replace(b'\r', b'\n')
new_ending = b'\n'
elif target_ending == 'CRLF':
# 先将 CR 转为 LF,再将 LF 转为 CRLF
content = content.replace(b'\r\n', b'\n')
content = content.replace(b'\r', b'\n')
content = content.replace(b'\n', b'\r\n')
new_ending = b'\r\n'
elif target_ending == 'CR':
# 先将 CRLF 转为 LF,再将 LF 转为 CR
content = content.replace(b'\r\n', b'\n')
content = content.replace(b'\n', b'\r')
new_ending = b'\r'
else:
print(f"未知的目标换行符: {target_ending}")
return False
# 写回文件
with open(file_path, 'wb') as f:
f.write(content)
return True
def process_files(directory, pattern='*', target='LF', recursive=True):
"""批量处理文件"""
directory = Path(directory)
if not directory.exists():
print(f"目录不存在: {directory}")
return
# 收集文件
if recursive:
files = list(directory.rglob(pattern))
else:
files = list(directory.glob(pattern))
if not files:
print(f"在 {directory} 中未找到匹配 {pattern} 的文件")
return
print(f"找到 {len(files)} 个文件")
success_count = 0
for file_path in files:
if file_path.is_file():
try:
if fix_line_endings(file_path, target):
success_count += 1
print(f"✓ {file_path.name}")
except Exception as e:
print(f"✗ {file_path.name}: {e}")
print(f"\n处理完成: {success_count}/{len(files)} 个文件已修改")
def main():
parser = argparse.ArgumentParser(description='批量修改文件末尾换行符')
parser.add_argument('directory', help='目标目录')
parser.add_argument('-p', '--pattern', default='*', help='文件匹配模式(如 *.txt, *.py)')
parser.add_argument('-t', '--target', choices=['LF', 'CRLF', 'CR'], default='LF',
help='目标换行符类型 (默认: LF)')
parser.add_argument('--no-recursive', action='store_true',
help='不递归子目录')
args = parser.parse_args()
process_files(
args.directory,
pattern=args.pattern,
target=args.target,
recursive=not args.no_recursive
)
if __name__ == '__main__':
main()
使用方法:
# 将当前目录所有文件转为 LF python fix_lineendings.py . -t LF # 将所有 .txt 文件转为 CRLF python fix_lineendings.py . -p "*.txt" -t CRLF # 递归处理子目录中的所有 .py 文件 python fix_lineendings.py . -p "*.py" -t LF
使用 sed 命令(Linux/Mac)
#!/bin/bash
# batch_fix_lineendings.sh
# 将目录下所有文件转换为 LF (Unix)
find . -type f -name "*.txt" -exec sed -i 's/\r$//' {} \;
# 将目录下所有文件转换为 CRLF (Windows)
find . -type f -name "*.txt" -exec sed -i 's/$/\r/' {} \;
# 更安全的方式:创建备份
find . -type f -name "*.txt" -exec sed -i.bak 's/\r$//' {} \;
使用 dos2unix/unix2dos 工具
#!/bin/bash
# batch_fix_lineendings_tool.sh
# 首先安装工具(如果还没有)
# Ubuntu/Debian: sudo apt-get install dos2unix
# CentOS/RHEL: sudo yum install dos2unix
# Mac: brew install dos2unix
# 批量转换所有 .txt 文件为 Unix 格式
find . -name "*.txt" -type f -exec dos2unix {} \;
# 批量转换为 Windows 格式
find . -name "*.txt" -type f -exec unix2dos {} \;
# 只检查不修改(查看哪些文件需要转换)
find . -name "*.txt" -type f -exec dos2unix -ic {} \;
高级 Python 脚本(带更多功能)
#!/usr/bin/env python3
"""
高级批量换行符修改脚本
功能:备份、预览、过滤二进制文件、统计
"""
import os
import sys
import shutil
from pathlib import Path
class LineEndingFixer:
def __init__(self, directory, target='LF', backup=False, dry_run=False):
self.directory = Path(directory)
self.target = target
self.backup = backup
self.dry_run = dry_run
self.stats = {'processed': 0, 'modified': 0, 'skipped': 0, 'errors': 0}
def is_text_file(self, file_path):
"""检查是否为文本文件(跳过二进制文件)"""
try:
with open(file_path, 'rb') as f:
chunk = f.read(1024)
# 检查是否有 null 字节
return b'\x00' not in chunk
except:
return False
def detect_line_ending(self, content):
"""检测文件当前的换行符类型"""
if b'\r\n' in content:
return 'CRLF'
elif b'\r' in content and b'\n' not in content:
return 'CR'
else:
return 'LF'
def fix_file(self, file_path):
"""修改单个文件"""
try:
with open(file_path, 'rb') as f:
content = f.read()
if not content:
return True
current = self.detect_line_ending(content)
if current == self.target:
return True
# 创建备份
if self.backup and not self.dry_run:
backup_path = file_path.with_suffix(file_path.suffix + '.bak')
shutil.copy2(file_path, backup_path)
# 转换
if self.target == 'LF':
content = content.replace(b'\r\n', b'\n')
content = content.replace(b'\r', b'\n')
elif self.target == 'CRLF':
content = content.replace(b'\r\n', b'\n')
content = content.replace(b'\r', b'\n')
content = content.replace(b'\n', b'\r\n')
elif self.target == 'CR':
content = content.replace(b'\r\n', b'\n')
content = content.replace(b'\n', b'\r')
if not self.dry_run:
with open(file_path, 'wb') as f:
f.write(content)
self.stats['modified'] += 1
return True
except Exception as e:
print(f" ❌ {file_path.name}: {e}")
self.stats['errors'] += 1
return False
def process(self, pattern='*', recursive=True):
"""处理文件"""
print(f"📁 目录: {self.directory}")
print(f"🎯 目标: {self.target}")
print(f"📋 预览模式: {'是' if self.dry_run else '否'}")
print(f"💾 创建备份: {'是' if self.backup else '否'}")
print("-" * 50)
if recursive:
files = list(self.directory.rglob(pattern))
else:
files = list(self.directory.glob(pattern))
# 过滤文本文件
files = [f for f in files if f.is_file() and self.is_text_file(f)]
print(f"📊 找到 {len(files)} 个文本文件")
for file_path in files:
self.stats['processed'] += 1
relative = file_path.relative_to(self.directory)
print(f" 📄 {relative}", end=' ')
if self.fix_file(file_path):
print("✅")
else:
print("❌")
print("-" * 50)
print(f"📊 统计:")
print(f" • 处理文件: {self.stats['processed']}")
print(f" • 修改文件: {self.stats['modified']}")
print(f" • 跳过文件: {self.stats['skipped']}")
print(f" • 错误文件: {self.stats['errors']}")
def main():
import argparse
parser = argparse.ArgumentParser(description='批量修改文件换行符')
parser.add_argument('directory', nargs='?', default='.', help='目标目录')
parser.add_argument('-p', '--pattern', default='*', help='文件匹配模式')
parser.add_argument('-t', '--target', choices=['LF', 'CRLF', 'CR'],
default='LF', help='目标换行符')
parser.add_argument('-b', '--backup', action='store_true', help='创建备份')
parser.add_argument('-n', '--dry-run', action='store_true', help='预览模式')
parser.add_argument('--no-recursive', action='store_true', help='不递归')
args = parser.parse_args()
fixer = LineEndingFixer(
args.directory,
target=args.target,
backup=args.backup,
dry_run=args.dry_run
)
fixer.process(
pattern=args.pattern,
recursive=not args.no_recursive
)
if __name__ == '__main__':
main()
高级用法:
# 预览要修改的文件(不实际修改) python advanced_fixer.py . -p "*.txt" -n # 修改并创建备份 python advanced_fixer.py . -t CRLF -b # 修改特定类型的文件 python advanced_fixer.py . -p "*.py" -t LF
建议
- 先备份:修改前确保有文件备份
- 先预览:使用
-n或--dry-run参数预览修改 - 测试小范围:先在小目录测试
- 注意编码:确保文件编码(UTF-8, GBK等)与换行符兼容
选择哪种方法取决于你的系统环境和需求,Python 脚本最灵活,适用于所有平台。