Возвращаемое значение функции в PowerShell

188

Я разработал функцию PowerShell, которая выполняет ряд действий, связанных с подготовкой сайтов SharePoint Team. В конечном итоге я хочу, чтобы функция возвращала URL-адрес подготовленного сайта в виде строки, поэтому в конце моей функции у меня будет следующий код:

$rs = $url.ToString();
return $rs;

Код, который вызывает эту функцию, выглядит следующим образом:

$returnURL = MyFunction -param 1 ...

Так что я ожидаю строку, но это не так. Вместо этого это объект типа System.Management.Automation.PSMethod. Почему он возвращает этот тип вместо типа String?

ChiliYago
источник
2
Можем ли мы увидеть объявление функции?
Здан
1
Нет, не вызов функции, покажите нам, как / где вы объявили «MyFunction». Что происходит , когда вы делаете: Get-Item функции: \ MyFunction
Zdan
1
Предложил альтернативный способ решения этой проблемы: stackoverflow.com/a/42842865/992301
Feru
Я думаю, что это один из самых важных вопросов PowerShell по переполнению стека, связанных с функциями / методами. Если вы планируете изучать скрипты PowerShell, вы должны прочитать всю эту страницу и понять ее полностью. Ты будешь ненавидеть себя позже, если не будешь.
Птичка

Ответы:

271

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

  • Весь вывод захватывается и возвращается
  • Ключевое слово return действительно просто указывает на логическую точку выхода

Таким образом, следующие два блока сценария будут эффективно выполнять одно и то же:

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

Переменная $ a во втором примере остается как вывод для конвейера, и, как уже упоминалось, весь вывод возвращается. Фактически, во втором примере вы можете полностью опустить возврат и получить то же поведение (возврат будет подразумеваться, когда функция естественным образом завершает работу и завершается).

Без большего определения вашей функции я не могу сказать, почему вы получаете объект PSMethod. Я предполагаю, что у вас, вероятно, есть несколько строк, которые не фиксируются и помещаются в выходной конвейер.

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

Вы можете прочитать больше о семантике возврата на странице about_Return в TechNet или вызвав help returnкоманду из самой PowerShell.

Goyuix
источник
13
так есть ли способ вернуть значение скалера из функции?
ChiliYago
10
Если ваша функция возвращает хеш-таблицу, то она будет обрабатывать результат как таковой, но если вы «выводите» что-либо в теле функции, включая вывод других команд, и результат неожиданно смешивается.
Люк Пуплетт
5
Если вы хотите выводить материал на экран из функции, не возвращая этот текст как часть результата функции, используйте команду write-debugпосле установки переменной $DebugPreference = "Continue"вместо"SilentlyContinue"
BeowulfNode42
7
Это помогло, но этот stackoverflow.com/questions/22663848/… помог еще больше. Передайте нежелательные результаты команд / вызовов функций в вызываемой функции через "... | Out-Null", а затем просто верните нужную вещь.
Страфф
1
@LukePuplett echoбыл проблемой для меня! Эта маленькая сторона очень и очень важна. Я возвращал хеш-таблицу, следуя всему остальному, но все же возвращаемое значение оказалось не таким, как я ожидал. Снял echoи работал как шарм.
Ayo I
54

Эта часть PowerShell, пожалуй, самый глупый аспект. Любые посторонние результаты, генерируемые во время функции, будут загрязнять результат. Иногда нет никакого выхода, а затем при некоторых условиях есть некоторый другой незапланированный выход, в дополнение к вашему запланированному возвращаемому значению.

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

Даже такие вещи, как резервирование переменных во внешних областях, вызывают вывод, например, [boolean]$isEnabledкоторый будет досадно выплевывать False, если вы не сделаете это [boolean]$isEnabled = $false.

Еще одна хорошая $someCollection.Add("thing")вещь, которая выплевывает счет новой коллекции.

