둘이 코딩하다 하나가 죽어도 모르는 유익한 쉘 기능 소개

둘이 코딩하다 하나가 죽어도 모르는 유익한 쉘 기능 소개

2020, Apr 15    

Index

  1. 들어가며
  2. iTerm2 & zsh 소개
  3. 화면 분할
  4. 히스토리 검색
  5. 커서 이동
  6. 커맨드 편집
  7. 마치며


1. 들어가며


정말 오랜만에 포스팅이다. 동시에 거의 1년 반만의 쉘 관련 포스팅인데, 2개월만의 복귀 포스트를 쉘 관련 포스트로 정한 것은 이유가 있다. 내가 일하는 곳에서는 만든 feature에 대해 배포 전에 무조건 QA를 진행하고, QA 업무만 보시는 QA 분들이 존재한다. 그래서 QA분들을 자주 접하게 되는데 이 분들이 단순히 최종 고객에게 노출되는 앱만 확인하시지 않고, 쉘로 커맨드를 돌려보거나, psql을 통해서 데이터베이스 조회를 하는 것을 알게 됐다.

그런데 작업하시는 것을 보니 쉘을 조금 더 편하게 쓸 수 있는 여지를 발견했고 해당 QA 분께 내용을 공유해드리니 꽤 만족하시는 것을 확인했다. 그렇다. 비단 QA 분들뿐 아니라 쉘을 많이 사용하는 개발자들조차도 쉘의 자잘한 편리성을 위한 기능을 모르는 경우가 많고 이 포스트는 이 분들을 위해 준비됐다. 이 글을 ‘높을 고(高)’를 성씨로 쓰시는 그분께 바친다.

오늘 포스트는 이전에 다뤘던 핵심적인 쉘 커맨드를 소개하거나 pipeline, redirection 등 중요한 쉘 기능을 다루지 않는다. 대신 몰라도 쉘 사용하는 데는 큰 지장이 없지만 알고 사용할 수 있다면 매우 건강한 쉘 경험을 할 수 있는 자잘한 쉘 기능에 대해 다룬다. 확실히 grep 커맨드를 잘 모르면 정말 필요한 순간에 난감하고 매우 귀찮은 노동을 하게 된다. 하지만 여기서 다룰, 가령 history 확장 같은 경우에는 몰라도 잘 살 수는 있지만 알고 있다면 쉘 활용에 천군만마를 단 느낌이 들 것이라 확신한다.

내가 소개할 기능들은 여러 쉘 자체에 범용적인 기능인 경우도 있고, 내가 사용하는 zshiTerm2 터미널에 한정된 기능일 수도 있다. 포스트는 먼저 zsh나 iTerm2를 처음 보는 분들을 위해 간단히 소개하고 각자의 어썸한 기능을 몇 개 소개한다. 그 다음 본격적으로 내가 애용하는 쉘 기능들을 살피는데 화면 분할, history 검색, 커맨드 커서 이동, 커맨드 편집 등의 순서로 진행하도록 한다.

참고로 나는 Mac을 사용하고, 또 설명했다시피 zsh, iTerm2를 사용하기 때문에 다른 환경에서는 똑같이 작동 안 할 수도 있다는 것을 염두하기 바란다.


2. iTerm2 & zsh 소개


내가 사용하는 iTerm2와 zsh를 소개한다. 처음 듣는다면 고민해보고 설득당했다면 사용해보는 것도 괜찮다.


2.1. iTerm2

iTerm2는 MacOS 10.12 이상 버전에서 사용할 수 있는, 기본 Mac 내장 Terminal을 대체할 수 있는 새로운 터미널로서 다양한 기능을 사용할 수 있어 많은 사람들에게 사랑받고 있는 터미널이다. 이 터미널에서는 많은 기능들을 지원하고 있는데:

  • 아래에서 따로 확인할 창 분할(split tab, split window)
  • 쉘 출력 내용 상의 내용 검색(단축키: <COMMAND> + f)
    • shell inner search
  • 검색 내용을 복사해 쉘에 자동 입력(단축키: <OPTION> + <ENTER>)
  • 커맨드 입력 시 <TAB> 키를 이용한 자동완성
  • profile, theme 등 더 풍부한 설정 기능 지원 등등

더 많은 기능은 iTerm2 기능 소개 페이지를 참고하라. 나도 더 알려주시면 감사하겠다.


2.2. zsh

리눅스나 맥북의 기본 쉘은 bash일 확률이 높다. bash는 Steven Bourne이라는 사람이 처음 만든 유닉스용 CLI 인터페이스인 sh의 확장판으로서, 이름조차 그를 계승한 Bourne Again Shell의 약자이다. 제작자의 성(Bourne)과 ‘다시 태어난’의 ‘Born’의 발음이 비슷한 것을 작명에 재치있게 활용한 예라고 할 수 있겠다. 어쨌든 bashsh를 계승한 쉘로서 뒤집어 말하면 bash 이외의 다른 쉘이 존재할 가능성도 충분히 있다. zshbash와는 다른 기능을 가진, 하지만 핵심 기능에 있어 똑같이 sh를 계승한 쉘이다.

현재는 개발자들이 많이 쓰며 공식 페이지에서 확인할 수 있듯이 공식 이름은 oh my zsh이다. github Star의 개수가 10만개를 훌쩍 넘는 위대한 프로그램이다.

내가 zsh를 사랑하는 이유는 개발자를 위한 세심한 alias 지원에 있다. 쉘의 주요 커맨드들의 이름 (ls, cp, mv) 등은 줄임말이다. (list, copy, move) 원 단어들도 긴 단어들이 아닌데도 굳이 줄였다는 것이 중요한데 쉘에서는 그만큼 조금이라도 더 편하게 기능을 사용할 수 있도록 커맨드 이름을 줄인 경우가 많다. 생각해보라. ls는 정말정말 많이 사용하는 커맨드인데 원 명령어 이름이 list였다면 입력에 2 바이트나 더 소모되었을 것이다. 생각만해도 소름이 돋는다.

zsh는 이에 대해 자체적으로 미리 alias를 많이 만들어놨다.

가령 ls를 활용해 l, ll 와 같은 명령어를 만들어놨다. 이 둘은 자주 사용되는 ls를 주요 옵션과 같이 저장해 놓은 것이다.

$ type l

l is an alias for ls -lah


$ type ll

ll is an alias for ls -lh

ls를 long option(-l)과 함께 사용하는 경우는 정말 빈번하다. 이를 위해서 각각의 주요한 옵션 활용을 l, ll 등의 이름으로 저장해놓은 것인데 이게 몇 바이트의 절약인지 생각해보라.

alias는 여기서 그치지 않는다.

alias | grep '\.\.\.'

...=../..
....=../../..
.....=../../../..
......=../../../../..

가령 이전, 이전 경로로 가고 싶다면 zsh에서는 cd ...라고 입력하면 된다. cd ../.. 보다 2 바이트나 절약된다.

$ alias | grep cd 


-='cd -'
1='cd -'
2='cd -2'
3='cd -3'
4='cd -4'
5='cd -5'
6='cd -6'
7='cd -7'
8='cd -8'
9='cd -9'

쉘 프롬프트에 2라고만 입력해도 이전, 이전 커서가 있던 디렉토리로 이동할 수 있다.


마지막 찐퉁이 남아 있다. 바로 수많은 git command들이 alias로 저장되어 있다.

예를 들어 git add -> ga, git commit -> gc, git status -> gst와 같다. git add, commit 등은 정말 많이 쓰는 커맨드인데, git add --all와 같이 13 바이트나 소모하며 입력해야 한다면 정말 마음이 아플 것이다. 그대신 gaa와 같이 한 번에 할 수 있다면. 이것이야말로 내가 생각하는 flex다.

zsh에서 지원하는 alias는 무수히 더 많다. 자세한 목록은 여기서 확인하도록 하자.


아. 잊고 갈 뻔 했네. zsh에서는 다양한 테마를 설치할 수 있고 쉘 프롬프트를 git integration으로 설정해서 git 을 활용한 개발에 정말 유용하게 사용할 수 있다. 이것까지 잊지 말자.

git integration in zsh

혹시 iTerm2와 zsh을 활용해 개발환경 세팅을 하고 싶다면 여기를 참고하도록 하자. 그외에도 다양한 맥 설정을 포함하고 있어 유용한 포스트다.


3. 화면 분할


내가 애용하는 iTerm2의 기능 중에는 화면 분할이 있다.

먼저 tab 분할

하나의 터미널에서 여러 tab을 사용할 수 있다. 탭을 생성하는 단축키는 <COMMAND> + t. 사용하면 추가적인 탭이 생겨 여러 탭 사이에서 작업할 수 있다. 이동 단축키는 <COMMAND> + 탭 번호. 크롬에서 탭 이동하는 것과 단축키가 같다.

이런 tab 분할이 유용한 경우는 참 많은데 지금 이 글을 쓰는 상황도 마찬가지다. 나는 블로그 포스트를 쓸 때 세 개의 tab을 열어놓는다. 하나는 vim을 열어 실제 글을 쓰고, 다른 하나는 jekyll 로컬 server를 동작시켜 작성하는 글을 자동 빌드 및 브라우저에서 확인하는 데 쓰며, 나머지는 파이썬 쉘이나 다른 커맨드를 확인하는 데 쓴다.

이것들을 각각의 터미널을 열고 이들을 옮겨다니며 작업해야 했다면 정말 귀찮았을 것이다. 대신 하나의 터미널에서 탭을 이동해가면서 작업하면 귀찮게 앱 이동을 안 할 수 있어서 정말 좋다.

shell dance tab split1

vim으로 작업하다가,

shell dance tab split2

다른 탭으로 이동해 쉘 커맨드를 확인해볼 수 있다.

window 분할

이번에는 윈도우 분할이다. 윈도우 분할은 하나의 탭 안에서 여러 윈도우로 화면을 쪼개 사용하는 것으로 단축키는 <COMMAND> + d다. 아마 ‘divide’의 약자가 아닌가 싶다.

하나의 탭에서 window 분할을 하면 다음과 같이 나뉘게 된다.

shell we dance split pane

이때 window간 이동 단축키는 <COMMAND> + [ or ]다. []는 각각 왼쪽, 오른쪽 이동을 의미한다.

참고로 tab이든 window든 닫을 때는 exit을 입력한다.


4. 히스토리 검색


히스토리 검색 기능은 쉘의 유용한 기능 중 하나인데 내 생각에 적극적으로 탐구되고 사용되지 않는 기능 중 하나다. 히스토리는 말 그대로 이전에 실행했던 커맨드를 말하며, 히스토리 검색은 히스토리를 다시 실행하거나 자동 완성해주는 것을 말한다.

대표적으로 누구나 쓰는 히스토리 검색은 화살표키를 이용해서 바로 이전, 이후 커맨드로 이동하는 것으로 이 정도의 검색은 누구나 한다. 하지만 무수히 많은 히스토리 목록 중에서 특정 키워드를 갖는 커맨드를 찾는다든가 하는 것도 할 수 있으면 좋을텐데…

여기서는 히스토리를 검색하는 다양한 방법을 살펴본다. 이 기능들은 내가 알기로 쉘 범용 기능이다.


4.1. history 명령어 사용하기

히스토리는 내가 커맨드를 입력할 때마다 순서대로 쌓인다.(in sequence) 무한정 쌓이는 것은 아니고 쉘마다 기록되는 최대 개수가 정해져 있는데 zsh 같은 곳에서는 기본값을 변경할 수도 있다.

쉘에서는 히스토리를 확인하는 history 명령어를 지원한다. 확인해보자.

$ history | less


    1  l
    2  ls
    3  sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
    4  ls
 ... 생략 ...
 9038  alias | grep cd
 9039  ..
 9040  cd
 9041  cd personal-workspace/shoark7.github.io
 9042  cd ...
 9043  alias | grep '...'
 9044  alias | grep '\.\.\.'
 9045  echo $(alias | grep '\.\.\.'; alias | grep cd) | sort
 9046  $(alias | grep '\.\.\.'; alias | grep cd) | sort
 9047  alias | grep '\.\.\.'
 9048  cd ...
 9049  cd
 9050  alias | grep cd
 9051  cd personal-workspace/shoark7.github.io

목록은 길 수 있으니 less 프로그램으로 확인하는 것이 유용하다. 출력 결과는 내가 실행한 순서대로 번호와 함께 나타난다.

내가 다시 실행하고 싶은 내용이 있으면 해당 커맨드를 복사해서 붙여넣으면 된다. 내가 다시 실행하고픈 기능이 최근 실행한 커맨드와 멀리 있다면 이 방법이 방향키보다 훨씬 유용하다.


이 방법 또한 쉘에서 범용적으로 지원하는 기능으로 앞선 방법의 업그레이드 버전이다. 빈 커맨드 창에서 <CTRL> + r 을 입력해보자.

backward search

zsh에서는 다음과 같은 창이 뜬다. 이때 검색법은 키워드 입력과 방향키 사용이 둘 다 가능하다. 키워드 입력을 사용하면 전체 히스토리 중 내가 입력한 키워드만 사용된 히스토리 목록이 출력되며 이때 방향키를 이용해 위아래로 이동하며 커맨드를 선택할 수 있다.

키워드를 입력하지 않으면 전체 히스토리 목록이 나오며 방향키를 사용해 위아래로 이동할 수 있다.이는 꽤 유용하기 때문에 사실 이 정도까지만 알아도 어지간하면 사람처럼 히스토리 검색을 할 수 있게 된다.

참고로 이 기능은 쉘마다 조금씩 다른 것 같다. 이전 우분투를 사용할 때는 검색 시 히스토리 목록이 가시적으로 출력되지 않고 입력, 방향키 조작만 가능했는데 여기서는 사용하기 더 쉽게 출력된다. 지금 생각해보면 zsh이 아닌 iTerm2에서 사용성이 더 개선되지 않았나 추측해본다. 왜냐하면 우분투에서도 zsh를 사용했기 때문이다.


4.3. 히스토리 확장을 사용하기

마지막으로 히스토리 확장을 사용하는 기능이다. 확장은 이전에 포스트로 따로 다룬 기억이 있다. 히스토리 확장은 그때 생략했는데 이유는 다른 확장에 비해서 그 중요도가 떨어진다고 판단했기 때문이다. 하지만 히스토리 확장도 쓸 수 있다면 굉장히 유용하기 때문에 이참에 확인하고 가자. 참고로 이 기능도 범용적이다.

확장은 쉽게 말해 커맨드를 실행할 때 특정 메타 캐릭터를 쉘에서 자체적으로 다른 인자로 변경해서 커맨드를 실행하는 것을 말한다. 예를 들어 다음 실행 결과는 확장을 전혀 모르면 이해할 수 없는 희한한 결과를 내게 된다.

$ cd ~
$ echo *

Applications Desktop Documents Downloads Library Movies Music Pictures Public logs personal-workspace screenshots tmp-directory workspace

자세한 내용은 위의 포스트에서 확인하고 아무튼 히스토리 확장을 통해 쉘에서 특정 메타 캐릭터를 이용해서 히스토리를 더 쉽게 검색할 수 있다고 생각하면 되겠다.


먼저 !number

이것은 쉽게 말해 ‘number’번 커맨드를 실행한다는 뜻이다. 예를 들어 !1000은 히스토리 목록 중 1000 번째 커맨드를 실행하게 된다.

$ history | grep 1000

1000  docker logs -f docker-boy

내 히스토리 중 1000번째에 입력된 커맨드는 도커 컨테이너의 로그를 보는 커맨드였다. 다음과 같이 입력하고 엔터를 누르면,

$ !1000<ENTER>

docker logs -f docker-boy

바로 커맨드가 자동 완성된다.


특정 키워드를 가진 최신 커맨드 검색: !!keyword, !?keyword

이 확장의 의미는 다음과 같다.

  • !!keyword: keyword로 시작하는 최신 커맨드를 자동완성한다.
  • !?keyword: keyword를 포함하는 최신 커맨드를 자동완성한다.

