Все, что вы хотели знать о массивах

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

Примечание.

Оригинал этой статьи впервые был опубликован в блоге автора @KevinMarquette. Группа разработчиков PowerShell благодарит Кевина за то, что он поделился с нами этими материалами. Читайте его блог — PowerShellExplained.com.

Что представляет собой массив?

Я начну с основного технического описания массивов и способов их использования в большинстве языков программирования, а затем расскажу о других вариантах их использования в PowerShell.

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

Далее я расскажу обо всем этом подробнее.

Базовое использование

Массивы представляют собой основную функцию PowerShell, поэтому в PowerShell предусмотрен простой синтаксис для работы с ними.

Создание массива

Для создания пустого массива можно использовать @().

PS> $data = @()
PS> $data.count
0

Мы можем создать массив и заполнить его значениями, просто заключив их в скобки @().

PS> $data = @('Zero','One','Two','Three')
PS> $data.count
4

PS> $data
Zero
One
Two
Three

В этом массиве 4 элемента. При вызове переменной $data отображается список этих элементов. Если речь о строковом массиве, в этом случае отображается по одному строковому элементу в строке.

Можно объявить массив для нескольких строковых элементов. В этом случае запятая является необязательной и, как правило, пропускается.

$data = @(
    'Zero'
    'One'
    'Two'
    'Three'
)

Я предпочитаю объявлять массивы для нескольких строковых элементов вот таким образом. Это не только упрощает чтение при наличии нескольких элементов, но и делает сравнение с предыдущими версиями более легким и удобным, если используется система управления версиями.

Другой синтаксис

Известно, что @() является синтаксисом для создания массива, однако в большинстве случаев чаще используются списки с разделителями-запятыми.

$data = 'Zero','One','Two','Three'

Write-Output для создания массивов

Один из полезных трюков, о котором следует упомянуть, заключается в использовании Write-Output для быстрого создания строк в консоли.

$data = Write-Output Zero One Two Three

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

Доступ к элементам

Теперь вам нужен доступ к имеющимся массивам с элементами, а также возможность обновлять их.

Смещение

Для доступа к отдельным элементам используются скобки [] со значением смещения не менее 0. Мы получаем первый элемент в массиве следующим образом.

PS> $data = 'Zero','One','Two','Three'
PS> $data[0]
Zero

Причина, по которой мы используем ноль, заключается в том, что первый элемент находится в самом начале списка, поэтому, чтобы его достичь, потребуется смещение на 0 элементов. Чтобы перейти ко второму элементу, требуется смещение на 1, чтобы пропустить первый элемент.

PS> $data[1]
One

Это означает, что для последнего элемента используется значение смещения 3.

PS> $data[3]
Three

Указатель

Теперь вы видите, почему я выбрал для примера именно эти значения. Я представил это как смещение, поскольку это по сути именно оно, однако в данном случае смещение чаще называется индексом. Это индекс, начинающийся с 0. Далее в этой статье я буду называть смещение индексом.

Специальные возможности использования индекса

В большинстве языков можно указать только одно число в качестве индекса, и в ответ вы получаете один элемент. PowerShell дает гораздо большую гибкость. Можно использовать несколько индексов одновременно. Благодаря списку индексов можно выбрать несколько элементов.

PS> $data[0,2,3]
Zero
Two
Three

Элементы возвращаются с учетом порядка предоставляемых индексов. Если индекс дублируется, то в обоих случаях вы получите именно этот элемент.

PS> $data[3,0,3]
Three
Zero
Three

Чтобы указать последовательность чисел, можно использовать встроенный оператор ...

PS> $data[1..3]
One
Two
Three

Это работает и в обратном порядке.

PS> $data[3..1]
Three
Two
One

Для смещения с конца можно указать отрицательные значения индекса. Поэтому, если требуется последний элемент в списке, можно использовать -1.

PS> $data[-1]
Three

При работе с оператором .. необходимо учитывать следующее. Последовательности 0..-1 и -1..0 вычислены со значениями 0,-1 и -1,0. Легко увидеть $data[0..-1] и предположить, что там будут перечислены все элементы, если забыть об этой детали. $data[0..-1] дает то же значение, что и $data[0,-1], предоставляя первый и последний элементы в массиве (и ни одного из остальных значений). Вот более обширный пример:

