터미널에 ls라고 입력하면 셸은 그 두 글자를 어떻게 실제로 실행 가능한 프로그램으로 바꾸는가. 이 장은 Bash가 명령어 이름 하나를 받아 실제로 무엇을 실행할지 결정하는 내부 규칙 — 함수·빌트인·PATH 탐색의 우선순위, 탐색 결과를 기억해 두는 hash, 그 판단 과정을 사용자가 직접 조회하는 type, 별칭·함수를 건너뛰고 강제로 외부 명령을 부르는 command — 를 GNU Bash Reference Manual 원문과 대조해 다룬다.
이 장을 읽기 전에
직전 챕터인 09장: which, whereis, locate에서 다룬 which·whereis·locate는 모두 셸 바깥에서 명령어의 위치를 알려주는 외부 유틸리티였다 — which조차도 PATH를 참고해 경로를 알려줄 뿐, 실제로 셸이 명령을 실행할 때 거치는 판단 과정과는 별개의 프로그램이다. 이 장은 관점을 뒤집는다. 셸 자신이 명령어 한 줄을 받았을 때 내부적으로 무엇을 먼저 확인하고, 어디를 탐색하고, 그 결과를 어떻게 기억해 두는지 — 즉 which가 흉내만 내던 그 판단 과정을 셸이 실제로 수행하는 메커니즘 자체를 다룬다.
난이도는 입문–중급이다. 01장: cd, pwd 수준의 셸 기본 조작과, 환경변수가 문자열 값을 담고 있다는 정도만 알면 충분하다. 다루지 않는 것: 별칭(alias)을 정의·삭제하는 문법 자체는 35장: alias에서, 로그인·비로그인 셸에 따라 PATH가 애초에 어떻게 채워지는지는 다음 11장: .bashrc와 로그인/비로그인 셸에서 다룬다. 이 장은 이미 설정된 PATH 값을 셸이 어떻게 소비하는지에 집중한다.
당신의 수준에 맞는 경로
| 수준 | 읽을 부분 | 핵심 목표 |
|---|---|---|
| 입문 | 개요 + 정신 모델, 핵심 개념의 PATH·type | PATH가 콜론으로 구분된 디렉터리 목록이라는 것과 type으로 명령어 종류를 확인하는 법을 이해한다 |
| 중급 | 핵심 개념 전체, 예시 | hash로 캐시를 조회·초기화하고 command로 별칭·함수를 우회할 수 있다 |
| 심화 | 주의사항·함정, 흔한 오개념 | hash 캐시가 오래된 경로를 가리켜 생기는 문제를 진단·해결하고, alias shadowing의 동작 범위를 예측할 수 있다 |
개요 + 정신 모델
Bash에게 명령어 이름 하나는 곧바로 실행할 프로그램이 아니라, 정해진 순서대로 확인해야 할 후보 목록에 대한 질의다. 셸은 이름을 받으면 가장 먼저 자신이 정의한 함수인지, 그다음 내장된 빌트인인지 확인하고, 둘 다 아니면 그제야 파일시스템으로 나가 PATH에 나열된 디렉터리를 왼쪽부터 순서대로 뒤져 실행 파일을 찾는다. 이 마지막 단계가 매번 반복되면 비용이 크기 때문에 Bash는 한 번 찾은 전체 경로를 해시 테이블에 기억해 뒀다가 같은 이름이 다시 나오면 탐색을 건너뛴다. type은 이 판단 과정의 결과를 사용자에게 그대로 보여주는 창이고, hash는 그 기억(캐시) 자체를 조회·조작하는 도구이며, command는 이 우선순위 목록의 앞부분(별칭·함수)을 의도적으로 건너뛰라고 지시하는 명령이다. 네 요소 모두 “셸이 이름을 실행 가능한 것으로 바꾸는 하나의 파이프라인"을 서로 다른 각도에서 다룬다는 점에서 한 장으로 묶일 이유가 있다.
핵심 개념
PATH — 콜론으로 구분된 탐색 경로 목록
PATH는 셸이 명령어를 찾을 때 뒤질 디렉터리 목록을 담은 환경변수다. GNU Bash Reference Manual은 이를 다음과 같이 정의한다.
“A colon-separated list of directories in which the shell looks for commands. A zero-length (null) directory name in the value of PATH indicates the current directory.” — GNU Bash Reference Manual, “Bourne Shell Variables”
즉 PATH는 콜론(:)으로 구분된 디렉터리 목록이며, 값이 비어 있는 항목(콜론이 연속되거나 값의 맨 앞·맨 뒤에 콜론이 오는 경우)은 현재 디렉터리를 의미한다. 탐색은 왼쪽부터 순서대로 이루어지고, 같은 이름의 실행 파일이 여러 디렉터리에 있으면 가장 먼저 매치되는(가장 왼쪽 디렉터리의) 파일이 채택된다 — 뒤쪽 디렉터리에 더 최신 버전이 있어도 소용없다.
| |
type — 명령어가 실제로 무엇인지 정확히 구분
type은 주어진 이름을 셸이 어떻게 해석할지 판정한다. 별칭(alias)·함수(function)·빌트인(builtin)·키워드(keyword)·외부 파일(file) 중 무엇인지 구분해 알려준다는 점에서, PATH만 뒤져 파일 경로를 찾는 which보다 훨씬 신뢰할 수 있다 — which는 셸의 내부 상태(그 셸에 정의된 별칭·함수)를 애초에 알지 못하는 별개의 외부 프로그램이기 때문이다.
| 옵션 | 설명 |
|---|---|
| (없음) | 이름이 별칭·함수·빌트인·키워드·파일 중 무엇인지 사람이 읽기 쉬운 문장으로 출력 |
-t | 위 판정 결과를 alias/function/builtin/keyword/file 중 한 단어로만 출력(스크립트에서 조건 분기에 쓰기 좋음) |
-a | PATH·함수·빌트인·별칭을 모두 뒤져 매치되는 모든 위치를 나열 |
-p | 파일로 실행될 경로만 출력(별칭·함수·빌트인이면 아무것도 출력하지 않음) |
-P | 분류와 무관하게 강제로 PATH 탐색을 수행해 경로를 출력 |
-f | 함수 검색을 건너뛰고 빌트인·파일만 확인 |
| |
GNU Bash Reference Manual은 type -p/-P가 캐시된 값을 우선 출력한다는 점도 명시한다.
“If a command is hashed, -p and -P print the hashed value, which is not necessarily the file that appears first in
$PATH.” — GNU Bash Reference Manual, “Bash Builtins”
즉 PATH를 바꿔도 이미 캐시(다음 항목의 hash)된 명령이라면 type -p가 새 PATH 기준 첫 번째 파일이 아니라 캐시된 예전 경로를 보여줄 수 있다.
hash — 탐색 결과 캐싱
Bash는 명령어를 한 번 찾을 때마다 그 전체 경로를 해시 테이블에 기억해 둔다. GNU Bash Reference Manual은 이 동작을 다음과 같이 설명한다.
“Bash uses a hash table to remember the full pathnames of executable files to avoid multiple
PATHsearches.” — GNU Bash Reference Manual, “Command Search and Execution”
hash 빌트인은 이 캐시 자체를 들여다보고 조작하는 도구다.
| 옵션 | 설명 |
|---|---|
| (없음) | 현재 캐시된 명령어와 경로 목록 전체 출력 |
-t name | 지정한 이름의 캐시된 전체 경로만 출력 |
-d name | 지정한 이름 하나의 캐시만 잊어버림(다음 호출 때 다시 탐색) |
-r | 캐시 전체를 잊어버림(다음 호출부터 전부 다시 탐색) |
-p path name | 실제로 탐색하지 않고 지정한 경로를 그 이름의 캐시 값으로 강제 등록 |
-l | 캐시 내용을 다시 hash 명령으로 재사용할 수 있는 형식으로 출력 |
| |
command — 별칭·함수를 우회해 진짜 외부 명령 호출
command는 지정한 이름을 셸 함수로 해석하지 않고 오직 빌트인이나 PATH 탐색으로 찾은 실행 파일로만 실행한다.
“Runs command with args suppressing the normal shell function lookup… Only shell builtin commands or commands found by searching the PATH are executed.” — GNU Bash Reference Manual, “Bash Builtins”
이 규칙은 명시적으로 함수 우회만 언급하지만, 실제로는 별칭도 함께 우회된다 — 다만 그 이유는 command가 별칭을 특별 취급해서가 아니다. Bash의 별칭 치환은 애초에 “각 단순 명령의 첫 단어"에만 적용된다.
“The first word of each simple command, if unquoted, is checked to see if it has an alias.” — GNU Bash Reference Manual, “Aliases”
command ls에서 별칭 치환 대상이 되는 첫 단어는 command이고 ls는 그 뒤에 오는 두 번째 단어이므로, ls에 별칭이 걸려 있어도 애초에 검사 대상이 아니다. 결과적으로 command는 함수는 규칙으로 직접 배제하고, 별칭은 첫 단어 규칙의 부수 효과로 함께 우회하게 된다.
| 옵션 | 설명 |
|---|---|
| (없음) | 함수 탐색을 생략하고 빌트인 또는 PATH에서 찾은 명령만 실행 |
-p | 표준 유틸리티를 확실히 찾을 수 있는 기본 PATH 값을 사용(사용자가 PATH를 이상하게 바꿔놔도 안전) |
-v | 실제로 어떤 이름·파일이 쓰일지 한 단어로 출력(스크립트의 존재 여부 확인에 자주 씀) |
-V | -v보다 더 자세한 설명 출력 |
| |
예시
| |
주의사항·함정
hash 캐시가 오래된 경로를 가리키는 문제: hash는 명령어를 한 번이라도 실행한 뒤에야 그 경로를 기억한다. 문제는 그 뒤에 같은 이름의 바이너리를 다른 경로로 옮기거나, 재설치해서 실제 파일이 사라지거나, 버전 관리 도구(nvm, pyenv, rbenv 등)로 활성 버전을 바꿔도 현재 셸 세션의 캐시는 자동으로 갱신되지 않는다는 점이다. 실무에서 실제로 자주 겪는 형태는 다음과 같다.
| |
이 문제는 새 셸을 열면(캐시가 비어 있으므로) 저절로 사라지지만, 이미 열려 있던 터미널·오래 살아있는 tmux 세션·백그라운드 스크립트에서는 그대로 남는다. PATH를 바꾸는 스크립트(버전 관리 도구의 use/switch 계열 명령 등)를 직접 짤 때는 마지막에 hash -r을 호출해 이 문제를 예방하는 것이 안전하다.
alias가 명령어를 가려서(shadowing) 예상과 다르게 동작하는 문제: 대화형 셸에서 흔한 alias rm='rm -i', alias grep='grep --color=auto' 같은 설정은 같은 이름의 명령을 호출할 때마다 조용히 다른 동작으로 바뀐다. 이 자체가 함정이 되는 지점은 두 가지다. 첫째, 스크립트 안에서는 기본적으로 별칭이 전혀 확장되지 않는다.
“Aliases are not expanded when the shell is not interactive, unless the
expand_aliasesshell option is set usingshopt.” — GNU Bash Reference Manual, “Aliases”
즉 터미널에서 손으로 입력할 때는 rm이 rm -i로 확장돼 삭제 전 확인을 받지만, 같은 이름으로 스크립트 파일 안에 적으면(기본 설정에서는) 확인 없이 그대로 삭제된다 — 대화형 환경에서 테스트하고 안심한 스크립트가 실제 배치 실행에서 다르게 동작하는 원인이 된다. 둘째, 원래의 외부 명령을 확실히 호출하고 싶을 때는 command 명령 또는 \명령으로 별칭을 우회해야 한다는 점을 앞서 다뤘다 — 이 우회는 함수에도 동일하게 적용되므로, 별칭인지 함수인지 굳이 구분하지 않고도 “원래 명령"을 부르는 공통 수단으로 쓸 수 있다.
| |
흔한 오개념
“PATH를 바꾸면 그 즉시 모든 명령어 탐색에 반영된다”는 가장 흔한 오해다. 실제로는 이미 그 셸 세션에서 한 번이라도 실행해 캐시된 명령은 hash -r을 하기 전까지 예전 경로를 그대로 유지한다. export PATH=...만으로는 부족하고, 이미 캐시된 명령에 한해서는 재탐색을 명시적으로 유도해야 한다.
"which이 실제로 실행될 명령을 항상 정확히 알려준다”도 틀리기 쉽다. which는 대부분의 시스템에서 셸 내부 상태(별칭·함수·빌트인 목록)를 전혀 모르는 별도의 외부 프로그램이라, PATH상의 파일만 보고하고 실제로는 별칭이나 함수가 먼저 가로채 다른 동작을 할 수 있다는 사실을 반영하지 못한다. 정확한 판정이 필요하면 셸 자신의 판단을 그대로 보여주는 type(특히 type -a)을 쓰는 편이 안전하다.
다음 장에서는
11장: .bashrc와 로그인/비로그인 셸에서는 지금까지 이미 만들어져 있다고 가정했던 PATH 값이 애초에 어떻게 채워지는지 — 로그인 셸과 비로그인 셸이 각각 어떤 설정 파일을 읽고, .bashrc와 .bash_profile이 왜 나뉘어 있는지를 다룬다. 이 장이 “이미 있는 PATH를 셸이 어떻게 소비하는가"였다면, 다음 장은 “그 PATH가 애초에 어떻게 만들어지는가"다.
평가 기준
PATH가 콜론으로 구분된 디렉터리 목록이며 왼쪽부터 순서대로 탐색된다는 것을 설명할 수 있다.- Bash가 명령어를 해석할 때 함수 → 빌트인 →
PATH탐색(해시 캐시 경유) 순서로 확인한다는 것을 설명할 수 있다. type(특히-a,-t)으로 명령어가 별칭·함수·빌트인·외부 파일 중 무엇인지 정확히 구분하고,which보다 신뢰할 수 있는 이유를 설명할 수 있다.hash캐시가 오래된 경로를 가리켜 생기는 문제를 진단하고hash -r로 해결할 수 있다.command로 별칭·함수를 우회해 원래의 외부 명령을 강제로 호출할 수 있고, 그 우회가 스크립트에서 별칭이 애초에 확장되지 않는 것과는 별개의 메커니즘임을 구분할 수 있다.
![Featured image of post [Bash Shell] 10. PATH, type, hash, command - 셸의 명령어 탐색 원리](/post/bashshell/path-type-hash-command-shell-command-lookup/wordcloud_hu_3ba086147d61af39.webp)
![[Bash Shell] 08. ln - 하드링크와 심볼릭 링크](/post/bashshell/ln-command-hard-symbolic-links-linux/wordcloud_hu_76b51dfeca0d4f59.webp)
![[Bash Shell] 09. which, whereis, locate - 명령어와 파일 찾기](/post/bashshell/which-whereis-locate-commands-find-command-location/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 10. PATH, type, hash, command - 셸의 명령어 탐색 원리](/post/bashshell/path-type-hash-command-shell-command-lookup/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 11. .bashrc와 로그인/비로그인 셸 - 셸 환경설정](/post/bashshell/bashrc-bash-profile-login-shell-startup-files/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 12. man, history - 매뉴얼과 명령 기록](/post/bashshell/man-history-commands-manual-pages-shell-history/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 43. ssh - 원격 셸 접속](/post/bashshell/ssh-command-remote-login-secure-shell/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 05. touch - 파일 생성과 타임스탬프 갱신](/post/bashshell/touch-command-create-file-update-timestamp/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 34. echo, export, env - 출력과 환경변수](/post/bashshell/echo-export-env-commands-shell-variables/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 35. alias - 명령 별칭](/post/bashshell/alias-command-shell-command-shortcuts/wordcloud_hu_7ec28326489f97a8.webp)