Canvas 1 Layer 1

셸에서 여러줄의 명령어를 에디터로 편집하고 실행하기

들어가며: 긴 명령어를 셸에서 편집하는 괴로움

커맨드라인 인터페이스에서 긴 명령어를 다루는 일은 상당히 어려운 일입니다. 특히 셸과 같은 인터렉티브한 인터페이스에서는 한 줄에 명령어 하나를 입력하는 것이 기본입니다. 환경에 따라 다르지만 한 줄에 긴 명령어를 입력하면 입력화면이 깨지는 일도 흔합니다. 여러 줄을 편집하다가 실수로 실행해버리는 일도 많고, 히스토리로 이전 명령어를 부르면 줄바꿈 문자가 이상하게 해석되기도 합니다. 긴 명령어나 여러 줄의 명령어를 입력하는 것이 불가능한 것은 아니지만 실제로 사용하기에는 상당히 불편합니다.

예를 들어보겠습니다. 다음과 같이 도커 컨테이너로 포스트그레SQLPostgreSQL을 실행하는 명령어가 있습니다. 도커 명령어는 환경변수를 통해서 컨테이너의 실행 환경을 정의하기 때문에 환경변수의 수에 비례해서 실명 명령어가 길어집니다. 도커를 사용해보신 분이라면 이 정도 길이의 명령어가 아주 흔하다는 걸 이해하실 겁니다.

$ docker run -d -p 5432:5432 --name my_postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=inviolable_password -e POSTGRES_DATABASE=postgres -e PGDATA=/var/lib/postgresql/my_data -v $(PWD)/data:/var/lib/postgresql/my_data postgres:11

아주 불편합니다. 온라인 튜토리얼을 따라하고 있다고 생각해보겠습니다. 아무리 길어도 차라리 실행 가능한 명령어를 한 줄로 알려주는 것은 편리한 일입니다. 왜냐면 복사해서 붙여넣으면 어떻게든 실행이 되기 때문입니다.

하지만 모든 경우에 완성된 명령어를 제공할 수는 없습니다. 다음과 같이 사용자가 환경이나 상황에 따라 명령어를 편집해야하는 경우도 있습니다.

$ docker run -d -p 5432:5432 --name my_postgres -e POSTGRES_USER=<USERNAME> -e POSTGRES_PASSWORD=<PASSWORD> -e POSTGRES_DATABASE=<DATABASE> -e PGDATA=/var/lib/postgresql/my_data -v $(PWD)/data:/var/lib/postgresql/my_data postgres:11

일반적으로 이러한 명령어의 경우 <USERNAME> , <PASSWORD>, <DATABASE>를 사용자가 직접 수정해서 실행해야만합니다. 이제 저 명령어를 그대로 셸에다 복사해놓고 편집하려고 하면 아주 고통스러운 상황이 펼쳐집니다. 어떻게든 수정해서 실행할 수는 있습니다. 그래서 글을 쓰는 사람들은 이렇게 명령어가 길어진다면 보통 아래와 같이 여러줄로 명령어를 표현합니다.

$ docker run -d \
  -p 5432:5432 \
  --name my_postgres \
  -e POSTGRES_USER=<USERNAME> \
  -e POSTGRES_PASSWORD=<PASSWORD> \
  -e POSTGRES_DATABASE=<DATABASE> \
  -e PGDATA=/var/lib/postgresql/my_data \
  -v $(PWD)/data:/var/lib/postgresql/my_data \
  postgres:11

보기 좋네요! 44bits.io에서도 긴 명령어를 표현할 때 자주 사용하는 방법입니다. 하지만 보기만 좋을 뿐 이러한 명령어를 셸에 그대로 복사해서 편집하는 것은 아주아주 어렵습니다. 셸에서 직접 멀티라인 명령어를 편집하는 것은 교묘합니다. 셸에 익숙하다면 도전해볼만합니다만, 커서는 생각처럼 움직이지 않고, 입력 인터페이스가 깨지기도 하며, 변수(<USERNAME> 등)를 편집하다가 실수로 명령어를 실행해버리는 일도 흔합니다. 디버깅은 더욱 어렵습니다. 일반적으로 멀티라인은 역슬래시(\) 문자로 구분합니다만, 역슬래시 뒤에 공백 문자라도 하나 들어가있다면, 이 명령어는 실행되지 않습니다.

