在Python中获取文本文件的换行符统计信息

5

我在 git 文件中遇到了一个令人讨厌的 CRLF/LF 冲突,很可能是从 Windows 机器提交的。有没有一种跨平台的方式(最好使用 Python)来检测文件中支配的换行符类型?

我有以下代码(基于https://dev59.com/CGPVa4cB1Zd3GeqP8sKF#10562258 的想法):

import sys
if not sys.argv[1:]:
  sys.exit('usage: %s <filename>' % sys.argv[0])

with open(sys.argv[1],"rb") as f:
  d = f.read()
  crlf, lfcr = d.count('\r\n'), d.count('\n\r')
  cr, lf = d.count('\r'), d.count('\n')
  print('crlf: %s' % crlf)
  print('lfcr: %s' % lfcr)
  print('cr: %s' % cr)
  print('lf: %s' % lf)
  print('\ncr-crlf-lfcr: %s' % (cr - crlf - lfcr))
  print('lf-crlf-lfcr: %s' % (lf - crlf - lfcr))
  print('\ntotal (lf+cr-2*crlf-2*lfcr): %s\n' % (lf + cr - 2*crlf - 2*lfcr))

但它给出的统计数据是错误的(对于这个文件):

crlf: 1123
lfcr: 58
cr: 1123
lf: 1123

cr-crlf-lfcr: -58
lf-crlf-lfcr: -58

total (lf+cr-2*crlf-2*lfcr): -116

就像sorrat一样,我得到了1123个crlf对于那个文件,而其他三个EOL标记都为0。 - PM 2Ring
@PM2Ring 我需要一个更好的测试文件。我以为这个文件实际上包含了混合的换行符。 - anatoly techtonik
4个回答

9
import sys


def calculate_line_endings(path):
    # order matters!
    endings = [
        b'\r\n',
        b'\n\r',
        b'\n',
        b'\r',
    ]
    counts = dict.fromkeys(endings, 0)

    with open(path, 'rb') as fp:
        for line in fp:
            for x in endings:
                if line.endswith(x):
                    counts[x] += 1
                    break
    print(counts)


if __name__ == '__main__':
    if len(sys.argv) == 2:
        calculate_line_endings(sys.argv[1])

    sys.exit('usage: %s <filepath>' % sys.argv[0])

为您的文件提供输出
crlf: 1123
lfcr: 0
cr: 0
lf: 0

足够了吗?


这个不错。你知道 line in open(filename, "rb"): 如何正确检测行吗?只是想了解一些边角情况。 - anatoly techtonik
抱歉,我不知道。可能是由于PEP-278引起的。 - sorrat

2
发布的代码不能正常工作,因为计数器正在计算文件中的字符 - 它不会寻找像\r\n和\n\r这样的字符对。下面是一些Python 2.6代码,它使用正则表达式查找每个EOL(行末)标记的4种出现形式:\r\n、\n\r、\r和\n。关键是在查找单个字符的EOL标记之前先查找\r\n和\n\r配对。为了测试目的,它创建了一些随机文本数据;我写这篇文章时还没有注意到你提供的测试文件链接。
#!/usr/bin/env python

''' Find and count various line ending character combinations

    From https://dev59.com/3onda4cB1Zd3GeqPEMwb

    Written by PM 2Ring 2015.04.17
'''

import random
import re
from itertools import groupby

random.seed(42)

#Make a random text string containing various EOL combinations
tokens = list(2*'ABCDEFGHIJK ' + '\r\n') + ['\r\n', '\n\r']
datasize = 300
data = ''.join([random.choice(tokens) for _ in range(datasize)])
print repr(data), '\n'

#regex to find various EOL combinations
pat = re.compile(r'\r\n|\n\r|\r|\n')

eols = pat.findall(data)
print eols, '\n'

grouped = [(len(list(group)), key) for key, group in groupby(sorted(eols))]
print sorted(grouped, reverse=True)

输出

'FAHGIG\rC AGCAFGDGEKAKHJE\r\nJCC EKID\n\rKD F\rEHBGICGCHFKKFH\r\nGFEIEK\n\rFDH JGAIHF\r\n\rIG \nAHGDHE\n G\n\rCCBDFK BK\n\rC\n\r\rAIHDHFDAA\r\n\rHCF\n\rIFFEJDJCAJA\r\n\r IB\r\r\nCBBJJDBDH\r FDIFI\n\rGACDGJEGGBFG\n\rBGGFD\r\nDBJKFCA BIG\n\rC J\rGFA HG\nA\rDB\n\r \n\r\n EBF BK\n\rHJA \r\n\n\rDIEI\n\rEDIBEC E\r\nCFEGGD\rGEF EC\r\nFIG GIIJCA\n\r\n\rCFH\r\n\r\rKE HF\n\rGAKIG\r\nDDCDHEIFFHB\n C HAJFHID AC\r' 

['\r', '\r\n', '\n\r', '\r', '\r\n', '\n\r', '\r\n', '\r', '\n', '\n', '\n\r', '\n\r', '\n\r', '\r', '\r\n', '\r', '\n\r', '\r\n', '\r', '\r', '\r\n', '\r', '\n\r', '\n\r', '\r\n', '\n\r', '\r', '\n', '\r', '\n\r', '\n\r', '\n', '\n\r', '\r\n', '\n\r', '\n\r', '\r\n', '\r', '\r\n', '\n\r', '\n\r', '\r\n', '\r', '\r', '\n\r', '\r\n', '\n', '\r'] 

[(17, '\n\r'), (14, '\r'), (12, '\r\n'), (5, '\n')]

这里有一个版本,它从一个命名的文件中读取数据,遵循问题中代码的模式。

import re
from itertools import groupby
import sys

if not sys.argv[1:]:
    exit('usage: %s <filename>' % sys.argv[0])

with open(sys.argv[1], 'rb') as f:
    data = f.read()

print repr(data), '\n'

#regex to find various EOL combinations
pat = re.compile(r'\r\n|\n\r|\r|\n')

eols = pat.findall(data)
print eols, '\n'

grouped = [(len(list(group)), key) for key, group in groupby(sorted(eols))]
print sorted(grouped, reverse=True)

不错的方法。尤其酷的是它有测试数据可以比较。 - anatoly techtonik

1

从我的观察来看,我建议检查以下情况:\r\n\r\n\r\n。根据你的代码,这将计算如下:

crlf: 3 -- [\r\n][\r\n][\r\n]
lfcr: 2 -- \r[\n\r][\n\r]\n
cr: 3   -- [\r]\n[\r]\n[\r]\n
lf: 3   -- \r[\n]\r[\n]\r[\n]

cr-crlf-lfcr: -2
lf-crlf-lfcr: -2

total (lf+cr-2*crlf-2*lfcr): -4

正如您所看到的,对于和,一些\n和一些\r被计算了两次。相反,您可以逐行阅读并计算行尾line.endswith()。要获取有关crlf的精确统计信息,则可以将\r\n\n\r计为cr + 1和lf + 1进行计数。

1

处理git中的行尾最好的方法是使用git配置。您可以全局定义行尾需要执行的操作,或者为特定存储库或特定文件定义行尾控制方式。在 .gitattributes 文件中,您可以定义某些文件在每次检出时必须转换为系统本地的行尾,并在提交时再转换回来。详见 GitHub 行尾帮助 获取详细说明。


我不想转换任何东西,Git 默认可以让我的文件保持原样吗? - anatoly techtonik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接