PowerShell ForEach 루프: 유형 및 모범 사례

PowerShell 스크립트 작성을 처음 시작할 때는 반드시 컬렉션에서 여러 항목을 처리해야 할 때가 옵니다. 그 때 PowerShell foreach 루프를 파해치고 그 기능을 배워야 합니다.

거의 모든 프로그래밍 언어에는 루프라는 구조가 있고, PowerShell도 예외는 아닙니다. PowerShell에서 가장 인기 있는 루프 유형 중 하나는 foreach 루프입니다. 기본적으로 foreach 루프는 전체 항목 컬렉션을 읽고 각 항목마다 코드를 실행합니다.

PowerShell foreach 루프에서 초보자에게 가장 혼란스러운 측면은 선택할 수 있는 옵션이 많다는 것입니다. 컬렉션의 각 항목을 처리하는 방법은 하나뿐이 아니라 세 가지가 있습니다!

본 문서에서는 각 유형의 foreach 루프가 어떻게 작동하며 어떤 경우에 어떤 것을 사용해야 하는지에 대해 배우게 될 것입니다. 이 문서를 마치면 각 유형의 foreach 루프에 대해 잘 이해하게 될 것입니다.

PowerShell ForEach 루프 기본 사항

PowerShell에서 가장 일반적으로 사용하는 루프 유형 중 하나는 foreach 루프입니다. foreach 루프는 객체 집합을 읽고 마지막 항목을 처리할 때 완료됩니다. 일반적으로 읽히는 객체의 집합은 배열이나 해시 테이블로 표현됩니다.

참고: 반복(iteration)은 루프의 각 실행을 가리키는 프로그래밍 용어입니다. 루프가 한 번 주기를 완료할 때마다 이를 반복(iteration)이라고 합니다. 일련의 객체에 대해 루프를 실행하는 행위는 일반적으로 집합을 반복(iterate)한다고 표현됩니다.

아마도 파일 시스템에 퍼져있는 몇 개의 폴더를 통해 텍스트 파일을 만들어야 할 것입니다. 예를 들어 폴더 경로는 C:\Folder, C:\Program Files\Folder2C:\Folder3입니다. 루프가 없는 경우, 우리는 세 번 Add-Content cmdlet을 참조해야 합니다.

Add-Content -Path 'C:\Folder\textfile.txt' -Value 'This is the content of the file'
Add-Content -Path 'C:\Program Files\Folder2\textfile.txt' -Value 'This is the content of the file'
Add-Content -Path 'C:\Folder2\textfile.txt' -Value 'This is the content of the file'

이 명령 참조 간의 유일한 차이점은 Path 값입니다. Path 값은 이 중에서 변하는 유일한 값입니다.

많은 코드가 중복됩니다. 타이핑하는 시간을 낭비하고, 나중에 문제가 발생할 수 있습니다. 대신에 변경되는 항목만 포함하는 “집합”을 만들어야 합니다. 이 예제에서는 배열을 사용합니다.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

이제 이러한 경로가 하나의 “집합” 또는 배열에 저장되었습니다. 이제 루프를 사용하여 각 요소를 반복적으로 사용할 수 있습니다. 그러나 그 전에, PowerShell을 처음 사용하는 사람들에게 혼란을 주는 주제를 언급할 때입니다. 다른 종류의 루프와 달리 foreach 루프는 같은 것들 중 하나가 아닙니다.

PowerShell에서는 기술적으로 세 가지 유형의 foreach 루프가 있습니다. 각각 사용법은 비슷하지만 차이점을 이해하는 것이 중요합니다.

foreach 문

첫 번째 유형의 foreach 루프는 입니다. foreach는 cmdlet도 함수도 아닌 PowerShell 내부 키워드입니다. foreach 문은 항상 다음과 같은 형식으로 사용됩니다: foreach ($i in $array).

위의 예제를 사용하면, $i 변수는 반복자를 나타내거나 $path의 각 항목의 값으로 반복합니다. 각 요소를 반복합니다.

반복자 변수는 반드시 $i일 필요는 없습니다. 변수 이름은 아무 것이나 될 수 있습니다.

아래 예제에서는 다음과 같이 Add-Content 참조를 반복하는 동일한 작업을 수행할 수 있습니다.

# 폴더의 배열 생성
$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