이런 상황이면 한 줄 짜리 명령어가 차라리 낫다고 느낄지도 모릅니다. 그래서 보통 이렇게 긴 명령어를 편집하고자할 때는 다른 에디터를 열어서 명령어를 편집하고 그 명령어를 다시 복사하고, 셸에 붙여넣어서 실행할 것입니다. (여전히 실행하기 전에 꼭 역슬래시 뒤에 공백이 있는지 확인해야합니다)

이러한 고통에서 벗어나기 위한 손쉬운 방법이 있습니다. 셸에서 에디터를 열고 명령어를 편집하고 바로 실행하는 방법입니다. 이 방법은 긴 명령어를 복사할 때 뿐만 아니라, 긴 명령어를 직접 입력할 때도 편리한 방법입니다. 이 글에서는 Bash 셸과 Zsh에서 긴 명령어를 에디터로 편집하고 실행하는 방법에 대해서 소개합니다.

Bash에서 명령어를 에디터로 편집하고 바로 실행하기

Bash 레퍼런스 매뉴얼에는 edit-and-execute-command 명령어가 소개되어있습니다.

edit-and-execute-command (C-x C-e) 현재 커맨드라인에서 에디터를 실행하고, 편집한 결과를 셸 명령어로 실행한다. Bash에서는 $VISUAL, $EDITOR, emacs 순으로 에디터를 찾아서 실행한다.

Bash 사용자라면 이 명령어를 사용해 곧바로 셸에서 에디터를 실행하고 편집 결과를 실행하는 것이 가능합니다. 여기서 C-x C-e는 키바인딩을 표현하는 방법으로 control + xcontrol + e를 연속해서 입력하라는 의미입니다.* 컨트롤 키를 누른 채로 입력해도 무방합니다. 이 때 실행하는 에디터 명령어는 환경변수 $VISUAL을 찾고, 값이 없으면 $EDITOR를 찾고, 그래도 값이 없으면 이맥스Emacs를 사용합니다. 물론 시스템에 이맥스가 설치되어있지 않다면 에디터를 실행하지 못 합니다.

* 맥OSmacOS에서도 C는 컨트롤 키입니다. 커맨드command 키가 아닙니다. 맥에서는 자주 사용되지 않지만, 키보드 왼쪽의 옵션option 키 왼쪽에 있습니다.

가능하면 이맥스나 빔 같은 커맨드라인에서 바로 편집가능한 에디터를 추천합니다만, GUI 에디터를 사용하는 것도 가능합니다. 여기서는 텍스트메이트TextMate로 같은 기능을 사용해보겠습니다.

먼저 텍스트메이트의 메뉴에서 설정Preferences을 찾아 선택합니다. 터미널Terminal 탭을 선택합니다. 셸 지원 기능을 설치Install합니다.

텍스트 메이트의 터미널 설정 메뉴
텍스트 메이트의 터미널 설정 메뉴

이제 셸에서 mate 명령어로 텍스트메이트를 실행할 수 있습니다. 이 명령어를 export를 사용해 $EDIOTR 환경변수의 값으로 설정합니다. *

* export 명령어는 현재 사용중인 셸에서만 적용됩니다. 이 값을 앞으로 사용하는 모든 셸에 적용하려면 .bashrc나 Bash 실행 시 로드하는 설정 파일에 추가해야합니다.

export EDITOR="/usr/local/bin/mate -w"

이제 준비는 끝났습니다. C-x C-e를 입력하면 Bash 셸은 대기 상태가 되고 텍스트 메이트 에디터가 실행됩니다.

명령어 입력을 위해 실행된 텍스트 메이트
명령어 입력을 위해 실행된 텍스트 메이트

현재 파일명은 bash-fc-1544864699라는 파일입니다. 이 파일은 명령어를 편집하기 위한 임시파일입니다. 이 파일을 편집하고 이 내용을 저장하고 에디터를 종료하면 곧바로 셸에서 실행이 됩니다. 앞서 예제로 보았던 도커 명령어를 입력해봅니다.

docker run -d \
  -p 5432:5432 \
  --name my_postgres \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=password \
  -e POSTGRES_DATABASE=postgres \
  -e PGDATA=/var/lib/postgresql/my_data \
  -v $(PWD)/data:/var/lib/postgresql/my_data \
  postgres:11

에디터를 사용해서 명령어를 복사하고 원하는대로 편집하면 됩니다.

텍스트 메이트에서 셸에서 실행할 명령어를 편집합니다
텍스트 메이트에서 셸에서 실행할 명령어를 편집합니다

이 내용을 저장하고(command + s), 에디터를 종료합니다(command + w).*

