about_ForEach

適用対象: Windows PowerShell 2.0, Windows PowerShell 3.0, Windows PowerShell 4.0, Windows PowerShell 5.0

トピック

about_Foreach

概要

項目のコレクションのすべての項目をスキャンする際の言語コマンドについて説明します。

詳細説明

Foreach ステートメント (Foreach ループとも呼ばれます) は、項目のコレクション内の一連の値をステップ実行する (反復する) ための言語コンストラクトです。

スキャンするコレクションの最も簡単かつ一般的な型は配列です。Foreach ループ内では、配列内の各項目に対して 1 つまたは複数のコマンドを実行するのが一般的です。

構文

Foreach 構文を次に示します。

          foreach ($<item> in $<collection>){<statement list>}

簡略化された構文

Windows PowerShell® 3.0 から、Where や ForEach などの言語キーワードを使用した構文が簡略化されました。コレクションのメンバーに対して機能する比較演算子は、パラメーターとして扱われます。コレクションのメンバーに対するメソッドは、スクリプト ブロックに含めたり、自動変数 "$_. " を追加したりすることなく、使用できます。次の 2 つの例を考えてみます。

          dir cert:\ -Recurse | foreach GetKeyAlgorithm
          dir cert:\ -Recurse | foreach {$_.GetKeyAlgorithm()}

どちらのコマンドも機能しますが、1 つ目はスクリプト ブロックや $_. 自動変数を使用せずに結果を返します。GetKeyAlgorithm メソッドは、ForEach のパラメーターとして扱われます。最初のコマンドは、同様の結果を返しますが、エラーが発生しません。簡略化された構文では、指定の引数が適用されなかった項目については結果を返さないためです。

この例では、Get-Process プロパティ Description が ForEach ステートメントのパラメーター引数として渡されます。結果は、アクティブなプロセスの説明です。

          Get-Process | ForEach Description

コマンド パイプラインの外部の Foreach ステートメント

かっこで囲まれた、Foreach ステートメントの一部では、変数、および反復処理するコレクションを表します。Windows PowerShell は、Foreach ループの実行時に変数 ($<item>) を自動的に作成します。ループで各イテレーションが実行される前に、変数がコレクション内の値に設定されます。Foreach ステートメントに続くブロック {<ステートメントの一覧>} には、コレクション内の各項目に対して実行するコマンドのセットが含まれています。

たとえば、次の例の Foreach ループは、$letterArray 配列内の値を表示します。

        
          $letterArray = "a","b","c","d"
          foreach ($letter in $letterArray)
          {
              Write-Host $letter
          }

この例では、$letterArray 配列を作成し、"a"、"b"、"c"、"d" の各文字列値で初期化しています。Foreach ステートメントは、初回実行時に、$letter 変数を $letterArray の最初の項目 ("a") に設定します。次に、Write-Host コマンドレットを使用して、文字 a を表示します。次回のループでは、$letter を "b" を設定し、以後同様に設定します。Foreach ループが文字 d を表示すると、Windows PowerShell はループを終了します。

Foreach ステートメントを Windows PowerShell コマンド プロンプトでコマンドとして実行するには、ステートメント全体を 1 行に表示する必要があります。そうする代わりにコマンドを .ps1 スクリプト ファイルに記載した場合は、Foreach ステートメント全体を 1 行に表示する必要はありません。

Foreach ステートメントは、項目のコレクションを返すコマンドレットと共に使用することもできます。次の例では、Foreach ステートメントが Get-ChildItem コマンドレットから返される項目の一覧をステップ実行します。

          foreach ($file in Get-ChildItem)
          {
              Write-Host $file
          }

If ステートメントを使用して返される結果を制限して、この例を調整できます。次の例では、Foreach ステートメントは前の例と同じループ操作を実行しますが、If ステートメントを追加して、100 キロバイト (KB) を超えるファイルに結果を制限します。

          foreach ($file in Get-ChildItem)
          {
              if ($file.length -gt 100KB) 
              {
                  Write-Host $file
              }
          }

