Posts on this page:
3-е задание на парсинг текста.
Потребуется файл Shot Put.txt из Competitors Pack.
Задача:
Решение:
Если внимательно посмотреть на файл, то в нём можно найти одну уникальную последовательность - `r`n`r`n. Она разделяет 2 абзаца. Поэтому эту последовательность можно использовать в качестве разделителя для Get-Content:
[↓] [vPodans] $file = gc '.\Shot Put.txt' -Delimiter "`r`n`r`n" [↓] [vPodans] $file[0] This is the first paragraph of the shot put text file. It will need to be placed into the first text file which you will create. The shot put is an event involving putting and heavy metal ball. The object of the event is to throw the put as far as possible, or to put the shot : anyway, you get the idea. [↓] [vPodans] $file[1] This is the second paragraph of the shot put text file. it contains information you will need to put into the second file which you will also create. [↓] [vPodans]
Если со вторым абзацем понятно, то у первого очень много пробелов и пустых строк. Их можно удалить при помощи метода Trim(), который использовался уже в Event1. Собственно, само решение:
$file = gc '.\Shot Put.txt' -Delimiter "`r`n`r`n" | %{$_.trim()} $file[0] > 'Shot Put A.txt' $file[1] > 'Shot Put B.txt' ren '.\Shot Put.txt' 'Shot Put.old'
потребуется файл Wordlist_ADV3.txt из Competitors Pack.
Задача:
Решение:
В принципе, тут есть только одно простое решение (считайте, что нагло стырено у Васи):
gc .\Wordlist_ADV3.txt | ?{$_ -replace '[^AEIOUY]' -match '^(.)\1*$'} > 'Wordlist_ADV3_new.txt'
суть решения заключается в том, чтобы из каждого слова убрать все согласные и проверить, что в слове используется только одна гласная 1 или более раз. Этот регексп описан в PowerShell Cookbook на странице 494.
Тут очень простое задание из серии фаллометрии :-)
Задача:
Решение:
Тут нам потребуется WMI класс Win32_Processor. И из него выберем нужные данные. При этом я проверю, что значения не равны $null или нулю. Если это так, то в это поле впишу текст N/A для читабельности. Вот что получилось:
$Host.UI.RawUI.BackgroundColor = "Black" cls $processor = gwmi win32_processor | Select AddressWidth, L2CacheSize, L2CacheSpeed, L3CacheSize, L3CacheSpeed, MaxClockSpeed, Name, NumberOfCores, NumberOfLogicalProcessors $processor | gm -MemberType NoteProperty | %{ if ($processor.$($_.name) -eq $null -or $processor.$($_.name) -eq 0) {$processor.$($_.name) = "N/A"} } Write-Host 'Strength evaluation for LocalHost' -ForegroundColor Green Write-Host 'Speed ... ' $processor.MaxClockSpeed ` `nL2 Cache Size: $processor.L2CacheSize ` `nL2 Cache Speed: $processor.L2CacheSpeed ` `nL3 Cache Size: $processor.L3CacheSize ` `nL3 Cache Speed: $processor.L3CacheSpeed -ForegroundColor Yellow Write-Host Strength ... ` `nNumber of Cores: $processor.NumberOfCores ` `nNumber of Logical Processors: $processor.NumberOfLogicalProcessors ` `nName: ```````````` $processor.Name -ForegroundColor Magenta Write-Host Agility ... ` `nAddress Width: $processor.AddressWidth -ForegroundColor Cyan
И вот вывод:
[↓] [vPodans] $processor = gwmi win32_processor | Select AddressWidth, L2CacheSize, L2CacheSpeed, L3CacheSize, >> L3CacheSpeed, MaxClockSpeed, Name, NumberOfCores, NumberOfLogicalProcessors >> $processor | gm -MemberType NoteProperty | %{ >> if ($processor.$($_.name) -eq $null -or $processor.$($_.name) -eq 0) {$processor.$($_.name) = "N/A"} >> } >> Write-Host 'Strength evaluation for LocalHost' -ForegroundColor Green >> Write-Host 'Speed ... ' $processor.MaxClockSpeed ` >> `n'L2 Cache Size:' $processor.L2CacheSize ` >> `n'L2 Cache Speed:' $processor.L2CacheSpeed ` >> `n'L3 Cache Size:' $processor.L3CacheSize ` >> `n'L3 Cache Speed:' $processor.L3CacheSpeed -ForegroundColor Yellow >> Write-Host Strength ... ` >> `n'Number of Cores:' $processor.NumberOfCores ` >> `n'Number of Logical Processors:' $processor.NumberOfLogicalProcessors ` >> `n'Name: $processor.Name -ForegroundColor Magenta >> Write-Host Agility ... ` >> `n"Address Width: `t`t`t`t" $processor.AddressWidth -ForegroundColor Cyan >> Strength evaluation for LocalHost Speed ... 2834 L2 Cache Size: 6144 L2 Cache Speed: 2000 L3 Cache Size: N/A L3 Cache Speed: N/A Strength ... Number of Cores: 4 Number of Logical Processors: 4 Name: Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz Agility ... Address Width: 64 [↓] [vPodans]
По всей видимости оно получилось как и требовалось.
И тут нас снова ждут соревнования. Здесь нам потребуется файл LongJump_Adv2.xls из Competitors Pack. Файл содержит данные об участниках прыжков в длину и результаты их 3-х попыток.
Задача:
Решение:
Вася Гусев решил окончательно сломать всем мозг разбором XLS в CSV, обработать его и собрать обратно в CSV – тиснуть сюда. Когда я это увидел – стало как-то не очень хорошо. Я решил работать прямо с XLS. И вот что у меня получилось:
# массив, который будет содержать результаты последней попытки прыжков, на основании # которых нужно вывести лучший результат $lastattempt = @() # создаём COM объект и прочую рутину. $excel = New-Object -ComObject Excel.Application $excel.Visible = $false $excel.DisplayAlerts = $false $excel.Workbooks.Open('C:\Users\vPodans\LongJump_Adv2.xls') | Out-Null # у нас есть известное кол-во участников - 18. 19-я строка - это заголовки. # поэтому я исходя из этого решил работать с колонкой Result (8-я по счёту). Т.к. # некоторые попытки были провалены и в таблице стояли крестики. Чтобы вручную не парсить # это всё, я в ячейку воткнул экселовскую формулу MAX, которая всё за меня сделает. 2..19 | %{$excel.Cells.Item($_,8).Value2 = "=max(e$_`:g$_)" # заодно в $lastattempt запишем результат последней попытки для текущего прыгуна $lastattempt += $excel.Cells.Item($_,7).Value2 # сравниваем лучший результат за сегодня с результатом за сезон if ($excel.Cells.Item($_,8).Value2 -lt $excel.Cells.Item($_,4).Value2) { # и в соответствующую графу пишем результат $excel.Cells.Item($_,9).Value2 = "Under Perform" # вот тут пришлось использовать читы. Я не очень силён в custom sort в экселе, поэтому # рядышком я для каждого статуса Exceed/Achieve/.. я приписываю число. Вот по этому числу # я потом буду сортировать таблицу $excel.Cells.Item($_,10).Value2 = "3" } elseif ($excel.Cells.Item($_,8).Value2 -eq $excel.Cells.Item($_,4).Value2) { $excel.Cells.Item($_,9).Value2 = "Achieve" $excel.Cells.Item($_,10).Value2 = "2" } else { $excel.Cells.Item($_,9).Value2 = "Exceed" $excel.Cells.Item($_,10).Value2 = "1" } } # выбираем из LastAttempt лучший результат, попутно заменяя крестики на нолики (лол) и выводя лучший результат $lastattempt | %{if ($_ -eq "x") {$_ = 0};$_} | sort | select -last 1 # выбираем диапазон изменения таблицы при сортировке. Заголовки не трогаем, поэтому начинаем с 2 $selection = $excel.Range("a2:j19") # указываем колонку, по которой будем сортировать $range = $excel.Range("j2:j19") # сортируем [void]$selection.Sort($range,"1") # удаляем читерские числа, которые использовались для сортировки. 2..19 | %{$excel.Cells.Item($_,10).Value2 = ""} # готово. $excel.Save() $excel.Quit()
Не скажу, что получилось коротко и идеально, но задачу вполне решает.
И оно свершилось! Хотя официально Scripting Games начинаются только 15-го июня, но уже со вчерашнего дня стали известны уже 2 задания. Для начала немного информации, что и как:
В принципе, вы можете решать задания как угодно, т.к. засчитываются все, которые отвечают условиям задания.
Итак, Event1 и оба дивизиона:
Из Competitors Pack нам потребуется файл 100 Meter Event.txt
Задача:
Решение:
Побегав курсором по файлу я обнаружил тот факт, что разделителем между столбцом Name и Country является табулятор ([Tab]), а между Country и Time стоит один пробел во второй строке, а в остальных строках стоит [Tab]. Следовательно, варианты использования командлетов с параметром –Delimeter не прокатят и нужно будет разбирать всё регулярными выражениями. Первый вопрос наталкивает на необходимость чтения файла не через Get-Content, а через [system.io.file]::ReadAllText(). Но я не вижу особого криминала в использовании родного для PowerShell командлета Get-Content. Это будет несложно. Основная проблема в том, как составить регулярное выражение. Я решил разобрать имя на First Name и LastName (т.к. правильней, всё же, будет, когда сначала идёт имя, а потом фамилия), далее выбрать Country от табуляции до первого числа. Причём, тут потребуется каждую часть сделать именованной (named capture). Если открыть PowerShell In Action на 111 странице, то там можно найти описание, как делать named captures. У меня регулярное выражение получилось вот такое:
^(?<ln>\w+)..(?<fn>.*)\t(?<country>.+)(?<time>\d\.\d+)
Что делает это выражение:
Давайте проверим его:
[↓] [vPodans] $file = gc '.\100 Meter Event.txt' [↓] [vPodans] $file[1] -match "^(?<ln>\w+)..(?<fn>.*)\t(?<country>.+)(?<time>\d\.\d+)" True [↓] [vPodans] $matches Name Value ---- ----- country Australia ln Aaberg fn Jesper time 8.57 0 Aaberg, Jesper Australia 8.57 [↓] [vPodans] $file[11] -match "^(?<ln>\w+)..(?<fn>.*)\t(?<country>.+)(?<time>\d\.\d+)" True [↓] [vPodans] $matches Name Value ---- ----- country Japan ln Hansen fn Anne Grethe time 8.85 0 Hansen, Anne Grethe Japan 8.85 [↓] [vPodans]
и теперь соберём объект с нужными свойствами и отправим его на выход. На выходе отсортируем объекты по параметру Time и выберем первые 3 объекта:
gc "100 Meter Event.txt" | ?{$_ -match "^(?<ln>\w+)..(?<fn>.*)\t(?<country>.+)(?<time>\d\.\d+)"} |%{ $obj = "" | Select @{n='Name';e={$matches.fn + " " + $matches.ln}}, @{n='Country';e={$matches.country}},@{n='Time';e={$matches.time}} $obj } | sort time | select -First 3 | ft -AutoSize
и вот вывод:
решил ещё отформатировать в Format-Table с ключом –AutoSize для красоты. Признаюсь, что это задание меня сильно озадачило. Для меня оно оказалось очень непростым (такие дела).
Для этой задачи нам потребуется файл Personal Information Cards_ADV1.txt из Competitors Pack.
Задача:
Решение:
В файле много пустых строк, но это не проблема. Итак, one-liner решение:
gc "Personal Information Cards_ADV1.txt" | ?{$_.trim().length -ne 0} | sort length | select -First 3
[↓] [vPodans] gc "Personal Information Cards_ADV1.txt" | ?{$_.trim().length -ne 0} | select –First 3 PPID Claims Street [↓] [vPodans]
Странно, что в Advanced Division получилось такое простое задание.
Работая с функциями в PowerShell можно столкнуться с одной особенностью – в качестве передачи аргументов в функции или скрипты вы можете использовать почти всё, кроме ключей (данных типа Switch). Их передавать можно, но тут есть одна особенность. Обычно это ощущается, когда вы работаете с командлетами. Возьмём простой пример:
function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { Write-Host '$Path is:' $Path Write-Host '$Filter is:' $Filter Write-Host '$Force is:' $Force }
и вызовем эту функцию:
[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { >> Write-Host '$Path is:' $Path >> Write-Host '$Filter is:' $Filter >> Write-Host '$Force is:' $Force >> } >> [↓] [vPodans] Test C:\ * -force $Path is: C:\ $Filter is: * $Force is: True [↓] [vPodans]
В принципе, всё как и ожидалось. Но если внимательно посмотреть на последний аргумент, то мы увидим лишь True, т.е. увидим факт, что ключ Force был передан. Однако, PowerShell не умеет подставять (биндить) переменную $Force (равно как и другие переменные) как именованный параметр в другую команду. Чтобы в этом убедиться, мы попробуем сымитировать нашу функцию как командлет Get-ChildItem:
[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { >> Get-ChildItem $Path $Filter $Force >> } >> [↓] [vPodans] Test C:\ * -Force Get-ChildItem : A positional parameter cannot be found that accepts argument 'True'. At line:2 char:14 + Get-ChildItem <<<< $Path $Filter $Force + CategoryInfo : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand [↓] [vPodans]
Как видно, командлет Get-ChildItem не смог сопоставить последний аргумент ни с одним из своих параметров. Т.е. по факту выполнилась следующая строка:
Get-ChildItem -Path C:\ -Filter * True
PowerShell не смог сказать командлету, что мы указали ключ Force и хотим именно его передать в командлет. По факту в этом коде Get-ChildItem не знал, какие параметры ему были переданы и подставлял их на основе номера позиции параметра. А т.к. у Get-ChildItem нету параметра с порядковым номером 3 (с номером 1 идёт Path или LiteralPath, а с номером 2 идёт Filter. Остальные параметры именованные), то мы получили ошибку. Но всё же, как выкручиваться из этой ситуации? Вы можете как угодно пытаться подставить аргументы, но ничего не выйдет. Для этих целей в PowerShell V2 появилась специальная переменная - $PSBoundParameters. Эта переменная по сути представляет собой хэш-таблицу:
[↓] [vPodans] function Test ([string]$Path, [String]$Filter, [switch]$Force) { >> $PSBoundParameters >> } >> [↓] [vPodans] Test C:\ * -Force Key Value --- ----- Force True Path C:\ Filter * [↓] [vPodans]
В отличии от первого примера переменная $PSBoundParameters содержит не только значения переменных, но и их имена (в перовм примере я вручную дописывал имена переменных), которые используются в качестве именованных параметров. Т.е. при подстановке аргументов в команду, она сначала выбирает имя переменной в качестве именованного параметра и значение переменной подставляет в качестве аргумента этого параметра. Синтаксис использования этой переменной очень прост:
function Name ($arg1, $arg2, $arg3 ... $argN) {
Command @PSBoundParameters
}
и в результате будет исполняться вот такая команда:
Command –arg1 <значение $arg1> –arg2 <значение $arg2> –arg3 <значение $arg3> … –argN <значение $argN>
Т.е. будут подставляться именованные параметры и значения переменных соответствующих аргументов. Давайте проверим, как это подействует на наш пример с Get-ChildItem:
function Test ([string]$Path, [String]$Filter, [switch]$Force) { Get-ChildItem $PSBoundParameters }
[↓] [vPodans] function Test ([string]$Path, [String]$Filter, [switch]$Force) { >> Get-ChildItem @PSBoundParameters >> } >> [↓] [vPodans] Test C:\ * -Force Directory: C:\ Mode LastWriteTime Length Name ---- ------------- ------ ---- d--hs 10.07.2007. 19:44 $Recycle.Bin d--hs 09.03.2008. 17:53 Boot d---- 23.11.2008. 19:41 inetpub d-rh- 26.06.2007. 22:27 MSOCache d---- 09.03.2008. 17:41 PerfLogs <...>
И вы можете видеть, что у нас всё получилось в наилучшем виде! В сравнении с предыдущим примером фактически выполнилась следующая команда:
Get-ChildItem -Path C:\ -Filter * -Force
Для формирования аргументов для командлетов в функциях (так называемые wrapped cmdlets) это самый красивый и идеальный вариант. Если вы захотите изменить логику стандартных командлетов или добавить в них свой функционал, то $PSBoundParamters сделает за вас очень много лишней работы. Но это не единственное полезное применение для этой переменной. Она так же позволяет сократить возможность ошибки при вызове функции внутри скрипта или другой функции, которая принимает те же аргументы. Давайте посмотрим ещё один пример:
function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { Write-Host '$Path in Test is:' $Path Write-Host '$Filter in Test is:' $Filter Write-Host '$Force in Test is:' $Force Write-Host -------------------------------- function Test2 ([switch]$Force, [string[]]$Path, [String]$Filter) {
Write-Host '$Path in Test2 is:' $Path Write-Host '$Filter in Test2 is:' $Filter Write-Host '$Force in Test2 is:' $Force } Test2 @PSBoundParameters }
Что мы делаем: мы создали функцию Test, которая принимает набор аргументов. Внутри этой функции есть другая функция Test2, которая принимает те же аргументы. Далее из функции Test вызываем функцию Test2 и с помощью $PSBoundParameters передаём в неё аргументы. Обратите внимание, что в функции Test2 я изменил порядок аргументов. Это сделано для того, чтобы показать, что $PSBoundParamters отсортирует наши аргументы. А теперь внимание на экран:
[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { >> Write-Host '$Path in Test is:' $Path >> Write-Host '$Filter in Test is:' $Filter >> Write-Host '$Force in Test is:' $Force >> Write-Host -------------------------------- >> function Test2 ([switch]$Force, [string[]]$Path, [String]$Filter) { >> Write-Host '$Path in Test2 is:' $Path >> Write-Host '$Filter in Test2 is:' $Filter >> Write-Host '$Force in Test2 is:' $Force >> } >> Test2 @PSBoundParameters >> } >> [↓] [vPodans] Test C:\ * -Force $Path in Test is: C:\ $Filter in Test is: * $Force in Test is: True -------------------------------- $Path in Test2 is: C:\ $Filter in Test2 is: * $Force in Test2 is: True [↓] [vPodans]
И смотрите, что у нас получилось. А у нас получилось, что функция Test2 получила тот же набор аргументов, причём они были подставлены в правильном соответствии. Фактически строка Test2 @PSBoundParameters была преобразована в:
Test2 -Path $Path -Filter $Filter -Force $true
Т.е. в вариант, который мы вынуждены использовать в PowerShell 1.0. Плюс, мы имеем возможность подставлять позиционно ключи (объекты типа Switch), что есть очень позитивно.
Иногда замечаю, что некоторые скриптописатели получают “поломанный” конвейер, когда данные из одной команды вышли, но никуда не пришли. Возьмём простой пример:
[↓] [vPodans] foreach ($n in 1..3) {$n} 1 2 3 [↓] [vPodans]
Тут всё просто, циклом foreach перебираются данные и выводятся на экран. Мы привыкли, что всё, что выводится на экран можно передать дальше на конвейер. В принципе, это правильно, за исключением ряда случаев, когда это не работает. Чтобы убедиться в этом – передадим вывод этой команды через конвейер на Set-Content:
[↓] [vPodans] foreach ($n in 1..3) {$n} 1 2 3 [↓] [vPodans] foreach ($n in 1..3) {$n} | Set-Content file.txt An empty pipe element is not permitted. At line:1 char:28 + foreach ($n in 1..3) {$n} | <<<< Set-Content file.txt + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException + FullyQualifiedErrorId : EmptyPipeElement [↓] [vPodans]
и мы получаем ошибку, что у нас пустой конвейер. Если написать простой проверочный фильтр:
filter Test-Input {'current $_ is: ' + $_}
и проверить, что у нас приходит с конвейера в этот фильтр, то вы получите такую же ошибку и можете долго гадать, почему так, на экране данные есть, а в конвейер ничего не поступает. Причиной этому является то, что flow control (не знаю, как это на русский первести) конструкции не транслируют свой вывод в конвейер и вот список этих конструкций:
Чтобы при использовании этих конструкций вывод транслировался на конвейер их нужно заключить в подвыражение – $( ):
[↓] [vPodans] $(foreach ($n in 1..3) {$n}) | Set-Content file.txt [↓] [vPodans] gc file.txt 1 2 3 [↓] [vPodans] $(foreach ($n in 1..3) {$n}) | Test-Input current $_ is: 1 current $_ is: 2 current $_ is: 3 [↓] [vPodans]
я заключил цикл Foreach в подвыражение и теперь данные стали поступать на конвейер. Это хорошо видно по выводу фильтра Test-Input, который приведён выше. Вот таким нехитрым способом мы решили проблему пустых конвейеров :-)