Contents of this directory is archived and no longer updated.

Данный пост немного не вписывается в формат моего блога, но я посчитал нужным рассказать про массивы и работу с ними в PowerShell. Мне просто нравятся массивы и всё, что с ними можно вытворять. Это очень интересно и полезно. Сразу оговорюсь, мозголомного материала здесь не будет, поэтому пост могут читать даже начинающие.

Что такое массив? Это просто набор данных. Массивы вокруг нас, ТЫСЯЧИ ИХ! Например, мой бложек — это тоже массив, массив постов. В PowerShell массивы наследуются из класса System.Array. Это означает, что любой массив будет содержать те же свойства и методы, что и упомянутый класс. Давайте посмотрим на самый простой массив и как он создаётся:

Variable = Value1, Value2, Value3 <...> ValueN

[↓] [vPodans] $a = 1,2,3
[↓] [vPodans] $a | Get-Member


   TypeName: System.Int32

Name        MemberType Definition
----        ---------- ----------
CompareTo   Method     int CompareTo(System.Object value), int CompareTo(int value)
Equals      Method     bool Equals(System.Object obj), bool Equals(int obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
GetTypeCode Method     System.TypeCode GetTypeCode()
ToString    Method     string ToString(), string ToString(string format), string ToString(System.IFormatProvider pro...


[↓] [vPodans] $a.GetType().fullname
System.Object[]
[↓] [vPodans] $a
1
2
3
[↓] [vPodans]

Мы создали простой массив состоящий из 3-х чисел: 1, 2 и 3. Однако, мы видим, что тип вовсе не System.Array, а System.Object[]. Квадратные скобки после названия класса означают, что рассматриваемый объект — массив объектов указанного класса. Например, массив объектов класса System.Object. PowerShell не использует напрямую System.Array для представления массива, а вместо этого создаёт новый объект, который наследуется из System.Array. Это означает, что массивы внутри могут содержать различную информацию и иметь любой тип (Char[], String[], Int[], ...), но набор свойств и методов будет всегда одинаковый. Чтобы убедиться в этом, посмотрите на исходный объект:

$a.PSBase | Get-Member

По умолчанию PowerShell создаёт универсальный массив, который может содержать любые данные. Доступ к элементам массива осуществляется посредством квадратных скобок следующих за переменной и индексом элемента в массиве внутри этих скобок:

[↓] [vPodans] $a[0]
1
[↓] [vPodans] $a[1]
2
[↓] [vPodans] $a[2]
3
[↓] [vPodans]

Как и во многих языках программирования, индекс элементов массива начинается с нуля (в VBS, напрмер, с 1). Вот так можно получать доступ к элементам массива. Давайте посмотрим, в чём универсальность этого массива. Сейчас он содержит только объекты типа Int32. Сейчас мы подмешаем в наш массив немного мусора:

[↓] [vPodans] $a[0] = "я - строчка!!!111одинодин"
[↓] [vPodans] $a
я - строчка!!!111одинодин
2
3
[↓] [vPodans] $a.GetType().FullName
System.Object[]
[↓] [vPodans]

И мусор успешно добавлен. Всё дело как раз в этом объекте System.Object, который может хранить какие угодно данные. Но мы можем создать массив определённого типа. Например, массив, содержащий только числа:

[↓] [vPodans] $a = [int[]](1,2,3)
[↓] [vPodans] $a.GetType().FullName
System.Int32[]
[↓] [vPodans] $a[0] = "я - строчка!!!111одинодин"
Array assignment to [0] failed: Cannot convert value "я - строчка!!!111одинодин" to type "System.Int32". Error: "Input
string was not in a correct format.".
At line:1 char:4
+ $a[ <<<< 0] = "я - строчка!!!111одинодин"
    + CategoryInfo          : InvalidOperation: (я - строчка!!!111одинодин:String) [], RuntimeException
    + FullyQualifiedErrorId : ArrayAssignmentFailed

[↓] [vPodans]

Я создал массив, который может содержать только целые числа.  Это видно по новому типу: System.Int32[]. Поэтому если теперь мы попробуем в массив подсунуть нечто нечисловое — получим ошибку. Как ещё можно создавать массивы? Например, хочу получить массив, состоящий из одного элемента. Или не содержащий ни одного элемента. Каково, а? Для этого можно использовать вот такую форму:

@()

Значок пёсика и круглых скобок будет означать массив. Внутри скобок можно положить какие угодно данные и они будут приведены к массиву. Смотрим:

[↓] [vPodans] # пустой массив
[↓] [vPodans] $a = @()
[↓] [vPodans] $a.GetType().FullName
System.Object[]
[↓] [vPodans] $a[0]
[↓] [vPodans] $a.Length
0
[↓] [vPodans] # массив из одного элемента
[↓] [vPodans] $a = @("я - единственный элемент в массиве")
[↓] [vPodans] $a.GetType().FullName
System.Object[]
[↓] [vPodans] $a[0]
я - единственный элемент в массиве
[↓] [vPodans] $a.Length
1
[↓] [vPodans]

Из этого примера мы видим, что пустой массив — это тоже себе массив. Просто пустой (это видно по свойству Length, которое показывает количество элементов в массиве). Такой же формой можно привести и единичный объект к массиву из одного элемента. Этот метод создания и приведения к массиву может быть очень полезным. Например, вы обрабатываете выход некоторой команды. Этот выход может содержать один объект, а может и несколько. Это может несколько затруднить вам жизнь, потому что не у всех типов есть свойство Length, а у тех, у кого оно есть, это свойство может означать всякую всячину. Например, у System.String это свойство будет означать количество символов в строке. Именно поэтому вы можете вывод команды заключить в такую конструкцию — @() и тогда будете точно знать сколько элементов вы получили. Вот пример:

[↓] [vPodans] (Get-Item .\).length
[↓] [vPodans] @(Get-Item .\).length
1
[↓] [vPodans]

Этот пример наглядно иллюстрирует указанную проблему. Если команда вернула много элементов в массиве, вы можете использовать свойство Length, чтобы узнать сколько их там. Если команда вернула ноль или один элемент, вам свойство Length может быть недоступно и вам придётся выяснять содержимое вывода другими средствами, например, сравнивать выход команды с $null. Но, если заключить вывод команды в @(), тогда он будет гарантированно приведён к массиву и вы всегда по свойству Length узнаете сколько данных вы получили, ноль, один или больше.

Есть ещё один простенький способ создать простой массив из одного элемента — при помощи унарного оператора запятой:

[↓] [vPodans] $a = ,5
[↓] [vPodans] $a.GetType().FullName
System.Object[]
[↓] [vPodans] $a.Length
1
[↓] [vPodans] $a = ,2,5,6
[↓] [vPodans] $a.Length
3
[↓] [vPodans]

Если запятая стоит в начале выражения, она интерпретируется как оператор и всё, что есть справа от оператора будет интерпретироваться как элементы массива. Я надеюсь, что многие знают о замечательном операторе двух точек. Этот оператор генерирует массив целых чисел (и только чисел) начиная от числа слева от оператора до числа (включительно), указанного справа от оператора:

[↓] [vPodans] $a = 5..10
[↓] [vPodans] $a.GetType().FullName
System.Object[]
[↓] [vPodans] $a.Length
6
[↓] [vPodans]

Вот так создаётся простой массив целых чисел.

Вам кажется, что у вас массив очень маленький? У соседа он больше и он первый парень на деревне? Сделайте себе ещё больше!

Массивы можно увеличивать в размере при помощи математического оператора сложения или умножения. Уменьшать массив операторами вычитания и деления нельзя (и правильно, уменьшите свой массив и девушки на вас смотреть не будут).

[↓] [vPodans] $a = 5..10
[↓] [vPodans] $a
5
6
7
8
9
10
[↓] [vPodans] $a.Length
6
[↓] [vPodans] $a += 0
[↓] [vPodans] $a.Length
7
[↓] [vPodans] $a
5
6
7
8
9
10
0
[↓] [vPodans]

Мы сначала создали массив из 6 элементов и оператором сложения с присвоением добавили ещё один.

Примечание: команда вида $a += 0 является упрощённой записью простейшего счётчика: $a = $a + 0. То же самое и касается оператора *=. Это равносильно команде $a = $a * 0.

Массивы не резиновые (это очевидно!) и растягивать их нельзя. Внутренне PowerShell создаёт ещё один массив размером, достаточным хранить существующий массив и добавляемый. Вы этого просто не видите, а оно происходит. Доказать это можно очень просто. В самом начале поста я пытался подменить элемент массива фиксированного типа и у меня ничего хорошего из этого не вышло. А теперь попробуем вот так:

[↓] [vPodans] $a = [int[]](1,2,3)
[↓] [vPodans] $a.GetType().FullName
System.Int32[]
[↓] [vPodans] $a += "я - строчка!!!111одинодин"
[↓] [vPodans] $a
1
2
3
я - строчка!!!111одинодин
[↓] [vPodans] $a.GetType().FullName
System.Object[]
[↓] [vPodans]

Мы сначала создали массив фиксированного типа, который может хранить только целые числа. Добавив к массиву ещё один элемент, массив стал снова универсальным, большего размера и без ошибок добавил нашу строку к числовому массиву. Это следует учитывать, если вы хотите иметь массив фиксированного типа.

Для пытливых умов: чтобы создать настоящий массив фиксированного типа следует использовать класс List(T). Такой массив не будет изменять свой тип при изменении размера. И если попытаться в такой массив добавить элемент неправильного типа, вы получите ошибку.

Как я уже говорил, массивы можно не просто складывать, но и умножать, что позволит вам достаточно просто обогнать массив вашего соседа. Если при сложении вы можете складывать 2 и более массивов, то с умножением немного иначе. Вы не можете умножить один массив на другой. Умножение массива просто копирует ваш массив столько раз, сколько указано в правой части от оператора умножения:

[↓] [vPodans] $a = 1,2
[↓] [vPodans] $a * 3
1
2
1
2
1
2
[↓] [vPodans]

Мы создали простой массив из двух элементов. Умножили его на 3 и получили тот же массив, только скопипастенных 3 раза. Здесь действуют те же законы оператора умножения, что и в математике. Т.е. если умножить массив на 1, мы получим исходный массив без изменений. Как известно из школьного курса математики, любое число умноженное на 1 не изменится. И если умножить любой массив на ноль, мы молучим совершенно пустой массив:

[↓] [vPodans] $a.Length
2
[↓] [vPodans] $a *= 1
[↓] [vPodans] $a.Length
2
[↓] [vPodans] $a *= 0
[↓] [vPodans] $a.Length
0
[↓] [vPodans] $a.GetType().FullName
System.Object[]

Я посмотрел размер исходного массива и умножил его на 1. Его размер не изменился. Потом я умножил наш массив на ноль и он резко опустел. Но наш массив сам никуда не делся, он остался.

До сих пор мы рассматривали одномерные массивы. Но бывают и двумерные массивы. Они представляют собой обычную плоскую матрицу или таблицу со строками и столбцами. В мире геометрии это будет просто плоскость длиной N столбцов и высотой M строк:

[↓] [vPodans] $b = New-Object "object[,]" 3,3
[↓] [vPodans] $b[0,0] = 1
[↓] [vPodans] $b[0,1] = 2
[↓] [vPodans] $b[0,2] = 3
[↓] [vPodans] $b[1,0] = 4
[↓] [vPodans] $b[1,1] = 5
[↓] [vPodans] $b[1,2] = 6
[↓] [vPodans] $b[2,0] = 7
[↓] [vPodans] $b[2,1] = 8
[↓] [vPodans] $b[2,2] = 9
[↓] [vPodans] $b
1
2
3
4
5
6
7
8
9
[↓] [vPodans] $b[1,2]
6
[↓] [vPodans] $b[2,1]
8
[↓] [vPodans] for ($n = 0; $n -lt 3; $n++) {
>>     Write-Host $b[0,$n] $b[1,$n] $b[2,$n]
>> }
>>
1 4 7
2 5 8
3 6 9
[↓] [vPodans]

Вот таким нехитрым образом создаются двумерные массивы. Первый индекс указывает на номер столбца, второй индекс указывает номер строки. Однако, двумерные массивы нельзя складывать или умножать. В этом случае PowerShell разбивает этот массив в одномерный путём присоединения каждого последуюшего столбца к предыдущему.

На сегодня всё. В следующий раз расскажу немного о некоторых полезных операциях, которые можно проделывать с массивами, включая поиск элементов.


Share this article:

Comments:

Comments are closed.