2017-02-22 50 views
-3

Мне нужно применить некоторые изменения в Col01 на основе значения в Col03Логические операции на CSV с Powershell

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
  • «среднего», если значение Col03 находится между 51 и 100
  • «большой», если значение Col03 находится между 51 и 100
  • 'текст', если значение 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

Что вы пробовали? Может ли Col03 быть целыми или текстовыми? Средний и большой те же требования. –

ответ

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 
+0

Я пробовал это, но он не выводит файл. Я noob :) $ csv = Import-CSV -Path C: \ marketing \ sandbox \ MyFile.csv $ csv | ForEach-Object { #get текущее значение Col03 $ col3 = $ _. Col03.Trim() #Calculate новое значение Col01 $ = Вэл, если ($ col3 -match '^ \ d + $') { # Col03 ([int] $ col3 -le 50) {"small"} elseif ([int] $ col3 -le 100) {"medium"} else {"large"} } else {"text" } #Replace Col01 значение $ _ Col01 = $ Вэл $ _ Out-File -Кодирование "utf8" -Path. "C: \ маркетинг \ песочница \ out.csv" } –

+0

можете ли вы Мольбы e добавить команду выходного файла, я такой noob ... –

+0

Добавить '| Export-CSV -Path MyOuput.csv -NoTypeInformation' до последней строки в моем ответе –

0

Это идеальный практик корм, вы можете написать его несколько способов, ядро ​​его является:

  • Чтение строк CSV
    • Вы можете сделать это в виде строк текста, но «правильным» способом в PowerShell является Import-Csv).
  • Тестирование текста/Номер детали

    1. обрабатывать текст условия первого (в коде) и что-нибудь обходя, что будет номера.
    2. попробуйте и сначала обработайте номера, все, что пройдет, что будет текст.
    3. Предположим, что это все числа, если они ломаются, это текст. Это работает, но использует Исключения для потока управления, и это плохо. Это плохо, потому что Исключения предназначены для исключительных условий, и вы ожидаете, что текст будет нормальной частью работы программы, и это будет плохо, потому что Исключения имеют много накладных расходов. Тем не менее, вы программист, и вы можете сделать выбор, если хотите - например, это особенно читаемо/понятно.
  • Экспорт 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/else или switch.

Итак, оставшаяся часть - это одна из 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 } 

OK, некоторые из них будут получать назначены «средний», а затем «маленький», но этот макет читать гораздо лучше, чтобы посмотреть, что он делает, не так ли?

И провал неявно происходит в switch, если вы не остановите его, так что у него будут переключающие шкафы с или без break, чтобы остановить провал.

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

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/проваливаюсь просто ждем кого-то, чтобы делать ошибки при чтении его.И с помощью обработки исключений в потоке управления, я получаю это, (который я изменил, чтобы прочитать весь файл в память первого из обзорных вопросов с $_ внутри блока улова):

$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 

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

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' } 

, а затем это понятнее они начала/конца диапазоны, а на самом деле, да мне нравится подход Фрод лучше всего, просто не отформатирован, как он отформатирован, и хочу я d прочитайте его ответ, прежде чем писать это.

+0

Хорошее (и длинное!) Объяснение :-). Не потратил много времени на то, чтобы мой код выглядел хорошо на этот раз, потому что все if's были уродливыми в любом случае ... –

+0

сейчас я потерян :) lol –