PowerShellで使えるループ構文まとめ:基本から応用まで

Windows

PowerShellでスクリプトを書いていると、「同じことを何度も繰り返したい」という場面によく出会います。

たとえば、フォルダ内のすべてのファイルを処理したり、1から100まで数えたり、条件を満たすまで処理を続けたりしたいときです。

そんなときに使うのが「ループ構文」です。ループ構文を使えば、手作業では時間がかかる繰り返し作業を、プログラムに任せることができます。

PowerShellには、いくつかの種類のループ構文があります。

それぞれに得意な場面があるので、使い分けを覚えると、もっと効率的なスクリプトが書けるようになります。

この記事では、PowerShellの代表的なループ構文を、基本から応用まで丁寧に解説します。


スポンサーリンク

ループの種類と使い分け

PowerShellで使える4つのループ

PowerShellには、主に4つのループ構文があります:

ループの種類いつ使う?特徴
for回数が決まっているときカウンターを使って繰り返す
foreach配列やリストの要素を処理するときデータの数だけ自動で繰り返す
while条件を満たす間続けたいとき条件をチェックしてから実行
do最低1回は実行したいときまず実行してから条件をチェック

どれを選べばいい?

迷ったときの選び方

  • 決まった回数を繰り返したい → forループ
  • ファイルや配列の中身を順番に処理したい → foreachループ
  • 条件を満たす間続けたい → whileループ
  • 最低1回は実行してから判断したい → doループ

forループ

どんなもの?

forループは、「○回繰り返す」というように、回数が決まっている処理に最適です。カウンターを使って、開始から終了まで順番に処理していきます。

基本の書き方

for (初期値; 条件; 増加値) {
    # 実行したい処理
}

実際に使ってみよう

例1:1から5まで数える

for ($i = 1; $i -le 5; $i++) {
    Write-Output "現在の数: $i"
}

実行結果:

現在の数: 1
現在の数: 2
現在の数: 3
現在の数: 4
現在の数: 5

例2:カウントダウン

for ($countdown = 10; $countdown -ge 1; $countdown--) {
    Write-Output "カウントダウン: $countdown"
    Start-Sleep -Seconds 1  # 1秒待つ
}
Write-Output "発射!"

例3:ファイル名を連番で作成

for ($fileNum = 1; $fileNum -le 3; $fileNum++) {
    $fileName = "test_file_$fileNum.txt"
    New-Item -Path $fileName -ItemType File
    Write-Output "$fileName を作成しました"
}

forループの仕組み

  1. 初期値$i = 1 – カウンターの開始値を設定
  2. 条件$i -le 5 – この条件が真の間、繰り返し続ける
  3. 増加値$i++ – 毎回、カウンターをどう変更するか

よく使う演算子

  • -le:以下(Less than or Equal)
  • -lt:未満(Less Than)
  • -ge:以上(Greater than or Equal)
  • -gt:より大きい(Greater Than)

実務での活用例

例:複数のサーバーを順番に確認

$serverList = @("server01", "server02", "server03")

for ($i = 0; $i -lt $serverList.Count; $i++) {
    $serverName = $serverList[$i]
    Write-Output "サーバー $($i + 1): $serverName を確認中..."
    
    # サーバーの状態確認(例)
    Test-NetConnection -ComputerName $serverName -Port 80
}

ここがポイント!

  • カウンターの名前は$iがよく使われますが、$count$numなど、わかりやすい名前でもOK
  • 条件を間違えると、無限ループになることがあるので注意
  • $i++$i = $i + 1と同じ意味

foreachループ

どんなもの?

foreachループは、配列やリストの要素を順番に処理するときに使います。「この箱の中身を、ひとつずつ取り出して処理する」というイメージです。

基本の書き方

foreach ($要素 in $配列) {
    # 各要素に対する処理
}

実際に使ってみよう

例1:果物のリストを表示

$fruits = @("りんご", "みかん", "ばなな", "ぶどう")

foreach ($fruit in $fruits) {
    Write-Output "好きな果物: $fruit"
}

実行結果:

好きな果物: りんご
好きな果物: みかん
好きな果物: ばなな
好きな果物: ぶどう

例2:ファイル一覧の取得と表示

# 現在のフォルダにあるテキストファイルを取得
$textFiles = Get-ChildItem -Path "*.txt"

