Как передать несколько параметров в функцию в PowerShell?

438

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

Скрипт быстрого теста:

Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC", "DEF")

Сгенерированный вывод

$arg1 value: ABC DEF
$arg2 value: 

Правильный вывод должен быть:

$arg1 value: ABC
$arg2 value: DEF

Кажется, это согласуется между v1 и v2 на нескольких машинах, поэтому, очевидно, я делаю что-то не так. Кто-нибудь может указать, что именно?

Насир
источник
3
Вы просто звоните так:Test "ABC" "DEF"
Ранадип Датта

Ответы:

576

Параметры в вызовах функций в PowerShell (все версии) разделяются пробелом, а не запятыми . Кроме того, круглые скобки совершенно не нужны и вызовут ошибку разбора в PowerShell 2.0 (или более поздней версии), если Set-StrictModeон активен. Аргументы в скобках используются только в методах .NET.

function foo($a, $b, $c) {
   "a: $a; b: $b; c: $c"
}

ps> foo 1 2 3
a: 1; b: 2; c: 3
x0n
источник
20
Самая важная вещь, которая, наконец, помогла мне «приклеить» это последнее предложение: «Аргументы в скобках используются только в .NET-методах».
Эшли
1
Я предпочитаю использовать парантез и запятую ... возможно ли это сделать в powershell?
Сам Йи
8
@ samyi Нет. Передача a (1,2,3)в функцию фактически рассматривается как массив; один аргумент. Если вы хотите использовать аргументы стиля метода OO, используйте модули:$m = new-module -ascustomobject { function Add($x,$y) { $x + $y } }; $m.Add(1,1)
x0n
4
Powershellявляется языком оболочки, и для языков оболочки характерно использование пробелов в качестве разделителя токенов. Я бы не сказал , Powershellв настоящее время здесь по- другому, это право в соответствии с другими оболочками системы по умолчанию , как cmd, sh, bashи т.д.
Bender Величайший
270

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

Я бы добавил это просто как комментарий, но я хотел бы добавить иллюстрацию - я сорвал это с моей быстрой справочной таблицы по функциям PowerShell. Это предполагает, что подпись функции f f($a, $b, $c):

Синтаксические ловушки при вызове функции

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

Для дальнейшего чтения см. Мою статью « Вниз по кроличьей норе: изучение конвейеров, функций и параметров PowerShell» . Статья содержит ссылку на краткий справочник / настенную диаграмму.

Майкл Соренс
источник
4
Объяснение с более подробным синтаксисом, вызывающим каждый параметр и присваивающим ему значение, действительно закрепило его. Спасибо!
КонстантинK
7
Спасибо, это сводило меня с ума, не понимая, что я делаю неправильно. Когда я наконец сделал это правильно, я был голоден для объяснения этого поведения.
BSAFH
1
Спасибо за публикацию этого в качестве ответа. Знание, почему что-то не так, так же важно, как и то, что неправильно.
Гэвин Уорд
4
Это гораздо лучший ответ. Это должно быть дальше.
Марк Бертеншоу
53

Здесь есть несколько хороших ответов, но я хотел бы отметить несколько других вещей. Параметры функций - это место, где светит PowerShell. Например, вы можете иметь именованные или позиционные параметры в расширенных функциях, например:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=1)]
         [int] $Id
    )
}

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

Get-Something -Id 34 -Name "Blah"
Get-Something "Blah" 34

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

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

function Get-Something
{
    [CmdletBinding(DefaultParameterSetName='Name')]
    Param
    (
         [Parameter(Mandatory=$true, Position=0, ParameterSetName='Name')]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=0, ParameterSetName='Id')]
         [int] $Id
    )
}

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

Get-Something "some name"
Get-Something 23
Get-Something -Name "some name"
Get-Something -Id 23

Вы также можете назначить дополнительные параметры для различных наборов параметров. (Это был довольно простой пример.) Внутри функции вы можете определить, какой набор параметров использовался с помощью свойства $ PsCmdlet.ParameterSetName. Например:

if($PsCmdlet.ParameterSetName -eq "Name")
{
    Write-Host "Doing something with name here"
}

Затем, на смежном примечании, есть также проверка параметров в PowerShell. Это одна из моих любимых функций PowerShell, и она делает код внутри ваших функций очень чистым. Есть множество проверок, которые вы можете использовать. Несколько примеров:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [ValidatePattern('^Some.*')]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=1)]
         [ValidateRange(10,100)]
         [int] $Id
    )
}

В первом примере ValidatePattern принимает регулярное выражение, которое гарантирует, что предоставленный параметр соответствует ожидаемому. Если это не так, выдается интуитивно понятное исключение, которое говорит вам, что именно не так. Таким образом, в этом примере «Something» будет работать нормально, но «Summer» не пройдет проверку.

