about_For

about_For

조건 검사 결과에 따라 명령을 반복해서 실행할 수 있는 언어 명령 for 문(일명 for 루프)을 소개해 드릴게요. 배열 같은 값의 묶음을 순회하면서 그중 일부만 골라 다루고 싶을 때 특히 유용한 문법이에요.

출처: PowerShell 공식 문서 — about_For

본문

짧은 설명

지정한 조건이 $true인 동안 명령 블록 안의 문장을 실행하는 언어 명령을 설명해요.

자세한 설명

for 문은 조건이 $true로 평가되는 동안 명령 블록 안의 명령을 반복 실행하는 루프를 만드는 언어 구조예요.

for 루프의 대표적인 용도는 배열의 값들을 순회하면서 그중 일부 값만 골라 다루는 거예요. 배열의 모든 값을 다 순회하고 싶다면 보통은 foreach 문을 쓰는 걸 권장해요.

구문

for 문의 구문은 다음과 같아요.

for (<Init>; <Condition>; <Repeat>)
{
    <Statement list>
}
  • Init 자리에는 루프가 시작되기 전에 실행할 명령이 한 개 이상 들어가요. 보통 이 부분에서 변수를 만들고 시작값으로 초기화해요. 이 변수가 이후 for 문의 조건 부분에서 검사할 기준이 되는 셈이죠.
  • Condition 자리는 for 문에서 $true 또는 $false 불리언 값으로 평가되는 부분이에요. PowerShell은 for 루프가 돌 때마다 이 조건을 평가해요. 조건이 $true면 명령 블록 안의 명령을 실행하고 다시 조건을 평가해요. 여전히 $trueStatement list 안의 명령을 또 실행하고, 조건이 $false가 될 때까지 이 과정을 반복해요.
  • Repeat 자리에는 루프가 반복될 때마다 쉼표로 구분해서 실행할 명령이 한 개 이상 들어가요. 보통 Condition 부분에서 검사하는 변수를 수정하는 데 써요.
  • Statement list 자리에는 루프에 진입하거나 반복될 때마다 실행하는 명령이 한 개 이상 들어가요. 내용은 중괄호로 감싸요.

여러 연산 지원

Init 문에서 여러 개의 할당 연산을 쓸 때는 다음 구문을 지원해요.

# Comma separated assignment expressions enclosed in parentheses.
for (($i = 0), ($j = 0); $i -lt 10; $i++)
{
    "`$i:$i"
    "`$j:$j"
}

# Sub-expression using the semicolon to separate statements.
for ($($i = 0;$j = 0); $i -lt 10; $i++)
{
    "`$i:$i"
    "`$j:$j"
}

Repeat 문에서 여러 개의 할당 연산을 쓸 때는 다음 구문을 지원해요.

# Comma separated assignment expressions.
for (($i = 0), ($j = 0); $i -lt 10; $i++, $j++)
{
    "`$i:$i"
    "`$j:$j"
}

# Comma separated assignment expressions enclosed in parentheses.
for (($i = 0), ($j = 0); $i -lt 10; ($i++), ($j++))
{
    "`$i:$i"
    "`$j:$j"
}

# Sub-expression using the semicolon to separate statements.
for ($($i = 0;$j = 0); $i -lt 10; $($i++;$j++))
{
    "`$i:$i"
    "`$j:$j"
}

참고 전치/후치 증감 연산 이외의 연산은 모든 구문에서 동작하지 않을 수 있어요.

여러 개의 Condition을 쓸 때는 아래 예시처럼 논리 연산자를 사용해요.

for (($i = 0), ($j = 0); $i -lt 10 -and $j -lt 10; $i++,$j++)
{
    "`$i:$i"
    "`$j:$j"
}

자세한 내용은 about_Logical_Operators 문서를 참고해 주세요.

구문 예시

for 문은 최소한 Init, Condition, Repeat 부분을 감싸는 괄호와 Statement list 부분에서 중괄호로 감싼 명령이 필요해요.

아래 예시들은 의도적으로 코드를 for 문 밖에 두고 보여드릴게요. 이후 예시에서는 코드를 for 문 안으로 통합할 거예요.

예를 들어 다음 for 문은 CTRL+C를 눌러 직접 명령에서 빠져나올 때까지 $i 변수의 값을 계속 표시해요.

$i = 1
for (;;)
{
    Write-Host $i
}

문장 목록에 명령을 추가해서 루프가 돌 때마다 $i 값을 1씩 증가시킬 수도 있어요. 다음 예시를 볼게요.

for (;;)
{
    $i++; Write-Host $i
}

CTRL+C로 명령에서 빠져나오기 전까지 이 문장은 루프가 돌 때마다 1씩 증가하는 $i 값을 계속 표시할 거예요.

변수 값을 for 문의 문장 목록 부분에서 바꾸는 대신 Repeat 부분을 활용할 수도 있어요.

$i=1
for (;;$i++)
{
    Write-Host $i
}

이 문장 역시 CTRL+C로 빠져나오기 전까지 무한히 반복돼요.

condition을 사용하면 for 루프를 끝낼 수 있어요. Condition 부분에 조건을 넣으면 되고, 조건이 $false로 평가되면 루프가 종료돼요.

다음 예시에서는 $i 값이 10 이하인 동안 for 루프가 실행돼요.

$i=1
for(;$i -le 10;$i++)
{
    Write-Host $i
}

변수를 for 문 밖에서 만들고 초기화하는 대신, Init 부분을 이용해 for 루프 안에서 초기화할 수도 있어요.

for($i=1; $i -le 10; $i++){Write-Host $i}

Init, Condition, Repeat 부분을 구분할 때 세미콜론 대신 캐리지 리턴을 쓸 수도 있어요. 다음 예시는 이 대체 구문을 사용한 for 문이에요.

for ($i = 0
  $i -lt 10
  $i++){
  $i
}

이 대체 형식의 for 문은 PowerShell 스크립트 파일과 PowerShell 명령 프롬프트에서 모두 동작해요. 다만 명령 프롬프트에서 대화형 명령을 입력할 때는 세미콜론을 쓰는 for 문 구문이 더 편해요.

for 루프는 배열이나 컬렉션의 값을 패턴에 맞춰 증가시킬 수 있어서 foreach 루프보다 유연해요. 다음 예시에서는 Repeat 부분에서 $i 변수를 2씩 증가시켜요.

for ($i = 0; $i -le 20; $i += 2)
{
    Write-Host $i
}

for 루프는 다음 예시처럼 한 줄로도 쓸 수 있어요.

for ($i = 0; $i -lt 10; $i++){Write-Host $i}

기능 예시

다음 예시는 for 루프로 파일 배열을 순회하며 파일 이름을 바꾸는 방법을 보여줘요. work_items 폴더의 파일들은 파일 이름이 작업 항목 ID로 되어 있어요. 루프가 파일들을 순회하면서 ID 번호가 다섯 자리로 0 패딩되도록 만들 거예요.

먼저 작업 항목 데이터 파일 목록을 가져와요. 모두 이름 형식이 <work-item-type>-<work-item-number>인 JSON 파일이에요. 파일 정보 객체를 $fileList 변수에 저장하고 나면 이름 순으로 정렬해서, 항목들이 유형별로 묶여 있지만 ID 순서는 뒤죽박죽이라는 걸 확인할 수 있어요.

$fileList = Get-ChildItem -Path ./work_items
$fileList | Sort-Object -Descending -Property Name
bug-219.json
bug-41.json
bug-500.json
bug-697.json
bug-819.json
bug-840.json
feat-176.json
feat-367.json
feat-373.json
feat-434.json
feat-676.json
feat-690.json
feat-880.json
feat-944.json
maint-103.json
maint-367.json
maint-454.json
maint-49.json
maint-562.json
maint-579.json

작업 항목을 알파벳·숫자 순으로 제대로 정렬하려면 작업 항목 번호를 0으로 패딩해야 해요.

이 코드는 먼저 숫자 접미사가 가장 긴 작업 항목을 찾아요. for 루프로 파일들을 순회하며 인덱스를 이용해 배열의 각 파일에 접근해요. 각 파일 이름을 정규식 패턴과 비교해서 작업 항목 번호를 정수 대신 문자열로 추출해요. 그다음 작업 항목 번호들의 길이를 비교해서 가장 긴 번호를 찾아요.

# Default the longest numeral count to 1, since it can't be smaller.
$longestNumeralCount = 1

# Regular expression to find the numerals in the filename - use a template
# to simplify updating the pattern as needed.
$patternTemplate = '-(?<WorkItemNumber>{{{0},{1}}})\\.json'
$pattern         =  $patternTemplate -f $longestNumeralCount

