ANSI 터미널 지원

ANSI 터미널 지원

PowerShell이 화면에 색을 입히고 글자를 굵게·밑줄로 꾸미는 걸 직접 제어하고 싶은 적 있나요? 그걸 가능하게 해 주는 게 바로 ANSI 이스케이프 시퀀스예요. 이 문서는 PowerShell이 이 ANSI 시퀀스를 얼마나 폭넓게 지원하는지, 그리고 그걸 손쉽게 다루게 해 주는 전용 변수 $PSStyle을 어떻게 쓰는지 차근차근 설명해 드릴게요.

출처: Microsoft Learn

본문

간단한 설명

PowerShell에서 ANSI 이스케이프 시퀀스를 어디까지 지원하는지에 대한 설명이에요.

자세한 설명

PowerShell에는 ANSI 이스케이프 시퀀스를 이용해, PowerShell을 호스팅하고 있는 터미널 애플리케이션의 출력 렌더링을 제어하는 기능이 여러 가지 있어요.

PowerShell 7.2에서 새로운 자동 변수 $PSStyle과 함께, ANSI로 꾸며진 텍스트 출력을 지원하도록 PowerShell 엔진이 개편되었어요.

ANSI 터미널 지원

이 ANSI 기능들은 xterm 기반 터미널과 호환되도록 설계되었어요. 자세한 내용은 xterm을 참고하세요.

Windows 10 이상에서는 Windows Console Host가 xterm과 호환되며, Windows Terminal 애플리케이션 역시 xterm과 호환돼요.

macOS에서는 기본 터미널 애플리케이션이 xterm과 호환돼요.

Linux의 경우 배포판마다 서로 다른 터미널 애플리케이션을 쓰고 있어요. 자기 배포판에 맞는 터미널 애플리케이션을 고르려면 배포판 문서를 확인해 보세요.

$PSStyle

$PSStyle 변수는 다음과 같은 속성을 갖고 있어요.

  • Reset - 모든 장식을 꺼요
  • Blink - 깜빡임을 켜요
  • BlinkOff - 깜빡임을 꺼요
  • Bold - 굵게를 켜요
  • BoldOff - 굵게를 꺼요
  • Dim - 흐리게를 켜요 (PowerShell 7.4에서 추가)
  • DimOff - 흐리게를 꺼요 (PowerShell 7.4에서 추가)
  • Hidden - 숨김을 켜요
  • HiddenOff - 숨김을 꺼요
  • Reverse - 반전을 켜요
  • ReverseOff - 반전을 꺼요
  • Italic - 이탤릭을 켜요
  • ItalicOff - 이탤릭을 꺼요
  • Underline - 밑줄을 켜요
  • UnderlineOff - 밑줄을 꺼요
  • Strikethrough - 취소선을 켜요
  • StrikethroughOff - 취소선을 꺼요
  • OutputRendering - 출력 렌더링을 언제 쓸지 제어해요
  • Formatting - 출력 스트림의 기본 포맷을 제어하는 중첩 개체예요
  • Progress - 진행률 막대 렌더링을 제어하는 중첩 개체예요
  • FileInfo - FileInfo 개체의 색칠을 제어하는 중첩 개체예요
  • Foreground - 전경 색을 제어하는 중첩 개체예요
  • Background - 배경 색을 제어하는 중첩 개체예요

기본 구성원들은 이름에 대응하는 ANSI 이스케이프 시퀀스 문자열을 돌려줘요. 값은 바꿀 수 있어서 원하는 대로 커스터마이징이 가능해요. 예를 들어 굵게를 밑줄로 바꿔버릴 수도 있죠. 속성 이름이 직관적이라서 탭 완성 기능을 쓰면서 꾸며진 문자열을 만들기가 훨씬 수월해요.

"$($PSStyle.Background.BrightCyan)Power$($PSStyle.Underline)$($PSStyle.Bold)Shell$($PSStyle.Reset)"