# 각 폴더에 동일한 파일을 생성하기 위해 반복 수행
foreach ($i in $folders) {
    Add-Content -Path "$i\SampleFile.txt" -Value "This is the content of the file"
}

foreach ForEach-Object cmdlet을 사용하는 것보다 빠른 대안으로 알려져 있습니다.

ForEach-Object CmdLet

foreach가 문이고 한 가지 방식으로만 사용할 수 있는 반면, ForEach-Object는 매개변수가 있는 cmdlet으로 여러 가지 방식으로 사용할 수 있습니다. foreach 문처럼 ForEach-Object cmdlet은 일련의 개체를 반복합니다. 다만 이번에는 그 개체 집합과 각 개체에 대해 수행할 작업을 매개변수로 전달합니다. 아래 예시와 같습니다.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')
$folders | ForEach-Object (Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file")

참고: 혼란스럽게도 ForEach-Object cmdlet에는 foreach라는 별칭이 있습니다. “foreach”라는 용어가 어떻게 호출되느냐에 따라 foreach 문이 실행되거나 ForEach-Object가 실행됩니다.

A good way to differentiate these situations is to notice if the term “foreach” is followed by ($someVariable in $someSet). Otherwise, in any other context, the code author is probably using the alias for ForEach-Object.

foreach() 메서드

PowerShell v4에서 소개된 가장 최신 foreach 루프 중 하나는 foreach() 메서드입니다. 이 메서드는 배열이나 컬렉션 개체에 존재합니다. foreach() 메서드에는 각 반복마다 수행할 작업을 포함하는 표준 스크립트 블록 매개변수가 있습니다. 다른 메서드들처럼요.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')
$folders.ForEach({
	Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file"
})

foreach() 메서드와 가장 큰 차이점은 내부에서 어떻게 작동하는지입니다.

대용량 데이터에 대해서도 상당히 빠르게 작동하며, 가능하다면 다른 두 가지 방법 대신 이 메서드를 사용하는 것이 권장됩니다.

미니 프로젝트: 서버 이름 집합 반복

PowerShell에서 루프를 사용하는 가장 일반적인 용도 중 하나는 어떤 소스에서 서버들의 집합을 읽고 각 서버에 대해 어떤 작업을 수행하는 것입니다. 첫 번째 미니 프로젝트로, 한 줄에 하나씩 서버 이름을 읽어 텍스트 파일에서 각 서버에 핑을 보내서 온라인인지 여부를 확인하는 코드를 작성해 보겠습니다.

List of server names in a text file

이 워크플로우에서 어떤 유형의 루프가 가장 적합한지 생각해 보세요.

서버 “집합”이라는 단어를 언급했습니다. 그 자체로 힌트입니다. 이미 지정된 수의 서버 이름이 있으며, 각 서버에 어떤 작업을 수행하려고 합니다. 이는 가장 일반적으로 사용되는 PowerShell 루프인 foreach 루프를 시도해 볼 좋은 기회입니다.

첫 번째 작업은 코드에서 서버 이름 집합을 생성하는 것입니다. 이를 수행하는 가장 일반적인 방법은 배열을 생성하는 것입니다. 우리에게 운이 좋은 것은 Get-Content가 기본적으로 텍스트 파일의 각 줄을 하나의 요소로 갖는 배열을 반환한다는 것입니다.

먼저, 모든 서버 이름으로 배열을 생성하고 $servers라고 부르십시오.

$servers = Get-Content .\servers.txt

이제 배열이 생성되었으므로 각 서버가 온라인인지 확인해야 합니다. 서버의 연결을 테스트하는 훌륭한 cmdlet은 Test-Connection이라고 합니다. 이 cmdlet은 컴퓨터의 온라인 여부를 확인하기 위해 몇 가지 연결 테스트를 수행합니다.

foreach 루프 내에서 각 파일 라인을 읽기 위해 Test-Connection을 실행하여 $server 변수로 표시된 각 서버 이름을 전달할 수 있습니다. 이는 PowerShell에서 텍스트 파일을 순환하는 방법입니다. 아래 예제에서 이 작업이 어떻게 수행되는지 볼 수 있습니다.

foreach ($server in $servers) {
	try {
		$null = Test-Connection -ComputerName $server -Count 1 -ErrorAction STOP
		Write-Output "$server - OK"
	}
	catch {
		Write-Output "$server - $($_.Exception.Message)"
	}
}

foreach 루프가 실행되면 다음과 같이 보일 것입니다:

Looping Test-Connection through a list of server names

이제 서버 이름이 포함된 텍스트 파일의 연결을 성공적으로 테스트했습니다! 이 시점에서 코드를 변경하지 않고도 텍스트 파일 내에 서버 이름을 자유롭게 추가하거나 제거할 수 있습니다.

우리가 주장한 것을 성공적으로 실천했습니다, DRY 방법론.

ForEach PowerShell 예제들

ForEach 루프를 사용하는 방법에 대한 몇 가지 예를 살펴보겠습니다. 이 예는 실제 사용 사례를 기반으로 하며 개념을 보여주며 요구 사항에 맞게 수정할 수 있습니다.

예제 1: ForEach 문을 사용하여 디렉토리의 각 하위 폴더에 파일 생성하기

이 예제는 PowerShell에서 디렉토리의 각 폴더에 대해 foreach 문을 일반적으로 사용하는 방법을 보여줍니다.

가정해보겠습니다. C:\ARCHIVE_VOLUMES 폴더 안에는 십 개의 하위 폴더가 있습니다. 각 하위 폴더는 매일 백업되는 아카이브 볼륨을 나타냅니다. 각 백업이 완료된 후에는 BackupState.txt라는 이름의 파일이 각 폴더 안에 생성되며 백업된 날짜가 포함됩니다.

아래의 예제에서 이러한 내용을 어떻게 표시할 수 있는지 확인할 수 있습니다.

Sub-directories under C:\ARCHIVE_VOLUMES

아래 스크립트는 다음 세 가지 작업을 수행합니다:

  • C:\ARCHIVE_VOLUMES 안의 모든 하위 폴더 목록을 가져옵니다.
  • 각 폴더를 순환합니다.
  • 현재 날짜와 시간 값을 포함하는 BackupState.txt라는 텍스트 파일을 생성합니다.

아래의 예제는 foreach 문을 사용합니다.

# 최상위 폴더 정의
$TOP_FOLDER = "C:\ARCHIVE_VOLUMES"

# 모든 하위 폴더 재귀적으로 가져오기
$Child_Folders = Get-ChildItem -Path $TOP_FOLDER -Recurse | Where-Object { $_.PSIsContainer -eq $true }

# 각 하위 폴더에 텍스트 파일 생성하고 현재 날짜/시간을 값으로 추가
foreach ($foldername in $Child_Folders.FullName) {
   (get-date -Format G) | Out-File -FilePath "$($foldername)\BackupState.txt" -Force
}

Get-ChildItem cmdlet을 사용하여 각 하위 폴더 내에서 파일이 생성되거나 업데이트되었는지 확인할 수 있습니다.

Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include backupstate.txt | Select-Object Fullname,CreationTime,LastWriteTime,Length

아래 스크린샷은 각 하위 디렉터리에서 찾은 모든 “BackupState.txt” 파일을 표시하는 스크립트의 출력을 보여줍니다.

A text file is created in each sub-directory

예제 2: 하위 디렉터리의 각 텍스트 파일의 내용 읽기

다음으로 PowerShell foreach 파일을 사용하는 방법을 보여주기 위해 아래 스크립트는 예제 1에서 만든 각 “BackupState.txt” 파일을 읽습니다.

  • 각 하위 디렉터리에서 모든 “BackupState.txt” 파일을 재귀적으로 찾습니다.
  • foreach 문을 사용하여 각 텍스트 파일을 읽어 “최근 백업 시간” 값을 가져옵니다.
  • 결과를 화면에 표시합니다.
## C:\ARCHIVE_VOLUMES에서 모든 BackupState.txt 파일 찾기
$files = Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include 'BackupState.txt' | Select-Object DirectoryName,FullName

## 각 파일의 내용 읽기
foreach ($file in $files) {
    Write-Output ("$($file.DirectoryName) last backup time - " + (Get-Content $file.FullName))
}

PowerShell 세션에서 스크립트를 실행하면 아래 스크린샷과 유사한 출력이 표시됩니다. 이는 PowerShell이 파일을 순환하고 내용을 읽고 출력을 표시하는 것을 보여줍니다.

Using foreach to loop through files and read its contents.

예제 3: 서비스 가져오기 및 ForEach-Object CmdLet을 사용하여 시작하기

시스템 관리자는 종종 서비스의 상태를 확인하고 실패한 서비스를 해결하기 위해 수동 또는 자동 워크플로우를 트리거해야 할 수 있습니다. ForEach-Object cmdlet을 사용한 샘플 스크립트를 살펴보겠습니다.

이 예제에서 아래 스크립트는 다음을 수행합니다:

  • 자동 시작으로 구성된 서비스 목록을 가져옵니다. 그러나 현재 실행 중인 상태가 아닙니다.
  • 다음으로, 목록의 항목들은 ForEach-Object cmdlet에 파이프되어 각 서비스를 시작하려고 시도합니다.
  • A message of either success or failed is displayed depending on the result of the Start-Service command.
## 중지된 자동 서비스의 목록을 가져옵니다.
$services = Get-Service | Where-Object {$.StartType -eq 'Automatic' -and $.Status -ne 'Running'}

## 각 서비스 개체를 파이프라인에 전달하고 Foreach-Object cmdlet으로 처리합니다.
$services | ForEach-Object {
    try {
        Write-Host "Attempting to start '$($.DisplayName)'"
        Start-Service -Name $.Name -ErrorAction STOP
        Write-Host "SUCCESS: '$($.DisplayName)' has been started"
    } catch {
        Write-output "FAILED: $($.exception.message)"
    }
}

스크립트를 실행하면 아래 스크린샷과 같이 출력됩니다. 보시다시피, 각 서비스에 시작 명령이 발행되었습니다. 일부는 성공적으로 시작되었지만 일부는 시작에 실패했습니다.

Using the ForEach-Object loop to start services

예제 4: ForEach() 메서드를 사용하여 CSV에서 데이터 읽기

CSV 파일에서 데이터를 사용하는 것은 시스템 관리자들 사이에서 인기가 있습니다. CSV 파일에 레코드를 넣으면 Import-CSVForEach 조합을 사용하여 대량 작업을 쉽게 실행할 수 있습니다. 이 조합은 주로 Active Directory에 여러 사용자를 생성하는데 사용됩니다.

다음 예제에서는, 두 개의 열 – FirstnameLastname – 가 있는 CSV 파일이 있다고 가정합니다. 이 CSV 파일에는 생성할 새 사용자의 이름이 기록되어 있어야 합니다. CSV 파일은 아래와 같이 보일 것입니다.

"Firstname","Lastname"
"Grimm","Reaper"
"Hell","Boy"
"Rick","Rude"

이제 다음 스크립트 블록을 진행하세요. 먼저, Import-CSV cmdlet에 콘텐츠 경로를 전달하여 CSV 파일을 가져옵니다. 그런 다음, foreach() 메서드를 사용하여 이름을 반복하고 Active Directory에 새 사용자를 생성하세요.

# CSV 파일에서 Firstname과 Lastname 목록 가져오기
$newUsers = Import-Csv -Path .\Employees.csv

Add-Type -AssemblyName System.Web

# 목록 처리
$newUsers.foreach(
    {
        # 무작위 비밀번호 생성
        $password = [System.Web.Security.Membership]::GeneratePassword((Get-Random -Minimum 20 -Maximum 32), 3)
        $secPw = ConvertTo-SecureString -String $password -AsPlainText -Force

        # 사용자 이름 구성
        $userName = '{0}{1}' -f $_.FirstName.Substring(0, 1), $_.LastName

        # 새 사용자 속성 구성
        $NewUserParameters = @{
            GivenName       = $_.FirstName
            Surname         = $_.LastName
            Name            = $userName
            AccountPassword = $secPw
        }

        try {
            New-AdUser @NewUserParameters -ErrorAction Stop
            Write-Output "User '$($userName)' has been created."
        }
        catch {
            Write-Output $_.Exception.Message
        }
    }
)

실행하면 CSV 파일의 각 행에 대해 AD 사용자가 생성됩니다!

요약

PowerShell의 foreach 루프의 논리는 다른 프로그래밍 언어와 다르지 않습니다. 단지 사용 방법과 특정 작업에 어떤 foreach 루프 변형을 선택하는지에 따라 달라집니다.

이 문서에서는 PowerShell에서 사용 가능한 foreach 루프의 다른 유형과 사용할 때 고려해야 할 사항을 배웠습니다. 또한 다양한 샘플 시나리오를 사용하여 세 가지 유형의 foreach 루프가 어떻게 작동하는지 확인했습니다.

더 읽기

Source:
https://adamtheautomator.com/powershell-foreach/