기술 가이드

셸 탭 자동완성 스크립트 작성 가이드

7 min read 쉘 개발 업데이트됨 22 Sep 2025
Bash와 zsh 탭 자동완성 스크립트 만들기
Bash와 zsh 탭 자동완성 스크립트 만들기

터미널 창에 Mutt 이메일 클라이언트 매뉴얼 페이지가 표시된 모습

개요

탭(Tab) 자동완성은 명령어 입력 시 반복 입력을 줄여주고 사용성을 크게 향상시킵니다. 이 문서에서는 간단한 예제로 zsh와 Bash용 자동완성 핸들러를 만들고, 이를 포터블하게 배포하는 방법과 실제 운영 환경에서 고려해야 할 모범 사례, 테스트 및 트러블슈팅 체크리스트까지 설명합니다.

정의 한 줄: 자동완성은 사용자가 탭을 눌렀을 때 가능한 입력 옵션을 제시하는 기능입니다.

중요: 이 가이드의 모든 코드 블록은 실제로 동작하는 예제입니다. 파일에 저장해 실행하거나 셸 설정에서 source하면 테스트할 수 있습니다.

탭 자동완성의 동작 원리

자동완성은 크게 세 가지 유형으로 나눌 수 있습니다.

  • 명령어 자동완성: PATH 환경변수를 검사해 입력한 접두사와 일치하는 실행 파일을 제안합니다.
  • 파일/경로 자동완성: 현재 디렉터리 또는 절대경로에 있는 파일과 디렉터리를 제안합니다.
  • 인수(Argument) 자동완성: 커맨드 이름 이후에 나오는 옵션, 서브커맨드, 또는 동적 제안을 처리합니다. 이 글은 주로 세 번째 유형을 다룹니다.

자동완성 시스템은 셸마다 내부 작동 방식이 다릅니다. zsh는 완성 시스템(compinit/compadd)이 잘 발달되어 있고, Bash는 개발자에게 더 많은 제어와 책임을 요구합니다.

예제 커맨드: todos

예제로 사용할 간단한 스크립트는 개인 TODO 파일을 관리하는 명령어입니다. 실제 기능은 단순하며, 아래 발췌는 관련 부분만 보여줍니다.

#!/usr/bin/env zsh  
  
FILE="$HOME/.local/state/todos/data"  
  
if [ "$#" -eq "1" ] && [ "$1" = "edit" ] ; then  
    "${EDITOR:-vi}" "$FILE"  
elif [ "$#" -eq "1" ] && [ "$1" = "help" ] ; then  
    echo "Usage: $(basename $0) [ edit | help ]"  
else  
    <"$FILE" grep -v ^~  
fi  

스크립트를 실행 가능한 상태로 PATH에 넣으면 다음처럼 동작합니다.

  • todos: 데이터 파일의 내용을 출력(앞에 ~로 시작하는 줄은 무시)
  • todos help: 사용법 출력
  • todos edit: 편집기에서 데이터 파일 열기

이 예제에서는 서브커맨드를 자동완성으로 제안하도록 만들겠습니다. 스크립트의 실제 동작은 예제 목적상 중요하지 않습니다 — 이름만 같으면 자동완성은 동작합니다.

zsh용 커스텀 자동완성 작성

가장 기본적인 zsh 자동완성 핸들러는 매우 간단합니다. 아래 코드는 compadd를 직접 호출해 고정된 제안 목록을 추가합니다.

_complete_todos() {  
    compadd help edit  
}  
  
autoload -Uz compinit  
compinit  
compdef _complete_todos todos  

설명:

  • _complete_todos 함수는 compadd를 호출해 제안 단어를 등록합니다.
  • compadd는 zsh의 compinit 모듈에 포함된 함수입니다.
  • autoload -Uz compinit; compinit은 완성 시스템을 초기화합니다.
  • compdef로 명령어 todos와 핸들러 _complete_todos를 연결합니다.

사용방법:

  • .zshrc에 위 코드를 추가하거나, _todos라는 파일로 fpath에 넣을 수 있습니다.
  • 개발 중에는 터미널에서 직접 위 코드를 실행해 테스트할 수 있습니다.

장점: 구현이 간단하고, 고정된 서브커맨드에는 즉시 적용됩니다.

