Atuin 셸 히스토리 완전 정복: Ctrl-R 검색부터 기기 간 동기화까지

터미널에서 예전에 쳤던 긴 명령어 하나를 다시 찾으려고 Ctrl-R을 누르고 몇 번씩 되짚어 올라간 경험은 누구에게나 있다. 노트북과 서버, 데스크톱을 오가다 보면 “그 명령어를 친 게 이 기기였나” 하고 히스토리가 흩어져 있는 것도 흔한 일이다. Atuin 셸 히스토리는 이 두 문제를 동시에 해결하는 도구다. 명령어를 텍스트 파일 대신 SQLite 데이터베이스에 저장하고, 종료 코드·실행 시간·작업 디렉터리까지 함께 기록하며, 원한다면 기기 사이를 암호화된 채로 동기화한다. 이번 포스팅에서는 Atuin 셸 히스토리를 설치하는 방법부터 기존 히스토리 임포트, 풀스크린 검색 UI, 설정 튜닝, 기기 간 sync, 그리고 셀프호스팅까지 단계별로 정리하고자 한다.

Atuin 셸 히스토리, 기존 히스토리와 뭐가 다른가

Atuin 셸 히스토리는 셸의 명령어 기록을 평문 .bash_history 파일이 아니라 SQLite 데이터베이스에 저장하고, 대화형 풀스크린 UI로 검색하게 해주는 도구다. 각 명령마다 실행 시각, 소요 시간, 종료 코드, 작업 디렉터리, 호스트명, 세션 ID를 함께 남긴다. 덕분에 “어제 오후 3시 이후 성공한 make 명령”처럼 조건을 건 검색이 가능하다.

기존 셸 히스토리는 단순한 텍스트 append라서 여러 세션이 동시에 쓰면 기록이 뒤섞이거나 유실되고, 종료 코드나 실행 위치 같은 맥락은 아예 남지 않는다. Atuin은 이 데이터를 구조화해 저장하므로 검색과 필터링, 통계까지 가능해진다. Rust로 작성된 단일 바이너리이며, 2026년 7월 기준 GitHub 스타 3만 개를 넘겼고 MIT 라이선스로 공개돼 있다. zsh, bash, fish, nushell, xonsh, PowerShell을 모두 지원한다. 프로젝트 개요는 공식 GitHub 저장소에서 확인할 수 있고, 버전별 변경 내역은 릴리스 페이지에 정리돼 있다.

5분이면 끝나는 Atuin 설치와 셸 연결

Atuin 설치는 공식 설치 스크립트를 실행하고, 셸 설정 파일에 초기화 한 줄을 넣으면 끝난다. 설치 스크립트가 바이너리를 받아 대부분의 셸 훅을 자동으로 걸어주지만, 초기화 라인은 직접 확인하고 넣어두는 편이 안전하다.

# 공식 설치 스크립트 (Linux / macOS)
curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh

# Homebrew
brew install atuin

# Cargo (Rust 툴체인이 있는 경우)
cargo install atuin
ShellScript

위 명령은 Atuin 바이너리를 시스템에 설치한다. 공식 설치 스크립트는 OS와 셸을 감지해 필요한 초기화 코드까지 설정 파일에 추가해주므로 가장 간단하다. Rust 환경이 이미 있다면 cargo install로 소스에서 빌드해도 된다.

# Zsh: ~/.zshrc 맨 아래에 추가
eval "$(atuin init zsh)"

# Bash: ~/.bashrc 에 추가 (bash-preexec 필요, 스크립트가 자동 설치)
eval "$(atuin init bash)"

# Fish: ~/.config/fish/config.fish 에 추가
atuin init fish | source
ShellScript

이 초기화 라인은 Atuin의 키 바인딩과 명령 기록 훅을 셸에 등록한다. 기본적으로 Ctrl-R과 위쪽 방향키(Up arrow)를 Atuin의 검색 UI로 덮어쓴다. 설정을 추가한 뒤 셸을 새로 열거나 source ~/.zshrc로 다시 읽으면 Atuin 셸 히스토리가 곧바로 활성화된다. 위 방향키 바인딩이 거슬리면 atuin init zsh --disable-up-arrow처럼 옵션을 붙여 끌 수 있다.

지금까지 쌓인 히스토리를 한 번에 가져오기

Atuin은 설치 시점부터의 명령만 기록하므로, 그동안 쌓인 기존 히스토리는 임포트 명령으로 한 번에 가져와야 한다. atuin import auto가 현재 셸을 감지해 알맞은 히스토리 파일을 자동으로 읽어들인다.