PS> $a = 1,2,3,4,5,6,7,8
PS> $a[2..-1]
3
2
1
8

Это аналогично следующему:

PS> $a[2,1,0,-1]
3
2
1
8

Вне допустимого диапазона

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

PS> $null -eq $data[9000]
True

Индексирование в массив значений null невозможно

Если используется переменная $null и вы пытаетесь индексировать ее как массив, выводится исключение System.Management.Automation.RuntimeException с сообщением Cannot index into a null array.

PS> $empty = $null
PS> $empty[0]
Error: Cannot index into a null array.

Убедитесь, что массивы не являются $null, прежде чем пытаться получить доступ к элементам внутри них.

Count

Массивы и другие коллекции содержат свойство Count, которое показывает количество элементов в массиве.

PS> $data.count
4

В PowerShell 3.0 в большинство объектов добавляется свойство Count. У вас может быть один объект, который должен сообщать свойство Count, равное 1.

PS> $date = Get-Date
PS> $date.count
1

Даже $null имеет свойство Count, только оно возвращает 0.

PS> $null.count
0

Однако в этом случае есть ряд сложностей, о которых я расскажу подробнее, когда буду говорить о проверке на наличие $null или пустых массивов далее в этой статье.

Ошибка завышения или занижения на единицу

Общая ошибка программирования возникает потому, что массивы начинаются с индекса 0. Ошибки завышения/занижения на единицу можно представить двумя способами.

Первый — просто думать, что вам нужен второй элемент, использовать индекс 2 и действительно получить третий элемент. Можно также думать, что у вас четыре элемента и вам нужен последний из них, и использовать свойство Count для доступа к последнему элементу.

$data[ $data.count ]

В PowerShell вполне достаточно сделать это и указать, какой элемент существует в индексе 4: $null. Следует использовать $data.count - 1 или -1, о которых мы говорили выше.

PS> $data[ $data.count - 1 ]
Three

В этом случае получить последний элемент можно с помощью индекса -1.

PS> $data[ -1 ]
Three

Ли Дейли также подсказал, что мы можем использовать $data.GetUpperBound(0) для получения максимального номера индекса.

PS> $data.GetUpperBound(0)
3
PS> $data[ $data.GetUpperBound(0) ]
Three

Второй наиболее распространенный способ — если вы выполняете итерацию по списку и не останавливаетесь в нужный момент. Я об этом расскажу подробнее, когда мы будем говорить об использовании цикла for.

Обновление элементов

Для обновления существующих элементов в массиве можно использовать тот же индекс. Это позволяет напрямую обновлять отдельные элементы.

$data[2] = 'dos'
$data[3] = 'tres'

При попытке обновить элемент, который находится за последним элементом, возникает ошибка Index was outside the bounds of the array..

PS> $data[4] = 'four'
Index was outside the bounds of the array.
At line:1 char:1
+ $data[4] = 'four'
+ ~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (:) [], IndexOutOfRangeException
+ FullyQualifiedErrorId : System.IndexOutOfRangeException

Я расскажу об этом позже, когда буду говорить об увеличении массива.

Итерация

В какой-то момент вам может потребоваться выполнить обход или итерацию по всему списку и применить действия к каждому элементу в массиве.

Pipeline

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

PS> $data = 'Zero','One','Two','Three'
PS> $data | ForEach-Object {"Item: [$PSItem]"}
Item: [Zero]
Item: [One]
Item: [Two]
Item: [Three]

Если вы еще не видели $PSItem, просто помните, что это то же самое, что и $_. Можно использовать любой из них, так как оба этих элемента представляют текущий объект в конвейере.

Цикл ForEach

Цикл ForEach эффективно работает с коллекциями. Используется следующий синтаксис: foreach ( <variable> in <collection> )

foreach ( $node in $data )
{
    "Item: [$node]"
}

Метод ForEach

Я часто забываю о нем, однако этот метод эффективен для простых операций. PowerShell позволяет вызывать .ForEach() для коллекции.

