В прошлый раз мы рассмотрели основные вопросы массивов и управления ими в Windows PowerShell. Мы теперь знаем, как они создаются, как изменять их размер (ресайзить) и какие математические операции можно проводить над массивами. В этой части я расскажу о сравнении массивов, поиске элементов и т.д.
Как известно, в PowerShell есть куча операторов (или групп операторов) сравнения, как –eq и –like. Но здесь у нас сразу появляется проблема — эти операторы нельзя использовать для сравнения массивов. Давайте посмотрим, что получится:
[↓] [vPodans] "abc" -eq "abc" True [↓] [vPodans] 1,2,3 -eq 1,2,3 [↓] [vPodans]
Оператор –eq ничего не вернул. Дело в том, что справа от оператора –eq может быть только один объект, буква, строка, число и т.д. В контексте массивов оператор –eq можно использовать для получения элементов массива, содержащих конкретное значение. Например, у нас есть массив из нескольких чисел и мы хотим узнать сколько раз конкретное число использовано в массиве. Вот простой пример:
[↓] [vPodans] 2,8,5,6,2,5,4,2,7 -eq 2 2 2 2 [↓] [vPodans] 2,8,5,6,2,5,4,2,7 -eq 5 5 5 [↓] [vPodans] 2,8,5,6,2,5,4,2,7 -eq 1 [↓] [vPodans] (2,8,5,6,2,5,4,2,7 -eq 2).Length 3 [↓] [vPodans] (2,8,5,6,2,5,4,2,7 -eq 5).Length 2 [↓] [vPodans] (2,8,5,6,2,5,4,2,7 -eq 1).Length 0 [↓] [vPodans]
Из показанных примеров мы видим, что оператор –eq возвращает элементы, которые соответствуют сравниваемому объекту. Поэтому при помощи оператора –eq можно узнать, содержит ли массив конкретное значение или нет и если да, то сколько раз. То же самое относится и к оператору –like, который больше подходит для сравнения строк по маске:
[↓] [vPodans] "abc","cba","bac","bca" -like "a*" abc [↓] [vPodans] "abc","cba","bac","bca" -like "?b?" abc cba [↓] [vPodans]
Здесь используется тот же принцип, что и с использованием оператора –eq, только с разницей, что оператор –like сравнивает по маске (нестрогое соответствие).
Для точного сравнения двух массивов следует использовать командлет Compare-Object:
[↓] [vPodans] Compare-Object 1,2,3 1,2,3 [↓] [vPodans] Compare-Object 1,2,3 1,2 InputObject SideIndicator ----------- ------------- 3 <= [↓] [vPodans] Compare-Object 1,2,3 1,2,2 InputObject SideIndicator ----------- ------------- 2 => 3 <= [↓] [vPodans]
Если массивы одинаковые, командлет ничего не вернёт. Если же есть различия, вы увидите, какие элементы отсутствуют в одном массиве (направление стрелочки указывает от массива с недостающим элментом). Может быть и так, что оба массива одинаковы по размеру, но какие-то элементы имеют разные значения. Тогда вы увидите стрелочки в оба направления. Командлет Compare-Object может выводить и одинаковые элементы:
[↓] [vPodans] Compare-Object -ref 1,2,3 -dif 1,2,2 -IncludeEqual InputObject SideIndicator ----------- ------------- 1 == 2 == 2 => 3 <= [↓] [vPodans]
В одинаковых элементах SideIndicator будет показывать двойной знак равенства (==).
Иногда бывает очень нужным найти индекс элемента массива, значение которого совпадает с какой-то величиной. К сожалению, в PowerShell нет стандартного механизма поиска индексов. Во времена, когда я в повершеле был ещё совсем чайником, Вася Гусев написал мне простенький ванлайнер, который выводит индексы элементов массивов, значения которых совпали по какой-то маске:
function findinarr ($array, $value) {for ($i=0; $i -lt $array.count;$i++){if($array[$i] -eq $value){$i}}}
или более развёрнутый вариант:
function findinarr ($array, $value) { for ($i=0; $i -lt $array.count;$i++) { if($array[$i] -eq $value){$i} } }
Например, узнать, индекс (или индексы) элемента массива, который содержит цифру 2 и 5:
[↓] [vPodans] $a = 2,8,5,6,2,5,4,2,7 [↓] [vPodans] findinarr $a 2 0 4 7 [↓] [vPodans] findinarr $a 5 2 5 [↓] [vPodans]
Мы видим, что цифра 2 в указанном массиве содержится в элементах с индексами 0, 4 и 7, а цифра 5 в элементах с индексами 2 и 5. Не забудьте, что индексы в массивах начинаются с нуля.
PowerShell отображает массивы в столбик, т.е. каждый элемент массива показывается на новой строке. А если вы хотите показать массив так, чтобы все элементы были в строчку, разделённой пробелами? Можно извратиться конструкцией вида:
$a = 1..100 $string = "" $a | ForEach-Object {$string += "$_" + " "}
[↓] [vPodans] $a = 1..100 [↓] [vPodans] $string = "" [↓] [vPodans] $a | ForEach-Object {$string += "$_" + " "} [↓] [vPodans] $string 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 8 3 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 [↓] [vPodans]
Но можно сделать ещё круче — заключить переменную с массивом в двойные кавычки:
[↓] [vPodans] $a = 1..100 [↓] [vPodans] "$a" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 8 3 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 [↓] [vPodans]
как видите, результат точно такой же, только кода израсходовано куда меньше.
Далеко не всегда мы имеем возможность получить готовый массив, наоборот, нам нужно разбить одну строку на массив строк. Для этого можно использовать метод Split() класса System.String или оператор –split:
[↓] [vPodans] "this is a single string".split() this is a single string [↓] [vPodans] -split "this is a single string" this is a single string [↓] [vPodans]
По умолчанию, строка разбивается на массив строк по пробелу и разделителю строк. Если вы хотите разделить по другому разделителю можно его указать явно. Например, разбить MAC адрес сетевой карты на октеты:
[↓] [vPodans] "00-18-DE-54-57-8E" -split "-" 00 18 DE 54 57 8E [↓] [vPodans]
Причём, обратите внимание, что оператор –split может быть как унарным (когда всё располагается только справа от оператора), так и бинарным (когда исходная строка находится слева от оператора, остальное располагается справа). Более подробно про оператор –split лучше всего прочитать в справке: http://technet.microsoft.com/en-us/library/dd347708.aspx. Функционал оператора достаточно интересен, но выходит за рамки рассматриваемой статьи.
Есть ещё один трюк, как разделить строку на массив символов. Для этого используется метод ToCharArray():
[↓] [vPodans] "00-18-DE-54-57-8E".tochararray() 0 0 - 1 8 - D E - 5 4 - 5 7 - 8 E [↓] [vPodans]
Есть ещё один трюк, как разбить строку на массив символов. Для этого надо строку привести к массиву символов (char[]) и результат будет точно такой же:
[char[]]"00-18-DE-54-57-8E"
Если у вас есть массив и его нужно преобразовать в одну строку, можно воспользоваться статическим методом Join() класса System.String или оператором –join:
[↓] [vPodans] $a = 1..10 [↓] [vPodans] $a 1 2 3 4 5 6 7 8 9 10 [↓] [vPodans] [string]::Join(",",$a) 1,2,3,4,5,6,7,8,9,10 [↓] [vPodans] $a -join "," 1,2,3,4,5,6,7,8,9,10 [↓] [vPodans] -join $a 12345678910 [↓] [vPodans]
Как видно из примера, оператор –join так же, как и –split бывает унарным и бинарным. Бинарная форма всегда используется, когда нужно явно указать разделитель или другие параметры оператора –join. Если оператор унарный, он не принимает явный разделитель и просто последовательно пристыковывает элементы массива в строку. Если вам нужно преобразовать несколько массивов в строки с использованием одного и того же разделителя, в PowerShell можно использовать специальную переменную $ofs и массив явно привести к типу string:
[↓] [vPodans] $ofs = "+" [↓] [vPodans] [string]$a 1+2+3+4+5+6+7+8+9+10 [↓] [vPodans] iex ([string]$a) 55 [↓] [vPodans]
Как можно видеть из примеров, с массивами в PowerShell можно делать что угодно (кроме вычитания и деления). Причём, зачастую, существует несколько способов выполнить одну и ту же задачу.
Вообще это используется не так часто, что можно было бы и опустить, но я достаточно часто работаю с CryptoAPI и бывает нужным первернуть массив. Дело в том, что CryptoAPI до мозга костей little-endian, а остальные API (даже тот же .NET) как правило big-endian. И чтобы перевернуть массив верх тормашками можно использовать статический метод
[↓] [vPodans] $a = 1..5 [↓] [vPodans] $a 1 2 3 4 5 [↓] [vPodans] [array]::Reverse($a) [↓] [vPodans] $a 5 4 3 2 1 [↓] [vPodans]
Следует учитывать, что этот метод меняет порядок следования элементов в самой перменной и не образует выходной информации.
В качестве эпилога скажу, что я рассмотрел лишь самые популярные действия с массивами и это составляет лишь малую часть того, что с ними можно сделать в реальности. Но этого материала (включая предыдущую статью) вам хватит на 95% случаев. И на этом всё.
Поиск индекса в массиве с помощью класса System.Array .NET: http://blogs.technet.com/b/heyscriptingguy/archive/2011/12/07/find-the-index-number-of-a-value-in-a-powershell-array.aspx Может кому пригодится.
Comments: