글자가 아니라 개체가 흐른다
다른 셸의 파이프에는 글자가 흐른다. 그래서 awk로 몇 번째 칸을 잘라 내고, 칸 너비가 바뀌면 스크립트가 깨진다. PowerShell의 파이프라인에는 개체가 흐른다. 화면에 보이는 표는 그 개체를 사람이 보기 좋게 그린 것일 뿐이다.
Get-ChildItem $PSHOME -File | Sort-Object Length -Descending | Select-Object -First 3
Length라는 칸을 글자 위치로 찾지 않는다. 파일 개체의 Length 속성을 이름으로 가리킨다.
Get-Member: 무엇이 들어 있나
개체에 어떤 속성과 메서드가 있는지는 Get-Member가 보여 준다. 화면의 표에는 속성 몇 개만 나오지만 실제로는 훨씬 많다.
Get-ChildItem $PSHOME -File | Get-Member -MemberType Property
한 개체의 속성을 전부 값과 함께 보고 싶으면 이렇게 한다.
Get-Item $PSHOME/pwsh.dll | Select-Object *
고르고, 거르고, 정렬하고
자주 쓰는 다섯 명령이다.
| 명령 | 하는 일 |
|---|---|
Where-Object | 조건에 맞는 것만 남긴다 |
Select-Object | 속성을 고르거나 앞뒤 몇 개만 남긴다 |
Sort-Object | 정렬한다 |
Group-Object | 같은 값끼리 묶는다 |
Measure-Object | 개수, 합, 평균, 최대, 최소를 낸다 |
Get-ChildItem $PSHOME -File |
Group-Object Extension -NoElement |
Sort-Object Count -Descending |
Select-Object -First 3
Count Name
----- ----
231 .dll
16 .so
3 .json
Where-Object는 조건이 하나면 짧게, 여럿이면 스크립트 블록으로 쓴다. 블록 안에서 $_는 지금 지나가는 개체다.
Get-Process | Where-Object CPU -GT 10
Get-Process | Where-Object { $_.CPU -gt 10 -and $_.Name -like 'p*' }
없는 속성은 만들어 붙인다
Select-Object에 해시 테이블을 주면 계산한 값을 새 속성으로 붙일 수 있다.
Get-ChildItem $PSHOME -File |
Sort-Object Length -Descending |
Select-Object -First 3 Name, @{ Name = 'SizeMB'; Expression = { [math]::Round($_.Length / 1MB, 1) } }
Name SizeMB
---- ------
System.Management.Automation.dll 27.300
Microsoft.CodeAnalysis.CSharp.dll 20.300
System.Private.CoreLib.dll 16.300
1MB, 1GB 같은 단위는 숫자로 바로 쓸 수 있다.
하나씩 처리하기
ForEach-Object는 지나가는 개체마다 블록을 실행한다. 속성 하나만 꺼낼 때는 이름만 적어도 된다.
Get-ChildItem *.log | ForEach-Object { Compress-Archive $_ "$($_.BaseName).zip" }
Get-ChildItem *.log | ForEach-Object Name
여럿을 한꺼번에: -Parallel
하나에 오래 걸리는 일을 여러 대상에 할 때는 -Parallel을 붙인다. 블록이 각각 다른 스레드에서 돈다.
Measure-Command {
1..6 | ForEach-Object -Parallel { Start-Sleep -Milliseconds 500; $_ } -ThrottleLimit 6
} | ForEach-Object TotalSeconds
0.849
차례로 하면 3초 걸릴 일이 0.85초에 끝났다. 알아 둘 것이 둘 있다.
- 블록은 바깥의 변수를 보지 못한다. 바깥 변수는
$using:이름으로 가져온다. - 스레드를 만드는 비용이 있다. 하나에 몇 밀리초면 끝나는 일은 병렬이 오히려 느리다. 네트워크 요청, 원격 명령, 파일 압축처럼 기다리는 시간이 긴 일에 쓴다.
$timeout = 1
'web1', 'web2', 'db1' | ForEach-Object -Parallel {
[pscustomobject]@{
Name = $_
Online = Test-Connection $_ -Count 1 -TimeoutSeconds $using:timeout -Quiet
}
}
Format 명령은 맨 끝에만
Format-Table, Format-List는 개체를 화면용 서식 개체로 바꾼다. 그 뒤에는 원래의 속성이 없다. 그래서 파이프라인의 마지막에만 쓴다.
# 틀림: 정렬할 Length가 이미 없다
Get-ChildItem | Format-Table Name, Length | Sort-Object Length
# 맞음
Get-ChildItem | Sort-Object Length | Format-Table Name, Length
파일로 내보낼 때는 서식 명령이 아니라 변환 명령을 쓴다.
Get-Process | Select-Object Name, Id, CPU | Export-Csv ./processes.csv
Get-Process | Select-Object Name, Id, CPU | ConvertTo-Json | Set-Content ./processes.json
개체를 직접 만들기
여러 값을 묶어 돌려줄 때는 [pscustomobject]를 쓴다. 2010년의 New-Object PSObject -Property @{...}보다 짧고 빠르며, 속성의 순서가 적은 그대로 유지된다.
[pscustomobject]@{
Name = 'talsu'
Lang = 'PowerShell'
Year = 2026
}
함수가 글자를 Write-Host로 찍는 대신 이런 개체를 돌려주면, 쓰는 사람이 정렬하고 거르고 CSV로 내보내는 일을 알아서 할 수 있다. PowerShell에서 좋은 명령을 만드는 첫째 요령이다.
다음 글: 연산자