말 그대로다. 내가 실제로 사용하는 예를 들어보자. 앞서 확인했듯이 내가 블로그를 작성할 때 작성하면서 수시로 렌더링을 확인하기 때문에 로컬 서버를 띄워둔다. 그 명령어는 bundle exec jekyll serve인데 이걸 외우고 있지도 않고 해당 키워드를 사용하는 다른 커맨드도 많이 사용하지 않는다.

따라서 로컬 서버를 실행할 때 나는 단순히 다음과 같이 입력한다.

$ !?serve<ENTER>

bundle exec jekyll serve

오호라, !? 문자가 ‘serve’를 포함하는 히스토리로 확장됐다. ‘포함’이 아닌, ‘~로 시작하는’으로 검색하려면 !!를 사용하면 된다.

이 방법의 장점은 특정 키워드가 많이 쓰이지 않거나 반드시 최신 커맨드로 검색되리라고 생각할 수 있을만큼 정확도 높게 키워드를 길게 작성하면 방향키를 사용하는 등의 추가 작업이 필요없다는 것이다. 반대로 말하면 워낙 빈번하게 사용되는 키워드로 검색하면 원하는 커맨드를 정확하게 특정할 수 없다는 것이 되기도 한다. 팁은 내가 입력할 내용이 최신 커맨드일지 확신이 없다면 키워드를 한 단어가 아닌 두 단어를 쓰는 등 길게 쓰자.


CLI를 자주 사용하게 된다면 히스토리 검색은 분명 유용하다. 파레토 법칙에 따라 자주 사용되는 커맨드는 정해져 있기 때문이다. 참고하도록 하자.


5. 커서 이동


이 포스트의 핵심이라고 할 수 있다. 이런 상황은 정말 많이들 겪어봤을 것이다. 가령 내가 장고 로컬 서버를 실행한다고 하자. 그런데 실수로 오타를 냈다.

$ pytho manage.py runserver|

|는 현재 커서 위치를 뜻한다.

‘python’을 ‘pytho’로 오타를 냈기 때문에 커서를 앞으로 옮겨 ‘n’을 추가해줘야 한다. 현재 커서에서 커맨드의 앞으로 커서를 옮길 때 일반적으로 가장 많이 사용하는 방법은 역시 방향키를 이용해 움직이는 것이다. 이는 지금처럼 커맨드가 짧을 때는 괜찮을지 몰라도 커맨드가 길어지면 길어질수록 방향키를 통해 뒤에서 앞으로 커서를 이동하는 것은 비효율적인 방법이 된다. 대신 한 번에 커서를 맨 앞이나 맨 뒤로 움직일 수 있다면 매우 행복할 것이다.

  • <CTRL> + a: 커서를 맨 앞으로 이동한다.
  • <CTRL> + e: 커서를 맨 뒤로 이동한다.

이는 매우 직관적이기 때문에 직접 해보기 바란다.

또 이번에는 커서를 기준으로 앞뒤를 삭제하는 단축키도 있다.

  • <CTRL> + u: 커서를 기준으로 앞의 커맨드 부분을 삭제한다.
  • <CTRL> + k: 커서를 기준으로 뒤의 커맨드 부분을 삭제한다.

이것도 마찬가지다. 매우 직관적이기 때문에 사용만 해보면 바로 이해할 수 있다.


커맨드 커서 이동은 앞의 장들보다 적은 부분을 차지했는데 이는 덜 중요해서가 아니고 단순히 직관적이어서 굳이 스크린샷 등이 필요하지 않았기 때문이다. ‘커맨드 커서 이동’ 단축키는 매우매우 유용하기 때문에 한 번 꼭 써보길 바란다.

이제 가슴 아프게 긴 커맨드를 방향키로 이동하는 불상사는 꼭 피하도록 하자. 이 포스트에서 하나만 가져가야 한다면 이 장을 보는 것이 낫다.


6. 커맨드 편집


이 내용은 포스트를 준비하면서 나도 처음 알게 됐는데 정말 dog honey tip인 것 같아서 나도 공부하고 공유하고 싶어 따로 한 장을 할애했다. 이 기능은 zsh에서만 지원하는 것 같다.