foreach ($file in $textFiles) {
    $fileSize = [Math]::Round($file.Length / 1KB, 2)
    Write-Output "ファイル名: $($file.Name), サイズ: ${fileSize}KB"
}

例3:複数のプロセスを確認

$processNames = @("notepad", "calculator", "explorer")

foreach ($processName in $processNames) {
    $process = Get-Process -Name $processName -ErrorAction SilentlyContinue
    
    if ($process) {
        Write-Output "$processName は実行中です"
    } else {
        Write-Output "$processName は実行されていません"
    }
}

foreachの便利な機能

パイプラインでも使える

# こんな書き方もできます
@("ファイル1.txt", "ファイル2.txt", "ファイル3.txt") | 
    ForEach-Object { Write-Output "処理中: $_" }

ハッシュテーブル(辞書)の処理

$userInfo = @{
    "名前" = "太郎"
    "年齢" = "25"
    "職業" = "エンジニア"
}

foreach ($key in $userInfo.Keys) {
    Write-Output "$key : $($userInfo[$key])"
}

実務での活用例

例:複数のWebサイトの状態確認

$websites = @(
    "https://www.google.com",
    "https://www.microsoft.com",
    "https://www.github.com"
)

foreach ($site in $websites) {
    try {
        $response = Invoke-WebRequest -Uri $site -TimeoutSec 10
        Write-Output "$site : 正常 (ステータス: $($response.StatusCode))"
    }
    catch {
        Write-Output "$site : エラー ($($_.Exception.Message))"
    }
}

ここがポイント!

  • 配列の要素数を気にしなくていいので、簡潔に書ける
  • $_は「現在処理中の要素」を表す特別な変数
  • ファイルやフォルダの処理によく使われる

whileループ

どんなもの?

whileループは、「条件を満たしている間、処理を続ける」ループです。何回繰り返すかわからないけれど、ある条件になったら止めたい場合に使います。

基本の書き方

while (条件) {
    # 条件が真の間、実行される処理
}

実際に使ってみよう

例1:カウンターを使った基本的な例

$count = 1

while ($count -le 3) {
    Write-Output "実行回数: $count"
    $count++  # カウンターを増やすのを忘れずに!
}

実行結果:

実行回数: 1
実行回数: 2
実行回数: 3

例2:ファイルができるまで待つ

$filePath = "C:\temp\important_file.txt"

Write-Output "ファイルの作成を待っています..."

while (-not (Test-Path $filePath)) {
    Write-Output "まだファイルがありません。5秒後に再確認します..."
    Start-Sleep -Seconds 5
}

Write-Output "ファイルが見つかりました!"

例3:ユーザーからの入力待ち

$userInput = ""

while ($userInput -ne "終了") {
    $userInput = Read-Host "コマンドを入力してください(終了するには '終了' と入力)"
    
    if ($userInput -eq "時刻") {
        Write-Output "現在時刻: $(Get-Date)"
    }
    elseif ($userInput -eq "日付") {
        Write-Output "今日の日付: $(Get-Date -Format 'yyyy年MM月dd日')"
    }
    elseif ($userInput -ne "終了") {
        Write-Output "不明なコマンドです: $userInput"
    }
}

Write-Output "プログラムを終了します"

注意:無限ループに気をつけよう

危険な例

# これは無限ループになります!
$count = 1
while ($count -le 3) {
    Write-Output $count
    # $count++ を忘れているので、永遠に終わらない
}

安全な書き方

$count = 1
$maxLoop = 100  # 安全装置

while ($count -le 3 -and $count -le $maxLoop) {
    Write-Output $count
    $count++
}

実務での活用例

例:サービスの起動を待つ

$serviceName = "Spooler"  # プリンタサービス
$maxWaitTime = 60  # 最大60秒待つ
$waitedTime = 0

Write-Output "$serviceName サービスの起動を待っています..."

while ((Get-Service $serviceName).Status -ne "Running" -and $waitedTime -lt $maxWaitTime) {
    Write-Output "まだ起動していません... ($waitedTime 秒経過)"
    Start-Sleep -Seconds 5
    $waitedTime += 5
}

if ((Get-Service $serviceName).Status -eq "Running") {
    Write-Output "$serviceName サービスが起動しました!"
} else {
    Write-Output "タイムアウト: $serviceName サービスが起動しませんでした"
}

