Readline

CLI (command line interface) 를 사용하는 프로그램에서 line editing, 명령 history, 자동완성 기능을 제공하는 라이브러리로 shell 프롬프트 상에서 명령문을 작성할때 사용하는 키조합이나, 명령 history, 자동완성 기능은 이 라이브러리를 이용하는 것입니다. line editing 에는 emacs 와 vi 두 종류의 키조합을 제공하는데 기본설정은 emacs 이며 set -o emacs or set -o vi 옵션을 통해 변경할 수 있습니다.

Readline init file

readline 은 ~/.inputrc 설정 파일을 통해 옵션을 설정하고 키조합을 커스터마이징 할 수 있습니다. 다음은 completion-ignore-case 옵션을 설정하여 tabalt-/ 키를 이용한 자동완성 시에 대, 소문자 구분없이 하고 alt-space 에는 "git " 을 ctrl-space 에는 "docker " 스트링이 프롬프트 상에 입력되게 하며 펑션키 F9, F10 에는 자주 사용하는 명령을 바인딩하는 예입니다.

키조합에 사용되는 키값은 read 명령으로 구할 수 있습니다.
구한 키값에서 ^[ 문자를 \e 로 수정하여 사용합니다.

$ read        # enter 후에 원하는 키를 입력
^[[20~        # F9 키를 누른 상태
              # 키값은 터미널별로 다른값이 나올 수 있습니다.

inputrc 파일을 수정한 후에 Ctrl-x Ctrl-r 를 입력하거나 새로 터미널 창을 열면 설정된 기능을 사용할 수 있습니다. 한가지 유의할 점은 window manager 나 터미널창 자체에서 이미 키조합이 설정돼있으면 적용이 되지 않으므로 필요없는 키조합은 먼저 삭제하시기 바랍니다.

~/.inputrc 활용 예제

# 대,소문자 구분없이 word completion
set completion-ignore-case on

# 모드 설정을 하지 않을경우 default 는 emacs 입니다.
# emacs 모드로 설정
# set editing-mode emacs
# vi 모드로 설정
# set editing-mode vi

# emacs 모드일경우 적용
# $if mode=emacs

# alt-space 치면 프롬프트에 git 입력
"\e ": "git "

# ctrl-space 치면 프롬프트에 docker 입력
"\C-@": "docker "

# 이 함수는 원래 \C-x\C-e 키에 바인딩 되어있는데 한 번에 실행하기 위해
# F2 키에 바인딩 합니다. 현재 명령 행상에서 작성 중인 내용과 함께 vi 에디터가 열립니다.
"\eOQ": edit-and-execute-command

# \C-k\C-u 는 각각 kill-line, unix-line-discard 함수로 바인딩되어 있으므로
# 현재 프롬프트에 입력돼있는 스트링을 모두 삭제합니다. \C-m 은 enter 를 의미합니다.

# F11 키에 git pull 명령 바인딩
"\e[23~": "\C-k\C-ugit pull\C-m"
"\e[23;2~": "\C-k\C-ugit diff master@{1} master\C-m"

# F12 키에 git push 명령 바인딩
"\e[24~": "\C-k\C-ugit push\C-m"

# 기존 키바인딩을 undefined 하고 싶으면 
# "키값": nop

# $endif

현재 바인딩 되어있는 사용자 정의 키조합은 bind -s 명령으로 조회해 볼 수 있습니다.
bind -v 로는 현재 설정되어 있는 readline 옵션을 조회해 볼 수 있습니다.

유용한 키보드 shortcut

  • 이전 명령에서 사용된 마지막 인수를 입력하고 싶을 때는 Alt-. 을 이용하면 됩니다. 연속해서 누르면 이전 명령으로 이동합니다.

  • Ctrl-r 은 명령 history 를 검색하여 입력한 패턴에 해당하는 명령을 불러와 실행할 수 있게 해줍니다. 연속해서 누르면 매칭되는 다음 항목으로 넘어갑니다.

  • 프롬프트에서 명령문을 작성중에 editor 를 불러오고 싶을때 Ctrl-x Ctrl-e 를 하면 현재 입력중인 내용과 함께 vi editor 가 열립니다. :wq 명령으로 vi 를 종료하면 수정된 명령이 실행되고 :cq 명령으로 종료하면 실행되지 않습니다.

  • tab 키로 파일이름 자동완성이 되지 않을 경우 Alt-/ 로 자동완성을 할 수 있습니다.

  • 그 외 여러 키조합은 http://readline.kablamo.org/emacs.html 에서 볼 수 있습니다. 이전에 실행된 키조합을 undo (Ctrl-/) 할수도 있고 cut & paste 도 할 수 있습니다.

  • 여러 단계의 키조합이 설정되어 사용될 경우 키조합 입력 중간에 취소하는 방법은 Ctrl-g 입니다.

현재 바인딩 되어있는 readline 함수 키조합은 bind -p 명령으로 조회해 볼 수 있습니다.
bind -l 명령으로 사용 가능한 readline 함수 목록을 볼 수 있습니다.

Quiz

앞서 이전 명령에서 사용된 마지막 인수는 Alt-. 키조합을 이용하여 입력할 수 있다고 하였는데요
그럼 마지막 인수가 아닌 경우는 어떻게 입력할까요?

똑같이 Alt-. 키조합을 이용하지만 그전에 인수의 위치를 Alt-숫자 로 먼저 지정을 합니다.
예를 들면 이전 명령에서 사용된 2 번째 인수를 입력하고 싶다면 Alt-2 Alt-. 을 입력하면 됩니다.


cut & paste, copy & paste

이 기능은 kill ring 과 Ctrl-y (yank) 함수를 이용합니다. 이름이 좀 생소한데 예전에는 cut & paste 라고 하지 않고 kill, yank 라고 했다고 합니다. Ctrl-u, Ctrl-k, Ctrl-w, Alt-backspace 같은 키조합들은 작성한 명령문의 일부분을 삭제하는데 이때 삭제된 내용이 kill ring 에 들어갑니다. 이후에 Ctrl-y 를 이용하여 paste 할 수 있습니다. 여기서 kill ring 이라고 하는 이유는 paste 할때 Alt-y 로 이전 값을 불러올 수가 있기 때문입니다.

다음은 Ctrl-w (공백을 기준으로 이전 스트링 삭제) 와 Alt-backspace (단어를 기준으로 이전 스트링 삭제) 를 이용해 cut & paste 를 하는 예입니다. cut 한 다음에 바로 paste 하면 copy & paste 를 할 수 있습니다.

Ctrl-w 와 Alt-backspace 는 공백과 단어를 기준으로 이전 스트링을 삭제하기 때문에 종종 kill 하고자 하는 경계가 맞지 않는 경우가 있습니다. 이때는 mark 와 kill 을 이용하여 직접 region 을 지정할 수 있습니다. 이 기능을 이용하기 위해서는 먼저 ~/.inputrc 에 다음과 같은 설정을 합니다.

# ~/.inputrc 에 추가할 내용
# Alt-m : 현재 커서위치에 mark 설정
"\em": set-mark

# Alt-k : mark 한 위치부터 현재 커서까지 내용을 kill ring 에 copy.
"\ek": copy-region-as-kill

# Alt-Shift-k : mark 한 위치부터 현재 커서까지 내용을 copy 함과 동시에 삭제.
"\eK": kill-region

bind toggling

다음은 bind 명령을 이용한 toggling 입니다.
Ctrl-space 에 docker 가 바인딩 되어 있는데 F2 키를 이용해서 vagrant 로 토글하는 기능입니다.

# bind -x 명령은 .inputrc 에서 설정할 수 없으므로 다음 내용을 .bashrc 에 추가합니다.
# bind_toggle.sh 파일을 $PATH 에서 찾을 수 없을 경우 전체 경로를 입력합니다.
# F1 키를 toggle 키로 설정합니다.

bind -x '"\eOP": "source bind_toggle.sh"'

---------------------------------------------------------

# ~/bin/bind_toggle.sh 파일의 내용
# source 명령으로 읽어 들이므로 shebang 라인과 실행 권한은 필요 없습니다.

case $(bind -S | grep -P -m1 -o "(?<=C-@ outputs )\S+") in
git)
    bind '"\C-@": "docker "'
    echo docker
    ;;

docker)
    bind '"\C-@": "vagrant "'
    echo vagrant
    ;;

vagrant)
    bind '"\C-@": "git "'
    echo git
    ;;