この例では、Foreach ループは $file 変数のプロパティを使用して、比較操作 ($file.length -gt 100KB) を実行します。$file 変数には、Get-ChildItem コマンドレットから返されるオブジェクト内のすべてのプロパティが含まれています。そのため、ファイル名だけを返すことができます。次の例では、Windows PowerShell はステートメント リスト内の長さと最終アクセス時刻を返します。

          foreach ($file in Get-ChildItem)
          {
              if ($file.length -gt 100KB) 
              {
                  Write-Host $file
                  Write-Host $file.length
                  Write-Host $file.lastaccesstime
              }
          }

この例では、ステートメント リスト内のコマンドを 1 つだけ実行するように制限されません。

Foreach ループの外側の変数を使用し、その変数をループ内で増分することもできます。次の例では、サイズが 100 KB を超えるファイルをカウントしています。

          $i = 0
          foreach ($file in Get-ChildItem)
          {
              if ($file.length -gt 100KB) 
              {
                  Write-Host $file "file size:" ($file.length / 
          1024).ToString("F0") KB
                  $i = $i + 1
              }
          }
          if ($i -ne 0)
          {
              Write-Host
              Write-Host $i " file(s) over 100 KB in the current 
          directory."}
          else 
          {
              Write-Host "No files greater than 100 KB in the current 
          directory."
          }

前の例では、$i 変数をループの外側で 0 に設定し、100 KB を超えるファイルが見つかるたびにループ内で増分しています。ループが終了すると、If ステートメントは $i の値を評価して 100 KB を超えるすべてのファイルの数を表示します。あるいは、100 KB を超えるファイルは見つからなかったことを示すメッセージを表示します。

前の例では、ファイルの長さの結果を書式設定する方法も示しています。

          ($file.length / 1024).ToString("F0")

結果をバイト単位ではなくキロバイト単位で示すため、値が 1,024 で除算されます。その結果、値は固定小数点の書式指定子を使用して書式設定され、結果から 10 進値が削除されます。0 の場合、書式指定子には小数点以下桁数が表示されません。

コマンド パイプライン内の Foreach ステートメント

Foreach がコマンド パイプラインに表示されている場合、Windows PowerShell は foreach エイリアスを使用して ForEach-Object コマンドを呼び出します。コマンド パイプラインで foreach エイリアスを使用する場合は、Foreach ステートメントを使用する場合のように、($<item> in $<collection>) 構文を含めないようにします。これは、パイプラインの prior コマンドがこの情報を提供するためです。コマンド パイプラインで使用する場合の foreach エイリアスの構文は次のとおりです。

          <command> | foreach {<command_block>}

たとえば、次のコマンドの Foreach ループは、ワーキング セット (メモリ使用量) が 20 メガバイト (MB) を超えるプロセスを表示します。

Get-Process コマンドは、コンピューター上のすべてのプロセスを取得します。Foreach エイリアスは、各プロセスでスクリプト ブロック内のコマンドを順に実行します。

If ステートメントは、ワーキング セット (WS) が 20 メガバイトよりも大きいプロセスを選択します。Write-Host コマンドレットは、プロセスの名前を書き込み、その後にコロンを続けます。バイト単位で格納されているワーキング セット値を 1 メガバイトで除算して、メガバイト単位のワーキング セット値を取得します。次に、結果を倍精度浮動小数点から文字列に変換します。値を小数位のない固定小数点数 (F0)、空白区切り文字 ("")、および "MB" として表示します。

          Write-Host "Processes with working sets greater than 20 MB."
          Get-Process | foreach { 
             if ($_.WS -gt 20MB)
             { Write-Host $_.name ": " ($_.WS/1MB).ToString("F0") MB -Separator ""}
          }

Foreach エイリアスは、開始コマンド ブロック、中間コマンド ブロック、および終了コマンド ブロックもサポートしています。開始および終了のコマンド ブロックは 1 回だけ実行され、中間コマンド ブロックは Foreach ループがコレクションまたは配列をステップ実行するたびに実行されます。

foreach エイリアスの構文は、開始、中間、終了の各コマンド ブロックがあるコマンド パイプラインで使用する場合、次のようになります。

          <command> | foreach {<beginning command_block>}{<middle 
          command_block>}{<ending command_block>}

次の例では、開始、中間、終了の各コマンド ブロックの使用方法を示します。

          Get-ChildItem | foreach {
          $fileCount = $directoryCount = 0}{
          if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}}{
          "$directoryCount directories and $fileCount files"}