ここがポイント!

  • 必ず条件が「偽」になる仕組みを作る
  • 無限ループ対策として、最大回数や時間制限を設ける
  • ネットワークやファイルの処理でよく使われる

do-while / do-until ループ

どんなもの?

doループは、「まず処理を実行してから、条件をチェックする」ループです。

つまり、条件に関係なく、最低1回は必ず実行されます。

2つの種類

  • do-while:条件が真の間、繰り返す
  • do-until:条件が偽の間、繰り返す

基本の書き方

# do-while の場合
do {
    # 最低1回は実行される処理
} while (条件)

# do-until の場合  
do {
    # 最低1回は実行される処理
} until (条件)

実際に使ってみよう

例1:do-while の基本

$count = 0

do {
    Write-Output "実行回数: $count"
    $count++
} while ($count -lt 3)

実行結果:

実行回数: 0
実行回数: 1
実行回数: 2

例2:do-until の基本

$count = 0

do {
    Write-Output "実行回数: $count"
    $count++
} until ($count -eq 3)

実行結果は同じ:

実行回数: 0
実行回数: 1
実行回数: 2

例3:ユーザー入力の検証

do {
    $age = Read-Host "年齢を入力してください(18〜100)"
    $ageNumber = [int]$age
    
    if ($ageNumber -lt 18 -or $ageNumber -gt 100) {
        Write-Output "18歳以上100歳以下で入力してください"
    }
} while ($ageNumber -lt 18 -or $ageNumber -gt 100)

Write-Output "入力された年齢: $age 歳"

whileとdoの違い

while ループ:条件をチェックしてから実行

$condition = $false
while ($condition) {
    Write-Output "実行されません"  # 1回も実行されない
}

do ループ:実行してから条件をチェック

$condition = $false
do {
    Write-Output "必ず1回は実行されます"  # 必ず1回実行される
} while ($condition)

実務での活用例

例:設定ファイルの作成

$configPath = "C:\temp\config.ini"

do {
    # 設定ファイルを作成する処理
    Write-Output "設定ファイルを作成しています..."
    
    try {
        @"
[設定]
サーバー名=localhost
ポート=8080
タイムアウト=30
"@ | Out-File -FilePath $configPath -Encoding UTF8
        
        Write-Output "設定ファイルを作成しました"
        $created = $true
    }
    catch {
        Write-Output "作成に失敗しました。5秒後に再試行します..."
        Start-Sleep -Seconds 5
        $created = $false
    }
} while (-not $created)

ここがポイント!

  • 最低1回は実行したい処理に最適
  • ユーザー入力の検証によく使われる
  • do-whiledo-untilは条件の意味が逆なので注意

breakとcontinueの使い方

どんなもの?

breakcontinueは、ループの流れを制御するための特別なコマンドです。

  • break:ループを途中で終了させる
  • continue:現在の繰り返しをスキップして、次の繰り返しに進む

breakの使い方

基本例

for ($i = 1; $i -le 10; $i++) {
    if ($i -eq 5) {
        Write-Output "5に到達したので終了します"
        break
    }
    Write-Output "現在の数: $i"
}
Write-Output "ループ終了後の処理"

実行結果:

現在の数: 1
現在の数: 2
現在の数: 3
現在の数: 4
5に到達したので終了します
ループ終了後の処理

実用例:エラーが発生したら処理を停止

$files = @("file1.txt", "file2.txt", "important.txt", "file3.txt")

foreach ($file in $files) {
    Write-Output "$file を処理中..."
    
    # 重要なファイルに到達したら停止
    if ($file -eq "important.txt") {
        Write-Output "重要ファイルに到達。安全のため処理を停止します"
        break
    }
    
    # ファイル処理のシミュレーション
    Write-Output "$file の処理が完了しました"
}

continueの使い方

基本例

for ($i = 1; $i -le 5; $i++) {
    if ($i -eq 3) {
        Write-Output "3はスキップします"
        continue
    }
    Write-Output "処理中: $i"
}

実行結果:

処理中: 1
処理中: 2
3はスキップします
処理中: 4
処理中: 5

実用例:特定の条件のファイルだけ処理

$files = Get-ChildItem -Path "C:\temp\*.*"