PS> $data.foreach({"Item [$PSItem]"})
Item [Zero]
Item [One]
Item [Two]
Item [Three]

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

$data.foreach{"Item [$PSItem]"}

Такой синтаксис менее популярен, но он работает точно так же. Метод foreach добавлен в PowerShell 4.0.

Цикл For

Цикл for активно используется в большинстве других языков, но в PowerShell он применяется редко. Однако если он все же используется, то зачастую это происходит в контексте обхода массива.

for ( $index = 0; $index -lt $data.count; $index++)
{
    "Item: [{0}]" -f $data[$index]
}

В первую очередь мы инициализируем $index для 0. Затем мы добавляем условие, что $index должно быть меньше $data.count. Наконец, мы указываем, что каждый раз, когда мы циклим, на который необходимо увеличить индекс 1. В этом случае $index++ расшифровывается как $index = $index + 1. Оператор Format (-f) используется для вставки значения $data[$index] в выходную строку.

При каждом использовании цикла for следует обращать особое внимание на условие. Здесь я использовал $index -lt $data.count. При этом довольно легко получить слегка неверное условие и в результате получить в логике ошибку завышения/занижения на единицу. При использовании $index -le $data.count или $index -lt ($data.count - 1) всегда получается слегка неверное условие. Это может привести к тому, что в вашем результате будет обрабатываться слишком много или слишком мало элементов. Это классическая ошибка завышения/занижения на единицу.

Цикл Switch

Эту ошибку легко упустить из виду. Если вы укажете массив для оператора switch, он проверит каждый элемент в массиве.

$data = 'Zero','One','Two','Three'
switch( $data )
{
    'One'
    {
        'Tock'
    }
    'Three'
    {
        'Tock'
    }
    Default
    {
        'Tick'
    }
}
Tick
Tock
Tick
Tock

С помощью оператора switch можно выполнить массу полезных действий. Об этом я подробно рассказываю в другой статье.

Обновление значений

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

Исключением для этого оператора является цикл for. Если вам требуется обход массива и обновление значений в нем, вам нужен цикл for.

for ( $index = 0; $index -lt $data.count; $index++ )
{
    $data[$index] = "Item: [{0}]" -f $data[$index]
}

В этом примере принимается значение по индексу, вносится ряд изменений, а затем тот же индекс используется, чтобы назначить значение обратно.

Массивы объектов

До сих пор мы помещали в массив только тип значения, однако массивы также могут содержать и объекты.

$data = @(
    [pscustomobject]@{FirstName='Kevin';LastName='Marquette'}
    [pscustomobject]@{FirstName='John'; LastName='Doe'}
)

Многие командлеты возвращают коллекции объектов в виде массивов, когда они назначаются переменной.

$processList = Get-Process

Все основные функции, о которых мы говорили, по-прежнему применяются для массивов объектов с некоторыми оговорками.

Доступ к свойствам

Можно использовать индекс для доступа к отдельному элементу в коллекции, как и в случае с типами значений.

PS> $data[0]

FirstName LastName
-----     ----
Kevin     Marquette

Мы можем напрямую получать доступ к свойствам и обновлять их.

PS> $data[0].FirstName

Kevin

PS> $data[0].FirstName = 'Jay'
PS> $data[0]

FirstName LastName
-----     ----
Jay       Marquette

Свойства массива

Обычно для доступа ко всем свойствам необходимо перечислить весь список следующим образом:

PS> $data | ForEach-Object {$_.LastName}

Marquette
Doe

Кроме того, можно использовать командлет Select-Object -ExpandProperty.

PS> $data | Select-Object -ExpandProperty LastName

Marquette
Doe

Однако PowerShell дает возможность запрашивать LastName напрямую. PowerShell самостоятельно перечисляет все эти данные и возвращает пустой список.

PS> $data.LastName

Marquette
Doe

Перечисление выполняется в обычном порядке, но мы избавлены от всех связанных с этим сложностей.

Фильтрация Where-Object

Именно здесь в игру вступает Where-Object, поэтому мы можем отфильтровать и выбрать объекты, которые следует исключить из массива, с учетом их свойств.

