Анализ файла, разделенного запятыми, с помощью powershell

#.net #regex #powershell #csv

#.net #регулярное выражение #powershell #csv

Вопрос:

У меня есть текстовый файл, который содержит несколько строк, каждая из которых является строкой, разделенной запятыми. Формат каждой строки:

<Name, Value, Bitness, OSType>

Bitness и OSType являются необязательными.

Например, файл может быть таким:

 Name1, Value1, X64, Windows7
Name2, Value2, X86, XP
Name3, Value3, X64, XP
Name4, Value3, , Windows7
Name4, Value3, X64 /*Note that no comma follows X64 */
....
....
  

Я хочу разобрать каждую строку на 4 переменные и выполнить над ней некоторую операцию. Это скрипт PowerShell, который я использую..

 Get-Content $inputFile | ForEach-Object {
    $Line = $_;

    $_var = "";
    $_val = "";
    $_bitness = "";
    $_ostype = "";

    $envVarArr = $Line.Split(",");
    For($i=0; $i -lt $envVarArr.Length; $i  ) {
        Switch ($i) {
            0 {$_var = $envVarArr[$i].Trim();}
            1 {$_val = $envVarArr[$i].Trim();}
            2 {$_bitness = $envVarArr[$i].Trim();}
            3 {$_ostype = $envVarArr[$i].Trim();}
        }                                    
    }
    //perform some operation using the 4 temporary variables
}
  

Однако я хотел знать, возможно ли это сделать с помощью regex в PowerShell. Не могли бы вы, пожалуйста, предоставить пример кода для этого? Обратите внимание, что 3-е и 4-е значения в каждой строке могут быть необязательно пустыми.

Комментарии:

1. Пожалуйста, не используйте регулярные выражения для разбора CSV. 🙂 Это преступление против человечности.

Ответ №1:

Вы можете указать альтернативную строку заголовка столбца для импортируемого файла file с помощью -Header параметра Import-Csv командлета:

 Import-Csv .test.txt -Header Col1,Col2,Bitness,OSType
  

Ответ №2:

Не было бы лучше использовать Import-Csv который делает все это (и более надежно) за вас?

Комментарии:

1. И если у вас уже есть строка CSV, вы можете передать ее в ConvertFrom-Csv , чтобы превратить в объект. Обе команды также позволяют указать пользовательский разделитель.

Ответ №3:

Как предлагает Тим, вы можете использовать Use Import-Csv. Разница в том, что Import-Csv считывает данные из файла.

 @"
Name1, Value1, X64, Windows7
Name2, Value2, X86, XP
Name3, Value3, X64, XP
Name4, Value3, , Windows7
Name4, Value3, X64 /*Note that no comma follows X64 */
"@ | ConvertFrom-Csv -header var, val, bitness, ostype

# Result

var   val    bitness                                 ostype  
---   ---    -------                                 ------  
Name1 Value1 X64                                     Windows7
Name2 Value2 X86                                     XP      
Name3 Value3 X64                                     XP      
Name4 Value3                                         Windows7
Name4 Value3 X64 /*Note that no comma follows X64 */         
  

Ответ №4:

Медленнее, чем меласса, но, потратив 20 лет на создание дюжины или более частичных решений, я решил заняться этим окончательно. Конечно, со временем теперь доступны все виды библиотек синтаксического анализа.


 function SplitDelim($Line, $Delim=",", $Default=$Null, $Size=$Null) {

    # 4956968,"""Visible,"" 4D ""Human"" Torso Anatomy Kit (4-5/8)",FDV-26051,"" ,"",,,,,,a
    # "4956968,"""Visible,"" 4D ""Human"" Torso Anatomy Kit (4-5/8)",FDV-26051,"" ,"",,,,,,a
    # ,4956968,"""Visible,"" 4D ""Human"" Torso Anatomy Kit (4-5/8)",FDV-26051,"" ,"",,,,,,a

    $Field = ""
    $Fields = @()
    $Quotes = 0
    $State = 'INF' # INFIELD, INQFIELD, NOFIELD
    $NextState = $Null

    for ($i=0; $i -lt $Line.length; $i  ) {
        $Char = $Line.substring($i,1)

        if($State -eq 'NOF') {

            # NOF and Char is Quote
            # NextState becomes INQ
            if ($Char -eq '"') {
                $NextState = 'INQ'
            }

            # NOF and Char is Delim
            # NextState becomes NOF
            elseif ($Char -eq $Delim) {
                $NextState = 'NOF'
                $Char = $Null
            }

            # NOF and Char is not Delim, Quote or space
            # NextState becomes INF
            elseif ($Char -ne " ") {
                $NextState = 'INF'
            }

        } elseif ($State -eq 'INF') {

            # INF and Char is Quote
            # Error
            if ($Char -eq '"') {
                return $Null}

            # INF and Char is Delim
            # NextState Becomes NOF
            elseif ($Char -eq $Delim) {
                $NextState = 'NOF'
                $Char = $Null
            }

        } elseif ($State -eq 'INQ') {

            # INQ and Char is Delim and consecutive Quotes mod 2 is 0
            # NextState is NOF
            if ($Char -eq $Delim -and $Quotes % 2 -eq 0) {
                $NextState = 'NOF'
                $Char = $Null
            }
        }

        # Track consecutive quote for purposes of mod 2 logic
        if ($Char -eq '"') {
            $Quotes  
        } elseif ($NextState -eq 'INQ') {
            $Quotes = 0
        }

        # Normal duty
        if ($State -ne 'NOF' -or $NextState -ne 'NOF') {
            $Field  = $Char
        }

        # Push to $Fields and clear
        if ($NextState -eq 'NOF') {
            $Fields  = (IfBlank $Field $Default)
            $Field = ''
        }

        if ($NextState) {
            $State = $NextState
            $NextState = $Null
        }
    }

    $Fields  = (IfNull $Field $Default)

    while ($Size -and $Fields.count -lt $Size) {
        $Fields  = $Default
    }

    return $Fields
}