foreach ($file in $files) {
    # フォルダはスキップ
    if ($file.PSIsContainer) {
        Write-Output "$($file.Name) はフォルダなのでスキップ"
        continue
    }
    
    # サイズが0のファイルもスキップ
    if ($file.Length -eq 0) {
        Write-Output "$($file.Name) は空ファイルなのでスキップ"
        continue
    }
    
    # 条件を満たすファイルだけ処理
    Write-Output "$($file.Name) を処理します (サイズ: $($file.Length) バイト)"
}

ネストしたループでの使い方

複数のループが入れ子になっている場合、breakcontinueはどのループに影響するでしょうか?

for ($i = 1; $i -le 3; $i++) {
    Write-Output "外側ループ: $i"
    
    for ($j = 1; $j -le 3; $j++) {
        if ($j -eq 2) {
            Write-Output "  内側ループの2をスキップ"
            continue  # 内側ループの次の繰り返しへ
        }
        
        if ($i -eq 2 -and $j -eq 3) {
            Write-Output "  内側ループを終了"
            break  # 内側ループを終了
        }
        
        Write-Output "  内側ループ: $j"
    }
}

実務での活用例

例:ログファイルから特定の情報を抽出

$logFile = "C:\logs\application.log"
$errorCount = 0
$maxErrors = 5

if (Test-Path $logFile) {
    $logLines = Get-Content $logFile
    
    foreach ($line in $logLines) {
        # 空行はスキップ
        if ([string]::IsNullOrWhiteSpace($line)) {
            continue
        }
        
        # エラーログを発見
        if ($line -match "ERROR") {
            Write-Output "エラー発見: $line"
            $errorCount++
            
            # エラーが多すぎる場合は処理を停止
            if ($errorCount -ge $maxErrors) {
                Write-Output "エラーが$maxErrors 件に達したため、処理を停止します"
                break
            }
        }
    }
}

ここがポイント!

  • break:「もうやめる」
  • continue:「今回はパス、次へ」
  • ネストしたループでは、最も内側のループに影響する
  • エラーハンドリングや条件分岐でよく使われる

実践的な活用例

例1:複数サーバーの監視スクリプト

# 監視対象のサーバーリスト
$servers = @("web01", "web02", "db01", "app01")
$checkInterval = 30  # 30秒間隔
$maxRetries = 3

Write-Output "サーバー監視を開始します..."

# 無限ループで監視継続
while ($true) {
    Write-Output "$(Get-Date): 監視開始"
    
    foreach ($server in $servers) {
        $retryCount = 0
        $isOnline = $false
        
        # 各サーバーを最大3回まで試行
        do {
            $retryCount++
            Write-Output "  $server を確認中 (試行: $retryCount)"
            
            $pingResult = Test-NetConnection -ComputerName $server -Port 80 -WarningAction SilentlyContinue
            
            if ($pingResult.TcpTestSucceeded) {
                Write-Output "  ✓ $server はオンラインです"
                $isOnline = $true
                break
            } else {
                Write-Output "  ✗ $server に接続できません"
                
                if ($retryCount -lt $maxRetries) {
                    Write-Output "    5秒後に再試行します..."
                    Start-Sleep -Seconds 5
                }
            }
        } while ($retryCount -lt $maxRetries -and -not $isOnline)
        
        # 最終的に接続できない場合
        if (-not $isOnline) {
            Write-Output "  ⚠️ $server への接続に失敗しました($maxRetries 回試行)"
        }
    }
    
    Write-Output "次の監視まで $checkInterval 秒待機..."
    Start-Sleep -Seconds $checkInterval
}

例2:ファイル整理の自動化

# 整理対象のフォルダ
$sourceFolder = "C:\Downloads"
$photoFolder = "C:\Photos"
$documentFolder = "C:\Documents"

# 写真とドキュメントの拡張子
$photoExtensions = @(".jpg", ".jpeg", ".png", ".gif", ".bmp")
$documentExtensions = @(".pdf", ".docx", ".xlsx", ".pptx", ".txt")

Write-Output "ファイル整理を開始します..."

# フォルダ内のファイルを取得
$files = Get-ChildItem -Path $sourceFolder -File

