Цель @ в циклических массивах?

#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. Спасибо. Узнать немного больше о технической стороне и закулисной информации о том, как все это работает, очень полезно.