- •Введение
- •Часть 1: Четыре командлета - ключи, открывающие PowerShell
- •Ключ #1: Get-Command
- •Ключ #2: Get-Help
- •Ключ #3: Get-Member
- •Ключ #4: Get-PSDrive
- •Дополнение для PowerShell 2.0
- •Часть 2: Понимание вывода объектов
- •Вывод - это всегда объект .NET
- •Функция возвращает все, что не попало в поток вывода
- •Другие типы вывода, которые не могут быть захвачены
- •Часть 3: Как объекты передаются по конвейеру
- •Часть 4: Разнообразие вывода - скаляры, коллекции и пустые наборы - о, боже!
- •Скаляры
- •Работа с коллекциями
- •Работа с пустыми наборами
- •Часть 5: Используй объекты, Люк. Используй объекты!
- •Часть 6: Как форматируется вывод
- •Часть 7: Режимы синтаксического разбора PowerShell
- •Часть 8: Параметры привязки элементов конвейера ByPropertyName (по имени)
- •Часть 9: Параметры привязки элементов конвейера ByValue (по значению)
- •Часть 10: Регулярные выражения – один из мощнейших инструментов PowerShell
- •Дополнение для PowerShell 2.0
- •Часть 11: Сравнение массивов
- •Часть 12: Старайтесь использовать Set-PSDebug -Strict в своих сценариях
- •Примечание для PowerShell 2.0
- •Часть 13: Комментирование строк в файле сценария
- •Дополнение для PowerShell 2.0
- •Дополнительные материалы
Часть 11: Сравнение массивов
В PowerShell используется несколько полезных операторов вроде -contains, которые проверяют, содержит ли массив заданный элемент. Но насколько я могу сказать, в PowerShell отсутствует простой способ проверить, является ли содержимое двух массивов идентичным. Такая потребность возникает довольно часто, и меня слегка удивило отсутствие такой возможности.
Я столкнулся с такой необходимостью при ответе на вопрос в группе новостей microsoft.public.windows.powershell. Его автор спрашивал о поиске файлов с кодировкой UTF-8 при помощи проверки метки порядка байтов (англ. Byte Order Mark, BOM - способ определения формата представления Юникода в текстовом файле; для обозначения формата UTF-8 в заголовке файла используется последовательность EF BB BF - прим. переводчика). Одним из простых способов решения такой задачи может быть таким:
PS> $preamble = [System.Text.Encoding]::UTF8.GetPreamble() PS> $preamble | foreach {"0x{0:X2}" -f $_}
0xEF
0xBB
0xBF
PS> $fileHeader = Get-Content Utf8File.txt -Enc byte -Total 3 PS> $fileheader | foreach {"0x{0:X2}" -f $_}
0xEF
0xBB
0xBF
Визуально, конечно, легко проверить, совпадают значения или нет. Но, к сожалению, визуальная проверка не работает в сценариях. Можно также проверять каждый отдельный элемент, что годится для трех элементов массива, но когда вы столкнётесь, скажем, с 10 элементами, этот подход начинает выглядеть утомительным. Вы думаете, что мы могли бы просто сравнить эти два массива непосредственно примерно так:
PS> $preamble -eq $fileHeader | Get-TypeName
WARNING: Get-TypeName did not receive any input. The input may be an empty collection. You can either prepend the collection expression with the comma operator e.g. ",$collection | gtn" or you can pass the variable or expression to Get-TypeName as an argument e.g. "gtn $collection".
PS> $preamble -eq 0xbb 187
ЗАМЕЧАНИЕ: Get-TypeName - это функция, написанная сообществом PowerShell Community Extensions.
Сравнение массивов с помощью оператора -eq на самом деле не сравнивает содержимое этих двух массивов. Как показывает код в первой строке, эта конструкция ничего не возвращает. Когда слева от оператора -eq расположен массив, PowerShell возвращает элементы массива, значение которых совпадает со значением, заданным справа от оператора. Последние две строки в примере выше это поясняют - при сравнении с '0xbb' возвращен элемент со значением 187.
Похоже, нам придётся создать свой собственный механизм сравнения массивов. Вот один из способов:
50
function AreArraysEqual($a1, $a2) {
if ($a1 -isnot [array] -or $a2 -isnot [array]) { throw "Both inputs must be an array"
}
if ($a1.Rank -ne $a2.Rank) { return $false
}
if ([System.Object]::ReferenceEquals($a1, $a2)) { return $true
}
for ($r = 0; $r -lt $a1.Rank; $r++) {
if ($a1.GetLength($r) -ne $a2.GetLength($r)) { return $false
}
}
$enum1 = $a1.GetEnumerator() $enum2 = $a2.GetEnumerator()
while ($enum1.MoveNext() -and $enum2.MoveNext()) { if ($enum1.Current -ne $enum2.Current) {
return $false
}
}
return $true
}
Он работает так, как и ожидалось:
PS> AreArraysEqual $preamble $fileHeader
True
Однако существует способ сделать сравнение и штатными средствами PowerShell, но он не совсем очевиден. По крайней мере, для меня.
PS> @(Compare-Object $preamble $fileHeader -sync 0).Length -eq 0
True
Compare-Object сравнит массивы, и если различий не будет, на выход ничего передано не будет. Если мы "обернём" вывод Compare-Object в массив с помощью оператора @(), то получим массив, содержащий 0 или более элементов, в зависимости от результата. Таким образом, если длина этого массива будет равна 0, это будет означать, что сравниваемые массивы равны.
Compare-Object сравнивает два объекта с точки зрения одинакового набора элементов. Обычно при этом не учитывается последовательность расположения элементов. Посмотрите:
PS> $a1 = 1,1,2
PS> $a2 = 1,2,1
PS> @(Compare-Object $a1 $a2).length -eq 0
True
51
Скажем так, это не совсем то, когда мы говорим о сравнении на эквивалентность. К счастью, можно использовать параметр SyncWindow, установив ему значение 0. Это заставит Compare-Object сравнивать объекты, соблюдая последовательность расположения элементов в них.
Давайте замерим производительность этих двух методов:
PS> $a1 = 1..10000 PS> $a2 = 1..10000
PS> (Measure-Command { AreArraysEqual $a1 $a2 }).TotalSeconds 1.236252
PS> (Measure-Command { @(Compare-Object $a1 $a2 -sync 0).Length -eq 0 }).TotalSeconds 0.3259954
Как видим, Compare-Object "уверенно побеждает" мою функцию AreArraysEqual, что, впрочем, и не удивительно*.
* - за исключением случая, когда происходит сравнение объектов, ссылающихся на один и тот же массив. В этом случае функция оказывается на два порядка быстрее. Видимо, Compare-Object не использует проверку System.Object.ReferenceEquals. Следует признать, что это частный случай сценария.
Всё-таки, Compare-Object использует скомпилированный код, а функция - интерпретируется. Если вам необходим быстрый способ сравнить массивы, просто запомните, что массивы - это тоже объекты, а для сравнения объектов лучше всего использовать Compare-Object.
52