開始ブロックでは、2 つの変数を作成し、それぞれ 0 に初期化します。

          {$fileCount = $directoryCount = 0}

中間ブロックでは、Get-ChildItem から返される各項目がディレクトリなのかファイルなのかを評価します。

          {if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}}

返される項目がディレクトリである場合は、$directoryCount 変数が 1 ずつ増分されます。項目がディレクトリでない場合は、$fileCount 変数が 1 ずつ増分されます。中間ブロックがループ操作を完了し、操作結果を返した後で、終了ブロックが実行されます。

          {"$directoryCount directories and $fileCount files"}

開始、中間、終了の各コマンド ブロック構造とパイプライン演算子を使用すると、前に示した例を次のように書き直して 100 KB を超えるファイルを検出できます。

          Get-ChildItem | foreach{
              $i = 0}{
              if ($_.length -gt 100KB)
              {
                  Write-Host $_.name "file size:" ($_.length / 
          1024).ToString("F0") KB
                  $i++
              }
              }{
              if ($i -ne 0)
              {
                  Write-Host
                  Write-Host "$i file(s) over 100 KB in the current 
          directory."
              }
              else 
              {
              Write-Host "No files greater than 100 KB in the current 
          directory."}
              }

次の例では、通常と異なるか一貫性なく空白が挿入された関数定義があるために複数行にまたがって関数名を宣言している場合でも、スクリプトおよびスクリプト モジュールで使用される関数を返す関数が、MoveNext メソッド (For ループの "skip X" と同様に機能) と、foreach スクリプト ブロック内の $foreach 変数の Current プロパティを使用する方法を示します。この例は、スクリプトまたはスクリプト モジュールで使用されている関数にコメントがある場合にも機能します。

         function Get-FunctionPosition {
             [CmdletBinding()]
             [OutputType('FunctionPosition')]
             param(
                 [Parameter(Position=0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
                 [ValidateNotNullOrEmpty()]
                 [Alias('PSPath')]
                 [System.String[]]
                 $Path
             )
             process {
                 try {
                     $filesToProcess = if ($_ -is [System.IO.FileSystemInfo]) {
                         $_
                     } else {
                         Get-Item -Path $Path
                     }
                     foreach ($item in $filesToProcess) {
                         if ($item.PSIsContainer -or $item.Extension -notin @('.ps1','.psm1')) {
                             continue
                         }
                         $tokens = $errors = $null
                         $ast = [System.Management.Automation.Language.Parser]::ParseFile($item.FullName,([REF]$tokens),([REF]$errors))
                         if ($errors) {
                             Write-Warning "File '$($item.FullName)' has $($errors.Count) parser errors."
                         }
                         :tokenLoop foreach ($token in $tokens) {
                             if ($token.Kind -ne 'Function') {
                                 continue
                             }
                             $position = $token.Extent.StartLineNumber
                             do {
                                 if (-not $foreach.MoveNext()) {
                                     break tokenLoop
                                 }
                                 $token = $foreach.Current
                             } until ($token.Kind -in @('Generic','Identifier'))
                             $functionPosition = [pscustomobject]@{
                                       Name = $token.Text
                                 LineNumber = $position
                                       Path = $item.FullName
                             }
                             Add-Member -InputObject $functionPosition -TypeName FunctionPosition -PassThru
                         }
                     }
                 } catch {
                     throw
                 }
             }
         }

関連項目

about_Automatic_Variables

about_If

Foreach-Object