다음 구성원들은 ANSI 포맷을 언제, 어떻게 쓸지 제어해요.

  • $PSStyle.OutputRenderingSystem.Management.Automation.OutputRendering 열거형으로, 값은 다음과 같아요.

  • ANSI: ANSI 이스케이프 시퀀스를 항상 그대로 통과시켜요.

    중요

    아래쪽 파이프라인에서 실행될 예정인 출력을 파일이나 파이프라인으로 리다이렉트할 때는 ANSI 모드를 쓰는 게 좋아요. 그래야 출력이 변경되지 않아요. 다른 모드를 쓰면 ANSI 이스케이프 시퀀스가 제거되면서 출력이 바뀌고, 실행 동작까지 달라질 수 있어요.

  • PlainText: ANSI 이스케이프 시퀀스를 항상 제거해서 순수 텍스트만 남겨요. 원격 세션에서 원격 호스트가 PlainText로 설정되어 있으면, 로컬 클라이언트로 되돌려 보내기 전에 출력에서 ANSI 이스케이프 시퀀스를 떼어내요. 이 모드는 텍스트 포맷과 렌더링 시퀀스만 제거하는 거라, 포괄적인 터미널 제어 방역(새니타이제이션) 메커니즘은 아니에요.

  • Host: 기본 동작이에요. 리다이렉트되거나 파이프된 출력에서는 ANSI 이스케이프 시퀀스가 제거돼요. 자세한 내용은 Redirecting output을 참고하세요.

  • $PSStyle.Background$PSStyle.Foreground 구성원은 16가지 표준 콘솔 색에 대한 ANSI 이스케이프 시퀀스를 담은 문자열이에요.

    • Black
    • BrightBlack
    • White
    • BrightWhite
    • Red
    • BrightRed
    • Magenta
    • BrightMagenta
    • Blue
    • BrightBlue
    • Cyan
    • BrightCyan
    • Green
    • BrightGreen
    • Yellow
    • BrightYellow

    값은 바꿀 수 있고, ANSI 이스케이프 시퀀스를 얼마든지 담을 수 있어요. 24비트 색을 지정할 수도 있는데 그때 쓰는 게 FromRgb() 메서드예요. FromRgb() 호출 방법은 두 가지가 있어요.

    string FromRgb(byte red, byte green, byte blue)
    string FromRgb(int rgb)
    

    아래 예시 둘 다 배경색을 24비트 색인 Beige로 설정해요.

    $PSStyle.Background.FromRgb(245, 245, 220)
    $PSStyle.Background.FromRgb(0xf5f5dc)
    
  • $PSStyle.Formatting은 debug, error, verbose, warning 메시지와 목록·테이블 헤더의 기본 포맷을 제어하는 중첩 개체예요. 굵게나 밑줄 같은 속성도 제어할 수 있어요. 포맷 렌더링의 색을 관리하던 기존 방식인 $Host.PrivateData를 이 변수가 대체해요. $Host.PrivateData는 하위 호환성을 위해 계속 존재하지만, $PSStyle.Formatting과는 연결되지 않아요. $PSStyle.Formatting은 다음 구성원을 갖고 있어요.

    • FormatAccent - 목록 항목의 포맷
    • ErrorAccent - 오류 메타데이터의 포맷
    • Error - 오류 메시지의 포맷
    • Warning - 경고 메시지의 포맷
    • Verbose - 자세한(verbose) 메시지의 포맷
    • Debug - 디버그 메시지의 포맷
    • TableHeader - 테이블 헤더의 포맷
    • CustomTableHeaderLabel - 실제로는 개체의 속성이 아닌 테이블 헤더의 포맷
    • FeedbackName - 피드백 제공자 이름의 포맷 (PowerShell 7.4에서 실험 기능으로 추가)
    • FeedbackText - 피드백 메시지의 포맷 (PowerShell 7.4에서 실험 기능으로 추가)
    • FeedbackAction - 피드백 제공자의 제안 동작 포맷 (PowerShell 7.4에서 실험 기능으로 추가)
  • $PSStyle.Progress는 진행률 보기 막대 렌더링을 제어할 수 있게 해 줘요.

    • Style - 렌더링 스타일을 지정하는 ANSI 문자열이에요.
    • MaxWidth - 보기의 최대 너비를 설정해요. 기본값은 120이고, 최솟값은 18이에요.
    • View - MinimalClassic 값을 갖는 열거형이에요. Classic은 변경 없는 기존 렌더링이고, Minimal은 한 줄짜리 최소 렌더링이에요. 기본값은 Minimal이에요.
    • UseOSCIndicator - 기본값은 $false예요. OSC 표시기를 지원하는 터미널에서는 $true로 설정하면 돼요.

    참고

    호스트가 Virtual Terminal을 지원하지 않으면 $PSStyle.Progress.View는 자동으로 Classic으로 설정돼요.

    아래 예시는 렌더링 스타일을 최소(minimal) 진행률 막대로 설정해요.

    $PSStyle.Progress.View = 'Minimal'
    
  • $PSStyle.FileInfo는 FileInfo 개체의 색칠을 제어하는 중첩 개체예요.

    • Directory - 디렉터리의 색을 지정하는 내장 구성원
    • SymbolicLink - 심볼릭 링크의 색을 지정하는 내장 구성원
    • Executable - 실행 파일의 색을 지정하는 내장 구성원
    • Extension - 파일 확장자별 색을 정의할 때 쓰는 구성원. Extension 구성원은 압축 파일과 PowerShell 파일 확장자에 대한 색을 기본으로 정의해 둬요.

    아래 예시는 다양한 FileInfo 설정과 특정 파일 확장자의 색을 바꾸는 방법을 보여줘요. 고른 색들은 밝은 터미널 배경에서 잘 보이도록 한 거예요.

    $PSStyle.FileInfo.Directory = $PSStyle.Background.FromRgb(0x2f6aff) +
        $PSStyle.Foreground.BrightWhite
    $PSStyle.FileInfo.SymbolicLink = $PSStyle.Foreground.Cyan
    $PSStyle.FileInfo.Executable = $PSStyle.Foreground.BrightMagenta
    $PSStyle.FileInfo.Extension['.ps1'] = $PSStyle.Foreground.Cyan
    $PSStyle.FileInfo.Extension['.ps1xml'] = $PSStyle.Foreground.Cyan
    $PSStyle.FileInfo.Extension['.psd1'] = $PSStyle.Foreground.Cyan
    $PSStyle.FileInfo.Extension['.psm1'] = $PSStyle.Foreground.Cyan
    

