使用Powershell对CSV进行逻辑操作

时间:2017-02-22 18:27:23

标签: powershell csv logical-operators

我需要根据Col03中的值

在Col01中应用一些更改
Col01,Col02,Col03
empty,empty,6
empty,empty,19
empty,empty,75
empty,empty,87
empty,red,145
empty,empty,625
empty,empty,abc

将Col01中的内容设为:

    如果Col03值小于或等于50 ,则
  • 'small'
  • 'medium'如果Col03值介于51和100之间
  • 如果Col03值介于51和100之间,则
  • 'large'
  • 'text'如果Col03值是文本(不是数字)

结果:

Col01,Col02,Col03
small,empty,6
small,empty,19
medium,empty,75
medium,empty,87
large,empty,145
large,empty,625
text,empty,abc

2 个答案:

答案 0 :(得分:0)

这是对的吗?有多种方法可以实现这一目标。例如:

#Sample data
$csv = @"
Col01,Col02,Col03
empty,empty,6
empty,empty,19
empty,empty,75
empty,empty,87
empty,red,145
empty,empty,625
empty,empty,abc
"@ | ConvertFrom-Csv

#Uncomment to read from file
#$csv = Import-CSV -Path C:\MyFile.csv

$csv | ForEach-Object {

    #Get current Col03 value
    $col3 = $_.Col03.Trim()

    #Calculate new Col01 value 
    $val = if($col3 -match '^\d+$') {
        #Col03 is integer
        if([int]$col3 -le 50) { "small" }
        elseif ([int]$col3 -le 100) { "medium" }
        else { "large" }
    } else { "text" }

    #Replace Col01-value
    $_.Col01 = $val

    #Output modified object
    $_
}

或使用开关。此示例还将结果保存到文件中:

$csv = @"
Col01,Col02,Col03
empty,empty,6
empty,empty,19
empty,empty,75
empty,empty,87
empty,red,145
empty,empty,625
empty,empty,abc
"@ | ConvertFrom-Csv

#Uncomment to read from file
#$csv = Import-CSV -Path C:\MyFile.csv

$csv | ForEach-Object {

    $_.Col01 = switch($_.Col03.Trim()) {
        #Contains non-digit - text
        {$_ -match '\D'} { 'text'; break; }
        #Number - pick category
        {[int]$_ -le 50} { 'small'; break; }
        {[int]$_ -le 100} { 'medium'; break; }
        {[int]$_ -gt 100} { 'large'; break; } 
    }  

    #Output modified object
    $_
} | Export-CSV -Path MyOuput.csv -NoTypeInformation 

输出:

Col01  Col02 Col03
-----  ----- -----
small  empty 6    
small  empty 19   
medium empty 75   
medium empty 87   
large  red   145  
large  empty 625  
text   empty abc  

答案 1 :(得分:0)

它是完美的练习素材,你可以用几种方式写它,它的核心是:

  • 读取CSV行
    • 您可以将其作为文字行,但正确的' PowerShell中的方式是Import-Csv)。
  • 测试文字/数字部分

    1. 首先处理文本条件(在您的代码中),并且的所有内容都将数字。
    2. 首先尝试处理数字案例,任何超过 文本的内容。
    3. 假设它是所有数字,如果它打破了它的文本。这是可行的,但是它使用Exceptions来控制流程,这很糟糕。这很糟糕,因为异常是针对特殊条件的,并且您期望将文本作为程序操作的正常部分,并且它有点不好,因为异常有很多开销。但是,您是程序员,如果需要,您可以选择使用它们,例如它特别易读/清晰。
  • 导出CSV行

    • 您再次可以将其作为文字行进行,但Export-Csv可以与Import-Csv配对,这样就可以使用'对等'在PowerShell中的方式。

在PowerShell术语中,您可以安排它:

Import-Csv | ForEach-Object { process one CSV row at a time } | Export-Csv