PS> $data | Where-Object {$_.FirstName -eq 'Kevin'}

FirstName LastName
-----     ----
Kevin     Marquette

Можно написать тот же запрос, чтобы получить искомый FirstName.

$data | Where FirstName -eq Kevin

Where()

В массивах доступен метод Where(), который позволяет указать scriptblock для фильтра.

$data.Where({$_.FirstName -eq 'Kevin'})

Эта функция добавлена в PowerShell 4.0.

Обновление объектов в циклах

Если применяются типы значений, единственным способом обновить массив является использование цикла, поскольку нам нужно знать индекс, чтобы заменить значение. Для объектов предусмотрено больше возможностей, поскольку они относятся к ссылочным типам. Приведем краткий пример:

foreach($person in $data)
{
    $person.FirstName = 'Kevin'
}

Этот цикл обходит каждый объект в массиве $data. Поскольку объекты являются ссылочными типами, переменная $person ссылается на тот же объект, который находится в массиве. Таким образом, обновления для свойств применяются к исходным свойствам.

Этот способ по-прежнему не позволяет заменить весь объект. При попытке назначить новый объект переменной $person ссылка на переменную обновляется на другой элемент, который больше не указывает на исходный объект в массиве. То есть это работает не так, как вы ожидали:

foreach($person in $data)
{
    $person = [pscustomobject]@{
        FirstName='Kevin'
        LastName='Marquette'
    }
}

Операторы

Операторы в PowerShell также эффективны для массивов. Некоторые из них работают несколько иначе.

-join

Оператор -join является самым очевидным примером, поэтому давайте рассмотрим его первым. Мне нравится оператор -join, я его часто использую. Он объединяет все элементы в массиве с помощью заданного символа или строки.

PS> $data = @(1,2,3,4)
PS> $data -join '-'
1-2-3-4
PS> $data -join ','
1,2,3,4

В операторе -join мне особенно нравится то, что он обрабатывает единичные элементы.

PS> 1 -join '-'
1

Я использую его для ведения журнала и подробных сообщений.

PS> $data = @(1,2,3,4)
PS> "Data is $($data -join ',')."
Data is 1,2,3,4.

-join $array

Вот еще один полезный прием, о котором мне рассказал Ли Дейли. Если нужно объединить все без использования разделителя, вместо этого:

PS> $data = @(1,2,3,4)
PS> $data -join $null
1234

Можно использовать -join с массивом в качестве параметра без префикса. Этот пример наглядно демонстрирует то, о чем я говорил.

PS> $data = @(1,2,3,4)
PS> -join $data
1234

-replace и -split

Другие операторы, такие как -replace и -split, выполняются для каждого элемента в массиве. Не скажу, что я их когда-нибудь таким образом использовал, но вот пример этого.

PS> $data = @('ATX-SQL-01','ATX-SQL-02','ATX-SQL-03')
PS> $data -replace 'ATX','LAX'
LAX-SQL-01
LAX-SQL-02
LAX-SQL-03

содержит-

Оператор -contains позволяет проверить массив значений, чтобы определить, содержит ли он указанное значение.

PS> $data = @('red','green','blue')
PS> $data -contains 'green'
True

-in

Если одно значение, которое вам нужно проверить, совпадает с одним или несколькими значениями, можно использовать оператор -in. Значение должно быть в левой, а массив — в правой части оператора.

PS> $data = @('red','green','blue')
PS> 'green' -in $data
True

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

PS> $data = @('red','green','blue')
PS> $pattern = "^({0})$" -f ($data -join '|')
PS> $pattern
^(red|green|blue)$

PS> 'green' -match $pattern
True

-eq и -ne

Равенство и массивы могут оказаться достаточно сложными. Если массив располагается в левой части, выполняется сравнение всех элементов. Вместо возврата True возвращается совпадающий объект.

PS> $data = @('red','green','blue')
PS> $data -eq 'green'
green

Если используется оператор -ne, вы получаете все значения, которые не равны имеющемуся.

PS> $data = @('red','green','blue')
PS> $data -ne 'green'
red
blue

При использовании в операторе if() возвращается значение True. Если значение не возвращается, то речь о значении False. Оба этих оператора оцениваются как True.