# Iterate, checking the length of the work item number as a string.
for (
    $i = 0                 # Start at zero for first array item.
    $i -lt $fileList.Count # Stop on the last item in the array.
    $i++                   # Increment by one to step through the array.
) {
    if ($fileList[$i].Name -match $pattern) {
        $numeralCount = $Matches.WorkItemNumber.Length
        if ($numeralCount -gt $longestNumeralCount) {
            # Count is higher, check against it for remaining items.
            $longestNumeralCount = $numeralCount
            # Update the pattern to speed up the search, ignoring items
            # with a smaller numeral count using pattern matching.
            $pattern = $patternTemplate -f $longestNumeralCount
        }
    }
}

이제 작업 항목의 최대 숫자 길이를 알았으니, 파일들을 다시 순회하면서 필요한 만큼 패딩해 이름을 바꿀 수 있어요. 다음 코드 조각은 파일 목록을 다시 순회하면서 패딩을 적용해요. 이번에는 최대 길이보다 숫자 길이가 짧은 파일만 처리하도록 또 다른 정규식 패턴을 사용해요.

# Regular expression to find the numerals in the filename, but only if the
# numeral count is smaller than the longest numeral count.
$pattern = $patternTemplate -f 1, ($longestNumeralCount - 1)
for (
    $i = 0                 # Start at zero for first array item.
    $i -lt $fileList.Count # Stop on the last item in the array.
    $i++                   # Increment by one to step through the array.
) {
    # Get the file from the array to process
    $file = $fileList[$i]

    # If the file doesn't need to be renamed, continue to the next file
    if ($file.Name -notmatch $pattern) {
        continue
    }

    # Get the work item number from the regular expression, create the
    # padded string from it, and define the new filename by replacing
    # the original number string with the padded number string.
    $workItemNumber = $Matches.WorkItemNumber
    $paddedNumber   = "{0:d$longestNumeralCount}" -f $workItemNumber
    $paddedName     = $file.Name -replace $workItemNumber, $paddedNumber

    # Rename the file with the padded work item number.
    $file | Rename-Item -NewName $paddedName
}

파일 이름을 바꿨으니 파일 목록을 다시 가져와서 이전 파일들과 새 파일들을 이름 순으로 정렬할 수 있어요. 다음 코드 조각은 파일들을 다시 가져와 새 배열에 담아 처음의 객체 집합과 비교해요. 그리고 두 배열을 모두 정렬해서 정렬된 배열들을 $sortedOriginal$sortedPadded라는 새 변수에 저장해요. 마지막으로 for 루프로 배열들을 순회하면서 다음 속성을 가진 객체를 출력해요.

  • Index — 정렬된 배열에서 현재 인덱스를 나타내요.
  • Original — 현재 인덱스에 있는 원래 파일 이름의 정렬된 배열 항목이에요.
  • Padded — 현재 인덱스에 있는 패딩된 파일 이름의 정렬된 배열 항목이에요.
$paddedList = Get-ChildItem -Path ./work_items

# Sort both file lists by name.
$sortedOriginal = $fileList    | Sort-Object -Property Name
$sortedPadded   = $renamedList | Sort-Object -Property Name

# Iterate over the arrays and output an object to simplify comparing how
# the arrays were sorted before and after padding the work item numbers.
for (
  $i = 0
  $i -lt $fileList.Count
  $i++
) {
    [pscustomobject] @{
        Index    = $i
        Original = $sortedOriginal[$i].Name
        Padded   = $sortedPadded[$i].Name
    }
}
Index Original       Padded
----- --------       ------
    0 bug-219.json   bug-00041.json
    1 bug-41.json    bug-00219.json
    2 bug-500.json   bug-00500.json
    3 bug-697.json   bug-00697.json
    4 bug-819.json   bug-00819.json
    5 bug-840.json   bug-00840.json
    6 feat-176.json  feat-00176.json
    7 feat-367.json  feat-00367.json
    8 feat-373.json  feat-00373.json
    9 feat-434.json  feat-00434.json
   10 feat-676.json  feat-00676.json
   11 feat-690.json  feat-00690.json
   12 feat-880.json  feat-00880.json
   13 feat-944.json  feat-00944.json
   14 maint-103.json maint-00049.json
   15 maint-367.json maint-00103.json
   16 maint-454.json maint-00367.json
   17 maint-49.json  maint-00454.json
   18 maint-562.json maint-00562.json
   19 maint-579.json maint-00579.json

출력 결과를 보면 패딩을 적용한 뒤의 작업 항목이 기대한 순서대로 정렬된 걸 확인할 수 있어요.

더 알아보기