Скрипт 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:])

🤔 А знаете ли вы, что...
Python - это универсальный язык программирования.


51
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!