$data = @('red','green','blue')
if ( $data -eq 'green' )
{
    'Green was found'
}
if ( $data -ne 'green' )
{
    'And green was not found'
}

Я еще вернусь к этому вопросу позднее, когда мы будем говорить о тестировании $null.

-match

Оператор -match пытается сопоставить все элементы в коллекции.

PS> $servers = @(
    'LAX-SQL-01'
    'LAX-API-01'
    'ATX-SQL-01'
    'ATX-API-01'
)
PS> $servers -match 'SQL'
LAX-SQL-01
ATX-SQL-01

При использовании -match с одним значением специальная переменная $Matches заполняется сведениями о соответствии. Этого не происходит, если массив обрабатывается таким образом.

Этот же подход можно использовать в случае с Select-String.

$servers | Select-String SQL

Я подробно расскажу о переменных Select-String, -match и $matches в другой публикации под названием The many ways to use regex (Множество способов использования регулярных выражений).

$null или empty

Проверка на наличие $null или пустых массивов может быть непростой задачей. Далее описаны самые распространенные проблемы, связанные с массивами.

На первый взгляд этот оператор выглядит вполне работоспособным.

if ( $array -eq $null)
{
    'Array is $null'
}

Однако я только что рассказал, как -eq проверяет каждый элемент в массиве. Таким образом, у нас может быть массив из нескольких элементов с одним значением $null и результатом вычисления будет $true

$array = @('one',$null,'three')
if ( $array -eq $null)
{
    'I think Array is $null, but I would be wrong'
}

Именно поэтому рекомендуется размещать $null в левой части оператора. Благодаря этому сценарий выполняется без проблем.

if ( $null -eq $array )
{
    'Array actually is $null'
}

Массив $null не равен пустому массиву. Если вы уверены, что у вас есть массив, проверьте количество объектов в нем. Если это массив $null, число объектов равно 0.

if ( $array.count -gt 0 )
{
    "Array isn't empty"
}

Однако есть еще одна сложность, которую нужно учитывать в этом случае. Можно использовать count даже при наличии одного объекта, если только этот объект не является PSCustomObject. Эта ошибка исправлена в PowerShell 6.1. Это хорошая новость, однако многие люди по-прежнему используют версию 5.1, так что им стоит иметь в виду указанную ошибку.

PS> $object = [PSCustomObject]@{Name='TestObject'}
PS> $object.count
$null

Если вы по-прежнему используете PowerShell 5.1, можно перенести объект в массив перед проверкой количества объектов, чтобы получить точное число.

if ( @($array).count -gt 0 )
{
    "Array isn't empty"
}

Чтобы безопасно воспроизвести этот сценарий, проверьте $null, а затем проверьте число объектов.

if ( $null -ne $array -and @($array).count -gt 0 )
{
    "Array isn't empty"
}

All -eq

Недавно кто-то спрашивал, как проверить, соответствует ли каждое значение в массиве заданному значению. Пользователь Reddit /u/bis предложил это разумное решение, которое проверяет наличие некорректных значений, а затем инвертирует результат.

$results = Test-Something
if ( -not ( $results -ne 'Passed') )
{
    'All results a Passed'
}

Добавление в массивы

На этом этапе вас начинает интересовать, как добавить элементы в массив. Собственно говоря, никак. Массив имеет фиксированный размер в памяти. Если необходимо увеличить массив или добавить в него один элемент, то в этом случае необходимо создать новый массив и скопировать в него все значения из старого. На первый взгляд это требует значительных усилий, однако PowerShell значительно упрощает создание нового массива. В PowerShell реализован оператор сложения (+) для массивов.

Примечание.

В PowerShell не реализована операция вычитания. Если требуется гибкая альтернатива массиву, необходимо использовать универсальный объект List.

Сложение массивов

Для создания нового массива можно использовать оператор сложения. Итак, дано два массива:

$first = @(
    'Zero'
    'One'
)
$second = @(
    'Two'
    'Three'
)

Мы можем сложить их вместе, чтобы получить новый массив.

PS> $first + $second

Zero
One
Two
Three

