LainyZine: 프로그래머 가이드 🐣

[리눅스] which 명령어: 실행 파일 위치 출력

리눅스 셸에서 어떤 명령어들은 경로명을 지정하지 않더라도 명령어 이름만으로 실행이 됩니다. 예를 들어 unzip을 실행하면 다음과 같이 버전 정보와 도움말이 출력됩니다.

$ unzip
UnZip 6.00 of 20 April 2009, by Info-ZIP.  Maintained by C. Spieler.
...

그런데 이 명령어는 어느 디렉터리에 있는 걸까요? 이 때 사용할 수 있는 명령어가 바로 which입니다. 이 글에서는 which 명령어 사용법과 기본적인 동작 원리, 그리고 옵션들을 알아봅니다.

which 명령어로 실행 파일 경로 찾기

which의 첫 번째 인자로 명령어 이름을 넘겨주면 이 실행 파일의 위치를 확인할 수 있습니다. 앞에서 실행해본 unzip의 경로가 궁금하다면 다음과 같이 실행합니다.

$ which unzip
/usr/bin/unzip

간단하죠? which 명령어는 이 정도만 알고 있어도 활용도가 매우 높으니 꼭 기억해두시기 바랍니다.

자주 사용하는 다른 명령어들의 위치도 한 번 찾아봅니다.

$ which whoami
/usr/bin/whoami

$ which mkdir
/bin/mkdir

$ which ping
/sbin/ping

다들 시스템 어딘가에 있었네요. 명령어의 위치는 배포판이나 셸에 따라서 다를 수 있습니다.

윈도우에는 which 명령어가 없는데, 대신 같은 역할을 하는 where.exe 명령어가 있습니다. 이 명령어에 대해서는 다음 글을 참고해주세요.

$PATH 환경변수와 which 명령어

which를 사용해서 명령어들의 위치를 찾을 수 있습니다만, 여전히 의문은 남을 수 있습니다. which 명령어는 어디에서 이 명령어를 탐색하는 걸까요?

리눅스를 사용하다보면 $PATH 변수에 자주 접할 수 있습니다. 셸에서 어떤 명령어를 전체 경로 없이 실행할 때 기본적으로 이 $PATH 환경변수의 값을 참조합니다. 이 변수에는 디렉터리 목록이 담겨있습니다.

다음 명령어로 현재 $PATH 환경변수 내용을 출력할 수 있습니다.

$ echo $PATH
/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/opt/homebrew/sbin

$PATH에는 다수의 디렉터리가 : 문자로 연결되어있습니다. 다음 명령어로 한 줄 씩 나눠서 경로를 확인할 수 있습니다.

$ IFS=':' PATHS=($(echo $PATH))
$ for string in "${PATHS[@]}"; do echo "$string"; done
/opt/homebrew/bin
/opt/homebrew/sbin
/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin
/opt/homebrew/bin
/opt/homebrew/sbin

앞에서 확인했던 명령어들의 위치는 모두 이 디렉터리 목록에 포함되어있습니다.

그렇다면 정말 $PATH 변수가 which 명령어 동작에 영향을 끼칠까요? $PATH 값을 빈 값으로 변경하고 which 명령어로 unzip의 위치를 찾아보겠습니다. export 명령어로 PATH 환경변수 값을 지정합니다.

$ export PATH=
$ echo $PATH

이제 $PATH 값은 비어있습니다. which unzip을 실행해봅니다.

$ which unzip
bash: which: No such file or directory

앗차, $PATH 값이 비어있어서 which 명령어도 찾을 수 없게 되어버렸습니다 😅 which 명령어는 /usr/bin/which에 있으니 전체 경로를 입력해 명령어를 실행해보겠습니다.

$ /usr/bin/which unzip

아무런 값도 출력되지 않습니다. unzip을 실행해봐도 해당 실행 파일을 찾을 수 없다는 에러가 출력됩니다.

$ unzip
bash: unzip: No such file or directory

다시 $PATH 변수에 unzip이 있었던 /usr/bin/을 추가하고 실행해보겠습니다.

$ export PATH=/usr/bin

$ which unzip
/usr/bin/unzip

$ unzip
UnZip 6.00 of 20 April 2009, by Info-ZIP.  Maintained by C. Spieler.  Send
bug reports using http://www.info-zip.org/zip-bug.html; see README for details.
....

다시 원래대로 동작하는 것을 확인할 수 있습니다!

$PATH 환경변수와 which의 동작 원리

그렇다면 여러 디렉터리에 같은 이름을 가진 실행파일이 있는 경우 어떻게 동작할까요? which 명령어는 기본적으로 $PATH 환경변수의 디렉터리 순서를 차례대로 탐색합니다.

간단한 테스트를 위해 이름이 같은 가짜 실행파일 hello_world를 몇 개 디렉터리에 복사해보겠습니다.

# 임시 디렉터리 생성
$ mkdir -p /tmp/a /tmp/b /tmp/c

# 가짜 명령어 파일 생성
$ touch hello_world

# hello_world 파일에 실행 권한 추가
$ chmod +x hello_world

# 임시 디렉터리들로 hello_world 명령어 복사
$ cp hello_world /tmp/a/hello_world
$ cp hello_world /tmp/b/hello_world
$ cp hello_world /tmp/c/hello_world

다음으로 아래와 같이 $PATH 환경변수를 변경합니다.

$ export PATH='/usr/bin:/tmp/a:/tmp/b:/tmp/c'

이제 whichhello_world 명령어 위치를 찾아봅니다.

$ which hello_world
/tmp/a/hello_world

/tmp/a가 출력됩니다. 이번에는 $PATH 환경변수의 디렉터리 순서를 변경하고 실행해봅니다.

$ export PATH='/usr/bin:/tmp/c:/tmp/b:/tmp/a'
$ which hello_world
/tmp/c/hello_world

이번에는 /tmp/c가 출력됩니다. 즉, PATH 변수의 디렉터리를 순서대로 탐색해서 처음 만나는 실행 파일의 위치를 알려주는 것을 확인할 수 있습니다.

-a 옵션: 모든 실행 파일 경로 출력

그럼 같은 이름을 가진 실행파일을 모두 찾으려면 어떻게 해야할까요?

이 때는 -a 옵션을 사용하면 됩니다.

$ which -a hello_world
/tmp/c/hello_world
/tmp/b/hello_world
/tmp/a/hello_world

이제 hello_world 명령어가 있는 모든 디렉터리가 출력됩니다.

종료 코드(Exit Status)

which는 실행 파일을 탐색 성공 여부에 따라서 다른 종료 코드를 가집니다. 이 종료 코드는 셸 스크립팅에서 유용하게 사용할 수 있습니다. 예를 들어 탐색한 명령어가 존재한다면 정상 종료 코드인 0으로 프로그램을 종료합니다. $? 변수를 출력해 마지막 명령어의 종료 코드를 확인할 수 있습니다.

$ which unzip
/usr/bin/unzip
$ echo $?
0

존재하지 않는 실행파일을 탐색하면 종료 코드 1로 프로그램을 종료합니다.

$ which not_existed
$ echo $?
1

잘못된 옵션을 사용하면 2로 종료됩니다.

명령어의 모든 옵션이나 종료 코드는 운영체제나 셸에 따라서 다를 수 있습니다. 이에 대해서는 현재 사용중인 리눅스의 man 페이지를 참고해주세요. 여기까지 리눅스 명령어 which의 사용법을 알아보았습니다.

추천 문서

LainyZine은 쿠팡 파트너스 활동에 따른 수수료를 제공받습니다.