[Dev] Shell Completion
CLI 도구를 쓰다 보면 kubectl get po 까지 치고 Tab을 눌러 pods로 완성하는 일이 자연스럽다. 이 자동 완성이 어떻게 동작하는지, 왜 매번 “completion 스크립트를 shell에 로드하라”는 안내가 나오는지, 그리고 completion을 지원하지 않는 CLI에는 어떻게 직접 만들어 붙이는지를 정리한다.
TL;DR
- Shell completion: Tab 키를 누르면 커맨드, 서브커맨드, 옵션, 리소스 이름 등을 자동으로 완성해 주는 shell 기능이다.
- 동작 원리: CLI 도구가 shell 문법에 맞는 자동 완성 스크립트를 생성(출력)하고, 이 스크립트를 shell에 로드하면 Tab 자동 완성이 활성화된다.
- 일반적인 패턴:
<command> completion <shell-type>명령으로 스크립트를 출력하고, 이를 shell 설정 파일에 등록한다. - CLI 프레임워크: cobra(Go), click(Python), clap(Rust) 등이 completion 생성을 내장 지원하여, 개발자가 completion 코드를 따로 작성할 필요가 없다.
- 직접 작성: completion을 지원하지 않는 CLI 도구는 각 shell의 completion 시스템 문법에 맞춰 스크립트를 직접 작성할 수 있다.
개념
Shell completion(탭 완성)은 사용자가 커맨드 입력 도중 Tab 키를 누르면, shell이 입력 가능한 후보 목록을 보여주거나 자동으로 완성해 주는 기능이다. Shell 자체에 기본 completion 기능이 내장되어 있다. 파일 경로, 디렉토리, 환경 변수 등은 별도 설정 없이 Tab으로 완성된다. 여기서 한 걸음 더 나아가, 각 CLI 도구가 자신의 명령어 구조에 맞는 자동 완성 로직을 제공할 수 있다. kubectl get po<TAB>에서 pods가 완성되고, kubectl get pods -n <TAB>에서 네임스페이스 목록이 표시되는 것이 그 예다.
동작 원리
CLI 도구의 completion이 동작하는 과정은 생성과 로드 두 단계로 나뉜다.
1단계: completion 스크립트 생성
CLI 도구가 shell 문법에 맞는 자동 완성 스크립트를 stdout으로 출력한다.
# 일반적인 패턴
<command> completion <shell-type>
# 예시
kubectl completion bash
helm completion zsh
docker completion bash
gh completion -s zsh
예를 들어 kubectl completion bash를 실행하면, bash의 completion 시스템(complete, compgen, COMPREPLY 등)을 활용하는 수백 줄의 shell 스크립트가 출력된다.
$ kubectl completion bash
# Copyright 2016 The Kubernetes Authors.
# ...
__kubectl_debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE-} ]]; then
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
fi
}
# ... (수백 줄의 자동 완성 로직) ...
complete -o default -F __start_kubectl kubectl
이 스크립트 안에는 커맨드·서브커맨드·옵션 파싱 로직과 함께, shell의 completion 시스템에 함수를 등록하는 코드가 들어 있다. 마지막 줄의 complete -o default -F __start_kubectl kubectl이 핵심인데, 각 부분의 의미는 다음과 같다.
complete -o default -F __start_kubectl kubectl
├────────┘ ├─┘ ├───────────┘ ├─────┘
│ │ │ └─ 대상 커맨드
│ │ └─ Tab 시 호출할 함수
│ └─ -F: 함수(Function) 기반 completion 사용
└─ -o default: 매칭 결과가 없으면 기본 파일명 완성으로 fallback
즉, “사용자가 kubectl 뒤에서 Tab을 누르면 __start_kubectl 함수를 호출하고, 그 함수가 COMPREPLY 배열에 채운 값을 후보 목록으로 보여준다”는 등록 명령이다. 스크립트의 나머지 수백 줄은 이 __start_kubectl 함수와 그 안에서 호출하는 헬퍼 함수들의 정의다.
2단계: shell에 로드
생성된 스크립트를 shell이 인식하도록 로드한다. 대부분의 도구는 함수 기반(-F) 방식을 사용하며, 로드 방법은 크게 두 가지다.
# 방법 1: 파일로 저장 후 source
kubectl completion bash > ~/.kubectl-completion.sh
echo 'source ~/.kubectl-completion.sh' >> ~/.bashrc
# 방법 2: process substitution으로 직접 로드
echo 'source <(kubectl completion bash)' >> ~/.bashrc
방법 2에서 source <(kubectl completion bash)는 <(...) 구문(process substitution)으로 명령어 출력을 파일처럼 취급하여, 자동 완성 스크립트를 현재 shell 세션에 즉시 로드한다.
한편, Terraform이나 AWS CLI처럼 커맨드 기반(-C) 방식을 사용하는 도구도 있다. 이 경우 completion 스크립트를 생성하지 않고, Tab을 누를 때마다 바이너리 자체를 실행하여 후보를 반환한다.
# -F: shell 함수 호출 (kubectl, helm 등 대부분의 도구)
complete -o default -F __start_kubectl kubectl
# -C: 외부 커맨드 실행 (Terraform, AWS CLI)
complete -o nospace -C /usr/local/bin/terraform terraform
결과적으로 어느 방식이든 Tab을 누르면 해당 CLI 도구의 서브커맨드, 옵션, 리소스 이름 등이 완성된다.
kubectl get po<TAB> # → pods로 완성
kubectl get pods -n <TAB> # → 네임스페이스 목록 표시
kubectl describe pod my-<TAB> # → 실제 pod 이름들 표시
Completion 스크립트의 실행 구조
kubectl completion bash로 출력되는 스크립트를 처음 보면 당황스럽다. 분명 bash인데 COMPREPLY, compgen, COMP_CWORD 같은 처음 보는 변수와 함수가 가득하고, zsh는 '(-h --help)'{-h,--help}'[Show help]' 같은 낯선 구문이 나온다. 이유는 completion 스크립트가 일반 shell 스크립트와 프로그래밍 모델 자체가 다르기 때문이다.
일반 shell 스크립트는 위에서 아래로 순차 실행하는 명령형(imperative) 코드다.
name="hello"
echo "$name"
if [ -f "$1" ]; then
cat "$1"
fi
반면 completion 스크립트는 shell의 completion 시스템이 Tab 키 이벤트 시 호출하는 콜백 함수를 등록하는 코드다. 스크립트가 직접 실행되는 것이 아니라, shell이 특정 시점에 대신 호출한다.
complete -o default -F __start_kubectl kubectl
# → "kubectl 입력 중 Tab을 누르면 __start_kubectl 함수를 호출하라"
Completion 전용 변수와 함수
각 shell은 자체 completion API를 가지고 있으며, completion 스크립트는 이 전용 변수와 함수를 사용한다. 일반 shell 스크립팅에서는 마주칠 일이 없는 것들이다.
bash:
COMPREPLY=() # Tab 목록으로 보여줄 후보 배열
COMP_WORDS # 현재까지 입력된 단어들의 배열
COMP_CWORD # 커서가 위치한 단어의 인덱스
compgen -W "pods services" -- "$cur" # 현재 입력과 매칭되는 항목만 필터링
compopt -o nospace # 완성 후 공백 자동 추가 억제
zsh:
$words # 현재까지 입력된 단어들의 배열 (bash의 COMP_WORDS에 해당)
$CURRENT # 커서가 위치한 단어의 인덱스 (bash의 COMP_CWORD에 해당)
compadd # 후보 목록에 항목 추가
_describe 'command' commands # 설명과 함께 후보 목록 표시
_arguments -C '...' # 옵션/인자 선언 및 자동 파싱
fish: 변수 기반이 아니라 complete 명령으로 선언적으로 규칙을 등록하므로, 별도의 completion 전용 변수가 없다.
이 변수들은 shell이 completion 함수를 호출할 때 자동으로 채워 준다. 일반 스크립트에서 echo $COMP_WORDS(bash)나 echo $words(zsh)를 해봐야 아무것도 없다. completion 컨텍스트에서만 의미를 갖는 변수이므로, 일반 shell 스크립팅에서는 마주칠 일이 없다.
zsh _arguments DSL
zsh의 _arguments는 shell 안에 내장된 별도의 DSL(Domain-Specific Language)이다.
_arguments -C \
'(-h --help)'{-h,--help}'[Show help]' \
'--model[Specify AI model]:model:(opus sonnet haiku)' \
'1: :_claude_commands' \
'*:: :->args'
이 구문을 분해하면 다음과 같다.
'(-h --help)'{-h,--help}'[Show help]'
├─────────┘ ├────────┘ ├─────────┘
│ │ └─ Tab 시 보여줄 설명
│ └─ 실제 옵션 이름 (brace expansion)
└─ 상호 배타 그룹 (하나 쓰면 다른 건 후보에서 제거)
'--model[Specify AI model]:model:(opus sonnet haiku)'
├─────┘├───────────────┘ ├────┘ ├────────────────┘
│ │ │ └─ 허용되는 값 목록
│ │ └─ 인자의 표시 이름
│ └─ 설명
└─ 옵션 이름
'1: :_claude_commands' → 첫 번째 위치 인자를 _claude_commands 함수로 완성
'*:: :->args' → 나머지 인자를 args state로 넘겨 서브커맨드별 분기 처리
shell 문법이라기보다 _arguments 함수가 정의한 자체 문법 규칙이다. zsh 매뉴얼에서 별도의 문법 명세가 있을 정도다.
암묵적 실행 흐름
일반 스크립트는 if → else → echo처럼 흐름이 보이지만, completion 스크립트는 shell이 중간에서 제어권을 가져간다.
1. 사용자가 "kubectl get po" 입력 후 Tab
2. shell이 complete 테이블에서 kubectl → __start_kubectl 매핑 발견
3. shell이 COMP_WORDS=("kubectl" "get" "po"), COMP_CWORD=2 세팅
4. shell이 __start_kubectl() 호출
5. 함수가 COMPREPLY=("pods" "podtemplates") 세팅
6. shell이 COMPREPLY를 읽어서 Tab 목록으로 표시
이 과정 중 2~4, 6은 shell 내부에서 일어나고, 코드에는 5만 보인다. 스크립트만 보면 “이 함수를 누가 언제 부르는지”가 직관적이지 않다.
정리
Completion 스크립트가 낯선 이유는 같은 shell 언어이지만 shell의 completion API를 대상으로 작성된 콜백 코드이기 때문이다. 문법은 같은데, 사용하는 내장 변수·함수와 실행 구조가 일반 스크립트와 전혀 다르다.
| 구분 | 일반 shell 스크립트 | completion 스크립트 |
| — | — | — |
| 실행 주체 | 사용자가 직접 실행 | shell이 Tab 이벤트 시 호출 |
| 실행 구조 | 명령형 (위→아래 순차) | 콜백 등록 (이벤트 기반) |
| 사용하는 API | 일반 shell 문법 | completion 전용 변수/함수 |
| zsh 특이점 | — | _arguments 전용 DSL |
| 결과 전달 | stdout 출력, exit code | COMPREPLY(bash) / _describe(zsh)에 세팅 |
지원 shell
대부분의 CLI 도구가 여러 shell의 completion을 지원한다.
| shell | completion 시스템 | 특징 |
| — | — | — |
| bash | complete -F, compgen, COMPREPLY | 단순 문자열 매칭 위주, 표현력 낮음 |
| zsh | #compdef, _arguments, _describe | 설명 텍스트, 타입 검증 등 기능 풍부 |
| fish | complete 명령 한 줄씩 선언 | 문법이 가장 단순하고 직관적 |
| PowerShell | Register-ArgumentCompleter | Windows 환경 |
도구별 Completion 설정 방법
kubectl
Kubernetes 공식 문서에서 안내하는 방법이다.
# bash
kubectl completion bash > /etc/bash_completion.d/kubectl
# 또는
echo 'source <(kubectl completion bash)' >> ~/.bashrc
# zsh
kubectl completion zsh > ~/.zsh/completions/_kubectl
# ~/.zshrc에 아래 추가
# fpath=(~/.zsh/completions $fpath)
# autoload -Uz compinit && compinit
# fish
kubectl completion fish > ~/.config/fish/completions/kubectl.fish
alias를 사용하는 경우 alias에 대한 completion도 등록할 수 있다.
# bash
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
# zsh
echo 'alias k=kubectl' >> ~/.zshrc
echo 'compdef k=kubectl' >> ~/.zshrc
Helm
# bash
helm completion bash > /etc/bash_completion.d/helm
# zsh
helm completion zsh > ~/.zsh/completions/_helm
# fish
helm completion fish > ~/.config/fish/completions/helm.fish
Terraform
Terraform은 별도의 completion 서브커맨드 대신, 셸 설정 파일에 직접 등록하는 커맨드 기반(-C) 방식을 사용한다.
# bash/zsh 자동 설치
terraform -install-autocomplete
# 제거
terraform -uninstall-autocomplete
terraform -install-autocomplete를 실행하면 ~/.bashrc 또는 ~/.zshrc에 다음과 같은 코드가 추가된다.
autoload -U +X bashcompinit && bashcompinit
complete -o nospace -C /usr/local/bin/terraform terraform
kubectl 등이 사용하는 -F(함수 호출) 방식과 달리, -C는 Tab을 누를 때마다 /usr/local/bin/terraform 바이너리를 직접 실행하여 후보를 반환한다. completion 스크립트를 별도로 생성·로드할 필요가 없는 대신, Tab마다 프로세스가 실행되는 비용이 있다.
GitHub CLI (gh)
# bash
gh completion -s bash > /etc/bash_completion.d/gh
# zsh
gh completion -s zsh > ~/.zsh/completions/_gh
# fish
gh completion -s fish > ~/.config/fish/completions/gh.fish
AWS CLI
AWS CLI도 Terraform과 같은 커맨드 기반(-C) 방식이다. aws_completer라는 별도 바이너리를 사용한다.
# completer 경로 확인
which aws_completer
# bash
complete -C '/usr/local/bin/aws_completer' aws
# zsh (bashcompinit 필요)
autoload bashcompinit && bashcompinit
complete -C '/usr/local/bin/aws_completer' aws
Docker
# bash (Docker Desktop이 아닌 경우)
docker completion bash > /etc/bash_completion.d/docker
# zsh
docker completion zsh > ~/.zsh/completions/_docker
# fish
docker completion fish > ~/.config/fish/completions/docker.fish
공통 패턴
정리하면, 대부분의 CLI 도구가 다음 패턴을 따른다.
# 스크립트 출력
<command> completion <shell>
# shell 설정 파일에 등록
# bash: /etc/bash_completion.d/ 또는 ~/.bashrc에 source
# zsh: $fpath 디렉토리에 _<command> 파일로 저장
# fish: ~/.config/fish/completions/<command>.fish로 저장
cobra, click 같은 CLI 프레임워크가 이 패턴을 표준화했기 때문에, 프레임워크 기반 CLI 도구들은 거의 동일한 방식으로 completion을 제공한다.
CLI 프레임워크와 Completion 자동 생성
왜 자동 생성이 가능한가
CLI 프레임워크는 커맨드, 서브커맨드, 옵션 정의를 이미 알고 있다. 이 정보를 각 shell의 completion 문법으로 변환하여 출력하는 것이 자동 생성의 원리다. 개발자가 completion 코드를 따로 작성할 필요 없이 프레임워크가 알아서 생성해 준다.
cobra (Go)
kubectl, helm, gh, minikube, kind 등 Go 생태계의 대부분의 CLI 도구가 cobra를 사용한다. cobra 기반 CLI는 completion 서브커맨드가 자동으로 추가된다.
kubectl completion bash
kubectl completion zsh
helm completion fish
gh completion -s zsh
minikube completion bash
kind completion zsh
cobra가 _arguments, complete -F, complete -c 등 각 shell에 맞는 문법의 스크립트를 자동으로 출력하므로, 사용자는 kubectl completion zsh >> ~/.zshrc 한 줄이면 된다.
click (Python)
Flask CLI, black 등 Python 생태계에서 사용된다. 환경변수를 통해 completion 스크립트를 생성한다.
# bash
_MY_CLI_COMPLETE=bash_source my-cli > ~/.bash_completions/my-cli
# zsh
_MY_CLI_COMPLETE=zsh_source my-cli > ~/.zsh/completions/_my-cli
# fish
_MY_CLI_COMPLETE=fish_source my-cli > ~/.config/fish/completions/my-cli.fish
# 예시: Flask CLI
_FLASK_COMPLETE=zsh_source flask > ~/.zsh/completions/_flask
argparse + argcomplete (Python 표준 라이브러리)
Python 표준 라이브러리인 argparse 자체는 completion을 지원하지 않는다. argcomplete 라이브러리를 별도로 설치해야 한다.
pip install argcomplete
# 전역 활성화 (모든 argcomplete 사용 CLI에 적용)
activate-global-python-argcomplete
# 특정 커맨드만 등록
eval "$(register-python-argcomplete my-cli)"
코드에서는 한 줄만 추가하면 된다.
import argcomplete
parser = argparse.ArgumentParser()
# ... 인자 정의 ...
argcomplete.autocomplete(parser)
clap (Rust)
Rust 생태계의 CLI 프레임워크다. bash, zsh, fish, PowerShell, elvish까지 지원한다.
mycli completions bash > ~/.bash_completions/mycli
mycli completions zsh > ~/.zsh/completions/_mycli
mycli completions fish > ~/.config/fish/completions/mycli.fish
프레임워크별 비교
| 프레임워크 | 언어 | 생성 방법 | shell 지원 |
| — | — | — | — |
| cobra | Go | mycli completion <shell> | bash, zsh, fish, PowerShell |
| click | Python | _CMD_COMPLETE=<shell>_source mycli | bash, zsh, fish |
| argparse | Python | argcomplete 라이브러리 별도 설치 | bash, zsh, fish |
| clap | Rust | mycli completions <shell> | bash, zsh, fish, PowerShell, elvish |
Completion 스크립트 직접 작성하기
CLI 프레임워크 기반 도구가 아니거나, completion 생성 기능이 구현되어 있지 않은 CLI에는 스크립트를 직접 작성해야 한다. Claude Code를 예시로 각 shell별 작성 방법을 살펴본다.
Claude Code는 cobra/click 같은 프레임워크가 아닌 자체 바이너리로 빌드되어 있어,
claude completion zsh같은 명령이 없다.zsh
동작 원리
Tab 키를 누르면 zsh가
$fpath에 등록된 디렉토리에서_<커맨드명>파일을 찾아 실행한다.Tab 입력 → zsh가 $fpath 탐색 → _claude 파일 발견 → 정의된 완성 목록 표시zsh completion의 핵심 내장 함수는 다음과 같다. | 함수/구문 | 역할 | | — | — | |
#compdef claude| 이 파일이claude의 completion임을 선언 | |_arguments -C| 옵션/인자 선언.-C는 subcommand 분기 활성화 | |'1: :_fn'| 첫 번째 인자를_fn함수의 목록으로 완성 | |'*:: :->state'| 나머지 인자를 state로 넘겨 subcommand별 분기 처리 | |_describe| 설명과 함께 완성 목록 표시 | bash 대비_arguments,_describe같은 고수준 함수가 있어 설명 텍스트 표시, 타입 검증 등 표현력이 풍부하다.스크립트
~/.zsh/completions/_claude파일로 저장한다. 핵심 구조만 발췌하면 다음과 같다.#compdef claude _claude() { local context state state_descr line typeset -A opt_args _arguments -C \ '(-h --help)'{-h,--help}'[Show help]' \ '--model[Specify AI model]:model:(claude-opus-4-6 claude-sonnet-4-6 ...)' \ '--output-format[Output format]:format:(text json stream-json)' \ '1: :_claude_commands' \ # 첫 번째 인자 → _claude_commands로 완성 '*:: :->args' # 나머지 → state 분기 case $state in args) case $words[1] in auth) _arguments '1: :_claude_auth_commands' ;; mcp) _arguments '1: :_claude_mcp_commands' ;; # ... esac ;; esac } _claude_commands() { local commands commands=( 'agents:List configured agents' 'auth:Manage authentication' 'mcp:Configure and manage MCP servers' # ... ) _describe 'command' commands } _claude "$@"
_arguments -C로 글로벌 옵션과 서브커맨드 분기를 한 번에 정의하고,_describe로 각 서브커맨드의 설명을 함께 표시한다.'1: :_claude_commands'는 첫 번째 위치 인자를_claude_commands함수로 완성하고,'*:: :->args'는 나머지 인자를argsstate로 넘겨 서브커맨드별 분기를 처리한다.
전체 스크립트
#compdef claude
_claude() {
local context state state_descr line
typeset -A opt_args
_arguments -C \
'(-h --help)'{-h,--help}'[Show help]' \
'(-v --version)'{-v,--version}'[Show version]' \
'(-p --print)'{-p,--print}'[Print response without interactive mode]' \
'(-c --continue)'{-c,--continue}'[Continue most recent conversation]' \
'--resume[Resume a specific conversation by session ID]:session ID:' \
'--model[Specify AI model]:model:(claude-opus-4-6 claude-sonnet-4-6 claude-haiku-4-5-20251001)' \
'--output-format[Output format]:format:(text json stream-json)' \
'--verbose[Enable verbose logging]' \
'--debug[Enable debug mode]' \
'--add-dir[Additional directories to allow tool access]:directory:_files -/' \
'--allowedTools[Allowed tools]:tools:' \
'--disallowedTools[Disallowed tools]:tools:' \
'--max-turns[Maximum turns]:number:' \
'--system-prompt[System prompt]:prompt:' \
'--append-system-prompt[Append to system prompt]:prompt:' \
'--mcp-config[MCP config file]:file:_files' \
'--permission-mode[Permission mode]:mode:(default acceptEdits bypassPermissions)' \
'1: :_claude_commands' \
'*:: :->args'
case $state in
args)
case $words[1] in
auth) _arguments '1: :_claude_auth_commands' ;;
mcp) _arguments '1: :_claude_mcp_commands' ;;
install) _arguments '1: :(stable latest)' ;;
plugin|plugins) _arguments '1: :_claude_plugin_commands' ;;
esac
;;
esac
}
_claude_commands() {
local commands
commands=(
'agents:List configured agents'
'auth:Manage authentication'
'auto-mode:Inspect auto mode classifier configuration'
'doctor:Check the health of your Claude Code auto-updater'
'install:Install Claude Code native build'
'mcp:Configure and manage MCP servers'
'plugin:Manage Claude Code plugins'
'setup-token:Set up a long-lived authentication token'
'update:Check for updates and install if available'
)
_describe 'command' commands
}
_claude_auth_commands() {
local commands
commands=(
'login:Sign in to your Anthropic account'
'logout:Log out from your Anthropic account'
'status:Show authentication status'
)
_describe 'auth command' commands
}
_claude_mcp_commands() {
local commands
commands=(
'add:Add an MCP server'
'add-from-claude-desktop:Import MCP servers from Claude Desktop'
'add-json:Add an MCP server with a JSON string'
'get:Get details about an MCP server'
'list:List configured MCP servers'
'remove:Remove an MCP server'
'reset-project-choices:Reset all approved and rejected project-scoped servers'
'serve:Start the Claude Code MCP server'
)
_describe 'mcp command' commands
}
_claude_plugin_commands() {
local commands
commands=(
'disable:Disable an enabled plugin'
'enable:Enable a disabled plugin'
'install:Install a plugin'
'list:List installed plugins'
'marketplace:Manage Claude Code marketplaces'
'uninstall:Uninstall an installed plugin'
'update:Update a plugin to the latest version'
'validate:Validate a plugin or marketplace manifest'
)
_describe 'plugin command' commands
}
_claude "$@"
설정
mkdir -p ~/.zsh/completions
# 위 스크립트를 ~/.zsh/completions/_claude 로 저장
# ~/.zshrc에 추가
fpath=(~/.zsh/completions $fpath)
autoload -Uz compinit && compinit
# 적용
source ~/.zshrc
bash
동작 원리
bash는 zsh처럼 $fpath 기반 자동 탐색이 없다. complete 명령으로 커맨드와 완성 함수를 명시적으로 연결해야 한다.
Tab 입력
→ bash가 complete 등록 테이블 조회
→ _claude 함수 호출
→ COMPREPLY 배열에 후보 목록 저장
→ 목록 표시
bash completion의 핵심 요소는 다음과 같다.
| 요소 | 역할 |
| — | — |
| complete -F _fn cmd | cmd 입력 시 _fn 함수를 호출하도록 등록 |
| COMPREPLY | 완성 후보를 담는 배열. 이 값이 Tab 목록으로 표시됨 |
| compgen -W "목록" -- "$cur" | 현재 입력($cur)과 매칭되는 항목만 필터링 |
| $COMP_CWORD | 현재 커서가 몇 번째 단어인지 (0=커맨드, 1=첫 인자) |
| $prev | 바로 이전 단어 (옵션 값 완성에 활용) |
zsh의 _describe 같은 설명 표시 기능이 없어 단순 목록만 보여준다.
스크립트
~/.bash_completions/claude 파일로 저장한다. 핵심 구조만 발췌하면 다음과 같다.
_claude() {
local cur prev words cword
_init_completion || return
# 옵션 값 완성: 직전 단어가 옵션이면 해당 값 목록 제공
case "$prev" in
--model)
COMPREPLY=($(compgen -W "claude-opus-4-6 claude-sonnet-4-6 ..." -- "$cur"))
return ;;
esac
# 첫 번째 인자: 서브커맨드 목록
if [ "$COMP_CWORD" -eq 1 ]; then
COMPREPLY=($(compgen -W "agents auth mcp plugin ..." -- "$cur"))
return
fi
# 두 번째 인자: 서브커맨드별 분기
if [ "$COMP_CWORD" -eq 2 ]; then
case "${words[1]}" in
auth) COMPREPLY=($(compgen -W "login logout status" -- "$cur")) ;;
mcp) COMPREPLY=($(compgen -W "add get list remove ..." -- "$cur")) ;;
esac
return
fi
}
complete -F _claude claude # bash에서의 등록 방식
_init_completion으로 cur, prev 등 변수를 초기화하고, $prev로 옵션 값 완성, $COMP_CWORD로 서브커맨드 위치를 판단한다. zsh에 비해 직접 문자열 비교로 처리해야 해서 장황하다.
전체 스크립트
_claude() {
local cur prev words cword
_init_completion || return
case "$prev" in
--model)
COMPREPLY=($(compgen -W "claude-opus-4-6 claude-sonnet-4-6 claude-haiku-4-5-20251001" -- "$cur"))
return ;;
--output-format)
COMPREPLY=($(compgen -W "text json stream-json" -- "$cur"))
return ;;
--permission-mode)
COMPREPLY=($(compgen -W "default acceptEdits bypassPermissions" -- "$cur"))
return ;;
--add-dir|--mcp-config)
_filedir
return ;;
esac
if [ "$COMP_CWORD" -eq 1 ]; then
local commands="agents auth auto-mode doctor install mcp plugin plugins setup-token update upgrade"
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
return
fi
local subcmd="${words[1]}"
if [ "$COMP_CWORD" -eq 2 ]; then
case "$subcmd" in
auth)
COMPREPLY=($(compgen -W "login logout status" -- "$cur")) ;;
mcp)
COMPREPLY=($(compgen -W "add add-from-claude-desktop add-json get list remove reset-project-choices serve" -- "$cur")) ;;
install)
COMPREPLY=($(compgen -W "stable latest" -- "$cur")) ;;
plugin|plugins)
COMPREPLY=($(compgen -W "disable enable install list marketplace uninstall update validate" -- "$cur")) ;;
esac
return
fi
if [[ "$cur" == --* ]]; then
local opts="--help --version --print --continue --resume --model --output-format --verbose --debug --add-dir --allowedTools --disallowedTools --max-turns --system-prompt --append-system-prompt --mcp-config --permission-mode"
COMPREPLY=($(compgen -W "$opts" -- "$cur"))
fi
}
complete -F _claude claude
_init_completion은bash-completion패키지가 필요하다. 없는 환경에서는 다음과 같이 대체한다.local cur="${COMP_WORDS[COMP_CWORD]}" local prev="${COMP_WORDS[COMP_CWORD-1]}"설정
mkdir -p ~/.bash_completions # 위 스크립트를 ~/.bash_completions/claude 로 저장 # ~/.bashrc에 추가 source ~/.bash_completions/claude # 적용 source ~/.bashrcfish
동작 원리
fish는
complete명령 한 줄씩 선언하는 방식으로, 문법이 가장 단순하고 직관적이다.~/.config/fish/completions/디렉토리를 자동으로 탐색하므로 파일을 저장하면 즉시 적용된다.스크립트
~/.config/fish/completions/claude.fish파일로 저장한다.complete명령 한 줄이 하나의 완성 규칙이다.# 옵션 complete -c claude -l model -d 'AI model' -r -a 'claude-opus-4-6 claude-sonnet-4-6 ...' complete -c claude -l output-format -d 'Output format' -r -a 'text json stream-json' # 서브커맨드 (최상위) — __fish_use_subcommand: 아직 서브커맨드 입력 전일 때 complete -c claude -n '__fish_use_subcommand' -a auth -d 'Manage authentication' complete -c claude -n '__fish_use_subcommand' -a mcp -d 'Configure and manage MCP servers' # 서브커맨드 하위 — __fish_seen_subcommand_from: 해당 서브커맨드 입력 후일 때 complete -c claude -n '__fish_seen_subcommand_from auth' -a login -d 'Sign in' complete -c claude -n '__fish_seen_subcommand_from auth' -a logout -d 'Log out'zsh, bash와 달리 함수 정의 없이 선언적으로 한 줄씩 규칙을 나열한다.
전체 스크립트
# 옵션
complete -c claude -l model -d 'AI model' -r -a 'claude-opus-4-6 claude-sonnet-4-6 claude-haiku-4-5-20251001'
complete -c claude -l output-format -d 'Output format' -r -a 'text json stream-json'
complete -c claude -l permission-mode -d 'Permission mode' -r -a 'default acceptEdits bypassPermissions'
complete -c claude -s p -l print -d 'Print response without interactive mode'
complete -c claude -s c -l continue -d 'Continue most recent conversation'
# 서브커맨드 (최상위)
complete -c claude -n '__fish_use_subcommand' -a agents -d 'List configured agents'
complete -c claude -n '__fish_use_subcommand' -a auth -d 'Manage authentication'
complete -c claude -n '__fish_use_subcommand' -a auto-mode -d 'Inspect auto mode classifier'
complete -c claude -n '__fish_use_subcommand' -a doctor -d 'Check auto-updater health'
complete -c claude -n '__fish_use_subcommand' -a install -d 'Install Claude Code native build'
complete -c claude -n '__fish_use_subcommand' -a mcp -d 'Configure and manage MCP servers'
complete -c claude -n '__fish_use_subcommand' -a plugin -d 'Manage Claude Code plugins'
complete -c claude -n '__fish_use_subcommand' -a update -d 'Check for updates'
# auth 서브커맨드
complete -c claude -n '__fish_seen_subcommand_from auth' -a login -d 'Sign in'
complete -c claude -n '__fish_seen_subcommand_from auth' -a logout -d 'Log out'
complete -c claude -n '__fish_seen_subcommand_from auth' -a status -d 'Show auth status'
# mcp 서브커맨드
complete -c claude -n '__fish_seen_subcommand_from mcp' -a add -d 'Add an MCP server'
complete -c claude -n '__fish_seen_subcommand_from mcp' -a add-from-claude-desktop -d 'Import from Claude Desktop'
complete -c claude -n '__fish_seen_subcommand_from mcp' -a add-json -d 'Add server with JSON'
complete -c claude -n '__fish_seen_subcommand_from mcp' -a get -d 'Get server details'
complete -c claude -n '__fish_seen_subcommand_from mcp' -a list -d 'List MCP servers'
complete -c claude -n '__fish_seen_subcommand_from mcp' -a remove -d 'Remove an MCP server'
complete -c claude -n '__fish_seen_subcommand_from mcp' -a serve -d 'Start MCP server'
# plugin 서브커맨드
complete -c claude -n '__fish_seen_subcommand_from plugin' -a disable -d 'Disable an enabled plugin'
complete -c claude -n '__fish_seen_subcommand_from plugin' -a enable -d 'Enable a disabled plugin'
complete -c claude -n '__fish_seen_subcommand_from plugin' -a install -d 'Install a plugin'
complete -c claude -n '__fish_seen_subcommand_from plugin' -a list -d 'List installed plugins'
complete -c claude -n '__fish_seen_subcommand_from plugin' -a marketplace -d 'Manage marketplaces'
complete -c claude -n '__fish_seen_subcommand_from plugin' -a uninstall -d 'Uninstall a plugin'
complete -c claude -n '__fish_seen_subcommand_from plugin' -a update -d 'Update a plugin'
complete -c claude -n '__fish_seen_subcommand_from plugin' -a validate -d 'Validate a plugin manifest'
설정
mkdir -p ~/.config/fish/completions
# 위 스크립트를 ~/.config/fish/completions/claude.fish 로 저장
# 저장하면 바로 적용 (source 불필요)
PowerShell
$PROFILE에 추가한다.
Register-ArgumentCompleter -Native -CommandName claude -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$subcommands = @('agents','auth','auto-mode','doctor','install','mcp','plugin','setup-token','update')
$authCmds = @('login','logout','status')
$mcpCmds = @('add','add-from-claude-desktop','add-json','get','list','remove','serve')
$pluginCmds = @('disable','enable','install','list','marketplace','uninstall','update','validate')
$tokens = $commandAst.CommandElements
if ($tokens.Count -eq 2) {
$subcommands | Where-Object { $_ -like "$wordToComplete*" } |
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
} elseif ($tokens.Count -ge 3) {
$sub = $tokens[1].Value
$list = switch ($sub) {
'auth' { $authCmds }
'mcp' { $mcpCmds }
'plugin' { $pluginCmds }
}
$list | Where-Object { $_ -like "$wordToComplete*" } |
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
}
}
설정:
notepad $PROFILE # 프로필 파일 열기 (없으면 생성)
# 위 코드 붙여넣고 저장
. $PROFILE # 적용
shell별 비교
| shell | 등록 방식 | 파일 위치 | 특징 |
| — | — | — | — |
| zsh | #compdef, _arguments | $fpath 내 _<command> | 설명 텍스트, 타입 검증 등 기능 풍부 |
| bash | complete -F _fn cmd | ~/.bashrc 또는 /etc/bash_completion.d/ | 단순 문자열 매칭 위주, 표현력 낮음 |
| fish | complete 명령 한 줄씩 | ~/.config/fish/completions/ | 문법이 가장 단순, 저장 즉시 적용 |
| PowerShell | Register-ArgumentCompleter | $PROFILE | Windows 환경 |
유지보수
CLI 도구가 업데이트되어 서브커맨드나 옵션이 변경되면, completion 스크립트도 함께 업데이트해야 한다.
프레임워크 기반 도구 (kubectl, helm 등): completion 명령을 다시 실행하여 스크립트를 재생성하면 된다.
kubectl completion zsh > ~/.zsh/completions/_kubectl
직접 작성한 스크립트: --help로 변경 사항을 확인하고 스크립트를 수동으로 수정해야 한다.
claude --help
claude mcp --help
claude plugin --help
# 스크립트 수정 후 적용
source ~/.zshrc # zsh
source ~/.bashrc # bash
# fish: 파일 저장만 하면 자동 적용
정리
Shell completion은 Tab 키 하나로 CLI 작업 효율을 크게 높여 주는 기능이다. 대부분의 현대 CLI 도구들이 cobra, click, clap 등의 프레임워크를 통해 completion 스크립트를 자동으로 생성해 주지만, 그 이면에는 각 shell의 completion 시스템이 자리 잡고 있다. 핵심은 두 가지다.
- CLI 도구가 제공하는 completion:
<command> completion <shell>패턴으로 출력된 스크립트를 shell 설정 파일에 등록하면 끝이다. kubectl, helm, gh 등 대부분의 도구가 이 방식이다. - 직접 작성하는 completion: completion을 지원하지 않는 도구에는 해당 shell의 completion 시스템 문법을 알아야 한다.
_arguments와_describe(zsh),complete와COMPREPLY(bash),complete -c(fish) 등 shell마다 문법은 다르지만, “커맨드 구조를 정의하고 shell에 등록한다”는 구조는 동일하다.
댓글남기기