이런 상황을 가정해보자. 내가 정말 긴 커맨드를 입력하게 됐는데 중간 부분에서 오타를 발견해서 수정해야 한다. 이런 예를 들 수 있겠다.

$ curl -L -X POST -d '{"data": "okboy"}' -b "oraclelicense=a" -O http://dawnload.oracle.com/otn-pub/java/jdk/10.0.2+13/19aef61b38124481863b1413dce1855f/jdk-10.0.2_linux-x64_bin.rpm|

curl을 통해 만든 매우 긴 커맨드다. 이 커맨드의 의미는 지금 중요하지 않은데 핵심은 가끔씩 이렇게 긴 커맨드를 작성할 경우가 생길 수 있다는 것이다.

중간에 오타가 있는데 보이는지? -O 인자 뒤에 ‘download’를 ‘dawnload’로 오타를 냈다. 참 바보같은 실수를 했는데 현재 커서는 맨 뒤에 있다. 오타는 중간 부분에 있는데 여기서 어떻게 중간 부분을 쉽게 수정할 수 있을까? 오타가 중간 부분에 있기 때문에 커서를 맨 앞으로 옮기나 맨 뒤로 옮기나 그게 그것 같다…

여기서 등장한다. 현재 상태에서 <CTRL>를 누른 채 x, e를 연속해서 누른다.

vim formatting

맨 위의 빨간색 박스를 보자. 음? vim이 켜진 것을 확인할 수 있고 첫 줄에 내가 입력한 명령어가 그대로 보인다. 여기서 명령어를 수정한 후(두 번째 박스) 저장 후 닫기를 하면 수정된 새로운 커맨드가 자동으로 입력되어 있다. vim 상에서 중간 부분으로 이동하는 것은 매우 쉽기 때문에 방향키를 통해 커서를 흐느적흐느적 이동하는 것보다는 훨씬 유용하고 빠르다.

특히 이 기능은 커맨드가 길면 길수록, 수정해야 하는 부분이 많으면 많을수록 강력해지기 때문에 긴 커맨드를 수행할 일이 있을 수도 있는 개발자에게는 정말 유용할 수 있다. 위의 curl 예제는 사실 며칠 전에 내가 개발한 API를 테스트할 때 사용했어서 가져왔다. 바로 며칠 전에 저 긴 curl 커맨드에서 커서를 왔다갔다 하며 고생해서 그런지 이 기능이 강력하다는 것을 더 빨리 인지할 수 있었다. 개발자라면 알아두면 분명 도움되는 순간이 있으리라 생각된다.

참고로 실행되는 에디터는 변경할 수 있으며 이것은 따로 찾아보기 바란다.


7. 마치며


정말 오랜만에 포스트를 썼다. 이게 얼마만인지. 여러분들의 성원이 있기에 블로그를 계속할 동기를 찾을 수 있었고 오늘도 짧지 않은 시간을 투자해 블로그를 작성했다. 나는 개발자라면 CLI를 애용해야 한다고 생각하고, 난 CLI가 좋다. 처음에 러닝커브가 있지만 그 낮은 언덕만 넘으면 GUI에 비해 생산성이 보장된다고 믿고 있다. 이번 포스트를 통해 많은 분들이 더 나은 쉘 경험을 했으면 좋겠다.

오랜만에 포스트를 작성하면서 질에 대한 의구심이 들었다. 아직 퇴고를 더 해야 하는 상황이지만 왜 이렇게 쓰면서 나 자신이 만족스럽지 않지? 일단 좀더 개선해보고 생각해보겠다. 아, 내가 몰라서 적지 못한 유용한 쉘 또는 터미널의 기능들이 있을 수 있다. 내가 놓친 부분이 있다면 댓글로 소개해주시면 나도 써보고 내용을 추가하겠다.

참고로 포스트 로고는 오래된 일본 영화 ‘Shall we dance’의 포스터다. 1990년대 후반에 나온 일본 영화인데 초등학생 때 정말 재미있게 본 기억이 있다. 난 저 제목을 패러디해 이렇게 말하고 싶었다.

Shell we dance? 나랑 같이, 쉘로 춤추지 않겠습니까?

이상 포스트를 마칩니다.