* 이 방법을 사용해서 명령어를 입력할 때는 에디터를 종료하기 전에 미리 저장하는 방식을 추천드립니다. 이렇게 사용하면 에디터 종료 시에 저장하겠냐고 묻지 않으며, 명령어 편집 후 에디터에 임시 파일이 남아있지 않습니다.

텍스트 메이트에서 편집한 명령어가 셸에서 곧바로 실행됩니다
텍스트 메이트에서 편집한 명령어가 셸에서 곧바로 실행됩니다

에디터에서 편집한 내용이 곧바로 셸에서 실행되는 것을 확인할 수 있습니다. 또한 셸에서 명령어를 입력하는 중에 edit-and-execute-command를 실행하면, 입력중인 명령어가 에디터로 그대로 복사됩니다. 이 기능을 사용하는데 특별히 어려운 점은 없습니다.

edit-and-execute-command를 사용할 때 몇 가지 주의해야할 점이 있습니다. 자신이 선호하는 에디터를 사용할 수 있습니다만, 여기서 사용하는 에디터는 가벼우면 가벼울 수록 좋습니다. 셸에서 바로 사용 가능한 이맥스나 빔을 추천하는 이유는 빠르고 원격 환경에서도 사용할 수 있기 때문입니다. 텍스트메이트나 서브라임 에디터는 비교적 빠르게 실행되는 편이지만, 다른 에디터들은 처음 실행 시간이 꽤 길어질 수 있으니 주의가 필요합니다.

Zsh의 경우

Bash와 달리 Zsh의 경우에는 edit-and-execute-command 명령어가 없습니다. 대신 edit-command-line 함수가 준비되어 있으며 이 함수를 C-x C-e에 키바인딩해서 주로 사용합니다. 이 함수는 Bash와 다른 점이 몇 가지 있습니다.

먼저 에디터 실행 순서가 조금 다릅니다. 환경변수 $VISUAL을 찾고, 값이 없으면 $EDITOR를 찾고, 그래도 값이 없으면 vi를 사용합니다. 이 함수의 소스 코드는 zsh 저장소에서 찾아볼 수 있습니다.

local editor=( "${(@Q)${(z)${VISUAL:-${EDITOR:-vi}}}}" )

그리고 이름에서 유추 가능하듯이 명령어를 편집만할 뿐 실행을 하지는 않습니다. 앞서 Bash의 경우 에디터를 종료하는 즉시 명령어가 실행되었습니다. edit-command-line 함수는 편집한 내용을 복사만 하고, 직접 엔터를 입력해야 명령어가 실행됩니다.

이 기능을 사용하려면 아래 내용을 ~/.zshrc에 추가해야합니다.*

* oh-my-zsh을 사용하는 경우 기본적으로 edit-command-line 이 설정되어 있습니다. 추가적인 설정 없이 C-x C-e를 사용할 수 있습니다.

autoload -U edit-command-line
zle -N edit-command-line
bindkey '^xe' edit-command-line
bindkey '^x^e' edit-command-line

설정 파일을 수정한 후에는 source ~/.zshrc로 설정 파일을 다시 불러오거나, 셸을 다시 실행합니다. 자신이 사용하고자 하는 에디터를 $EDITOR에 지정합니다. 그리고 C-x C-e를 입력해합니다. 기본적인 사용법은 Bash와 다르지 않습니다. 에디터에서 임시 파일을 편집하고 이를 저장하고(command + s) 종료합니다(command + w).

텍스트 에디터의 명령어가 셸에 입력된 모습
텍스트 에디터의 명령어가 셸에 입력된 모습

앞서 설명했듯이 zsh에서는 명령어가 바로 실행되지 않습니다. 이 상태에서 명령어를 확인하거나 추가적으로 편집한 후 엔터를 입력하면 명령어가 실행됩니다.

마치며

edit-and-execute-commandedit-command-line는 사소하지만 유용한 기능입니다. 다른 셸에서도 찾아보면 비슷한 기능을 지원할 것입니다. 셸의 입력 환경에는 여러가지 제약이 있다보니, 여기서 긴 명령어를 입력하는 게 가장 좋은 방법은 아닙니다. 그렇다고 명령어가 길어질 때마다 간단한 스크립트를 만들어 사용하는 것도 한계가 있습니다. 따라서 이러한 기능은 잘 활용하면 아주 유용합니다. 더 이상 역슬래시로 나눠진 여러 줄로 구성된 명령어를 보더라도 겁먹지 마시기 바랍니다.