about_For
about_For
조건 검사 결과에 따라 명령을 반복해서 실행할 수 있는 언어 명령 for 문(일명 for 루프)을 소개해 드릴게요. 배열 같은 값의 묶음을 순회하면서 그중 일부만 골라 다루고 싶을 때 특히 유용한 문법이에요.
본문
짧은 설명
지정한 조건이 $true인 동안 명령 블록 안의 문장을 실행하는 언어 명령을 설명해요.
자세한 설명
for 문은 조건이 $true로 평가되는 동안 명령 블록 안의 명령을 반복 실행하는 루프를 만드는 언어 구조예요.
for 루프의 대표적인 용도는 배열의 값들을 순회하면서 그중 일부 값만 골라 다루는 거예요. 배열의 모든 값을 다 순회하고 싶다면 보통은 foreach 문을 쓰는 걸 권장해요.
구문
for 문의 구문은 다음과 같아요.
for (<Init>; <Condition>; <Repeat>)
{
<Statement list>
}
Init자리에는 루프가 시작되기 전에 실행할 명령이 한 개 이상 들어가요. 보통 이 부분에서 변수를 만들고 시작값으로 초기화해요. 이 변수가 이후for문의 조건 부분에서 검사할 기준이 되는 셈이죠.Condition자리는for문에서$true또는$false불리언 값으로 평가되는 부분이에요. PowerShell은for루프가 돌 때마다 이 조건을 평가해요. 조건이$true면 명령 블록 안의 명령을 실행하고 다시 조건을 평가해요. 여전히$true면Statement 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
출력 결과를 보면 패딩을 적용한 뒤의 작업 항목이 기대한 순서대로 정렬된 걸 확인할 수 있어요.