Плюс равно (+=)

Мы можем создать новый массив и добавить в него элемент следующим образом:

$data = @(
    'Zero'
    'One'
    'Two'
    'Three'
)
$data += 'four'

Просто помните, что каждый раз, когда вы используете +=, выполняется дублирование и создание нового массива. Это не проблема, если речь идет о небольших наборах данных, но масштабирование в этом случае выполняется очень ограниченно.

Назначение конвейера

Можно назначить результаты любого конвейера для добавления в любую переменную. Она будет считаться массивом, если содержит несколько элементов.

$array = 1..5 | ForEach-Object {
    "ATX-SQL-$PSItem"
}

Обычно если мы говорим об использовании конвейера, мы подразумеваем типичные однострочные сценарии PowerShell. Можно использовать конвейер с операторами foreach() и другими циклами. Таким образом, вместо добавления элементов в массив в цикле можно поместить элементы в конвейер.

$array = foreach ( $node in (1..5))
{
    "ATX-SQL-$node"
}

Типы массивов

По умолчанию массив в PowerShell создается как тип [PSObject[]]. Благодаря этому он может содержать любые типы объектов и значений. Это возможно, поскольку все наследуется из типа PSObject.

Строго типизированные массивы

Массив любого типа можно создать с помощью сходного синтаксиса. Если вы создаете строго типизированный массив, он может содержать только значения или объекты заданного типа.

PS> [int[]] $numbers = 1,2,3
PS> [int[]] $numbers2 = 'one','two','three'
ERROR: Cannot convert value "one" to type "System.Int32". Input string was not in a correct format."

PS> [string[]] $strings = 'one','two','three'

ArrayList

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

Как правило, если нам нужен массив, поддерживающий более быструю работу, мы в первую очередь вспоминаем о ArrayList. Он выступает в качестве массива объектов везде, где это необходимо, и при этом поддерживает быстрое добавление элементов.

Мы создаем ArrayList и добавляем в него элементы следующим образом.

$myarray = [System.Collections.ArrayList]::new()
[void]$myArray.Add('Value')

Для получения этого типа вызывается .NET. В этом случае для его создания используется конструктор по умолчанию. Затем вызывается метод Add, чтобы добавить в него элемент.

Причина, по которой я использую [void] в начале строки, заключается в подавлении кода возврата. Некоторые вызовы .NET выполняют такую функцию, и в результате могут быт получены неожиданные выходные данные.

Если единственными данными в массиве являются строки, ознакомьтесь также с использованием StringBuilder. Это практически то же самое, но некоторые методы предназначены только для работы со строками. StringBuilder специально предназначен для повышения производительности.

Обычно пользователи переходят на ArrayList с массивов. Однако это наследие тех времен, когда в C# еще не была предусмотрена универсальная поддержка. От ArrayList отказываются в пользу универсального List[]

Универсальный список

Универсальный тип — это особый тип в C#, который определяет обобщенный класс, при этом пользователь указывает типы данных, которые используются в процессе создания. Поэтому если требуется список чисел или строк, необходимо определить, что требуется список типов int или string.

Список для строк создается следующим образом.

$mylist = [System.Collections.Generic.List[string]]::new()

Так же создается список чисел.

$mylist = [System.Collections.Generic.List[int]]::new()

Можно привести существующий массив к списку следующим образом, не создавая сначала объект:

$mylist = [System.Collections.Generic.List[int]]@(1,2,3)

Синтаксис можно сократить с помощью оператора using namespace в PowerShell 5 и более поздних версиях. Оператор using должен быть первой строкой сценария. Объявляя пространство имен, PowerShell позволяет исключить из него типы данных при ссылке на них.

using namespace System.Collections.Generic
$myList = [List[int]]@(1,2,3)

Это позволяет сделать List гораздо более удобным.

Вам доступен аналогичный метод Add. В отличие от ArrayList метод Add не возвращает значение, поэтому для него не нужно выполнять void.

$myList.Add(10)

Кроме того, мы по-прежнему можем получить доступ к таким элементам, как другие массивы.

PS> $myList[-1]
10

List[PSObject]

