Как распечатать все пары ключ-значение определенных секций в INI-файле?

Вопрос или проблема

INI файл – это profiles.ini Firefox.
Я хочу напечатать все под [Profilen], включая заголовки секций, где n – это неотрицательные целые числа, с пустой строкой между каждой секцией.
Существует также необязательный ключ Default под [Install*], который отличается от ключей Default в секциях профилей, и я хочу напечатать его, если он присутствует.
По сути, shell-скрипт был просто grep -E 'Default=[^1]' profiles.ini и grep -A4 '^\[Profile' profiles.ini раньше (чтобы напечатать ключи и значения Name, IsRelative, Path и необязательный Default), но это потерпит неудачу, если ключи будут добавлены или удалены, и опция -A grep не является переносимой.

Вот мое хакерское решение, которое не является идиоматичным или надежным AWK:

/^[[]Profile[0123456789]{1,}[]]$/ {
    print
    while ((getline) > 0) {
        if ($0 ~ /^$/) { # На самом деле нужно прерывать на новых секциях.
            print ""
            break
        } else {
            print
        }
    }
}
/^Default=/ {
    print # Путь по умолчанию для профиля, указанный в разделе Install*.
}

Пример входных данных:

[Profile2]
Name=default-test
IsRelative=0
Path=/home/user/ffprofiles/f9bwn86n.default-test

[Profile1]
Name=default
IsRelative=1
Path=x64qf7nv.default
Default=1

[Profile0]
Name=default-release
IsRelative=1
Path=9hv1fbkk.default-release-3426201712696

[General]
StartWithLastProfile=1
Version=2

[Install22379532B4E49482]
Default=9hv1fbkk.default-release-3426201712696
Locked=1

Пример выходных данных:

[Profile2]
Name=default-test
IsRelative=0
Path=/home/user/ffprofiles/f9bwn86n.default-test

[Profile1]
Name=default
IsRelative=1
Path=x64qf7nv.default
Default=1

[Profile0]
Name=default-release
IsRelative=1
Path=9hv1fbkk.default-release-3426201712696

Default=9hv1fbkk.default-release-3426201712696

Как я могу сделать это более лаконично и правильно?
Решение не обязательно должно быть в AWK, но я считаю, что awk более подходит в этом случае, чем sed или любые другие утилиты Unix.
Тем не менее, решение должно быть переносимым и соответствовать POSIX.
Спасибо заранее.

То, что вы описываете, – это структурированный текстовый файл с контекстом.

awk может (будет) извлекать конкретный раздел, но ему потребуется делать много предположений, которые не основаны на том, как на самом деле работает формат файла, а на том, как конкретный пример, о котором вы думаете, выглядит (начиная с “легких” вопросов, таких как “имеет ли значение регистр” до более интересных вопросов, таких как “как мне справиться с двумя секциями с одинаковым именем и перекрывающимися ключами?”).

Поэтому просто не используйте awk, sed или какой-либо другой в первую очередь свободный от контекста подход для разбора чего-то вроде ini файлов. Используйте парсер с пониманием формата.

Формат здесь – это TOML, кстати.

Вы используете , что указывает на то, что вы можете использовать компилятор C99 (это инструмент такой же POSIX, как awk!). Так что вместо парсера TOML на основе awk, используйте устоявшуюся и хорошо работающую библиотеку TOML. toml-c – это библиотека, которую вы можете просто разместить рядом со своим .c файлом. Директория examples/ содержит два примера, которые вы можете напрямую адаптировать под свой случай; просто замените toml_parse(char*,…) на toml_parse_file(FILE*,…), и откройте файл, который вы передали как argv[1]; легко.

Не собираюсь предоставлять полное решение на C99 только потому, что вы думаете, что утилиты POSIX – это способ переносимости – на самом деле это, к сожалению, не так; разные реализации одной и той же утилиты POSIX, как правило, более несовместимы между платформами, чем, скажем, интерпретаторы Python, и на каждой платформе, для которой у вас установлен awk, очень вероятно, также есть python.

Говоря о Python, он поставляется со встроенным парсером toml, вот ваш 9-строчный скрипт, включая помощь по использованию, чтобы напечатать все ключи/значения из заданного раздела. Поскольку он выдаст ошибку с ненулевым значением возвращаемого кода, когда вы дадите имя раздела, которого нет в вашем вводе, простая оболочка может быть использована для обработки Profile0, Profile1… до тех пор, пока ProfileN+1 больше не существует. Более элегантным было бы действительно сделать это в самом Python, но это оставлено читателю в качестве примера, поскольку это было бы очень специфично, в то время как этот инструмент более общий и полезный:

#!/usr/bin/env python3
import tomllib
from sys import argv, stdin, stderr, exit

if len(argv != 2):
  stderr.print(f"USAGE: {argv[0]} SECTION-NAME < inputfile.ini\n")
  stderr.print(f"Выводит все пары ключ/значение из раздела в INI\n")
  stderr.print(f"(или вообще, файле TOML), разделенных овцой.\n")
  exit(127)

ini = tomllib.load(stdin)
section = ini[argv[1]]
for key, value in section.items():
  print(f"{key} 🐑 {value}")

awk способен сохранять состояние между строками без необходимости использования getline(). Если вам нужно решение на основе awk, самый простой способ будет следующим:

cat profiles.ini | awk '
    /^\[/                 {ok=0}
    /^\[Install\]$/       {ok=1; header=$0}
    ok && /^Default=/     {print header; print}
'
cat profiles.ini | awk '
    /^\[/                 {ok=0}
    /^\[Profile[0-9]*\]$/ {ok=1}
    ok                    {print}
'

Как единый скрипт awk, это может сработать:

/^\[/                 {ok=0}
/^\[Profile[0-9]*\]$/ {ok=1}
/^\[Install\]$/       {ok=2; header=$0}
ok==1                 {print}
ok==2 && /^Default=/  {print header; print}

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

/^\[/                 {hdr=""}
/^\[Profile[0-9]*\]$/ {hdr=$0}
/^\[Install\]$/       {hdr=$0}
hdr ~ /\[Profile/               {print}
hdr ~ /\[Install/ && /Default=/ {print hdr; print}

Используя любой awk:

$ awk '
    /^\[/ { sect = substr($0,2) }
    (sect ~ /^Profile/) || ( (sect ~ /^Install/) && /^Default=/ )
' profiles.ini
[Profile2]
Name=default-test
IsRelative=0
Path=/home/user/ffprofiles/f9bwn86n.default-test

[Profile1]
Name=default
IsRelative=1
Path=x64qf7nv.default
Default=1

[Profile0]
Name=default-release
IsRelative=1
Path=9hv1fbkk.default-release-3426201712696

Default=9hv1fbkk.default-release-3426201712696

Поскольку вы довольны извлечением одного ключевого блока за раз, вы можете обойтись одним awk-командой. Конец каждого блока удобно обозначен другим [key] или концом файла.

Вот команда для извлечения, скажем, [Profile0]:

$ awk '/^\[Profile0/{p=1; print; next} /^\[/{p=0}; p>0{print}' ini.txt 
[Profile0]
Name=default-release
IsRelative=1
Path=9hv1fbkk.default-release-3426201712696

или извлечь блок, который начинается с [General]:

$ awk '/^\[Gen/{p=1; print; next} /^\[/{p=0}; p>0{print}' ini.txt 
[General]
StartWithLastProfile=1
Version=2

Я использовал [Gen и [Profile0 в этих примерах. Замените их на достаточное количество символов из вашего целевого [key], чтобы уникально идентифицировать блок.

Файл ini.txt – это ваш ввод, т.е.:

$ cat ini.txt 
[Profile2]
Name=default-test
IsRelative=0
Path=/home/user/ffprofiles/f9bwn86n.default-test

[Profile1]
Name=default
IsRelative=1
Path=x64qf7nv.default
Default=1

[Profile0]
Name=default-release
IsRelative=1
Path=9hv1fbkk.default-release-3426201712696

[General]
StartWithLastProfile=1
Version=2

[Install22379532B4E49482]
Default=9hv1fbkk.default-release-3426201712696
Locked=1

Ответ или решение

Чтобы напечатать все пары ключ-значение в секциях INI-файла, например, в файле profiles.ini браузера Firefox, можно использовать язык программирования awk, который позволяет эффективно обрабатывать текстовые файлы. В данной статье мы разберем пошаговый подход для достижения этой цели, включая создание универсального решения для извлечения данных.

Анализ задачи

INI-файлы являются простыми текстовыми файлами, которые используют секции для группировки параметров. Каждая секция начинается с заголовка, заключенного в квадратные скобки, и содержит пары ключ-значение. В нашем случае нас интересуют секции [ProfileN], где N является неотрицательным целым числом, а также опциональный ключ Default в секции, начинающейся с [Install*].

Подход с использованием awk

Мы можем использовать awk для обработки нашего файла, чтобы извлечь нужные секции и ключи. Пример простой команды выглядит следующим образом:

awk '
    /^\[/                 { sect = substr($0, 2, length($0)-2); print; blank=0 }
    sect ~ /^Profile[0-9]+$/ { inProfile = 1 }
    inProfile {
        print;
        if (/^$/) { blank = 1 }  # Если строка пустая
    }
    /^\[Install/ {
        install_section = 1; header = $0 
    }
    install_section && /^Default=/ {
        print header; print;
    }
    /^\[/ { install_section = 0; inProfile = 0 }  # Сброс флагов
' profiles.ini

Объяснение кода

  1. Инициализация условий: Команда начинается с условия, которое определяет, если строка начинается с [, что означает начало секции, и сохраняет ее заголовок без квадратных скобок.

  2. Признак секции профиля: Используется регулярное выражение для определения секции профиля. Если мы находимся в одной из таких секций, мы печатаем все строки, пока не встретим пустую строку или другую секцию.

  3. Обработка секции установки: Если обнаруживается секция [Install], то проверяется наличие ключа Default и печатаем его значение, если он существует.

  4. Сброс состояния: Как только встречается другая секция, мы сбрасываем все активные флаги.

Вывод

Данный скрипт позволяет выводить пары ключ-значение из нужных секций INI-файла, структурируя вывод таким образом, чтобы он был легко читаемым. Этот подход гибкий и переносимый на многие Unix-подобные системы благодаря использованию стандартного инструмента awk.

Пример использования

Предположим, у вас есть файл profiles.ini, содержащий:

[Profile2]
Name=default-test
IsRelative=0
Path=/home/user/ffprofiles/f9bwn86n.default-test

[Profile1]
Name=default
IsRelative=1
Path=x64qf7nv.default
Default=1

Запустив приведенный выше скрипт, вы получите аккуратный вывод всех секций [ProfileN] и, если существует, ключа Default из секции установка.

Заключение

Использование awk для обработки INI-файлов позволяет создавать мощные и гибкие решения для извлечения данных. Наш метод ориентирован на производительность и простоту, что делает его отличным выбором для администраторов и разработчиков. С помощью данных примеров и подходов вы сможете легко адаптировать код под свои нужды и расширить его функционал.

Если у вас возникли вопросы или нужно больше информации, не стесняйтесь обращаться!

Оцените материал
Добавить комментарий

Капча загружается...