Разбор файла INI в PowerShell

Я разбираю простой (без разделов) INI-файл в PowerShell. Вот код, который я придумал, есть ли способ его упростить?

convertfrom-stringdata -StringData ( `
  get-content .\deploy.ini `
  | foreach-object `
    -Begin { $total = "" }  `
    { $total += "`n" + $_.ToString() } `
    -End { $total } `
).Replace("\", "\\")

person Artem Tikhomirov    schedule 06.01.2009    source источник
comment
Об этом было написано в блоге Hey, Scripting Guy!: Оливер Липкау, 2011-08-20, Используйте PowerShell для работы с любым файлом INI. (Заархивировано здесь.) И обновленная версия скрипта находится в галерее скриптов MS: _ 1_. (Заархивировано здесь.)   -  person StackzOfZtuff    schedule 10.05.2016
comment
Хороший совет, @StackzOfZtuff. Этот код теперь доступен в галерее PowerShell по адресу powershellgallery.com/packages/PsIni как модуль PSIni , устанавливается как Install-Module PSIni   -  person mklement0    schedule 22.03.2019


Ответы (6)


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

Есть более простые, и, поскольку мне не нужна письменная поддержка, я выбрал следующий очень элегантный фрагмент кода:

Function Parse-IniFile ($file) {
  $ini = @{}

  # Create a default section if none exist in the file. Like a java prop file.
  $section = "NO_SECTION"
  $ini[$section] = @{}

  switch -regex -file $file {
    "^\[(.+)\]$" {
      $section = $matches[1].Trim()
      $ini[$section] = @{}
    }
    "^\s*([^#].+?)\s*=\s*(.*)" {
      $name,$value = $matches[1..2]
      # skip comments that start with semicolon:
      if (!($name.StartsWith(";"))) {
        $ini[$section][$name] = $value.Trim()
      }
    }
  }
  $ini
}

Это Жака Баратона.

Обновление Спасибо Aasmund Eldhuset и @msorens за улучшения: обрезку пробелов и поддержку комментариев.

Обновление 2. Пропустите любые name=value пары, где name начинается с точки с запятой ;, которые представляют собой строки комментариев. Заменен $ini [$section] = @{} на $ini[$section] = @{}.

person Artem Tikhomirov    schedule 07.01.2009
comment
+1; работает как шарм! Однако я бы предложил изменить регулярное выражение «ключ-значение» на (.+?)=(.+) (нежадный +), чтобы обрабатывать значения, содержащие =, такие как строки подключения к SQL Server (которые, кстати, и есть в моем файле INI :-)) . Кроме того, правая часть, возможно, должна быть (.*) вместо (.+), чтобы разместить пустые значения. - person Aasmund Eldhuset; 16.05.2011
comment
Благодарим @ Aasmund-Eldhuset за улучшение, позволяющее встроить знаки равенства. Я обнаружил, что необходимо еще одно улучшение: сценарий как бы анализирует закомментированные строки, как если бы они были активными строками! Исправление состоит в том, чтобы изменить одно и то же регулярное выражение на ^\s*([^#].+?)=(.*), чтобы прокомментированные строки игнорировались. - person Michael Sorens; 18.05.2011
comment
Сделайте эти два улучшения: он также должен игнорировать лишние пробелы вокруг ключа и значения. Регулярное выражение становится ^\s*([^#].+?)\s*=\s*(.*), а присваивание становится $ini[$section][$name] = $value.trim() - person Michael Sorens; 18.05.2011
comment
отлично, но между $ ini и [$ section] в строке 6 есть пробел, который вызовет ошибку - person mishkin; 02.10.2012
comment
@mishkin только что это исправил. - person Jeroen Wiert Pluimers; 10.07.2014
comment
Это не сработало для односимвольных имен - не совпало. Я изменил регулярное выражение для имени / значения на ^ \ s * (. +?) \ S * = \ s * (. *), Которое, похоже, работает (я не проводил обширного тестирования) - person Straff; 30.06.2016
comment
при использовании этой функции, если я пытаюсь напечатать значения на объекте $ ini, например: $ ini [server] [ip], это дает мне ошибку. Невозможно индексировать в нулевой массив. Есть указатели на это? - person Rahul; 29.06.2018

Дон Джонс был почти прав. Пытаться:

ConvertFrom-StringData((Get-Content .\deploy.ini) -join "`n")

-join преобразует Object [] в одну строку, где каждый элемент в массиве разделен символом новой строки. ConvertFrom-StringData затем разбирает строку на пары ключ / значение.

person Steve Beckert    schedule 22.11.2011
comment
Теперь можно использовать параметр Raw для чтения всего файла в одну строку. Get-Content -Path file.ini -Raw - person Nathan Hartley; 07.08.2015
comment
ConvertFrom-StringData не работает с комментариями и другими строками, не являющимися ключевыми = значениями. - person majkinetor; 13.06.2018

Это действительно расширение текущего ответа (не удалось добавить комментарий).

Я возился с этим, чтобы сделать элементарную обработку целых и десятичных чисел ...

function Parse-IniFile ($file)
{
  $ini = @{}
  switch -regex -file $file
  {
    #Section.
    "^\[(.+)\]$"
    {
      $section = $matches[1].Trim()
      $ini[$section] = @{}
      continue
    }
    #Int.
    "^\s*([^#].+?)\s*=\s*(\d+)\s*$"
    {
      $name,$value = $matches[1..2]
      $ini[$section][$name] = [int]$value
      continue
    }
    #Decimal.
    "^\s*([^#].+?)\s*=\s*(\d+\.\d+)\s*$"
    {
      $name,$value = $matches[1..2]
      $ini[$section][$name] = [decimal]$value
      continue
    }
    #Everything else.
    "^\s*([^#].+?)\s*=\s*(.*)"
    {
      $name,$value = $matches[1..2]
      $ini[$section][$name] = $value.Trim()
    }
  }
  $ini
}
person WaffleSouffle    schedule 13.07.2011
comment
Что произойдет, если у вас длинное значение ini. Приведение к типу int вызовет проблемы. - person Bruno; 09.05.2016

Одна из возможностей - использовать ini-библиотеку .NET. Например, Nini.

Я перевел Простой пример из документации Nini в PowerShell ниже. Вам нужно поместить nini.dll в тот же каталог, что и сценарий.

$scriptDir = Split-Path -parent $MyInvocation.MyCommand.Definition
Add-Type -path $scriptDir\nini.dll

$source = New-Object Nini.Config.IniConfigSource("e:\scratch\MyApp.ini")

$fileName = $source.Configs["Logging"].Get("File Name")
$columns = $source.Configs["Logging"].GetInt("MessageColumns")
$fileSize = $source.Configs["Logging"].GetLong("MaxFileSize")
person dan-gph    schedule 22.11.2010
comment
Как бы то ни было, в наши дни я использую INI File Parser. Это работает хорошо. - person dan-gph; 17.05.2016

Я не совсем уверен, как выглядят ваши исходные данные и какова ваша цель. Для чего именно парсите? Можете выложить образец файла? Как есть, похоже, что вы просто объединяете символы возврата каретки к существующим строкам файла и заменяете \ на \.

Не уверен, почему вы используете $_.ToString(), поскольку $_ уже является строковым объектом, выводимым Get-Content.

Является ли цель просто взять файл, содержащий кучу пар имя = значение, и преобразовать его в хеш-таблицу? Это то, что делает ConvertFrom-StringData, хотя этот командлет доступен только в предварительной версии PowerShell v2.

Если ваш файл выглядит как ...

key1=value1
key2=value2
key3=value3

Тогда все, что вам нужно, это

ConvertFrom-StringData (Get-Content .\deploy.ini)

Я не уверен, что понимаю, почему вы прибегаете к лишнему возврату каретки. Также нет необходимости использовать параметры -Begin и -End, по крайней мере, насколько я могу судить из того, что вы опубликовали.

person Don Jones    schedule 06.01.2009
comment
Ваш пример не работает из-за «унифицированного» вывода командлета get-content. Он выводит System.Object []: ConvertFrom-StringData: невозможно преобразовать 'System.Object []' в тип 'System.String', требуемый параметром 'StringData'. Вот почему я повторяю, обработал его и повторно собрал в строку. - person Artem Tikhomirov; 07.01.2009
comment
К сожалению, это не то, что я мог бы определить, не глядя на ваши исходные данные. Как я пытался указать, я пытался помочь ответить на ваш вопрос, но мне нужна дополнительная информация, чтобы сделать это точно. Я думал, что ясно дал понять - извините. - person Don Jones; 07.01.2009
comment
Не нужно давать ему -1 за то, что он полезен. И он заявляет, что не знает наверняка, чего вы хотите. - person Lars Truijens; 08.01.2009
comment
Ini-файл без разделов может быть чем угодно. У меня есть файлы INI, которые используют ключ: значение, другие, которые используют ключ = значение, и другие, которые используют совершенно другой формат. И пока я сижу здесь и тестирую его, Get-Content возвращает объекты String, когда я передаю ему текстовый файл. Если вам не нужна помощь, не просите. - person Don Jones; 08.01.2009

nini выглядит как библиотека ... не уверен в PowerShell

сбой PowerShell

первый шаг

[void][system.reflection.assembly]::loadfrom("nini.dll") (refer add-type now in ps2 )

после того, как вы сможете использовать это

$iniwr = new-object nini.config.iniconfigsource("...\ODBCINST.INI") 

$iniwr.Configs et boom 
person raoul    schedule 24.10.2011