ValidateRange гарантирует, что значение параметра находится в диапазоне, который вы ожидаете для целого числа. Таким образом, 10 или 99 сработают, но 101 сгенерирует исключение.

Еще одним полезным является ValidateSet, который позволяет явно определять массив допустимых значений. Если будет введено что-то еще, будет сгенерировано исключение. Есть и другие, но, вероятно, наиболее полезным является ValidateScript. Для этого требуется блок скрипта, который должен иметь значение $ true, поэтому пределом является небо. Например:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [ValidateScript({ Test-Path $_ -PathType 'Leaf' })]
         [ValidateScript({ (Get-Item $_ | select -Expand Extension) -eq ".csv" })]
         [string] $Path
    )
}

В этом примере мы уверены, что не только $ Path существует, но и что это файл (в отличие от каталога) и имеет расширение .csv. ($ _ относится к параметру, находящемуся внутри вашего скриптового блока.) Вы также можете передать гораздо большие многострочные скриптовые блоки, если требуется этот уровень, или использовать несколько скриптовых блоков, как я сделал здесь. Это чрезвычайно полезно и делает для хороших чистых функций и интуитивных исключений.

user2233949
источник
3
+1 за демонстрацию My_Function -NamedParamater "ParamValue"стиля вызова функции. Это шаблон, которому должен следовать код PS-сценария для удобства чтения.
Mister_Tom
46

Вы вызываете функции PowerShell без скобок и без использования запятой в качестве разделителя. Попробуйте использовать:

test "ABC" "DEF"

В PowerShell запятая (,) является оператором массива, например

$a = "one", "two", "three"

Устанавливается $aв массив с тремя значениями.

Тодд
источник
16
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test "ABC" "DEF"
Джон Б
источник
11

Если вы разработчик на C # / Java / C ++ / Ruby / Python / Pick-A-Language-From-This-Century и хотите вызывать свою функцию с запятыми, потому что это то, что вы всегда делали, тогда вам нужно что-то нравится:

$myModule = New-Module -ascustomobject { 
    function test($arg1, $arg2) { 
        echo "arg1 = $arg1, and arg2 = $arg2"
    }
}

Теперь позвоните:

$myModule.test("ABC", "DEF")

и вы увидите

arg1 = ABC, and arg2 = DEF
Райан Шиллингтон
источник
Java, C ++, Ruby и Python не из этого века (только C #), предполагая григорианский календарь (хотя некоторые эволюционировали больше, чем другие).
Питер Мортенсен
Хех. @PeterMortensen ваш аргумент в том, что я должен сказать «Выберите язык из этого века или из прошлого»? :-)
Райан Шиллингтон
10

Если вы не знаете (или не заботитесь), сколько аргументов вы будете передавать функции, вы также можете использовать очень простой подход, например;

Код :

function FunctionName()
{
    Write-Host $args
}

Это распечатало бы все аргументы. Например:

FunctionName a b c 1 2 3

Вывод

a b c 1 2 3

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

Вот еще один пример из реальной жизни (создание функции для команды tracert, которую я ненавижу запоминать усеченное имя);

Код :

Function traceroute
{
    Start-Process -FilePath "$env:systemroot\system32\tracert.exe" -ArgumentList $args -NoNewWindow
}
Draino
источник
7

Если вы пытаетесь:

PS > Test("ABC", "GHI") ("DEF")

ты получаешь:

$arg1 value: ABC GHI
$arg2 value: DEF

Итак, вы видите, что скобки разделяют параметры


Если вы пытаетесь:

PS > $var = "C"
PS > Test ("AB" + $var) "DEF"

ты получаешь:

$arg1 value: ABC
$arg2 value: DEF

Теперь вы можете найти некоторую непосредственную полезность скобок - пробел не станет разделителем для следующего параметра - вместо этого у вас есть функция eval.

RASOR
источник
4
Парены не разделяют параметры. Они определяют массив.
13
1
Парены не определяют массив, они определяют группу, которую PowerShell может интерпретировать как массив. Массивы определяются с символа ( @) перед ведущим Paren, как этот пустой массив: @(); или этот массив с двумя числами: @(1, 2).
VertigoRay
5

Поскольку это часто рассматривается вопрос, я хочу отметить , что функция PowerShell следует использовать утвержденные глаголы ( Verb существительного в качестве имени функции). Глагольная часть имени определяет действие, которое выполняет командлет. Существительная часть имени идентифицирует сущность, над которой выполняется действие. Это правило упрощает использование ваших командлетов для опытных пользователей PowerShell.