(as opposed to:
$foo = import-csv
$bar = @()
foreach ($line in $foo) {
   #...
   $bar += $line
}
which is workable but ugly and wasteful of memory and CPU and won't scale nicely)

好的,我们已经处理了读/处理/写入部分的结构。现在,您将数字值分配给存储桶。

0-10    11-20    21-30    31-40
\__/    \___/    \___/    \___/

或任何尺寸范围/铲斗条件。

该模式尖叫if/elseswitch

所以剩下的部分是,你选择哪一个1. 2. 3.方法,你在哪里以及如何从数字中分割文本,以及如何将数字分配到桶中。

大多数选择都与可读性和偏好有关。

具有开始和结束的桶意味着像start -le $num -and $num -lt end这样的双重测试,然后两个边缘情况只有一个测试。但是你的三个桶意味着需要一个双重测试,两个需要一个测试。

if ($foo -gt 100)
elseif (51 -lt $foo -and $foo -le 100)
elseif ($foo -lt 50)

看看if / elseif和单/双测试的混搭。但是因为你的桶很好地相互碰撞,你可以使用/误用掉落测试:

if ($foo -gt 100) { big    }
if ($foo -le 100) { medium }
if ($foo -le 50 ) { small  }

好的,有些人会被分配到“中等”状态。然后小'但这个布局要好看,看看它做了什么,不是吗?

除非你阻止它,否则隐含地发生在switch中,因此有或没有break的转换案例可以阻止坠落。

如果您选择首先匹配文本,您可能会使用正则表达式来识别不是数字(向后)的内容,所以我明白了:

Import-Csv .\t.csv | ForEach-Object { 

    if ($_.Col03 -notmatch '^\d+$')
    {
        $_.Col01 = 'text'
    }
    else 
    {
        if ([int]$_.Col03 -gt 100) { $_.Col01 = 'large'  }
        if ([int]$_.Col03 -le 100) { $_.Col01 = 'medium' }
        if ([int]$_.Col03 -le 50)  { $_.Col01 = 'small'  }
    }

    $_

} # | Export-Csv out.csv -NoTypeInformation

这是可行的,但是呃。如果您选择首先识别数字,则可以使用相同的正则表达式模式,也可以将.Net框架告诉TryParse - 将文本作为数字。我明白了:

Import-Csv .\t.csv | ForEach-Object { 

    [int]$n = 0

    if ([int]::TryParse($_.Col03, [ref]$n)) 
    {
        if ($n -gt 100) { $_.Col01 = 'large'  }
        if ($n -le 100) { $_.Col01 = 'medium' }    
        if ($n -le 50)  { $_.Col01 = 'small'  }
    }
    else 
    {
        $_.Col01 = 'text'
    }

    $_

} # | Export-Csv out.csv -NoTypeInformation

哪个不太漂亮。去寻找一个正则表达式/开关组合,然后我得到:

Import-Csv .\t.csv | ForEach-Object { 

    switch -regex ($_)
    {
        {$_.Col03 -notmatch '^\d+$'} { $_.Col01 = 'text'   ; break }
        {[int]$_.Col03 -gt 100}      { $_.Col01 = 'large'          }
        {[int]$_.Col03 -le 100}      { $_.Col01 = 'medium'         }
        {[int]$_.Col03 -le 50 }      { $_.Col01 = 'small'          }
    }

    $_
} # | Export-Csv out.csv -NoTypeInformation

这是相当漂亮的,但部分; break / fallthrough只是等待有人在阅读时犯错误。并使用异常处理作为控制流,我得到了这个(由于catch块中$_的范围问题,我已将其更改为首先将整个文件读入内存):

$Rows = foreach ($Row in Import-Csv .\t.csv)
{ 
    try {
        if ([int]$Row.Col03 -gt 100) { $Row.Col01 = 'large'  }
        if ([int]$Row.Col03 -le 100) { $Row.Col01 = 'medium' }
        if ([int]$Row.Col03 -le 50)  { $Row.Col01 = 'small'  }
    } catch {
        $row.Col01 = 'text'
    }

    $row
}

$Rows # | Export-Csv out.csv -NoTypeInformation

但是他们都做了基本相同的事情,我无法想出一个更好的方式来做分组,所以我的所有答案都是完全相同的形状,即使它们完全贯穿不同的代码。

Frode F使用PowerShell分配if再次以不同的方式工作,但在这种方法中,我不能使用通过桶检查 - 因此他使用if / elseif / if而不是if / if / if。这有点好,可以格式化为:

$row.Col01 =     if (                $n -le  50) { 'small'  }
             elseif ( 51 -lt $n -and $n -le 100) { 'medium' }
             elseif (100 -lt $n                ) { 'large'  }

然后它更清晰,它们是开始/结束范围,实际上是的,我最喜欢Frode的方法,只是没有格式化他如何格式化它,并希望我&#dd; d在写这篇文章之前先阅读他的答案。