Повернення значення функції в PowerShell


188

Я розробив функцію PowerShell, яка виконує ряд дій, пов’язаних із забезпеченням сайтів SharePoint Team. Зрештою, я хочу, щоб функція повертала URL-адресу наданого веб-сайту як String, тому наприкінці своєї функції я маю такий код:

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

Код, який викликає цю функцію, виглядає так:

$returnURL = MyFunction -param 1 ...

Тож я очікую струну, однак це не так. Натомість це об’єкт типу System.Management.Automation.PSMethod. Чому він повертає цей тип замість типу String?


2
Чи можемо ми побачити декларацію функції?
zdan

1
Ні, не виклик функції, покажіть нам, як / де ви оголосили "MyFunction". Що станеться, коли ви робите: Функція Get-Item: \ MyFunction
zdan

1
Віддається альтернативний спосіб вирішення цієї: stackoverflow.com/a/42842865/992301
Feru

Я думаю, що це одне з найважливіших питань PowerShell щодо переповнення стека, що стосується функцій / методів. Якщо ви плануєте вивчити сценарії PowerShell, ви повинні прочитати всю цю сторінку та досконало її зрозуміти. Пізніше ти будеш ненавидіти себе, якщо цього не зробиш.
Birdman

Відповіді:


271

PowerShell має насправді дурну семантику повернення - принаймні, якщо дивитися з традиційної перспективи програмування. Є дві основні ідеї, щоб обернути голову:

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

Таким чином, наступні два блоки сценаріїв будуть ефективно робити те саме:

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

Змінна $ a у другому прикладі залишається як вихід на конвеєр, і, як було сказано, весь вихід повертається. Насправді, у другому прикладі ви могли повністю опустити повернення, і ви отримали б таку саму поведінку (повернення мається на увазі, оскільки функція, природно, завершується і залишається).

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

Варто також зазначити, що вам, мабуть , не потрібні такі крапки з комою, якщо ви не вкладаєте кілька виразів у одному рядку.

Докладніше про семантику повернення можна прочитати на сторінці about_Return в TechNet або за допомогою виклику help returnкоманди від самого PowerShell.


13
так чи є спосіб повернути значення функції масштабування з функції?
ChiliYago

10
Якщо ваша функція повертає хеш-файл, тоді вона буде розглядати результат як такий, але якщо ви "повторюєте" все, що знаходиться в тілі функції, включаючи вихід інших команд, і раптом результат змішується.
Люк Пуплетт

5
Якщо ви хочете вивести матеріали на екран із функції, не повертаючи цей текст як частину результату функції, використовуйте команду write-debugпісля встановлення змінної $DebugPreference = "Continue"замість"SilentlyContinue"
BeowulfNode42

6
Це допомогло, але це stackoverflow.com/questions/22663848/… допомогло ще більше. Створіть небажані результати команд / функцій викликів у виклику функції через "... | Out-Null", а потім просто поверніть потрібну вам річ.
Straff

1
@LukePuplett echoбуло проблемою для мене! Ця маленька бічна сторона дуже важлива. Я повертав хеш-версію, дотримуючись всього іншого, але все ж повернене значення було не таким, як я очікував. Зняв echoі працював як шарм.
Ayo I

54

Ця частина PowerShell - це, мабуть, найглупіший аспект. Будь-який сторонній вихід, отриманий під час функції, забруднює результат. Іноді немає жодного результату, а за певних умов, крім запланованого значення повернення, є ще якийсь позаплановий вихід.

Отже, я видаляю призначення з початкового виклику функції, тож результат закінчується на екрані, а потім переходжу до тих пір, поки щось не планувалося вискочити у вікні налагодження (використовуючи PowerShell ISE ).

Навіть такі речі, як резервування змінних у зовнішніх областях, спричиняють вихід, наприклад, [boolean]$isEnabledякий буде дратує виплюнути помилковий, якщо ви не зробите це [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"?!? Напевно, принаймні вбудовані оператори могли використати більш сприятливий для людини синтаксис?
Даг

5
@TheDag, оскільки це оболонка по-перше, мова програмування друга; >і <вже використовуються для перенаправлення потоку вводу / виводу до / з файлів. >=пише stdout у файл, який називається =.
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. Клас є на зразок гарантії, що він поверне лише тип, оголошений або недійсний.


6
Примітка. Він також підтримує staticметоди. Ведучий до синтаксису[test_class]::return_what()
Sancarn

1
"Якщо це функція, очікуваний вихід буде" Hello World \ n808979 "- Я не думаю, що це правильно? Вихідне значення - це завжди лише значення останнього твердження, у вашому випадку return 808979функціонує чи ні.
атп

1
@amn Дякую! Ви дуже правильні, приклад повернеться лише тоді, якщо він створив би результат у межах функції. Що мене дратує. Іноді ваша функція може генерувати вихід, і цей результат плюс будь-яке значення, яке ви додаєте в оператор return, повертається. Класи, як вам відомо, повертають лише тип, оголошений або недійсний. Якщо ви скоріше використовуєте функції, то одним простим методом є присвоєння функції змінній, і якщо повернуто більше, ніж повернене значення, var - це масив, а значення, що повертається, буде останнім елементом у масиві.
DaSmokeDog

1
Ну що ви очікуєте від команди, що називається Write-Output? Тут не йдеться про "консольний" вихід, це вихід для функції / cmdlet. Якщо ви хочете скинути матеріал на консолі є Write-Verbose, Write-Hostі Write-Errorфункція, серед інших. В основному, Write-Outputце ближче до returnсемантики, ніж до процедури виведення консолі. Не зловживайте цим, використовуйте Write-Verboseдля багатослів’я, ось для чого це потрібно.
атп

1
@amn Я дуже знаю, що це не консоль. Це був непростий швидкий спосіб показати спосіб повернення справи з несподіваним результатом всередині функції проти класу. у функції весь вихід + значення, яке ви повертаєте, всі передаються назад. ЯКЩО передається лише значення, вказане при поверненні класу. Спробуйте запустити їх для себе.
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 А що, якщо я хочу вивести речі у функції, в той же час я також хочу повернути значення?
Utkan Gezer

@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 як відповідь. При заміні виразів додавання для рядків він повертає перший рядок.

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()метод.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.