Как заменить значение в строке

Всем привет!

Пытаюсь автоматизировать работу freeradius для замены значений в конфиге.

Вводные данные:

  1. Словарь для оборудования APC в radius/dictionary
  2. Блок значений для пользователя в виде,
    ipetrov
                Auth-Type = ntlm_auth,
                APC-Service-Type = Outlet,
                APC-Outlets = "1[1,3,5];"
    
    где необходимо менять значения внутри квадратных скобок, всего значений от 1 до 24, одновременное количество значений неизвестно (т.е. пользователь может работать как с одним значением, например APC-Outlets = "1[24];", так и с несколькими, например APC-Outlets = "1[11,18,22];"
  3. Пользователей, естественно, под сотню. А значит и таких блоков в файле radius/users.conf будет под сотню. Не обязательно, что все сто пользователей будут работать одновременно, но иметь блоки пользователей необходимо заранее (а может, и нет?)

Задача:

  1. Иметь возможность динамически менять значения в APC-Outlets = "1[11,18,22];" для нужного пользователя.

Как это вижу я:

  1. Пользователь запускает программу, программа принимает значение username через команду whoami, пользователь указывает нужные ему значения через запятую
    ./script.sh 1,2,3
    ИЛИ программа предлагает ввести нужны значения через input
  2. Далее выбирается рабочая директория freeradius
    workdir=/etc/freeradius/3.0/mods-config/files/
  3. Программа находит блок пользователя username в файле users.conf
  4. Программа меняет значение внутри скобок APC-Outlets = "1[];" на введенные значения из input

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

  1. Пробую создать блок целиком, меняя значение переменной:
username = input('Имя пользователя: ')
apc_outlets = str(input('Номера розеток: '))
file_path = ('test.conf')


block = (username + '\n'
                  '\t' + '\t' + '\t' + '\t' + "Auth-Type = ntlm_auth," + '\n'
                  '\t' + '\t' + '\t' + '\t' + "APC-Service-Type = Outlet," + '\n'
                  '\t' + '\t' + '\t' + '\t' + 'APC-Outlets = "1[' + apc_outlets + '];"' + '\n' + '\n')

with open(file_path, 'a+') as file:
  file.write(block)

Окей, как создавать блоки я понял, пробую менять значение внутри блока у переменной apc_outlets:

with open(file_path, 'a+') as file:
    apc_outlets_new = apc_outlets
    apc_replaced = block.replace(apc_outlets, apc_outlets_new)
    file.write(apc_replaced)

И тут я понял, что метод replace не меняет значение внутри строки, а записывает новую строку с новым значением, т.е. сейчас я имею два блока с одним юзернеймом, но с разными значениями APC-Outlets = "1[];". Увы, это не то, что я хотел.

Ладно, попробуем посчитать строки, будем отталкиваться от них

username = input('Имя пользователя: ')
apc_outlets = str(input('Номера розеток: '))
apc = 'APC-Outlets = '
file_path = ('test.conf')

with open(file_path, 'a+') as file:
    n = 0
    for line in file:
        n += 1
        if username in line:
            print(username, 'найден в строке', n)
            n = 0
            for line_two in file:
                n += 1
                if apc in line_two:
                    print(apc, 'найден в строке', n)
                    break

Отлично! Я могу найти номер строки переменной username, а потом найти номер нужной мне строки с параметрами APC-Outlets = "1[];"

И все. Дальше не могу сдвинуться с места уже неделю. Я не могу понять, как можно осуществить замену значений без удаления всего содержимого файла (например, через with open(file_path, 'r+') as file:) или без создания нового содержимого через метод replace.

Есть ли какой-то вариант решения моей задачи? Как можно менять значение в подстроке, не меняя больше ничего?

Мне очень жаль, что пришлось написать такую простыню, но я не представляю, как можно описать задачу по иному.

Возможно, вы подскажете новый вариант решения этой задачи.


Ответы (1 шт):

Автор решения: Arty

Вот реализовал центральную функцию modify_config() которая делает нужные действия. Её вызывает некая main() в которой читается файл плюс имя пользователя и аутлеты, модифицируется конфиг через modify_config() и записывается результирующий конфиг.

Поиск и чтение/запись именно нужного конфиг файла не делал, т.к. посчитал что основная трудность это именно внесение изменений в конфиг.

Попробовать онлайн!

import re

def modify_config(text, user, outlets):
    cuser, res, ufound, fixed = None, '', False, False
    for iline, line in enumerate(text.splitlines()):
        if line.strip():
            m = re.fullmatch(r'^([a-zA-Z0-9_\-]+?)\s*$', line)
            if m:
                cuser = m.group(1)
                if cuser == user:
                    ufound = True
            else:
                m = re.fullmatch(r'^\s+(.+?)\s*\=\s*(.+?)\s*$', line)
                assert m, f'Unmatched line "{line}" at line {iline}!'
                l, r = m.group(1), m.group(2)
                if cuser == user and l == 'APC-Outlets':
                    m2 = re.fullmatch(r'^\".*\[(.*)\].*\"$', r)
                    assert m2, f'Unmatched outlets "{r}" at line {iline}!'
                    line = line[:m.span(2)[0] + m2.span(1)[0]] + outlets + line[m.span(2)[0] + m2.span(1)[1]:]
                    m3 = re.fullmatch(r'^\s+APC-Outlets\s*\=\s*\".*\[' + outlets + r'\]\.*\"\s*$', line)
                    assert m3, f'Unmatched resulting outlets "{line}" at line {iline}!'
                    fixed = True
        res += line + '\n'
    assert ufound, f'User "{user}" not found in config!'
    assert fixed, f'No modifications made!'
    return res

def main():
    user = input('Username: ').strip()
    outlets = ','.join([str(int(e)) for e in re.split(r'[\s,;]+', input('Outlets: ').strip()) if e.strip()])
    with open('0296.txt', 'r', encoding = 'utf-8-sig') as f:
        text = f.read()
    rtext = modify_config(text, user, outlets)
    with open('0296.txt2', 'w', encoding = 'utf-8') as f:
        f.write(rtext)

if __name__ == '__main__':
    main()

Пример входного файла:

ipetrov
            Auth-Type = ntlm_auth,
            APC-Service-Type = Outlet,
            APC-Outlets = "1[1,3,5];"
isidorov
            Auth-Type = ntlm_auth,
            APC-Service-Type = Outlet,
            APC-Outlets = "1[];"

Пример ввода консоли:

Username: ipetrov
Outlets: 1,2,3

Пример результирующего файла:

ipetrov
            Auth-Type = ntlm_auth,
            APC-Service-Type = Outlet,
            APC-Outlets = "1[1,2,3];"
isidorov
            Auth-Type = ntlm_auth,
            APC-Service-Type = Outlet,
            APC-Outlets = "1[];"
→ Ссылка