В прошлый раз мы рассмотрели основные вопросы массивов и управления ими в 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. И чтобы перевернуть массив верх тормашками можно использовать статический метод Reverse() класса System.Array:
[↓] [vPodans] $a = 1..5
[↓] [vPodans] $a
1
2
3
4
5
[↓] [vPodans] [array]::Reverse($a)
[↓] [vPodans] $a
5
4
3
2
1
[↓] [vPodans]
Следует учитывать, что этот метод меняет порядок следования элементов в самой перменной и не образует выходной информации.
Эпилог
В качестве эпилога скажу, что я рассмотрел лишь самые популярные действия с массивами и это составляет лишь малую часть того, что с ними можно сделать в реальности. Но этого материала (включая предыдущую статью) вам хватит на 95% случаев. И на этом всё.