본문으로 건너뛰기

PowerShell 다시 배우기 4 - 개체와 파이프라인

PowerShell 다시 배우기 4/11

글자가 아니라 개체가 흐른다

다른 셸의 파이프에는 글자가 흐른다. 그래서 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에서 좋은 명령을 만드는 첫째 요령이다.

다음 글: 연산자

이 글을 가리키는 글