• 이전 문서: [[/mac/terminal-guide/04]]
  • 다음 문서: [[/mac/terminal-guide/06]]

프롬프트를 꾸며 봅시다

이번 섹션은 [[/cmd/bash]] 사용자를 위한 것입니다. [[/cmd/zsh]] 사용자는 건너뛰어도 됩니다.

이번에는 프롬프트를 커스터마이징해보죠.

먼저 내가 어느 셸을 사용하고 있는지 확인해 봅시다.

echo $0

[[/cmd/zsh]]을 사용하고 있다면 다음과 같이 나올 겁니다.

$ echo $0
zsh

[[/cmd/bash]]를 사용하고 있다면 이렇게 나올 거고요.

$ echo $0
bash

이 섹션은 [[/cmd/bash]] 셸 사용자의 프롬프트 커스터마이즈를 다룹니다. [[/cmd/zsh]] 사용자는 이 섹션을 건너뛰어도 됩니다.

그러나 [[/cmd/zsh]]를 사용하고 있더라도 이 섹션을 따라해보고 싶다면 다음과 같이 [[/cmd/bash]] 서브셸을 실행한 다음 진행하면 됩니다.

bash

PS1

[[/cmd/bash]]는 많은 (환경) 변수들을 갖고 있는데 [[/cmd/bash/parameters#prompt]]{PS1}은 우리가 터미널을 사용하며 많이 보게 되는 프롬프트와 관련된 변수입니다.

시험삼아 나의 PS1이 어떻게 설정되어 있는지 확인해 보도록 합시다.

echo $PS1

저는 평소 사용하던 프롬프트를 이 가이드를 작성하기 위해 잠시 디폴트 설정으로 바꿔놓은 상태입니다. 그래서 다음과 같이 나왔습니다.

JohnGrib-Macmini:dotfiles johngrib$ echo $PS1
\h:\W \u$

출력된 내용은 다음을 의미합니다.

  • \h: 호스트 이름 (여기서는 JohnGrib-Macmini)
  • :: 구분 기호로 집어넣어준 것
  • \W: 작업 디렉토리 이름 (여기서는 dotfiles)
  • \u: 로그인한 사용자 이름 (여기서는 johngrib)
  • $: 프롬프트 종결 기호로 집어넣어준 것

PS1 변수를 변경해주면 프롬프트가 바뀌게 되어 있습니다.

다음 명령을 입력하면 PS1 변수를 변경할 수 있습니다.

PS1="hello prompt $ "

명령을 입력하고 나면 바로 다음 줄부터 프롬프트가 hello prompt $ 로 바뀌게 됩니다.

JohnGrib-Macmini:dotfiles johngrib$ PS1="hello prompt $ "
hello prompt $ 

PS1에 날짜와 시간 넣기

이번에는 프롬프트에 오늘 날짜와 시간이 나오도록 해봅시다. [[/cmd/date]]를 사용해 보도록 하죠.

date '+%Y-%m-%d-%a %T'
hello prompt $ date '+%Y-%m-%d-%a'
2024-01-29-Mon 23:25:24

년월일, 요일과 시간까지 잘 나오네요.

이 명령을 다음과 같이 PS1에 넣어주면 프롬프트에 오늘 날짜가 나오게 됩니다.

PS1="\$(date '+%Y-%m-%d-%a %T') $ "
hello prompt $ PS1="\$(date '+%Y-%m-%d-%a %T') $ "
2024-01-29-Mon 23:26:30 $ 
2024-01-29-Mon 23:26:32 $ 
2024-01-29-Mon 23:26:33 $ 
2024-01-29-Mon 23:26:33 $ 

PS1에 날짜를 넣어고 여러번 엔터를 입력한 결과입니다. 시간이 흐르는 것을 프롬프트를 통해 알 수 있게 되었습니다.

프롬프트에 시계가 있으면 시간이 오래 걸린 이전 명령이 언제 끝났는지를 프롬프트를 통해 알 수 있다는 장점도 있습니다.

다음은 sleep 명령을 써서 일부러 5초간 기다리도록 해 본 것입니다.

2024-01-29-Mon 23:29:47 $ sleep 5
2024-01-29-Mon 23:29:53 $

명령이 끝난 이후 곧바로 다음 프롬프트가 출력되므로, 명령이 끝난 시간이 23:29:53이라는 것을 알 수 있습니다.

이 예제에서는 5초라는 짧은 시간이었지만, 가끔은 아주 오래 걸리는 명령을 실행할 때도 있습니다. 화장실을 다녀오거나 커피를 마시고 온 다음 "아 이게 16분 걸렸구나" 하고 알 수 있다면, 다음번에 실행할 때에는 이전 소요 시간을 참고할 수 있을 것입니다.

팁: 명령이 끝나면 알림 받기

이건 프롬프트와는 상관없는 것이긴 한데… sleep 명령을 소개하고 보니 이 팁을 여기에 넣으면 재미있을 것 같아서 넣어봅니다.

다음 명령을 실행해 봅시다.

osascript -e 'display notification "Hello, World!" with title "터미널 알림"'

그러면 모니터 오른쪽 위에 다음과 같은 알림이 나타나게 됩니다.

이 명령은 macOS 컴퓨터에서 작동하는 애플스크립트를 실행해 줍니다. (애플스크립트에 대해 학습하는 건 이 가이드의 목적에서 꽤 벗어나기 때문에 굳이 자세히 설명하지는 않겠습니다.)

이제 위에서 사용했던 sleep 명령 뒤에 세미콜론을 하나 추가하고, 이 명령을 붙여서 실행해 봅시다.

sleep 5; osascript -e 'display notification "Hello, World!" with title "터미널 알림"'

이렇게 하면 앞의 명령(sleep 5)이 끝나고 나서 뒤의 명령(osascript ...)이 실행되게 됩니다.

오래 걸리는 명령을 실행하고 나서 웹서핑 같은 걸 하고 있다가 화면 오른쪽 위에 알림이 뜨면, 명령이 끝났다는 걸 알 수 있게 될 테니 터미널로 돌아가서 하던 작업을 이어서 하면 됩니다.

PS1에 작업 디렉토리 넣기

이제 다시 PS1 작업으로 돌아갑시다. 이번에는 작업 디렉토리를 프롬프트에 넣어보도록 하죠.

작업 디렉토리를 확인하는 쉬운 방법은 pwd 명령을 사용하는 것과 $PWD 변수를 출력하는 것입니다.

2024-01-31-Wed 22:30:17 $ pwd
/Users/johngrib/johngrib.github.io
2024-01-31-Wed 22:30:52 $ echo "$PWD"
/Users/johngrib/johngrib.github.io

둘 중에 무엇을 써도 상관없지만 위에서 [[/cmd/date]]를 사용했으니 이번에는 변수를 사용해 보도록 하겠습니다.

PS1="\$(date '+%Y-%m-%d-%a %T') \$PWD $ "

이제 다음과 같이 작업 디렉토리도 표시되게 되었습니다.

2024-01-31-Wed 22:31:20 $PS1="\$(date '+%Y-%m-%d-%a %T') \$PWD $ "
2024-02-02-Fri 23:51:08 /Users/johngrib/johngrib.github.io $ 

그런데 작업 디렉토리 경로가 좀 길군요.

$HOME 경로를 ~로 줄여서 표시하면 좀 더 깔끔해 보일 것 같습니다. (~$HOME 경로로 확장되는 특수한 문자입니다. 이런 확장에 대해서는 나중에 자세히 설명하겠습니다.)

이제 PS1$PWD를 사용한 곳을 다음과 같이 바꿔 봅시다.

PS1="\$(date '+%Y-%m-%d-%a %T') \w $ "

\w$PS1 해석기가 현재 작업 디렉토리를 표시하도록 하는 특수한 문자입니다.

이제 다음과 같은 프롬프트가 나타나게 됩니다.

2024-02-02-Fri 23:51:08 ~/johngrib/johngrib.github.io $ 

이제 프롬프트가 꽤 길어졌기 때문에 명령을 입력하는 커서가 화면 왼쪽에서 꽤 멀어졌습니다.

그래서 저는 마지막의 $ 앞에 \n을 넣어서 명령 입력 커서가 다음 줄에 나오도록 설정하는 것을 좋아합니다.

2024-02-03-Sat 00:23:59 ~/johngrib.github.io $ PS1="\$(date '+%Y-%m-%d-%a %T') \w \n$ "
2024-02-03-Sat 00:27:05 ~/johngrib.github.io
$

PS1에 색깔 넣기

기본적인 프롬프트의 모양은 갖춰졌습니다. 하지만 보기가 좀 지루하니 색깔을 넣어보도록 하겠습니다.

색깔을 넣어보려면 색을 출력하는 방법을 먼저 알아야겠죠. 다음 명령들을 한번 실행해 봅시다.

printf "\e[0;31m 빨간색 \e[0m \n"
printf "\e[4;32m 밑줄친 녹색 \e[0m \n"
printf "\e[1;34m 굵은 파란색 \e[0m \n"
printf "\e[7;93m 반전 노란색 \e[0m \n"

제 컴퓨터에서는 이렇게 나옵니다.

각자의 터미널 애플리케이션에 따라 색이 조금씩 다르게 나올 수 있습니다. 그러니 출력 결과가 좀 다르게 나온다고 해서 너무 걱정하지 않아도 됩니다.

위에서 사용한 명령들은 대충 이런 의미를 갖고 있습니다.

  • 이스케이프 시퀀스 시작
    • \e
  • 스타일
    • 0;: 일반 스타일
    • 1;: 굵게
    • 4;: 밑줄
    • 7;: 색반전
  • 색깔
    • 31m: 빨간색
    • 32m: 녹색
    • 34m: 파란색
    • 93m: 노란색
  • 스타일 리셋
    • \e[0m

좀 더 많은 색을 출력해서 확인해보고 싶다면 [[/cmd/bash/term-colors]] 문서를 참고하세요.

이제 PS1에 색을 넣어봅시다. 빨간색부터 시작해 보죠.

PS1="\e[0;31m\$(date '+%Y-%m-%d-%a %T') \w \n$ "

프롬프트가 빨간색이 되었습니다. 그런데 마지막에 스타일을 리셋하는 \e[0m이 없어서 입력하는 명령어도 빨간색으로 나오게 되고 말았네요.

마지막에 \e[0m을 넣어서 스타일을 리셋하도록 하고, 이후에 오는 명령어는 기본 색으로 나오도록 해봅시다.

PS1="\e[0;31m\$(date '+%Y-%m-%d-%a %T') \w \n\e[0m$ "

잘 나오네요.

이번에는 작업 디렉토리 경로를 굵은 파란색으로 표시해 보죠.

PS1="\e[0;31m\$(date '+%Y-%m-%d-%a %T') \e[1;34m\w \n\e[0m$ "

잘 나옵니다. 이제 이렇게 만든 프롬프트가 재부팅 후에도 계속 유지되도록 설정해 봅시다.

~/.bash_profile이나 ~/.bashrcPS1을 설정하는 명령을 추가하면 됩니다.

여기에서는 ~/.bash_profile에 추가해 보겠습니다.

echo "PS1=\"\e[0;31m\$(date '+%Y-%m-%d-%a %T') \e[1;34m\w \n\e[0m$ \"" >> ~/.bash_profile
  • echo "PS1=...": 지금까지 작성한 PS1을 출력합니다.
  • >> ~/.bash_profile: ~/.bash_profile의 마지막 줄에 echo의 출력을 추가합니다.
    • 만약 ~/.bash_profile이 없다면 새로 생성되고, 마지막 줄에 PS1 설정이 추가됩니다.

이제 터미널 애플리케이션을 종료했다가 다시 시작하거나, 새 탭을 열어도 색깔이 들어간 프롬프트가 나오게 됩니다.

PS1 에 git status 넣기

이번에는 PS1에 현재 디렉토리가 git 저장소인 경우에는 git 상태를 표시하도록 해보겠습니다.

위에서 PS1을 이루는 문자열을 만들 때 \$(date '+%Y-%m-%d-%a %T')를 사용했던 것처럼, $(...) 안에 특정한 명령 파이프라인을 넣는 방식으로 git 상태를 표시하도록 하면 됩니다.

이를 위해 몇 가지 명령이 잘 작동하나 확인해보고, 명령들을 잘 연결해서 PS1에 넣어보도록 하죠.

  • 현재 경로가 git 저장소인지 확인하는 명령
  • git branch를 출력하는 명령
  • git status를 출력하는 명령
  • git stash 사이즈를 출력하는 명령

이렇게 4개 정도면 충분할 것 같습니다.

현재 경로가 git 저장소인지 확인하기

프롬프트에서 git status를 표시하려면 현재 디렉토리가 git 저장소인지 확인해야 합니다.

git rev-parse --is-inside-work-tree

git 저장소인 디렉토리와 아닌 디렉토리에서 이 명령을 실행해 봅시다.

git 저장소인 디렉토리에서는 true가 나오지만, 그렇지 않은 디렉토리에서는 에러 출력이 나온다는 것을 알 수 있습니다.

git branch를 출력하기

이건 다음 명령을 입력하면 간단하게 출력할 수 있습니다.

git branch --show-current

git status를 출력하기

git status를 출력하는 것은 어렵지 않지만, 프롬프트에 짧게 표시하기 위해서는 작업이 좀 필요합니다.

명령 파이프라인을 만들기 위해 다음과 같이 변경사항이 좀 있는 git 저장소를 준비해 보았습니다.

  • git이 추적하지 않는(untracked) 파일이 2개 있습니다.
  • 삭제된 파일(deleted)이 1개 있습니다.
  • 수정된(modified) 파일이 3개 있습니다.

이제 이 정보들이 짧게 표시되도록 --short 옵션을 줘서 git status를 실행해 봅시다.

git status --short

우리는 각 상태에 대한 파일의 수만 필요하기 때문에 각 파일의 이름은 필요하지 않습니다.

[[/cmd/cut]]로 각 줄의 첫 3글자만 잘라봅시다.

$ git status --short | cut -c 1-3
 M
 D
 M
 M
??
??

수정(D), 삭제(M), 추적하지 않는(??) 파일을 의미하는 기호만 출력됐습니다.

이제 이 기호들을 종류별로 카운트하면 되겠네요. 일단 종류별로 그룹화하기 좋게 먼저 정렬을 하고…

$ git status --short | cut -c 1-3 | sort
 D
 M
 M
 M
??
??

중복을 제거하고 중복된 수를 세어주는 [[/cmd/uniq]]{uniq -c} 명령을 사용해 보죠.

$ git status --short | cut -c 1-3 | sort | uniq -c
   1  D
   3  M
   2 ??

거의 다 된 느낌입니다. 공백이 좀 거슬리니까 이번에는 [[/cmd/tr]]를 써서 공백을 없애봅시다.

$ git status --short | cut -c 1-3 | sort | uniq -c | tr -d ' '
1D
3M
2??

거의 다 된 느낌입니다. 이제 이 '수직' 리스트를 '수평' 리스트로 바꾸기 위해 [[/cmd/xargs]]를 쓰면 되겠네요.

$ git status --short | cut -c 1-3 | sort | uniq -c | tr -d ' ' | xargs
1D 3M 2??

git status를 짧게 요약하는 데 성공했습니다.

git stash 사이즈를 출력하기

git stash 목록을 먼저 확인해 봅시다.

$ git stash list
stash@{0}: WIP on master: 2f6dbd9 새로운 tmux 설정 파일 추가
stash@{1}: WIP on master: 4c8dfc1 주석 삭제, ll 에 color 추가
stash@{2}: WIP on master: 9c77836 vimwiki coc-references 명령 추가

3개가 들어있네요. 몇 줄인지만 세어주면 되니까 간단하게 [[/cmd/wc]]{wc -l} 명령을 쓰면 될 것 같습니다.

$ git stash list | wc -l
3

잘 나오는군요.

모두 조합해서 git status 요약을 출력해주는 함수 만들기

이제 ~/.bashrc~/.bash_profile을 열고 함수를 하나 만들어 봅시다.

앞에서 ~/.bash_profile에서 작업했으니까 이번에도 ~/.bash_profile에서 작업해보죠.

function gbr {
    if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
        branch="$(git branch --show-current)"
        branch_str="\033[1;031m$branch\033[0m"

        stat=$(git status --short \
            | cut -c 1-3 \
            | sort | uniq -c \
            | tr -d ' ' \
            | xargs)

        stash_size=$(git stash list | wc -l | sed 's/ //g')
        stash_icon=" \e[0;92m≡\033[0m"
        printf "[$branch_str] $stat$stash_icon$stash_size"
        return 0
    fi
}

PS1="\e[0;31m\$(date '+%Y-%m-%d-%a %T') \e[1;34m\w \$(gbr)\n\e[0m$ "
 #                                                    ↑ 여기에 gbr 함수 추가

gbr 이라는 이름의 함수를 작성한 다음 PS1 후반에 $(gbr)을 추가했습니다.

~/.bash_profile 편집을 마쳤으면 터미널을 재시작하거나 source ~/.bash_profile 명령을 실행해주면 gbr 함수와 PS1이 적용될 것입니다.

잘 나오는군요.

PS2 는 무엇인가요?

지금까지는 PS1에 대해서만 다뤘습니다. 그런데 1이 있다면 2도 있지 않을까라고 생각한 분이 있을 것입니다.

PS2는 무엇일까요?

그것에 대해서는 [[/cmd/bash/parameters#prompt]]{bash의 매뉴얼을 열어보면 관련 파라미터들에 대한 설명을 찾을 수 있습니다}.

man bash

[[/cmd/less]]{less 뷰어}가 나타나면 /PS2를 입력해서 PS2를 검색해서 읽어봅시다.

PS2PS1과 비슷한데, 명령어가 여러 줄로 이어질 때 나타나는 프롬프트입니다.

보통은 기본적으로 > 두 글자로 이루어져 있습니다.

$ echo $PS2
>

$ echo __"$PS2"__
__> __

다음과 같이 \를 사용해가며 여러 줄로 이어지는 명령어를 입력할 때 나타나는 > 가 바로 PS2입니다.

$ git status --short \
> | cut -c 1-3 \
> | sort \
> | uniq -c
   2  M
   2 ??

PS3 이나 다른 PS 시리즈에 대해서는 딱히 몰라도 문제없으므로 여기에서는 굳이 설명하지 않도록 하겠습니다.

이전, 다음 문서

  • 이전 문서: [[/mac/terminal-guide/04]]
  • 다음 문서: [[/mac/terminal-guide/06]]