Кроме того, вы можете указать, например, является ли параметр обязательным и положение параметра:

function Test-Script
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$arg1,

        [Parameter(Mandatory=$true, Position=1)]
        [string]$arg2
    )

    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Для передачи параметра в функцию вы можете использовать позицию :

Test-Script "Hello" "World"

Или вы указываете имя параметра :

Test-Script -arg1 "Hello" -arg2 "World"

Вы не используете круглые скобки, как при вызове функции в C #.


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

Мартин Брандл
источник
К вашему сведению, ссылка на список утвержденных глаголов больше не работает, но теперь ее можно найти здесь - docs.microsoft.com/en-us/powershell/developer/cmdlet/…
Кейт Лэнгмид,
@KeithLangmead Спасибо, Кит, я тоже обновил свой ответ.
Мартин Брандл,
1
«Глагол-существительное» как в глаголе, так и в существительном с большой буквы? Может быть, изменить ответ, чтобы быть более явным об этом?
Питер Мортенсен
@PeterMortensen Спасибо за упоминание этого. Я обновил свой ответ.
Мартин Брандл
1
ну, подумайте, вы выставляете Get-Nodeкомандлет. Для нас было бы ясно, что мы должны ссылаться Get-Node, ни Retrieve-Node, ни Receive-Node, ни .....
Мартин Брандл
4

Я не знаю, что вы делаете с функцией, но посмотрите на использование ключевого слова «param». Он немного более мощный для передачи параметров в функцию и делает его более удобным для пользователя. Ниже приведена ссылка на слишком сложную статью Microsoft об этом. Это не так сложно, как статья озвучивает.

Использование Param

Также вот пример из вопроса на этом сайте:

Проверьте это.

Родни Фиск
источник
Спасибо за ответ. Однако у меня были проблемы при вызове функции. Не имеет значения, была ли функция объявлена ​​с параметром или без него.
Насир
3
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC") ("DEF")
Kleopatra
источник
Результат получится как следует:
2

Я заявил следующее ранее:

Общая проблема заключается в использовании формы единственного числа $arg, которая является неправильной. Это всегда должно быть во множественном числе $args.

Проблема не в этом. На самом деле $argможет быть все что угодно. Проблема заключалась в использовании запятой и скобок.

Я запускаю следующий код, который работал, и вывод следующий:

Код:

Function Test([string]$var1, [string]$var2)
{
    Write-Host "`$var1 value: $var1"
    Write-Host "`$var2 value: $var2"
}

Тест "Азбука" "DEF"

Вывод:

$var1 value: ABC
$var2 value: DEF
Эрик
источник
4
Спасибо, мой друг, однако, ты опоздал на пару лет :-) В трех лучших ответах здесь достаточно решена проблема. Могу ли я предложить перейти к разделу без ответа и попробовать некоторые из этих вопросов?
Насир
2
Function Test {
    Param([string]$arg1, [string]$arg2)

    Write-Host $arg1
    Write-Host $arg2
}

Это правильное paramsзаявление.

Смотрите about_Functions_Advanced_Parameters .

И это действительно работает.

Сергей Кимлик
источник
1

Вы также можете передать параметры в функцию, подобную этой:

function FunctionName()
{
    Param ([string]$ParamName);
    # Operations
}
Каушал Хамар
источник
3
Это будет определение параметров для функции, первоначальный вопрос был о том, как указать параметры при вызове функции.
Насир
1

Я не вижу здесь упомянутого, но разбрызгивание ваших аргументов является полезной альтернативой и становится особенно полезным, если вы строите аргументы команды динамически (в отличие от использования Invoke-Expression). Вы можете использовать массивы для позиционных аргументов и хеш-таблицы для именованных аргументов. Вот некоторые примеры:

Сплат с массивами (позиционные аргументы)

Тест-соединение с позиционными аргументами

Test-Connection www.google.com localhost

С массивом Splatting

$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentArray

Обратите внимание, что при разборе мы ссылаемся на переменную со знаком @вместо a $. То же самое и при использовании Hashtable для сплаттинга.

Splat With Hashtable (именованные аргументы)

Тест-соединение с именованными аргументами

Test-Connection -ComputerName www.google.com -Source localhost

С хэштабличным

$argumentHash = @{
  ComputerName = 'www.google.com'
  Source = 'localhost'
}
Test-Connection @argumentHash

Сплат позиционные и именованные аргументы одновременно

Тест-соединение с позиционными и именованными аргументами

Test-Connection www.google.com localhost -Count 1

Splatting Array и Hashtables вместе

$argumentHash = @{
  Count = 1
}
$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentHash @argumentArray
Бендер Величайший
источник