Синтаксический анализ INI-файлов в PowerShell



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



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

7 ответов:

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

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

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 для улучшений: обрезка пробелов и поддержка комментариев.

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

Дон Джонс почти угадал. Попробуйте:

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

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

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

Я возился с этим, чтобы сделать элементарную обработку целых и десятичных чисел...
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
}

Одна из возможностей заключается в использовании библиотеки .NET ini. Нини например.

Я перевел Простой пример из документов Nini в PowerShell ниже. Тебе нужно посадить Нини.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")

Я оптимизировал это решение для моих нужд добавив некоторые вещи в функцию и новую функцию для записи обратно ini-файла:

  1. я сделал упорядоченный словарь из исходного словаря (хэш-таблицы), чтобы сохранить структуру файла
  2. И чтобы сохранить комментарии и пустые строки, я помещаю их в специальный ключ. Затем их можно игнорировать при использовании данных или выбрасывать при записи в файл, как показано ниже в функции Set-IniFile
  3. в Set-IniFile, используя опции -PrintNoSection и -PreserveNonData, можно управлять, следует ли использовать NO_SECTION и следует ли сохранять строки без данных (не совпадающие ключ=значение или [раздел]).

Function Get-IniFile ($file)       # Based on "https://stackoverflow.com/a/422529"
 {
    $ini = [ordered]@{}

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

    switch -regex -file $file 
    {    
        "^\[(.+)\]$" 
        {
            $section = $matches[1].Trim()
            $ini[$section] = [ordered]@{}
        }

        "^\s*(.+?)\s*=\s*(.*)" 
        {
            $name,$value = $matches[1..2]
            $ini[$section][$name] = $value.Trim()
        }

        default
        {
            $ini[$section]["<$("{0:d4}" -f $CommentCount++)>"] = $_
        }
    }

    $ini
}

Function Set-IniFile ($iniObject, $Path, $PrintNoSection=$false, $PreserveNonData=$true)
{                                  # Based on "http://www.out-web.net/?p=109"
    $Content = @()
    ForEach ($Category in $iniObject.Keys)
    {
        if ( ($Category -notlike 'NO_SECTION') -or $PrintNoSection )
        {
            # Put a newline before category as seperator, only if there is none 
            $seperator = if ($Content[$Content.Count - 1] -eq "") {} else { "`n" }

            $Content += $seperator + "[$Category]";
        }

        ForEach ($Key in $iniObject.$Category.Keys)
        {           
            if ( $Key.StartsWith('<') )
            {
                if ($PreserveNonData)
                    {
                        $Content += $iniObject.$Category.$Key
                    }
            }
            else
            {
                $Content += "$Key = " + $iniObject.$Category.$Key
            }
        }
    }

    $Content | Set-Content $Path -Force
}


### EXAMPLE
##
## $iniObj = Get-IniFile 'c:\myfile.ini'
##
## $iniObj.existingCategory1.exisitingKey = 'value0'
## $iniObj['newCategory'] = @{
##   'newKey1' = 'value1';
##   'newKey2' = 'value2'
##   }
## $iniObj.existingCategory1.insert(0, 'keyAtFirstPlace', 'value3')
## $iniObj.remove('existingCategory2')
##
## Set-IniFile $iniObj 'c:\myNewfile.ini' -PreserveNonData $false
##

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

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

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

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

key1=value1
key2=value2
key3=value3

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

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

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

Нини выглядит как библиотека ... не уверен для 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 

Comments

    Ничего не найдено.