#arrays #powershell #loops
Вопрос:
В настоящее время я изучаю PowerShell, начиная с основ, и я добрался до массивов. Более конкретно, циклические массивы. Я заметил, что при объявлении массива сам по себе он просто записывается как
$myarray = 1, 2, 3, 4, 5
Однако, когда массив объявляется с намерением его зациклить, он записывается как
$myarray = @(1, 2, 3, 4, 5)
Из любопытства я попытался запустить код для циклического перебора массива как со знаком@, так и без него, просто чтобы посмотреть, будет ли он работать, и он отображался в строке, которую я создал точно так же для обоих.
Мой вопрос в том, какова цель знака@? Я попытался поискать его, но не смог найти никаких результатов.
Комментарии:
1.
@(<some pipeline>)
гарантирует, что вывод из<some pipeline>
будет заключен в массив независимо от того, есть ли вывод 0, 1 или более объектов. В вашем случае это совершенно необязательно, потому,
что уже создает массивы, но это имело бы значение, если бы у вас было только 1 значение:1
vs@(1)
. Я не знаю, откуда у вас возникла идея, что «когда массив объявляется с намерением его зациклить, он записывается как [@(...)
]», это может быть просто стилистическим суеверием того, кто написал код, который вы просматриваете/изучаете 🙂2. @MathiasR.Jessen Спасибо, это очень полезно. Да, я думаю, что вы, возможно, правы насчет стилистического выбора. Я просматривал пару сайтов, и некоторые из них были написаны без@, пока они не начали зацикливаться, поэтому я просто предположил, что причиной был зацикливание. Несмотря ни на что, спасибо вам. Твой ответ был идеальным.
3. В настоящее время PowerShell значительно упрощает написание кода независимо от того, хранит ли переменная один объект или массив. Например, это не ошибка:
$a = 1; Write-Host $a[0]
. Вы также можете использовать отдельные объекты вforeach
ForEach-Object
циклах или. Все еще существуют случаи, когда один объект ведет себя иначе, чем массив из одного элемента, поэтому я предпочитаю создавать явные массивы с использованием@()
или[array] $myarray = ...
всякий раз, когда функция может возвращать более одного объекта. ИМО это также облегчает чтение кода.
Ответ №1:
Это альтернативный синтаксис для объявления статических массивов, но есть некоторые ключевые детали для понимания различий в синтаксисе между ними.
@()
является оператором вложенного выражения массива. Это работает аналогично оператору группового ()
выражения или оператору подвыражения $()
, но заставляет все возвращаемое быть массивом, даже если возвращается только 0 или 1 элемент. Это можно использовать встроенно везде, где ожидается тип массива или коллекции. Для получения дополнительной информации об этих операторах ознакомьтесь со Специальными операторами в документации по PowerShell.
1, 2, 3, 4
Это синтаксис выражения списка, и его можно использовать в любом месте, где ожидается массив. Он функционально эквивалентен @(1, 2, 3, 4)
оператору подвыражения массива, но с некоторыми отличиями в поведении от него.
@( Invoke-SomeCmdletOrExpression )
заставит возвращаемое значение быть массивом, даже если выражение возвращает только 0 или 1 элемент.
# Array sub-expression
$myArray = @( Get-Process msedge )
Обратите внимание, что это не обязательно должен быть один вызов командлета, это может быть любое выражение, использующее конвейер так, как вы считаете нужным. Например:
# We have an array of fruit
$fruit = 'apple', 'banana', 'apricot', 'cherry', 'a tomato ;)'
# Fruit starting with A
$fruitStartingWithA = @( $fruit | Where-Object { $_ -match '^a' } )
$fruitStartingWithA
следует вернуть следующее:
apple
apricot
a tomato ;)
Есть еще один способ принудительно ввести тип массива, и я вижу, что он часто упоминается в переполнении стека как классный трюк (каковым он и является), но с небольшим контекстом вокруг его поведения.
Вы можете использовать особенность синтаксиса выражения списка для принудительного ввода типа массива, но между этим и использованием оператора подвыражения массива есть два ключевых различия. Добавление префикса выражения или переменной с запятой ,
приведет к возвращению массива, но поведение между ними меняется. Рассмотрим следующий пример:
# Essentially the same as @( $someVar )
$myArray1 = , $someVar
# This behaves differently, read below
$myArray2 = , ( Invoke-SomeCmdletOrExpression )
@()
или добавление префикса переменной с ,
сгладит (другое слово, часто используемое здесь, — развернуть) результирующие элементы в один массив. Но с выражением вам придется использовать оператор группового выражения, если вы используете трюк с префиксом запятой. Из — за того, как интерпретируется сгруппированное выражение, вы получите массив, состоящий из одного элемента.
В этом случае он не будет сглаживать какие-либо результирующие элементы.
Рассмотрим Get-Process
приведенный выше пример. Если у вас msedge
запущено три процесса, $myArray.Count
отобразится число 3, и вы сможете получить доступ к отдельным процессам с помощью средства доступа к индексу массива $myArray[$i]
. Но если вы сделаете то же самое с $myArray2
приведенным выше примером второго списка-выражения, $myArray2.Count
будет возвращено количество 1. Теперь это, по сути, многомерный массив с одним элементом. Чтобы получить отдельные процессы, теперь вам нужно будет сделать $myArray2[0].Count
, чтобы получить количество процессов, и дважды использовать средство доступа к индексу массива, чтобы получить отдельный процесс:
$myArray2 = , ( Get-Process msedge )
$myArray2.Count # ======> 1
# I have 32 Edge processes right now
$myArray2[0].Count # ===> 32
# Get only the first msedge process
$myArray[0][0] # =======> Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
# =======> ------- ------ ----- ----- ------ -- -- -----------
# =======> 430 19 101216 138968 74.52 3500 1 msedge
Сначала это может быть неясно, потому что печать $myArray2
в выходной поток покажет тот же результат вывода, что и в первом $myArray
примере, и $myArray1
во втором примере.
Короче говоря, вы хотите избежать использования трюка с префиксом запятой, когда хотите использовать выражение, и вместо этого используйте оператор подвыражения массива @()
, поскольку это то, для чего он предназначен.
Примечание: Будут случаи, когда вы захотите определить статический массив массивов, но вы все равно будете использовать синтаксис выражения списка, поэтому префикс запятой становится избыточным. Единственный контрапункт здесь заключается в том, что вы хотите создать массив с массивом в первом элементе, чтобы добавить к нему дополнительные массивы позже, но вам следует использовать универсальный
List[T]
илиArrayList
вместо того, чтобы полагаться на один из операторов объединения для расширения существующего массива (или
=
почти всегда плохие идеи о нечисловых типах).
Вот некоторые дополнительные сведения о массивах в PowerShell, а также спецификация массивов для самой PowerShell.
Комментарии:
1. Это очень полезно и замечательно выражено. Кто — то еще здесь рассказал мне, что он сделал, но увидеть пример ТОГО, ПОЧЕМУ и КАК я хотел бы его использовать, на самом деле невероятно полезно. Спасибо.
2. Спасибо. Я добавил еще один способ принудительного использования массива вместе с контекстом и подводными камнями, чтобы избежать его использования.
Ответ №2:
Оператор , по которому вы ищете документацию, состоит не только из @
, но (
и из и )
тоже — вместе они составляют @()
, также известный как оператор подвыражения массива.
Это гарантирует, что вывод из любого конвейера или выражения, которое вы в него вложите, будет массивом.
Чтобы понять, почему это полезно, нам нужно понять, что PowerShell имеет тенденцию сглаживать массивы! Давайте рассмотрим эту концепцию с помощью простой тестовой функции:
function Test-PowerShellArray {
param(
$Count = 2
)
while($count--){
Get-Random
}
}
Эта функция будет выводить несколько случайных чисел — $Count
чисел, если быть точным:
PS ~> Test-PowerShellArray -Count 5
652133605
1739917433
1209198865
367214514
1018847444
Давайте посмотрим, какой тип вывода мы получаем, когда запрашиваем 5 чисел:
PS ~> $numbers = Test-PowerShellArray -Count 5
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Хорошо, итак, результирующий вывод, который мы сохранили, $numbers
имеет тип [Object[]]
— это означает, что у нас есть массив, который соответствует объектам типа Object
(любого типа .Система типов NET в конечном счете наследуется от Object
, так что на самом деле это просто означает, что у нас есть массив «вещей», он может содержать что угодно).
Мы можем повторить попытку с другим счетом и получить тот же результат:
PS ~> $numbers = Test-PowerShellArray -Count 100
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Пока все хорошо — мы собрали несколько выходных значений из функции и получили массив, все, как и ожидалось.
Но что происходит, когда мы выводим только 1 число из функции:
PS ~> $numbers = Test-PowerShellArray -Count 1
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
Что сказать? Теперь мы получаем System.Int32
— какой тип отдельных целочисленных значений — PowerShell заметил, что мы получили только 1 выходное значение и пошли «Только 1? Я не собираюсь упаковывать это в массив, вы можете оставить все как есть»
Именно по этой причине вы можете захотеть обернуть вывод, который вы собираетесь зациклить (или использовать другими способами, для которых требуется, чтобы он был массивом).:
PS ~> $numbers = Test-PowerShellArray -Count 1
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
PS ~> $numbers = @(Test-PowerShellArray -Count 1) # @(...) saves the day
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Комментарии:
1. Спасибо. Узнать немного больше о технической стороне и закулисной информации о том, как все это работает, очень полезно.