if ($files.Count -eq 0) {
    Write-Output "整理対象のファイルがありません"
} else {
    Write-Output "$($files.Count) 個のファイルを確認します"
    
    foreach ($file in $files) {
        $extension = $file.Extension.ToLower()
        $destinationFolder = $null
        
        # ファイルの種類を判定
        if ($photoExtensions -contains $extension) {
            $destinationFolder = $photoFolder
            $fileType = "写真"
        }
        elseif ($documentExtensions -contains $extension) {
            $destinationFolder = $documentFolder
            $fileType = "ドキュメント"
        }
        else {
            Write-Output "  $($file.Name) : 対象外のファイル形式です"
            continue
        }
        
        # 移動先フォルダが存在しない場合は作成
        if (-not (Test-Path $destinationFolder)) {
            New-Item -Path $destinationFolder -ItemType Directory -Force
            Write-Output "  フォルダを作成しました: $destinationFolder"
        }
        
        # ファイルを移動
        try {
            $destinationPath = Join-Path $destinationFolder $file.Name
            Move-Item -Path $file.FullName -Destination $destinationPath
            Write-Output "  ✓ $($file.Name) を $fileType フォルダに移動しました"
        }
        catch {
            Write-Output "  ✗ $($file.Name) の移動に失敗: $($_.Exception.Message)"
        }
    }
}

Write-Output "ファイル整理が完了しました"

例3:定期的なバックアップスクリプト

# バックアップ設定
$sourceFolder = "C:\ImportantData"
$backupBaseFolder = "D:\Backups"
$maxBackups = 7  # 保持するバックアップ数
$backupInterval = 3600  # 1時間間隔(秒)

Write-Output "自動バックアップシステムを開始します..."

while ($true) {
    $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
    $backupFolder = Join-Path $backupBaseFolder "Backup_$timestamp"
    
    Write-Output "$(Get-Date): バックアップを開始します"
    
    try {
        # バックアップフォルダを作成
        New-Item -Path $backupFolder -ItemType Directory -Force | Out-Null
        
        # ファイルをコピー
        $sourceFiles = Get-ChildItem -Path $sourceFolder -Recurse -File
        $totalFiles = $sourceFiles.Count
        $currentFile = 0
        
        foreach ($file in $sourceFiles) {
            $currentFile++
            
            # 進捗表示
            if ($currentFile % 10 -eq 0 -or $currentFile -eq $totalFiles) {
                $progress = [Math]::Round(($currentFile / $totalFiles) * 100, 1)
                Write-Output "  進捗: $progress% ($currentFile/$totalFiles)"
            }
            
            # 相対パスを保持してコピー
            $relativePath = $file.FullName.Substring($sourceFolder.Length + 1)
            $destinationPath = Join-Path $backupFolder $relativePath
            $destinationDir = Split-Path $destinationPath -Parent
            
            if (-not (Test-Path $destinationDir)) {
                New-Item -Path $destinationDir -ItemType Directory -Force | Out-Null
            }
            
            Copy-Item -Path $file.FullName -Destination $destinationPath
        }
        
        Write-Output "  ✓ バックアップが完了しました: $backupFolder"
        
        # 古いバックアップを削除
        $existingBackups = Get-ChildItem -Path $backupBaseFolder -Directory | 
                          Where-Object { $_.Name -like "Backup_*" } |
                          Sort-Object CreationTime -Descending
        
        if ($existingBackups.Count -gt $maxBackups) {
            $backupsToDelete = $existingBackups | Select-Object -Skip $maxBackups
            
            foreach ($oldBackup in $backupsToDelete) {
                Remove-Item -Path $oldBackup.FullName -Recurse -Force
                Write-Output "  古いバックアップを削除: $($oldBackup.Name)"
            }
        }
    }
    catch {
        Write-Output "  ✗ バックアップに失敗: $($_.Exception.Message)"
    }
    
    Write-Output "次のバックアップまで $($backupInterval / 60) 分待機..."
    Start-Sleep -Seconds $backupInterval
}

まとめ

PowerShellのループ構文は、繰り返し処理を効率的に行うための強力な機能です。

それぞれの特徴を理解して、適切に使い分けることが大切です。

使い分けのまとめ

構文こんなときに使うメリット
for決まった回数の繰り返しシンプルで分かりやすい
foreach配列やファイルリストの処理要素数を気にしなくていい
whileユーザー入力の検証最初の一回は必ず実行

コメント

タイトルとURLをコピーしました