ANSI 출력을 만드는 cmdlet

  • markdown cmdlet - Show-Markdown cmdlet은 markdown 텍스트가 담긴 파일의 내용을 표시해요. 출력은 서로 다른 스타일을 나타내는 ANSI 시퀀스로 렌더링돼요. 스타일 정의는 Get-MarkdownOptionSet-MarkdownOption cmdlet으로 관리할 수 있어요.

  • PSReadLine cmdlet - PSReadLine 모듈은 ANSI 시퀀스를 이용해 명령줄에서 PowerShell 구문 요소에 색을 입혀요. 색은 Get-PSReadLineOptionSet-PSReadLineOption으로 관리할 수 있어요.

  • Get-Error - Get-Error cmdlet은 Error 개체의 상세 보기를 돌려주는데, 읽기 쉽게 포맷되어 나와요.

  • Select-String - PowerShell 7.0부터 Select-String은 ANSI 시퀀스로 출력에서 일치하는 패턴을 강조 표시해요.

  • Write-Progress - 위에서 설명한 대로 ANSI 출력이 $PSStyle.Progress로 관리돼요. 자세한 내용은 Write-Progress를 참고하세요.

Host 모드에서 출력 리다이렉트

기본적으로 $PSStyle.OutputRenderingHost로 설정되어 있어요. 그 결과 리다이렉트되거나 파이프된 출력에서는 ANSI 이스케이프 시퀀스가 제거돼요.

OutputRendering은 Host, Out-File, Out-String에서의 렌더링에만 적용돼요. 네이티브 실행 파일의 출력에는 영향을 주지 않아요.

PowerShell 7.2.6은 다음과 같은 시나리오에 대해 Out-FileOut-String의 동작을 바꿨어요.

  • 입력 개체가 순수 문자열일 때, 이 cmdlet들은 OutputRendering 설정과 무관하게 문자열을 그대로 유지해요.

  • 입력 개체에 포맷 보기를 적용해야 할 때, 이 cmdlet들은 OutputRendering 설정에 따라 포맷 출력 문자열에서 이스케이프 시퀀스를 유지하거나 제거해요.

이건 PowerShell 7.2와 비교했을 때 이 cmdlet들의 중요한 변경(breaking change)이에요.

OutputRendering은 PowerShell 호스트 프로세스의 출력에는 적용되지 않아요. 예를 들어 명령줄에서 pwsh를 실행하고 출력을 리다이렉트하는 경우가 그렇죠.

아래 예시는 Linux에서 bash 위에서 PowerShell을 실행하는 상황이에요. Get-ChildItem cmdlet이 ANSI로 꾸며진 텍스트를 만들어 내는데, 리다이렉트는 PowerShell 호스트 밖인 bash 프로세스에서 일어나므로 출력은 OutputRendering의 영향을 받지 않아요.

pwsh -NoProfile -Command 'Get-ChildItem' > out.txt

out.txt의 내용을 열어 보면 ANSI 이스케이프 시퀀스가 그대로 보여요.

반대로 리다이렉트가 PowerShell 세션 안에서 일어나면 OutputRendering이 리다이렉트된 출력에 영향을 줘요.

pwsh -NoProfile -Command 'Get-ChildItem > out.txt'

이번엔 out.txt의 내용을 열어 보면 ANSI 이스케이프 시퀀스가 하나도 없어요.

ANSI 출력 끄기

ANSI 이스케이프 시퀀스 지원은 TERM 또는 NO_COLOR 환경 변수로 끌 수 있어요.

