Powershell выводит элементы массива при интерполяции в двойных кавычках

Я нашел странное поведение в Powershell, окружающем массивы и двойные кавычки. Если я создам и печатаю первый элемент в массиве, например:

$test = @('testing')
echo $test[0]

Output:    
testing

Все работает нормально. Но если я поставлю туда двойные кавычки:

echo "$test[0]"

Output:
testing[0]

Была оценена только переменная $ test, и маркер массива [0] обрабатывался буквально как строка. Легкое исправление заключается в том, чтобы просто избегать интерполирования переменных массива в двойных кавычках или сначала назначить их другой переменной. Но мне было интересно, если это поведение по дизайну?

Ответ 1

Поэтому, когда вы используете интерполяцию, по умолчанию она интерполирует только следующую переменную в toto. Поэтому, когда вы это делаете:

"$test[0]"

Он воспринимает $ test как следующую переменную, он понимает, что это массив и что у него нет хорошего способа отображения массива, поэтому он решает, что он не может интерполировать и просто отображает строку в виде строки. Решение состоит в том, чтобы явно указать powershell, где бит для интерполяции начинается и где он останавливается:

"$($test[0])"

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

"{0}" -f $test[0]

Ответ 2

В таких случаях вам нужно:

echo "$($test[0])"

Другой альтернативой является использование форматирования строк

echo "this is {0}" -f $test[0]

Обратите внимание, что это будет случай, когда вы также получаете доступ к свойствам в строках. Как "$a.Foo" - должен быть записан как "$($a.Foo)"

Ответ 3

Полезный ответ EBGreen содержит эффективные решения, но лишь поверхностное объяснение интерполяции строк PowerShell (расширение строки):

  • Только переменные в целом могут быть встроены непосредственно внутри "..." (строки с двойными кавычками, в отличие от строк с одиночными кавычками ('...'), как и во многих других языках, для буквального содержимого).

    • Это относится как к обычным переменным, так и к переменным, ссылающимся на конкретное пространство имен; например:
      "var contains: $var", "Path: $env:PATH"

    • Если первый символ после имени переменной может быть ошибочно принят за часть имени, что особенно включает в себя : - используйте {...} вокруг имени переменной для устранения неоднозначности; например:
      "${var}", "${env:PATH}"

    • Для того, чтобы использовать $ как литерал, вы должны экранировать его ', PowerShell бежать характер; например:
      "Variable '$var"

  • Любой символ после имени переменной - включая [ и . рассматривается как буквальная часть строки, поэтому для индексации во встроенные переменные ($var[0]) или доступа к свойству ($var.Count) вам нужен $(...), оператор подвыражения (в факт, $(...) позволяет вставлять целые утверждения); например:

    • "1st element: $($var[0])"
    • "Element count: $($var.Count)"
  • Stringification (преобразование в строку) применяется к любому результату переменной/оценке, который еще не является строкой:

    • Предостережение: в случае применения форматирования, специфичного для культуры, PowerShell выбирает инвариантную культуру, которая во многом совпадает с форматированием даты и числа в США и Англии; то есть, даты и номера будут представлены в США, как формат (например, месяц -f IRST дату и формат . в качестве десятичного знака).
    • По сути, метод .ToString() вызывается для любого результирующего .ToString() объекта или коллекции (строго говоря, это .psobject.ToString(), который в некоторых случаях переопределяет .ToString(), особенно для массивов/коллекций и PS пользовательские объекты)

      • Обратите внимание, что это не то же представление, которое вы получаете, когда вы выводите переменную или выражение напрямую, а многие типы не имеют значимых представлений строк по умолчанию - они просто возвращают полное имя типа.
        Однако вы можете встраивать $(... | Out-String), чтобы явно применять форматирование вывода по умолчанию PowerShell.
    • Более подробное обсуждение строения см. В моем ответе.


Как указано, используя -f, оператор строки -f ormatting (<format-string> -f <arg>[,...]) является альтернативой строковой интерполяции, которая отделяет литеральные части строки от переменные части:

'1st element: {0}; count: {1:x}'  -f  $var[0], $var.Count
  • Обратите внимание на использование '...' на LHS, потому что строка формата (шаблон) сама является литералом. Использование '...' в этом случае - хорошая привычка формироваться, как сигнализировать о намерении использовать литеральное содержимое, так и возможность встраивания $ символов без экранирования.

  • В дополнение к простым позиционным заполнителям ({0} для 1-го аргумента. {1} для второго,...), вы можете дополнительно использовать больше форматирования для преобразования в строку; в приведенном выше примере x запрашивает шестнадцатеричное представление числа.
    Для доступных форматов см. Документацию метода.NET Framework String.Format, на котором -f оператор -f.

  • Pitfall: -f имеет высокий приоритет, поэтому обязательно включайте выражения RHS, отличные от простого индекса или доступа к свойствам в (...); например, '{0:N2}' -f 1/3 не будет работать, как предполагалось, только '{0:N2}' -f (1/3)

  • Предостережения. Существуют важные различия между интерполяцией строк и -f - см. Ниже.


В отличие от интерполяции внутри "...", оператор -f чувствителен к культуре:

Поэтому следующие два, казалось бы, эквивалентных утверждения не дают такого же результата:

PS> [cultureinfo]::CurrentCulture = 'fr'; $n = 1.2; "interpolated: $n"; '-f: {0}' -f $n

interpolated: 1.2
-f: 1,2

Обратите внимание, что только команда -f -f одобрена французской (fr) десятичной меткой (,).
Опять же, см. Ранее связанный ответ для всестороннего просмотра, когда PowerShell является и не чувствителен к культуре.


В отличие от интерполяции внутри "...", -f массивы как <type-name>[] :

PS> $arr = 1, 2, 3; "'$arr: $arr"; '$arr: {0}' -f (, $arr)

$arr: 1 2 3
$arr: System.Object[]

Примечание: (,...) обертывает $arr в вспомогательный массив, который гарантирует, что -f видит выражение как единый операнд с массивом;по умолчанию элементы массива будут рассматриваться как отдельные операнды.

Обратите внимание, что интерполяция "..." создала список строковых разделителей всех элементов массива, в то время как -f -f ormatting напечатало только имя типа массива.
(Как обсуждалось, $arr внутри "..." эквивалентно:
(1, 2, 3).psobject.ToString() и это тип [psobject] который обеспечивает дружественное представление.)