esac

토글 단어를 여러개 사용하면 현재 설정을 알기 어려우므로 프롬프트에 표시해 주면 좋습니다. ~/.bashrc 파일의 PS1 프롬프트 설정하는 곳을 찾아서 적당한 위치를 정하여 += 연산자를 이용해 다음과 같이 추가해 줍니다. 설정된 값은 토글키를 누른 후 다음번 프롬프트가 표시될때 변경 됩니다.

# ~/.bashrc 파일에서 PS1 프롬프트 설정

PS1+='$(
    case $(bind -S | grep -P -m1 -o "(?<=C-@ outputs )\S+") in
        git) echo G ;;
        docker) echo D ;;
        vagrant) echo V ;;
    esac
)'

Command line history expansion

현재 작성중인 명령행상에서 앞서 타입한 인수를 다시 입력하고 싶을때가 있습니다. readline 함수인 history-expand-line 과 !#:$ !#:0 !#:1 ... command history 기능을 이용해 Alt-h 숫자 키조합에 바인딩 해보겠습니다.

# ~/.inputrc 에 추가할 내용
# "Alt-," 에는 !#:$ 을 바인딩 하고
# "Alt-h 1" 에는 !#:1 을 "Alt-h 2" 에는 !#:2 ... 을 각각 바인딩 합니다.
# "\e^" 는 history-expand-line 함수입니다. 앞서 입력한 !#:1 부분이 확장됩니다.

"\eh1": "!#:1\e^" 
"\eh2": "!#:2\e^" 
"\eh3": "!#:3\e^" 
"\eh4": "!#:4\e^" 
"\eh5": "!#:5\e^" 
"\eh6": "!#:6\e^" 
"\eh7": "!#:7\e^" 
"\eh8": "!#:8\e^" 
"\eh9": "!#:9\e^" 
"\eh0": "!#:0\e^" 
"\e,": "!#:$\e^"

이전 명령의 출력 결과를 사용

명령문을 작성할 때 이전 명령문이 아니라 명령의 출력 결과를 사용하고 싶을 때가 있습니다.
.inputrc 에 다음과 같은 설정을 통해 이전 명령의 출력을 입력할 수 있습니다.

# "\e[19~" : F8 키로 설정합니다.  "\e[19;2~"  는 SHIFT-F8 입니다.
# \C-u : 현재까지 타입한 명령을 삭제함과 동시에 copy 합니다.
# $(!!)\e\C-e : \e\C-e 는 shell-expand-line 함수로 앞서 입력한 $(!!) 부분이 확장됩니다.
# \C-a : 커서를 라인 처음으로 옮깁니다.
# \C-y : yank 함수로 paste 에 해당합니다. 앞서 \C-u 로 copy 했던 부분을 붙여 넣습니다.
# \C-e : 커서를 라인 끝으로 옮깁니다.

"\e[19~": "\C-u$(!! | awk 'END{print $NF}')\e\C-e\C-a\C-y\C-e"
"\e[19;2~": "\C-u$(!!)\e\C-e\C-a\C-y\C-e"