Поделиться через


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

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

Примечание.

Оригинальная версия этой статьи появилась в блоге, написанном @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 и вы пытаетесь индексировать ее как массив, вы получите исключение 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, которое указывает, сколько элементов находится в массиве.

PS> $data.Count
4

PowerShell 3.0 добавила свойство Count в большинство объектов. у вас может быть один объект, и он должен дать вам количество 1.

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

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

PS> $null.Count
0

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

Ошибки по одному

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

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

$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

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

Итерация

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

Трубопровод

Массивы и конвейер 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 используется в значительной степени в большинстве других языков, но вы не видите его в PowerShell. Когда вы видите это, это часто в контексте прогулки по массиву.

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

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

Всякий раз, когда вы используете цикл for, обратите особое внимание на условие. Я использовал $index -lt $data.Count здесь. Легко немного неверно задать условие и получить ошибку на единицу в логике. Использование $index -le $data.Count или $index -lt ($data.Count - 1) является небольшим промахом. Это приведет к тому, что результат будет обрабатывать слишком много или слишком мало элементов. Это классическая ошибка на единицу.

Цикл переключателя

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

$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.

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

С типами данных значений единственный способ обновления массива — это использование цикла for, потому что нам нужно знать индекс, чтобы заменить значение. У нас есть больше вариантов с объектами, так как они являются ссылочными типами. Ниже приведен краткий пример.

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

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

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

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

Операторы

Операторы в PowerShell также работают над массивами. Некоторые из них работают немного по-другому.

-соединить

Оператор -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

-заменить и -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. Значение будет находиться слева, а массив - справа от оператора.

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 пытается сопоставить каждый элемент в коллекции.

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 более подробно.

$null или пусто

Тестирование $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"
}

Все -eq

Недавно я увидел кого-то в Reddit с просьбой проверить, что каждое значение в массиве соответствует заданному значению. Пользователь 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)

Метод удаления возвращает 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

Инициализация с 0

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

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)
)

Запятая очень важна в этих примерах. Я дал более ранний пример обычного массива на нескольких строках, где запятая была необязательной. С многомерным массивом всё обстоит иначе.

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

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, чтобы обойти её.

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

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

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