Можно получить список любого типа, но, если вам неизвестен тип объектов, в качестве контейнера для них можно использовать [List[PSObject]].

$list = [List[PSObject]]::new()

Remove()

Как ArrayList, так и универсальные List[] поддерживают удаление элементов из коллекции.

using namespace System.Collections.Generic
$myList = [List[string]]@('Zero','One','Two','Three')
[void]$myList.Remove("Two")
Zero
One
Three

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

[list[System.Management.Automation.PSDriveInfo]]$drives = Get-PSDrive
$drives.remove($drives[2])
$delete = $drives[2]
$drives.remove($delete)

Метод Remove возвращает true, если удалось найти и удалить элемент из коллекции.

Другие коллекции

Можно использовать также множество других коллекций, однако эти оптимальны в качестве замены массивам. Если вам интересно узнать больше о других вариантах, взгляните на Gist, собранный Марком Краусом.

Другие особенности

Итак, мы рассмотрели все основные функции, и вот еще кое-что, о чем я хотел рассказать, прежде чем мы закончим.

Массивы с предварительно заданным размером

Я уже говорил, что вы не сможете изменить размер массива после того, как он создан. Можно создать массив с предварительно заданным размером, вызвав его с помощью конструктора new($size).

$data = [Object[]]::new(4)
$data.count
4

Умножение массивов

Еще один интересный прием — массив можно умножить на целое число.

PS> $data = @('red','green','blue')
PS> $data * 3
red
green
blue
red
green
blue
red
green
blue

Инициализация с нулевыми значениями

Один из распространенных сценариев предполагает создание массива, заполненного нулевыми значениями. Если планируется использовать только целые числа, строго типизированный массив целых чисел по умолчанию принимает все значения равными нулю.

PS> [int[]]::new(4)
0
0
0
0

Для этого также можно использовать умножение.

PS> $data = @(0) * 4
PS> $data
0
0
0
0

Особенно полезно в умножении то, что можно использовать любое значение. Поэтому если планируется по умолчанию использовать значение 255, это будет хорошим вариантом.

PS> $data = @(255) * 4
PS> $data
255
255
255
255

Вложенные массивы

Массив внутри массива называется вложенным массивом. Я не использую их в PowerShell, но часто использовал в других языках. Использовать массив массивов рекомендуется, если данные вписываются в шаблон типа сетки.

Существует два способа создания двумерного массива.

$data = @(@(1,2,3),@(4,5,6),@(7,8,9))

$data2 = @(
    @(1,2,3),
    @(4,5,6),
    @(7,8,9)
)

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

Для вложенного массива нотация индексов имеет некоторые изменения. Мы получаем значение 3, используя $data выше.

PS> $outside = 0
PS> $inside = 2
PS> $data[$outside][$inside]
3

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

Write-Output -NoEnumerate

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

Обычно я передаю объекты в Get-Member, чтобы получить дополнительные сведения о них. Когда я передаю туда массив, выполняется его разворачивание, и Get-Member видит элементы массива, а не фактический массив.

PS> $data = @('red','green','blue')
PS> $data | Get-Member
TypeName: System.String
...

Чтобы запретить разворачивание массива, можно использовать Write-Output -NoEnumerate.

PS> Write-Output -NoEnumerate $data | Get-Member
TypeName: System.Object[]
...

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

PS> ,$data | Get-Member
TypeName: System.Object[]
...

Возврат массива

Такое разворачивание массивов также выполняется при выводе или возврате значений из функции. Для получения массива по-прежнему можно присвоить выходные данные переменной, что обычно не вызывает проблем.

Хитрость в том, что создается новый массив. Если это проблематично, можно решить проблему с помощью Write-Output -NoEnumerate $array или return ,$array.

Что-нибудь еще?

Я знаю, что придется усвоить довольно большой объем информации. Но я надеюсь, что при каждом прочтении этой статьи вы будете узнавать что-то новое и эти знания окажутся полезными в долгосрочной перспективе. Если для вас эта статья была полезна, поделитесь ею с коллегами, которым она также может быть интересна.

Здесь, кстати, я рекомендую ознакомиться с похожей публикацией, посвященной хэш-таблицам.