$Env:TERM의 값에 따라 동작이 이렇게 바뀌어요.

  • dumb - $Host.UI.SupportsVirtualTerminal = $false로 설정해요
  • xterm-mono - $PSStyle.OutputRendering = PlainText로 설정해요
  • xterm - $PSStyle.OutputRendering = PlainText로 설정해요

$Env:NO_COLOR가 존재하면 $PSStyle.OutputRenderingPlainText로 설정돼요. NO_COLOR 환경 변수에 대한 자세한 내용은 https://no-color.org/ 를 참고하세요.

C#에서 $PSStyle 사용하기

C# 개발자는 아래 예시처럼 PSStyle을 싱글턴(singleton)으로 접근할 수 있어요.

string output = $"{PSStyle.Instance.Foreground.Red}{PSStyle.Instance.Bold}Hello{PSStyle.Instance.Reset}";

PSStyle은 System.Management.Automation 네임스페이스에 존재해요.

PowerShell 엔진에는 다음 변경 사항이 포함되어 있어요.

  • PowerShell 포맷팅 시스템이 $PSStyle.OutputRendering을 존중하도록 갱신되었어요.

  • ANSI 이스케이프된 문자열을 다루는 StringDecorated 형식이 추가되었어요.

  • 문자열에 ESC 또는 C1 CSI 문자 시퀀스가 있으면 true를 돌려주는 IsDecorated 부울 속성이 추가되었어요.

  • 문자열의 Length 속성은 ANSI 이스케이프 시퀀스를 제외한 텍스트의 길이를 돌려줘요.

  • StringDecoratedSubstring(int contentLength) 메서드는 인덱스 0부터 ANSI 이스케이프 시퀀스에 속하지 않는 콘텐츠 길이까지의 부분 문자열을 돌려줘요. printed 문자 공간을 차지하지 않는 ANSI 이스케이프 시퀀스는 보존하면서 문자열을 자르기 위해 테이블 포맷에서 필요해요.

  • 문자열의 ToString() 메서드는 그대로이며, 문자열의 일반 텍스트(plaintext) 버전을 돌려줘요.

  • 문자열의 ToString(bool Ansi) 메서드는 Ansi 매개 변수가 true이면 원시 ANSI가 포함된 문자열을 돌려줘요. 그렇지 않으면 ANSI 이스케이프 시퀀스가 제거된 일반 텍스트 버전을 돌려줘요.

  • FormatHyperlink(string text, uri link) 메서드는 하이퍼링크를 꾸미는 데 쓰는 ANSI 이스케이프 시퀀스가 포함된 문자열을 돌려줘요. Windows Terminal 같은 일부 터미널 호스트는 이 마크업을 지원해서, 렌더링된 텍스트를 터미널에서 클릭 가능하게 만들어 줘요.

PSStyle 클래스의 정적 메서드

PowerShell 7.4는 [System.Management.Automation.PSStyle] 클래스에 정적 메서드 세 개를 추가해요.

[System.Management.Automation.PSStyle] | Get-Member -Static -MemberType Method
   TypeName: System.Management.Automation.PSStyle

Name                               MemberType Definition
----                               ---------- ----------
Equals                             Method     static bool Equals(System.Object objA, System.Object objB)
MapBackgroundColorToEscapeSequence Method     static string MapBackgroundColorToEscapeSequence(System.ConsoleColor bac…
MapColorPairToEscapeSequence       Method     static string MapColorPairToEscapeSequence(System.ConsoleColor foregroun…
MapForegroundColorToEscapeSequence Method     static string MapForegroundColorToEscapeSequence(System.ConsoleColor for…
ReferenceEquals                    Method     static bool ReferenceEquals(System.Object objA, System.Object objB)

이 메서드들은 ConsoleColor 값을 전경·배경 색 또는 둘의 조합에 대한 ANSI 이스케이프 시퀀스로 변환하는 방법을 제공해요.

아래 예시들은 이 메서드들이 만들어 내는 ANSI 이스케이프 시퀀스를 보여줘요.

using namespace System.Management.Automation
[PSStyle]::MapBackgroundColorToEscapeSequence('Black') | Format-Hex
   Label: String (System.String) <3A04954D>

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 1B 5B 34 30 6D                                  �[40m
[PSStyle]::MapForegroundColorToEscapeSequence('Red') | Format-Hex
   Label: String (System.String) <38B50F41>

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 1B 5B 39 31 6D                                  �[91m
[PSStyle]::MapColorPairToEscapeSequence('Red','Black') | Format-Hex
   Label: String (System.String) <365A5875>

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 1B 5B 39 31 3B 34 30 6D                         �[91;40m

더 알아보기