제한사항: 동적 제안(파일 내용 기반, 원격 API 호출 등)을 하려면 함수 내부에서 조건문 및 외부 호출을 추가해야 합니다.

Bash의 차이점과 처리 방식

Bash는 자동완성 처리에 더 많은 책임을 부여합니다. 기본적인 Bash 핸들러 예시는 다음과 같습니다.

_complete_todos_bash() {  
    COMPREPLY=(help edit)  
}  
  
complete -F _complete_todos_bash todos  

설명:

  • complete -F로 todos 명령에 대한 핸들러 함수를 등록합니다.
  • 핸들러는 COMPREPLY 배열을 설정해 Bash에게 제안 목록을 전달합니다.

문제: 위 구현은 입력된 접두사를 고려하지 않기 때문에 “todos he” 시에도 help와 edit를 모두 제안합니다. Bash는 기본적으로 핸들러가 제공한 제안만 출력하므로, 컨텍스트 필터링을 직접 구현해야 합니다.

유용한 Bash 변수:

  • COMP_WORDS: 현재 명령줄을 단어별로 나눈 배열입니다.
  • COMP_CWORD: COMP_WORDS에서 커서가 위치한 단어의 인덱스입니다.

compgen 명령어:

  • compgen은 다양한 소스(명령어, 파일, 함수, 정적 단어 목록 등)로부터 제안 목록을 생성합니다.
  • -W 옵션은 고정 단어 목록을 검색합니다.

예시:

$ compgen -W "one two three" o  
one  
$ compgen -W "one two three" t  
two  
three  

따라서 실제로는 아래처럼 compgen을 사용해 접두사를 고려한 COMPREPLY를 만들면 됩니다.

_complete_todos_bash() {  
    COMPREPLY=( $( compgen -W "edit help" -- "${COMP_WORDS[$COMP_CWORD]}" ) )  
}  
  
complete -F _complete_todos_bash todos  

이렇게 하면 “todos he”은 “help”만 제안합니다.

zsh와 Bash를 모두 지원하는 포터블 스크립트

실제 배포용 스크립트는 실행 중인 셸을 감지해 각각의 초기화 절차를 실행해야 합니다. 아래 예시는 zsh와 Bash 둘 다 지원합니다.

SUBCOMMANDS=(help edit halt)  
  
_complete_todos_zsh() {  
    compadd $SUBCOMMANDS  
}  
  
_complete_todos_bash() {  
    COMPREPLY=( $( compgen -W "${SUBCOMMANDS[*]}" -- "${COMP_WORDS[$COMP_CWORD]}" ) )  
}  
  
if [ -n "${ZSH_VERSION:-}" ]; then  
    autoload -Uz compinit  
    compinit  
    compdef _complete_todos_zsh todos  
elif [ -n "${BASH_VERSION:-}" ]; then  
    complete -F _complete_todos_bash todos  
fi  

설명 및 팁:

  • ZSH_VERSION과 BASH_VERSION 환경변수를 통해 현재 셸을 감지합니다.
  • SUBCOMMANDS 배열을 사용해 제안 목록을 한곳에서 관리합니다.
  • Bash는 배열과 문자열 사이 변환이 필요하므로 “${SUBCOMMANDS[*]}” 같은 문법을 사용합니다.
  • 이 파일을 completion.sh로 저장한 뒤 .bashrc나 .zshrc에서 source하면 됩니다:
. /path/to/completion.sh  

동적 제안: 고정 목록을 넘어서

실무에서는 고정 목록만으로 충분하지 않을 때가 많습니다. 예를 들어 현재 디렉터리의 파일 목록, 원격 API에서 조회한 리소스, 또는 로컬 설정 파일의 섹션명을 제안해야 할 수 있습니다.

  • zsh: compadd 함수는 동적 제안 생성에 최적화되어 있습니다. 함수 내부에서 명령 실행 결과를 compadd에 파이프로 넘기면 됩니다.
  • Bash: compgen으로 파일명, 사용자명 등을 생성할 수 있고, 직접 명령을 실행해 결과를 COMPREPLY로 변환하면 됩니다.

예시(파일 기반 동적 제안):

zsh:

_complete_files_zsh() {  
    compadd -- $(ls *.md 2>/dev/null)  
}  

bash:

_complete_files_bash() {  
    local cur
    cur="${COMP_WORDS[$COMP_CWORD]}"
    COMPREPLY=( $(compgen -W "$(ls *.md 2>/dev/null)" -- "$cur") )
}

주의: 외부 명령을 호출할 때는 성능과 보안(특히 사용자 입력을 명령으로 전달할 때의 쉘 인젝션)을 고려해야 합니다. 가능한 경우 고정 목록이나 안전한 파싱을 우선하세요.

설치 및 배포 권장 방식

  • 개인 사용: .zshrc 또는 .bashrc에 직접 소스 또는 함수 정의.
  • 여러 사용자/시스템 공유: /etc/bash_completion.d 또는 시스템의 zsh fpath에 파일을 배포.
  • 패키지화: 스크립트를 패키지(배포 파일)로 만들고 설치 스크립트에 자동으로 source되도록 구성.

파일 권한: 스크립트는 읽을 수 있어야 하고, 실행을 요구하지 않는 경우가 많으므로 보통 “644” 권한이면 충분합니다.

테스트 케이스와 수용 기준

간단한 테스트 케이스 목록(수동 또는 자동화 가능):

  1. 기본 제안: 아무 입력 없이 todos를 눌러 모든 서브커맨드가 제시되는가.
  2. 접두사 필터링: todos he가 “help”만 제안하는가.
  3. 경계조건: 공백이 여러 개일 때 올바른 단어를 완성하는가.
  4. 동적 제안: 파일 기반 제안이 최신 파일 목록을 반영하는가.
  5. 멀티플랫폼: zsh와 bash에서 각각 동일한 동작을 보이는가.

수용 기준: 위 테스트가 모두 통과하고, 성능 저하(탭 누름 시 200ms 이상 지연)나 보안 취약점이 없을 것.

배포 전 체크리스트(역할별)

개발자:

  • 스크립트에 하드코딩된 절대 경로가 없는지 확인.
  • 외부 명령에 사용자 입력을 직접 전달하지 않는지 확인.

운영자:

  • 시스템 전체 배포 시 /etc 또는 /usr/local 경로 권한 검토.
  • 충돌하는 complete/compdef 엔트리가 없는지 확인.

테스터:

  • 모든 주요 셸(zsh, bash)에서 수동 테스트 수행.
  • 여러 환경(POSIX 쉘과의 호환성 등)에서 자동화 테스트 실행.

트러블슈팅 및 롤백(runbook)

  1. 현상: 자동완성 동작 안 함.
    • 확인: 해당 셸에서 complist/complete 등록 여부 확인.
    • zsh: type compdef로 등록 확인, compinit이 호출되었는지 점검.
    • bash: complete -p todos로 핸들러 등록 확인.
  2. 현상: 제안 목록이 오래된 값만 보여줌.
    • 원인: 캐싱 또는 동적 제안 생성 시 외부 데이터가 갱신 안 되는 경우.
    • 조치: 제안 생성 부분에서 캐시를 사용하고 있다면 무효화 로직 추가.
  3. 롤백: 문제 발생 시 .bashrc/.zshrc에서 source 라인을 주석 처리하고 셸 재시작.

보안 및 개인정보 주의사항

  • 자동완성 핸들러에서 민감한 데이터를 노출하지 않도록 주의하세요(예: 홈 디렉터리 구조, 인증 토큰 등).
  • 동적 제안에서 외부 API 호출을 할 경우 인증 정보를 안전하게 관리하고, 로깅 시 민감한 필드를 마스킹하세요.

중요: 자동완성 로직 자체는 사용자의 로컬 데이터에 접근할 수 있으므로, 공개 저장소에 올리기 전에 민감한 경로가 포함되어 있지 않은지 확인해야 합니다.

대안 및 확장 접근법

  • 외부 라이브러리 사용: 복잡한 자동완성 로직은 쉘 전용 라이브러리나 프레임워크(예: bash-completion 프로젝트)를 활용해 구현할 수 있습니다.
  • 도구 통합: CLI 프레임워크(예: Python의 Click, Go의 Cobra 등)는 자동완성 스크립트를 자동 생성해주는 기능을 제공합니다. 이미 프레임워크를 사용 중이라면 해당 기능을 검토하세요.
  • GUI 보조: 대규모 사용자 대상이라면 CLI 대신 간단한 GUI 또는 TUI도 고려할 수 있습니다.

