Linux

[Bash] 자동완성 스크립트(Completion Script) 작성 방법과 동작 원리

Posted 2019. 08. 28 Updated 2019. 08. 28 Views 8166 Replies 0
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

크게 작게 위로 아래로 댓글로 가기 인쇄

Bash의 내장 명령(Built-in Command)중 하나인 complete는 특정 명령에 대한 자동완성(Autocomplete) 목록을 정의한다.

자동완성 정의 명령 - complete

자동완성 추천 단어 목록(Suggestions)이 고정되어 있으면 ~/.bashrccomplete명령 한 줄만 추가해서 커스텀 자동완성을 정의할 수 있긴 하다. 하지만, 대개 추천 목록을 그때 그때 상황에 맞춰 동적으로 생성해야 하는 경우가 대부분이므로, 다음과 같이 -F 옵션으로 자동완성 함수를 지정하는 방식으로 사용한다.

complete -F <함수> <명령>

셸에 <명령>을 입력하고 [Tab]키를 눌러서 자동완성을 시도하면 <함수>가 호출된다. <함수>가 추천 단어 목록을 만들어 반환하면 자동완성이 되거나(추천 단어가 1개인 경우), 셸에 추천 단어들이 출력(추천 단어가 여러개인 경우)된다.

 

자동완성 스크립트

 커스텀 자동완성을 정의할 때는 (편의상) 명령별로 스크립트를 따로 만들어 /etc/bash_completion.d/ 디렉토리에 집어넣어 사용한다. 이 디렉토리 내의 자동완성 스크립트들은 로그인 시점에 실행되어 사용자 셸에 적용된다. (특정 사용자에게만 사용 가능한 자동완성을 정의하려면 ~/.bashrc를 활용하면 된다.)

다음은 mycmd 명령에 대해 /var/www/의 하위 디렉토리들에 대한 경로를 자동완성으로 제공하는 스크립트의 예시이다.

_mycmd_autocomplete() {
    local cur
    cur=${COMP_WORDS[COMP_CWORD]}
    COMPREPLY=( $(compgen -W '$(find /var/www -mindepth 1 -maxdepth 1 -type d -printf "%P ")' -- $cur ) )
}
complete -F _mycmd_autocomplete mycmd

자동완성 함수는 전역변수 COMPREPLY를 통해 자동완성 추천 단어 목록을 전달한다. 이 때, 목록에서 현재 입력중인 단어를 Prefix로 하는 단어들만 추려내기 위해 compgen 명령을 보조로 사용한다.

compgen -W '단어1 단어2 ... 단어N' -- "<Prefix>"

뒤쪽의 "-- $cur" 부분이 현재 입력중인 단어를 전달하는 인수이다. 이 $cur를 만들어내기 위해 다음 전역 변수들을 사용하였다. 이 전역 변수들은 자동완성 함수가 호출될 때 세팅되어 전달된다.

  • $COMP_WORDS: 현재 셸에 입력된 단어(공백으로 구분된 토큰) 목록
  • $COMP_CWORD: 현재 셸에 입력중인 단어의 순번(Index)

예를 들어, 셸에 다음과 같이 입력하고 자동완성을 위해 [Tab]키를 눌렀을 때, 자동완성 함수에 전달되는 전역변수들은 다음과 같이 세팅된다.

mycmd /var/www/andromeda /var/www/t
$COMP_WORDS[0] = "mycmd"
$COMP_WORDS[1] = "/var/www/andromeda"
$COMP_WORDS[2] = "/var/www/t"
$COMP_CWORD = 2

참고: $COMP_CWORD가 항상 $COMP_WORDS의 Index 최대값과 같지는 않은데, 입력 중인 명령의 중간 부분에서 자동완성을 시도할 수도 있기 때문이다.

 

자동완성 스크립트 응용

 자동완성 함수 호출시 함께 전달되는 전역변수들을 잘 활용하면 추천 목록을 보다 세밀하게 다듬어서 제공할 수 있다.

아래 자동완성 스크립트는 위의 예시를 조금 변형해서 다음과 같은 기능을 추가한 것이다.

  • 첫 번째 인수에 대해서만 자동완성을 실행한다.
  • 전체 경로가 아닌, 디렉토리 이름만을 자동완성으로 제공한다.
_mycmd_autocomplete() {
    local cur list
    cur=${COMP_WORDS[COMP_CWORD]}
    list=""
    if [ ${COMP_CWORD} != 1 ]; then
        return
    fi
    for dir in $(find /var/www/ -mindepth 1 -maxdepth 1 -type d); do
        list="${list} $(basename ${dir})"
    done
    COMPREPLY=( $(compgen -W '${list}' -- $cur ) )
}
complete -F _mycmd_autocomplete mycmd