PowerShellの関数戻り値


188

SharePointチームサイトのプロビジョニングに関連するいくつかのアクションを実行するPowerShell関数を開発しました。最終的に、関数がプロビジョニングされたサイトのURLを文字列として返すようにしたいので、関数の最後に次のコードを記述します。

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

この関数を呼び出すコードは次のようになります。

$returnURL = MyFunction -param 1 ...

だから私は文字列を期待していますが、そうではありません。代わりに、System.Management.Automation.PSMethod型のオブジェクトです。String型ではなくその型を返すのはなぜですか?


2
関数宣言を見ることができますか?
zdan

1
いいえ、関数の呼び出しではなく、「MyFunction」を宣言した方法/場所を示します。実行するとどうなるか:Get-Item function:\ MyFunction
zdan

1
これに対処する別の方法を提案しました: stackoverflow.com/a/42842865/992301
Feru

これは、関数/メソッドに関連するスタックオーバーフローに関する最も重要なPowerShellの質問の1つだと思います。PowerShellスクリプトの学習を計画している場合は、このページ全体を読んで完全に理解する必要があります。そうしないと、後で自分が嫌いになります。
Birdman

回答:


271

PowerShellには、少なくとも従来のプログラミングの観点から見ると、本当に奇抜な戻りのセマンティクスがあります。頭を包む主なアイデアは2つあります。

  • すべての出力がキャプチャされ、返されます
  • returnキーワードは、実際には論理的な出口点を示すだけです

したがって、次の2つのスクリプトブロックは、まったく同じことを効果的に実行します。

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

2番目の例の$ a変数は、パイプラインの出力として残され、前述のように、すべての出力が返されます。実際、2番目の例では、戻りを完全に省略でき、同じ動作が得られます(関数が自然に完了して終了するため、戻りは暗黙に示されます)。

関数定義をこれ以上追加しないと、PSMethodオブジェクトを取得する理由を説明できません。おそらく、キャプチャされておらず、出力パイプラインに配置されているラインが数行あると思います。

また、1行に複数の式をネストしない限り、セミコロンはおそらく必要ないことにも注意してください。

TechNet のabout_Returnページで、またはhelp returnPowerShell自体からコマンドを呼び出すことで、戻り値のセマンティクスについて詳しく読むことができます。


13
関数からスケーラー値を返す方法はありますか?
ChiliYago

10
関数がハッシュテーブルを返す場合、その結果はそのように扱われますが、他のコマンドの出力を含め、関数本体内のすべてを「エコー」すると、突然結果が混合されます。
ルークプレット2013年

5
あなたが関数の結果の一部としてそのテキストを返さずに関数の中から画面への出力のものにしたい場合は、コマンドを使用するwrite-debug変数を設定した後に$DebugPreference = "Continue"するのではなく"SilentlyContinue"
BeowulfNode42

7
これは役に立ちましたが、このstackoverflow.com/questions/22663848/…はさらに役立ちました。"... | Out-Null"を介して、呼び出された関数のコマンド/関数呼び出しの不要な結果をパイプ処理し、必要なものだけを返します。
スタッフ、2015年

1
@LukePuplett echoが私の問題でした!その小さなサイドポイントは非常に非常に重要です。他のすべてに従って、ハッシュテーブルを返していましたが、それでも戻り値は期待したものではありませんでした。を削除しecho、チャームのように機能しました。
Ayo I、2016

54

PowerShellのこの部分は、おそらく最も愚かな側面です。関数の実行中に生成される無関係な出力は結果を汚染します。場合によっては出力がなく、状況によっては、計画された戻り値に加えて、計画外の出力が他にもあることがあります。

そのため、元の関数呼び出しから割り当てを削除して、出力が画面に表示されるようにし、デバッガーウィンドウで(PowerShell ISEを使用して)予定外のものがポップアップ表示されるまでステップスルーします。

以下のように出力原因外側のスコープ内の変数を確保するようにも物事、[boolean]$isEnabledあなたがそれを作る場合を除きうるさく偽アウトを吐くなります[boolean]$isEnabled = $false

別の良いものは$someCollection.Add("thing")、新しいコレクション数を吐き出すことです。


2
明示的に定義された値で変数を適切に初期化するベストプラクティスを実行する代わりに、名前を予約するだけの理由は何ですか。
BeowulfNode42 2014年

2
私は、そのスコープで使用されるすべての変数の各スコープの先頭に変数宣言のブロックがあることを意味すると思います。C#を含む任意の言語で変数を使用する前に、すべての変数を初期化する必要があります。また[boolean]$a、Powershellで行うことは、変数$ aを実際に宣言しないことも重要です。これを確認するには、その行を次の行に続け、$a.gettype()その出力を文字列で宣言して初期化したときの出力と比較します$a = [boolean]$false
BeowulfNode42 2014年

3
おそらくあなたは正しいと思いますが、偶発的な書き込みを防ぐために、より明示的な設計を望んでいます。
ルークプレット

4
プログラマーの観点から見ると、私はPS-n00bですが、これがPS構文の多くの迷惑な側面の中で最悪であると確信しているわけではありません。「x> y」ではなく「x -gt y」と書く方が望ましいと誰が一体どう思いましたか。確かに、少なくとも組み込みの演算子は、より人間にやさしい構文を使用できただろうか?
ダグ2015

5
@TheDagは、シェルが最初、プログラミング言語が2番目であるためです。>および<ファイルへの/からのI / Oストリームのリダイレクトにすでに使用されています。>=と呼ばれるファイルにstdoutを書き込みます=
TessellatingHeckler、2015年

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だけです。クラスは、宣言された型またはvoidのみを返すという保証のようなものです。


6
注意。staticメソッドもサポートします。構文に[test_class]::return_what()
つながる

1
「これが関数である場合、期待される出力は 'Hello World \ n808979'です-これは正しくないと思いますか?出力値は常に、最後のステートメントの値のみreturn 808979です。あなたの場合、関数かどうかは関係ありません。
午前

1
@amnありがとうございます!関数内で出力を作成した場合にのみ、例がそれを返すことは非常に正しいです。それが私を困らせるものです。場合によっては、関数が出力を生成し、その出力と、returnステートメントで追加した値が返されることがあります。ご存じのとおり、クラスは宣言された型またはvoidのみを返します。関数を使用する場合、1つの簡単な方法は、関数を変数に割り当てることです。戻り値よりも多くが返される場合、varは配列であり、戻り値は配列の最後の項目になります。
DaSmokeDog

1
さて、あなたは呼ばれるコマンドから何を期待しますWrite-Outputか?ここで参照されているのは「コンソール」出力ではなく、関数/コマンドレットの出力です。コンソールにデータをダンプしたい場合はWrite-VerboseWrite-HostWrite-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して、それを直接出力します。
Duncan

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

とにかく、2つの提案:

オブジェクトを文字列にキャストします。

...
return [string]$rs

または、上記と同じように二重引用符で囲みますが、入力するのは短くなります。

...
return "$rs"

1
または単に"$rs"- return関数から早く戻るときにのみ必要です。見返りを省くことは、PowerShellイディオムの方が優れています。
David Clarke

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()ます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.