# 현재 셸을 감지해 자동 임포트
atuin import auto

# 특정 셸을 지정해서 임포트
atuin import zsh
atuin import bash

# 임포트 가능한 소스 목록 확인
atuin import
ShellScript

위 명령은 .zsh_history.bash_history 같은 기존 파일을 파싱해 Atuin 데이터베이스에 채워 넣는다. auto$SHELL 환경 변수를 기준으로 소스를 고르므로 대부분 이 한 줄이면 충분하다. 임포트는 기존 파일을 건드리지 않고 읽기만 하므로 원본 히스토리는 그대로 남는다.

임포트가 끝나면 데이터가 제대로 들어왔는지 통계로 바로 확인할 수 있다. atuin stats 명령은 가장 많이 쓴 명령어와 총 명령 수를 요약해준다. 여기서 수치가 나오면 기존 히스토리가 정상적으로 이관된 것이다.

Ctrl-R을 눌렀을 때 벌어지는 일

Atuin의 진짜 가치는 Ctrl-R을 눌렀을 때 뜨는 풀스크린 검색 UI에 있다. 타이핑할 때마다 후보가 실시간으로 좁혀지고, 각 항목 옆에는 실행 시각과 소요 시간, 종료 코드가 표시된다. 기본 검색 모드는 fuzzy라 정확한 문자열을 몰라도 순서만 맞으면 걸린다.

검색 창 안에서는 몇 가지 키로 결과 범위를 바꿀 수 있다. Ctrl-R을 다시 누르면 필터 모드가 순환하며, 전체 기록(global), 현재 호스트(host), 현재 세션(session), 현재 디렉터리(directory)를 오간다. 예를 들어 특정 프로젝트 폴더에서만 쳤던 명령을 찾고 싶으면 directory 모드로 좁히면 된다.

CLI에서 직접 조건을 걸어 검색할 수도 있다.

# 어제 오후 3시 이후, 종료 코드 0(성공)인 make 명령만 검색
atuin search --exit 0 --after "yesterday 3pm" make

# 특정 디렉터리에서 실행한 docker 명령 검색
atuin search --cwd . docker

# 최근 10건을 시간 정보와 함께 출력
atuin search --limit 10 --format "{time} {command}"
ShellScript

이 명령들은 대화형 UI를 거치지 않고 셸 스크립트나 파이프라인에서 히스토리를 질의할 때 유용하다. --exit, --after, --before, --cwd 같은 플래그를 조합하면 평문 히스토리로는 불가능했던 정밀 검색이 가능하다. 종료 코드와 작업 디렉터리를 함께 저장해두기 때문에 나오는 기능이다.

config.toml 한 파일로 취향껏 튜닝하기

Atuin의 동작은 ~/.config/atuin/config.toml 파일 하나로 대부분 조정한다. 검색 모드, 필터 범위, UI 높이, 프리뷰 표시 여부 등을 취향에 맞게 바꿀 수 있다. 설정 파일이 없으면 직접 만들면 되고, 지정하지 않은 항목은 기본값으로 동작한다.

# ~/.config/atuin/config.toml

# 검색 모드: prefix, fulltext, fuzzy, skim 중 선택
search_mode = "fuzzy"

# 기본 필터 범위: global, host, session, directory, workspace
filter_mode = "global"

# UI 스타일: auto, full, compact
style = "compact"

# 인라인 모드에서 UI가 차지할 최대 줄 수 (0이면 항상 전체 화면)
inline_height = 40

# 선택한 명령이 터미널 폭보다 길 때 미리보기 표시
show_preview = true

# Enter를 누르면 명령을 바로 실행 (false면 프롬프트에 붙여넣기만)
enter_accept = false

# 키맵 모드: emacs, vim-normal, vim-insert
keymap_mode = "emacs"
TOML

각 옵션은 실제 사용감을 크게 바꾼다. enter_accept는 특히 중요한데, 기본값 true에서는 검색 결과에서 Enter를 누르는 순간 명령이 곧바로 실행된다. 실행 전에 인자를 손보는 습관이 있다면 false로 두어 프롬프트에 붙여넣기만 하도록 바꾸는 편이 안전하다. search_modeprefix로 바꾸면 앞글자부터 일치하는 전통적인 검색으로 돌아가고, keymap_modevim-normal로 두면 Vim 사용자가 익숙한 키로 결과를 오갈 수 있다. 전체 옵션 목록은 Atuin 공식 문서에 정리돼 있다.

노트북과 서버 히스토리를 하나로 합치는 sync

Atuin의 sync 기능은 여러 기기의 셸 히스토리를 암호화된 상태로 하나로 합쳐준다. 계정을 등록하고 로그인하면, 이후의 명령이 자동으로 서버에 올라가고 다른 기기에서 내려받아진다. 핵심은 종단 간 암호화라는 점으로, 명령어 원문은 로컬에서 암호화된 뒤 전송되므로 서버는 내용을 볼 수 없다.

# 새 계정 등록 (공용 서버 api.atuin.sh 기준)
atuin register -u <사용자명> -e <이메일>

# 이미 계정이 있다면 로그인
atuin login -u <사용자명>

# 수동으로 즉시 동기화
atuin sync

# 암호화 키 확인 (다른 기기 로그인 시 필요, 절대 분실 금지)
atuin key
ShellScript

atuin register로 계정을 만들면 종단 간 암호화에 쓸 키가 로컬에 생성된다. 다른 기기에서 atuin login을 할 때 이 키가 필요하므로 atuin key로 출력한 값을 안전한 곳에 보관해야 한다. 키를 잃어버리면 서버에 올라간 기존 히스토리를 복호화할 수 없다.

동기화 주기는 config.toml에서 조정한다. sync_frequency = "5m"이 기본값이며 명령을 실행할 때마다 마지막 동기화 시각을 확인해 트리거된다. sync_frequency = "0"으로 두면 명령마다 즉시 동기화하고, auto_sync = false로 두면 자동 동기화를 끄고 필요할 때만 atuin sync를 직접 호출하게 된다.

남의 서버가 꺼림칙하다면, Docker 셀프호스팅

히스토리를 외부 서버에 두는 것이 부담스럽다면 Atuin 동기화 서버를 직접 띄울 수 있다. 서버 역시 단일 바이너리라 Docker로 몇 분이면 올라가고, 클라이언트의 sync_address만 자기 서버로 바꾸면 된다. 공용 서버와 기능은 완전히 동일하며 데이터가 자기 인프라 안에만 머문다.

# Docker로 Atuin 동기화 서버 실행
docker run -d \
  --name atuin \
  -p 8888:8888 \
  -v atuin-data:/config \
  -e ATUIN_HOST="0.0.0.0" \
  -e ATUIN_PORT=8888 \
  -e ATUIN_OPEN_REGISTRATION=true \
  ghcr.io/atuinsh/atuin:latest server start
ShellScript

위 명령은 8888 포트로 Atuin 서버를 띄우고 데이터를 이름 있는 볼륨에 영속화한다. 여기서 ATUIN_OPEN_REGISTRATION=true가 핵심이다. 서버의 open_registration 기본값은 false라, 이 변수를 켜지 않으면 뒤에서 atuin register를 실행해도 계정 생성이 거부된다. 첫 계정을 만든 뒤에는 다시 꺼서 외부 가입을 막는 편이 안전하다. PostgreSQL을 백엔드로 쓰려면 ATUIN_DB_URI 환경 변수로 연결 문자열을 넘기면 되고, 실서비스라면 Docker Compose로 DB와 함께 묶는 구성이 일반적이다.

# 클라이언트 ~/.config/atuin/config.toml
# 동기화 서버를 자기 서버로 지정
sync_address = "http://10.0.0.1:8888"
Plaintext

클라이언트 설정에서 sync_address를 자기 서버 주소로 바꾼 뒤 atuin register를 다시 실행하면 이제부터는 자체 서버에 계정과 히스토리가 저장된다. 종단 간 암호화는 셀프호스팅에서도 그대로 적용되므로, 내 서버라 해도 저장되는 데이터는 암호화된 상태다. sync 설정과 키 관리 상세 절차는 Atuin 동기화 가이드를 참고하면 된다.

Atuin vs 기존 셸 히스토리 한눈에 비교

Atuin으로 얻는 것과 기존 방식의 차이를 표로 정리하면 도입 판단이 쉬워진다. 저장 방식부터 검색, 동기화까지 성격이 다르다.

항목기본 셸 히스토리Atuin 셸 히스토리
저장 형식평문 텍스트 파일SQLite 데이터베이스
저장 메타데이터명령어(선택적 타임스탬프)시각·소요시간·종료코드·경로·호스트·세션
검색 방식Ctrl-R 순차 역방향풀스크린 fuzzy/prefix/fulltext
조건 검색불가종료코드·시간·디렉터리 필터
동시 세션기록 뒤섞임·유실 가능세션별 안전 저장
기기 간 동기화수동 파일 복사종단 간 암호화 sync
사용 통계없음atuin stats 제공

표에서 보듯 Atuin은 단순히 검색 UI만 바꾸는 게 아니라 히스토리 데이터 자체를 구조화한다. 종료 코드와 디렉터리가 남기 때문에 조건 검색과 통계가 열리고, 이 부분이 기존 방식과의 결정적 차이다. 반대로 극도로 가벼운 환경이나 셸 스크립트 전용 서버라면 기본 히스토리로 충분할 수도 있다.

마치며

Atuin을 처음 깐 날 가장 먼저 한 일은 enter_acceptfalse로 바꾼 것이었다. 검색 결과에서 Enter를 누르자마자 명령이 실행돼 버려서, 인자를 고쳐 쓸 틈도 없이 엉뚱한 배포 스크립트가 돌아간 적이 있었기 때문이다. 이 옵션 하나만 바꿔도 사고 위험이 확 줄어든다. 또 하나 체감이 컸던 건 directory 필터 모드다. 특정 프로젝트 폴더에서만 쳤던 긴 gradle이나 docker 명령을 그 폴더 안에서 Ctrl-R로 바로 불러올 수 있으니, 예전처럼 다른 프로젝트 명령까지 뒤섞여 올라오는 답답함이 사라졌다.

sync는 취향이 갈릴 부분이다. 공용 서버가 종단 간 암호화라고는 해도 명령어 히스토리를 남의 인프라에 두는 게 마음에 걸린다면, 처음부터 Docker로 셀프호스팅을 잡고 시작하는 편이 낫다. 어차피 10분이면 올라간다. 지금까지 Atuin 셸 히스토리에 대해서 정리해 보았다.

FAQ

Atuin을 설치하면 기존 .zsh_history 파일은 사라지나요?

사라지지 않는다. Atuin은 명령어를 별도의 SQLite 데이터베이스(~/.local/share/atuin/history.db)에 저장하며, atuin import auto는 기존 히스토리 파일을 읽기만 할 뿐 수정하거나 삭제하지 않는다. 원본 .zsh_history.bash_history는 그대로 남으므로 나중에 Atuin을 제거해도 기존 히스토리는 보존된다.

Atuin의 sync 서버는 내 명령어 내용을 볼 수 있나요?

볼 수 없다. Atuin은 종단 간 암호화를 사용하므로 명령어 원문이 로컬에서 암호화된 뒤 서버로 전송된다. 서버에는 암호문만 저장되고, 복호화 키는 각 클라이언트에만 존재한다. atuin key로 출력되는 키를 분실하면 서버에 올라간 기존 히스토리를 복호화할 수 없으니, 다른 기기 로그인을 위해 이 키는 안전하게 보관해야 한다.

검색 결과에서 Enter를 누르면 명령이 바로 실행돼서 위험한데 어떻게 하나요?

config.toml에서 enter_accept = false로 설정하면 된다. 이 값이 기본값 true일 때는 검색 UI에서 Enter를 누르는 순간 명령이 즉시 실행된다. false로 바꾸면 선택한 명령이 프롬프트에 붙여넣기만 되어, 실행 전에 인자를 수정하거나 확인할 수 있다. 배포·삭제처럼 위험한 명령을 자주 다룬다면 이 설정을 권장한다.

Atuin은 bash에서도 잘 동작하나요?

동작한다. 다만 bash는 명령 실행 전후 훅을 위해 bash-preexec가 필요하며, 공식 설치 스크립트가 이를 자동으로 설정해준다. zsh나 fish에 비해 일부 제약이 있을 수 있으므로, bash 사용자는 초기화 후 Ctrl-R과 명령 기록이 정상 동작하는지 확인하는 것이 좋다. bash 외에 zsh, fish, nushell, xonsh, PowerShell도 지원한다.

Atuin sync를 쓰지 않고 로컬 검색 기능만 사용할 수 있나요?

가능하다. sync는 완전히 선택적 기능이다. atuin registeratuin login을 하지 않으면 계정 없이 로컬 SQLite 데이터베이스만으로 풀스크린 검색, 필터, 통계 기능을 모두 사용할 수 있다. 나중에 기기 간 동기화가 필요해지면 그때 계정을 등록해 sync를 켜면 된다. config.toml에서 auto_sync = false로 두면 자동 동기화도 완전히 비활성화된다.