Люк Пуплетт
источник
2
Почему бы вам просто зарезервировать имя, вместо того, чтобы по возможности правильно инициализировать переменную с явно определенным значением.
BeowulfNode42
2
Я так понимаю, вы имеете в виду, что у вас есть блок объявлений переменных в начале каждой области для каждой переменной, используемой в этой области. Вы должны все еще или, возможно, уже инициализировать все переменные, прежде чем использовать их на любом языке, включая C #. Также важно, что выполнение [boolean]$aв powershell фактически не объявляет переменную $ a. Вы можете подтвердить это, следуя за этой строкой со строкой, $a.gettype()и сравнить этот вывод с тем, когда вы объявляете и инициализируете со строкой $a = [boolean]$false.
BeowulfNode42
3
Вы, вероятно, правы, я бы предпочел более явный дизайн, чтобы предотвратить случайные записи.
Люк Пуплетт
4
Исходя из точки зрения программиста, хотя я PS-n00b, я не уверен, что считаю это самым худшим из многих раздражающих аспектов синтаксиса PS. С какой стати кто-то подумал, что лучше написать «x -gt y», а не «x> y»?!? Конечно, по крайней мере, встроенные операторы могли бы использовать более удобный для человека синтаксис?
The Dag
5
@TheDag, потому что это сначала оболочка, затем язык программирования; >и <уже используются для перенаправления потока ввода-вывода в / из файлов. >=записывает стандартный вывод в файл с именем =.
TessellatingHeckler
38

С PowerShell 5 у нас теперь есть возможность создавать классы. Измените вашу функцию на класс, и return вернет только объект, непосредственно предшествующий ему. Вот очень простой пример.

class test_class {
    [int]return_what() {
        Write-Output "Hello, World!"
        return 808979
    }
}
$tc = New-Object -TypeName test_class
$tc.return_what()

Если бы это была функция, ожидаемый результат был бы

Hello World
808979

но в качестве класса возвращается только целое число 808979. Класс является своего рода гарантией того, что он вернет только объявленный или недействительный тип.

DaSmokeDog
источник
6
Заметка. Он также поддерживает staticметоды. Ведущий к синтаксису[test_class]::return_what()
Санкарн
1
«Если бы это была функция, ожидаемый результат был бы« Hello World \ n808979 »- я не думаю, что это правильно? Выходное значение всегда является только значением последнего оператора, в вашем случае return 808979, функция или нет.
атп
1
@amn Спасибо! Вы совершенно правы, что пример вернет это только в том случае, если будет создан вывод внутри функции. Что меня и раздражает. Иногда ваша функция может генерировать вывод, и этот вывод плюс любое значение, добавляемое в операторе return, возвращается. Классы, как вы знаете, будут возвращать только объявленный тип или void. Если вы предпочитаете использовать функции, то одним из простых способов является присвоение функции переменной, и если возвращается больше, чем возвращаемое значение, var является массивом, а возвращаемое значение будет последним элементом в массиве.
DaSmokeDog
1
Ну, что вы ожидаете от команды под названием Write-Output? Здесь упоминается не вывод консоли, а вывод функции / командлета. Если вы хотите сбросить материал на консоли есть Write-Verbose, Write-Hostи Write-Errorфункция, среди других. В принципе, Write-Outputближе к returnсемантике, чем к процедуре вывода на консоль. Не злоупотребляйте этим, используйте Write-Verboseдля многословия, вот для чего это.
атп
1
@ Я очень хорошо понимаю, что это не консоль. Это был очень быстрый способ показать, как возвращаемый результат имеет дело с неожиданным выводом внутри функции по сравнению с классом. в функции все выходные данные + возвращаемое вами значение передаются обратно. ОДНАКО передается только значение, указанное в возвращаемом классе. Попробуйте запустить их для себя.
DaSmokeDog
31

В качестве обходного пути я возвращал последний объект в массиве, который вы получили из функции ... Это не очень хорошее решение, но лучше, чем ничего:

someFunction {
   $a = "hello"
   "Function is running"
   return $a
}

$b = someFunction
$b = $b[($b.count - 1)]  # Or
$b = $b[-1]              # Simpler

В целом, более однолинейный способ написать то же самое может быть:

$b = (someFunction $someParameter $andAnotherOne)[-1]
Джоунси
источник
7
$b = $b[-1]было бы проще получить последний элемент или массив, но еще проще было бы просто не выводить значения, которые вам не нужны.
Дункан
@Duncan А что, если я хочу вывести материал в функцию, в то же время я хочу получить возвращаемое значение?
Уткан Гезер
@ThoAppelsin, если вы хотите записать что-то на терминал, используйте write-hostего для непосредственного вывода.
Дункан
7

Я передаю простой объект Hashtable с единственным членом результата, чтобы избежать сумасшествия возврата, поскольку я также хочу выводить на консоль. Это действует через проход по ссылке.

function sample-loop($returnObj) {
  for($i = 0; $i -lt 10; $i++) {
    Write-Host "loop counter: $i"
    $returnObj.result++
  }
}

function main-sample() {
  $countObj = @{ result = 0 }
  sample-loop -returnObj $countObj
  Write-Host "_____________"
  Write-Host "Total = " ($countObj.result)
}

main-sample

Вы можете увидеть реальный пример использования в моем проекте GitHub unpackTunes .

Что бы круто
источник
4

Трудно сказать, не глядя на код. Убедитесь, что ваша функция не возвращает более одного объекта, и что вы фиксируете результаты, полученные из других вызовов. Что вы получаете за:

@($returnURL).count

Во всяком случае, два предложения:

Приведите объект к строке:

...
return [string]$rs

Или просто заключите его в двойные кавычки, как указано выше, но короче, чтобы напечатать:

...
return "$rs"
Шей Леви
источник
1
Или даже просто "$rs"- returnтребуется только при раннем возврате из функции. Оставлять возврат лучше идиома PowerShell.
Дэвид Кларк
4

Описание функций, приведенных Люком в этих сценариях, кажется правильным. Я только хочу понять основную причину, и команда разработчиков PowerShell сделает что-то с этим поведением. Это кажется довольно распространенным явлением и стоило мне слишком много времени на отладку.

Чтобы обойти эту проблему, я использовал глобальные переменные, а не возвращал и использовал значение из вызова функции.

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

Тед Б.
источник
3

Следующий просто возвращает 4 в качестве ответа. Когда вы заменяете выражения add для строк, возвращается первая строка.

Function StartingMain {
  $a = 1 + 3
  $b = 2 + 5
  $c = 3 + 7
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b
}

StartingEnd(StartingMain)

Это также можно сделать для массива. Пример ниже вернет «Текст 2»

Function StartingMain {
  $a = ,@("Text 1","Text 2","Text 3")
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b[1]
}

StartingEnd(StartingMain)

Обратите внимание, что вы должны вызывать функцию под самой функцией. В противном случае при первом запуске он выдаст ошибку, что он не знает, что такое «StartingMain».

Эдвин
источник
2

Существующие ответы верны, но иногда вы не возвращаете что-то явно с помощью a Write-Outputили a return, но в результатах функции есть какое-то загадочное значение. Это может быть вывод встроенной функции, такой какNew-Item

PS C:\temp> function ContrivedFolderMakerFunction {
>>    $folderName = [DateTime]::Now.ToFileTime()
>>    $folderPath = Join-Path -Path . -ChildPath $folderName
>>    New-Item -Path $folderPath -ItemType Directory
>>    return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result


    Directory: C:\temp


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         2/9/2020   4:32 PM                132257575335253136
True

Весь этот дополнительный шум создания каталога собирается и выводится на выходе. Простой способ смягчить это - добавить | Out-Nullв конец New-Itemоператора, или вы можете присвоить результат переменной и просто не использовать эту переменную. Это будет выглядеть так ...

PS C:\temp> function ContrivedFolderMakerFunction {
>>    $folderName = [DateTime]::Now.ToFileTime()
>>    $folderPath = Join-Path -Path . -ChildPath $folderName
>>    New-Item -Path $folderPath -ItemType Directory | Out-Null
>>    # -or-
>>    $throwaway = New-Item -Path $folderPath -ItemType Directory 
>>    return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
True

New-ItemВероятно, наиболее известный из них, но другие включают все StringBuilder.Append*()методы, а также SqlDataAdapter.Fill()метод.

StingyJack
источник