Emacs Lisp에서 셸 커맨드를 터미널을 통해 실행시키기

Emacs // 2024년 07월 22일 작성

이 글은 Emacs Lisp에서 셸 CLI 커맨드를 Emacs 자체 터미널을 통해 실행시키는 방법을 정리한다.

요구사항

이 기능이 왜 필요했냐면 개인적으로 만든 스크립트를 Emacs 내에서 실행시키고 싶어서다. 단 해당 스크립트가 출력하는 내용을 확인하고 싶지만 현재 버퍼에서 하던 작업도 방해는 하지 않았으면 했기에 셸 커맨드를 실행시키는 전용 함수가 아닌 이맥스 내장 터미널을 통해 실행시키는 것을 원했다.

조금은 제한적인(?) 기능이라 이런 기능의 함수를 이맥스가 자체적으로 제공하는 것 같지는 않았고 결국 만드는 수밖에 없었다. 다행히도 힌트가 여기저기 있었기에 큰 삽질 없이 작성할 수 있었다.

완성된 코드 샘플

필요한 기능의 핵심은 make-term이라는 함수다. 이 함수를 이용하면 원하는 셸로 원하는 커맨드를 실행시킬 수 있는 내장 터미널 버퍼를 얻을 수 있다.

아래 코드는 사이드 윈도우에 터미널을 열고 거기서 zsh을 통해 어떤 셸 스크립트를 실행시키는 함수다.

(defun run-my-some-command-from-terminal ()
  (interactive)
  (let ((buffer (make-term "*my-some-terminal-command-buffer*"
                           "/bin/zsh"
                           nil
                           "-c" "some_weird_script.sh --foo --bar")))
    (display-buffer-in-side-window
     buffer `((window-height . ,multi-term-dedicated-window-height)))
    (select-window (get-buffer-window buffer))))

여기서 make-term의 매개변수로 넘어가는 부분들이 핵심이다. 버퍼 이름은 원하는 대로 짓거나 매번 랜덤하게 지어도 된다. 그리고 -c 부분 이후에 필요한 셸 커맨드를 적을 수 있다.

그 아래 부분들 중 display-buffer-in-side-window 함수는 사이드 윈도우에 터미널을 띄우기 위한 용도다. 따라서 사이드가 아닌 특정 윈도우에 터미널 버퍼를 띄우려면 대신 display-buffer 함수를 이용할 수 있다.

마지막의 select-window는 사실 꼭 필요한 부분은 아니다. 하지만 명령이 실행되면 사이드 윈도우에 터미널이 뜬 채로 종료되지 않아서 해당 윈도우를 닫아줘야 하는데 매번 윈도우를 직접 이동시킨 후 닫아야 하는 게 불편해서 아예 자동으로 터미널 윈도우로 포커스가 가도록 넣은 부분이다.

사족

출력 내용이 간단하거나 혹은 즉시 출력되는 내용을 알아야 하는 게 아니라면 그냥 shell-command-to-string 함수로 셸 명령을 실행시킨 결과를 얻어 바로 message 함수로 출력해 버리면 된다. 꼭 필요한 게 아니라면 굳이 먼 길을 돌아갈 필요는 없을 것 같다.