Скрипт Python читает LF без CR в текстовом файле и заменяет другим символом

У меня есть несколько текстовых файлов с разделителями табуляции, полученных с сайта FFIEC (https://cdr.ffiec.gov/public/PWS/DownloadBulkData.aspx) <Отчеты о вызовах — один период, расписание RIE>, которые имеют LF (строка подача) символы без символов CR (возврат каретки) в одном или нескольких полях. Файлы загружаются в SQL Server 2022 (или используются в Excel). Каждая запись (строка) файла заканчивается последовательностью CRLF. Проблема в том, что LF в поле интерпретируется как начало следующей записи при чтении текстового файла (в Excel или с использованием SSIS для импорта в SQL Server).

Я знаю о \r\n в Windows по сравнению с \n UNIX/Linux и подозреваю, что Python обрабатывает любой из них как последовательность. Я не пробовал кодировку Latin-1 или cp1252.

Я использую Windows 11 Pro. Сценарий вызывается из команды оболочки (хранимой процедуры SQL или Excel VBA) и является частью более крупной группы сценариев очистки файлов для импорта.

Моя попытка решения состояла в том, чтобы прочитать файл, перебрать его по одному символу за раз, найти LF '\n', которому не предшествует CR '\r', и заменить его точкой с запятой ';'.

Код Python (v3.12):

import sys

def stripLFwoCR_file(file_path):
    # Read the entire file contents
    with open(file_path, 'r', encoding='utf-8') as file:
        input_data = file.read()

    # Initialize output
    output_data = []

    # Iterate input content 1 character at a time
    # Replace line feed characters '\n' not preceded by carriage return characters '\r' with ';'
    i = 0
    while i < len(input_data):
        if input_data[i] == '\n':
            # If previous character is not '\r' then replace '\n' with ';'
            if i == 0 or input_data[i-1] != '\r':
                output_data.append(';')
            # Skip this '\n'
        else:
            output_data.append(input_data[i])
        i += 1

    # Write the modified content back to the file, overwriting it
    with open(file_path, 'w', encoding='utf-8') as file:
        file.write(''.join(output_data))

if __name__ == "__main__":
    args = sys.argv
    # args[0] = current file
    # args[1] = function name
    # args[2:] = function args : (*unpacked)
    globals()[args[1]](*args[2:])

Проблема заключается в том, что сценарий заменяет все LF И все CRLF в файле на ';'.

Образец оригинала документа (LF без CR) Строки 10–14 являются частью одной записи. Строки 16–21 — это одна запись.

Обновление: мне нужно прочитать руководство! Начиная с версии 3.x, в Python есть возможность игнорировать или использовать другую автозамену для новой строки. Мой исходный код также содержит логическую ошибку в цикле while.

В итоге я использовал это, потому что это требовало меньше переписывания остальной части моего кода. Я проверил ответ @JRiggles и отметил его как решение (более чистое, меньше кода):

import sys

def stripLFwoCR_file(file_path):
    # Read the entire file contents
    with open(file_path, 'r', encoding='utf-8', newline='\r\n') as file:
        input_data = file.read()

    # Initialize output
    output_data = []

    # Iterate input content 1 character at a time
    # Replace line feed characters '\n' not preceded by carriage return characters '\r' with ';'
    i = 0
    while i < len(input_data):
        if input_data[i] == '\n':
            # If previous character is not '\r' then replace '\n' with ';'
            if i == 0 or input_data[i-1] != '\r':
                # Skip this '\n' and replace
                output_data.append(';')
            else:
                output_data.append(input_data[i])
        else:
            output_data.append(input_data[i])
        i += 1

    # Write the modified content back to the file, overwriting it
    with open(file_path, 'w', encoding='utf-8', newline='\n') as file:
        file.write(''.join(output_data))

if __name__ == "__main__":
    args = sys.argv
    # args[0] = current file
    # args[1] = function name
    # args[2:] = function args : (*unpacked)
    globals()[args[1]](*args[2:])

Проблема в том, что любой файл, открытый в текстовом режиме, автоматически заменяется \r\n на \n, и отличить разницу невозможно. Вам нужно открыть файл в двоичном режиме с помощью 'rb'.

Mark Ransom 30.08.2024 20:19

@MarkRansom, это хороший момент!

JRiggles 30.08.2024 20:22

Проблема в том, что LF в поле вызывает новую запись. где это проблема? При чтении CSV? При написании SQL?

tdelaney 30.08.2024 20:25

Обратите внимание, что открытие в двоичном формате имеет ограничения на кодировку. В utf-8 \r и \n являются однобайтовыми, как и ascii и различные кодовые страницы Windows. Но с utf-16 нужно будет обращаться по-другому.

tdelaney 30.08.2024 20:45

Почему в текстовом файле вообще стоит голый \n?

Barmar 30.08.2024 21:05

@Barmar, поле ввода в программе ввода данных отчета о вызовах позволяет ввести новую строку. Аналогично <Alt><Enter> в ячейке Excel.

HBreshears 30.08.2024 21:42

Если вам нужно несколько строк в файле CSV, это можно сделать, поместив поле в кавычки. Анализатор CSV это понимает.

Barmar 30.08.2024 21:43

И в Windows он все равно должен быть представлен как \r\n внутри кавычек.

Barmar 30.08.2024 21:44

@Barmar, это общедоступные данные, полученные с правительственного сайта. Я не имею никакого контроля над тем, как они решат это представить, но мне приходится иметь дело с тем, как это представлено. Я согласен, что они могли бы справиться с этим лучше для общественности.

HBreshears 30.08.2024 22:16
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
9
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Звучит как работа для re.sub. Шаблон (?<!\r)\n будет соответствовать любым символам LF \n, которым не предшествует возврат каретки (CR) \r.

Вот пример файла: sample data.txt (скриншот, показывающий окончания строк)

Чтобы избежать преобразований окончания строк, откройте файл в режиме двоичного чтения 'rb'

import re


pattern = b'(?<!\r)\n'  # match any \n not preceded by \r

with open(r'<path to>\sample data.txt', 'rb') as file:
    data = file.read()
    print('Pre-substitution: ', data)
    # replace any matches with a semicolon ';'
    result = re.sub(pattern, b';', data)
    print('Post-substitution: ', result)

Это печатает:

Pre-substitution:  b'this line ends with CRLF\r\nthis line ends with LF\nthis line ends with CRLF\r\nthis line ends with LF\nthis line ends with CRLF\r\n'
Post-substitution:  b'this line ends with CRLF\r\nthis line ends with LF;this line ends with CRLF\r\nthis line ends with LF;this line ends with CRLF\r\n'

Стоит отметить, что все последовательные \n будут заменены, поэтому \n\n\n становится ;;;, а \r\n\n становится r\n;.

Также обратите внимание, что строка pattern и значение подстановки являются байтовыми строками (b'<str>') — если вы этого не сделаете, вы получите TypeError!

Хороший ответ, за исключением того, что он действительно должен показать, как это сделать с байтовой строкой и файлом, открытым в двоичном режиме. В противном случае вам придется иметь дело с преобразованиями новой строки Python в файлах.

tdelaney 30.08.2024 20:42

@tdelaney совершенно честно, и готово!

JRiggles 30.08.2024 21:22

Конечно, это работает только в том случае, если файл достаточно мал, чтобы поместиться в памяти. Это меньшая проблема, чем раньше, но я уверен, что иногда такое все еще случается.

Mark Ransom 30.08.2024 21:34

@MarkRansom Я, конечно, надеюсь, что это не проблема для OP - в настоящее время это приводит к созданию больших файлов! Но хорошо приглядеться.

JRiggles 30.08.2024 21:39

Другие вопросы по теме