정신 모델(heuristics)

  • 단순성 우선: 가능한 경우 정적 목록을 유지하고 동적 제안은 필요 시 추가.
  • 안전성 우선: 사용자 입력을 외부 명령에 바로 전달하지 않음.
  • 일관성 유지: zsh와 bash에서 동작이 최대한 유사하도록 설계.

호환성 및 마이그레이션 팁

  • 오래된 Bash(예: 3.x)와 최신 Bash(4.x 이상) 사이에 배열 처리 방식 차이가 있을 수 있으므로, 배열을 문자열로 변환할 때 표준 POSIX 호환 문법을 사용하세요.
  • zsh의 compadd는 고급 기능을 제공하므로, zsh 전용 로직은 파일명을 언더스코어로 시작하는 파일로 분리해 fpath에 두는 것이 깔끔합니다.

예제 체크리스트 템플릿

  • SUBCOMMANDS 배열에 모든 서브커맨드가 포함되어 있는가?
  • zsh에서 compinit 및 compdef가 호출되는가?
  • bash에서 complete -F로 핸들러가 등록되는가?
  • 접두사 기반 필터링이 작동하는가?
  • 동적 제안의 성능이 허용 범위인가?

간단한 사고 흐름(결정 트리)

flowchart TD
  A[탭 자동완성 적용 대상 확인] --> B{서브커맨드 고정 목록인가?}
  B -- Yes --> C[정적 목록으로 compadd/COMPREPLY 등록]
  B -- No --> D{동적 데이터가 로컬인가?}
  D -- Yes --> E[동적 생성'파일/디렉터리 기반']
  D -- No --> F[외부 API 사용: 캐시/비동기/오류 처리 설계]
  E --> G[성능 및 보안 검토]
  F --> G
  C --> G
  G --> H[테스트 및 배포]

요약

  • zsh: compadd + compinit + compdef를 사용해 자동완성을 쉽게 추가할 수 있습니다.
  • bash: COMPREPLY와 compgen을 사용하되, COMP_WORDS 및 COMP_CWORD로 컨텍스트를 파악해야 합니다.
  • 포터블 스크립트: ZSH_VERSION과 BASH_VERSION을 검사해 각각의 초기화 루틴을 호출하세요.
  • 배포 전 테스트, 보안 검토, 성능 검증을 반드시 수행하세요.

핵심 포인트:

  • 고정 목록이면 구현이 쉽고 안전하며 빠릅니다.
  • 동적 제안은 편의성이 크지만 성능과 보안 주의가 필요합니다.
  • 셸마다 초기화/등록 방식이 다르므로 포터블 스크립트는 셸 감지를 포함해야 합니다.

추가 자료 및 다음 단계 제안:

  • 자신의 CLI에 대해 더 정교한 제안을 만들고 싶다면, 서브커맨드별로 옵션/파일/리소스 제안을 추가해 보세요.
  • CLI 프레임워크를 사용 중이라면 해당 프레임워크의 자동완성 생성 기능을 확인하세요.
공유하기: X/Twitter Facebook LinkedIn Telegram
저자
편집

유사한 자료

Android 백그라운드 앱 차단 및 배터리 최적화 방법
모바일/안드로이드

Android 백그라운드 앱 차단 및 배터리 최적화 방법

손상된 RAID 어레이 크기 줄이기 가이드
Storage

손상된 RAID 어레이 크기 줄이기 가이드

아이폰 인스타그램 스토리 소리 안 날 때 해결법
모바일 팁

아이폰 인스타그램 스토리 소리 안 날 때 해결법

가짜 전화번호 추적 및 차단 가이드
보안

가짜 전화번호 추적 및 차단 가이드

Bash와 zsh 탭 자동완성 스크립트 만들기
쉘 개발

Bash와 zsh 탭 자동완성 스크립트 만들기

iOS용 Numbers '09에서 폼으로 데이터 수집하기
생산성

iOS용 Numbers '09에서 폼으로 데이터 수집하기