트러블슈팅

zsh에서 변수에 담은 명령이 no such file 오류로 실패하는 이유

개개발 노하우 노트2026년 11월 11일 · 3분 읽기

작업 기록을 남기는 도우미 스크립트를 여러 번 부르려고 명령을 변수에 담아 두었다가, zsh에서 호출이 전부 실패한 일을 정리했다. bash에서 옮겨 온 습관이 zsh에서 어떻게 깨지는지, 그리고 왜 함수로 바꾸는 편이 안전한지 궁금한 사람에게 필요한 글이다.

상황

자동화 플러그인 저장소에서 작업 기록을 남기는 Node 스크립트를 한 세션 안에서 수십 번 호출해야 했다. 매번 `node scripts/worklog.mjs`를 다 치기 번거로워서 셸 변수에 명령 문자열을 담아 두고 `$H log ...` 식으로 부르기로 했다.

```sh

H="node scripts/worklog.mjs"

$H log "첫 번째 기록"

```

bash에서는 흔히 쓰는 지름길이고, 실제로 bash에서는 대개 동작한다. 그런데 이 세션의 셸은 macOS 기본 셸인 zsh였다.

증상

`$H`로 시작하는 호출이 하나도 빠짐없이 `no such file or directory` 오류로 끝났다. 스크립트 경로를 절대 경로로 바꿔도, 작업 폴더를 옮겨도 결과는 같았다. 오류 메시지만 보면 스크립트 파일이 없다는 뜻으로 읽히기 때문에 처음에는 경로 문제를 의심하느라 시간을 썼다.

실제로 셸이 찾고 있던 것은 `node scripts/worklog.mjs`라는, 공백까지 포함한 이름의 실행 파일 하나였다.

원인

bash나 POSIX sh는 따옴표 없이 쓴 변수를 펼친 뒤 공백 기준으로 단어를 나눈다. 그래서 `$H`가 `node`와 `scripts/worklog.mjs` 두 단어가 되고 우연히 명령처럼 실행된다.

zsh는 기본값이 다르다. zsh FAQ는 `var="foo bar"`일 때 `$var`가 왜 기대대로 동작하지 않는지를 따로 다루며, zsh는 기본적으로 변수를 나누지 않고 값을 그대로 유지한다고 설명한다. 이 동작은 `SH_WORD_SPLIT` 옵션으로 바꿀 수 있지만 기본은 꺼져 있다. 결국 변수 전체가 명령 이름 하나로 해석되고, 그런 이름의 파일은 없으니 실패한다.

사실 bash에서 동작하는 것도 우연에 가깝다. 인자에 공백이나 따옴표가 섞이면 bash에서도 같은 방식이 깨진다. bash 사용자 사이에서 오래 공유된 FAQ 역시 "명령을 변수에 넣지 말고 함수에 넣으라"고 정리한다.

해결

명령을 셸 함수로 정의하고 인자는 `"$@"`로 그대로 넘겼다.

```sh

wl() { node scripts/worklog.mjs "$@"; }

wl log "첫 번째 기록"

```

함수는 zsh와 bash 모두에서 같은 방식으로 동작하고, `"$@"`는 인자 하나하나를 공백까지 보존한 채 전달한다. 이렇게 바꾼 뒤 모든 호출이 정상적으로 끝났다.

다른 방법도 있다. zsh FAQ가 안내하는 대로 배열에 명령과 인자를 나눠 담거나(`H=(node scripts/worklog.mjs)` 후 `$H log ...`), 그 변수 하나만 나누도록 `${=H}`를 쓰는 방식이다. 다만 셸마다 문법이 갈리므로 여러 셸에서 쓸 코드라면 함수가 가장 덜 헷갈린다. `setopt SH_WORD_SPLIT`을 전역으로 켜는 방법은 다른 스크립트의 동작까지 바꾸므로 권하지 않는다.

운영자 해설

이 문제가 까다로운 이유는 오류 메시지가 원인과 다른 곳을 가리키기 때문이다. "파일이 없다"는 말을 보면 누구나 경로부터 의심한다. 에이전트나 자동화 도구가 셸 명령을 대신 실행하는 환경에서는 더 자주 생긴다. 도구가 bash 문법을 가정하고 명령을 만들어도 실제로는 사용자의 기본 셸인 zsh에서 돌기 때문이다. macOS에서 실행되는 자동화라면 "이 명령이 어느 셸에서 도는가"를 먼저 확인할 가치가 있다.

다시 겪지 않으려면

  • 여러 번 부를 명령은 변수가 아니라 함수로 정의한다.
  • 함수 안에서는 인자를 `"$@"`로 넘긴다.
  • 꼭 변수에 담아야 하면 배열을 쓰고, 셸마다 문법이 다르다는 점을 주석으로 남긴다.
  • `no such file or directory`가 나왔는데 경로가 맞다면, 명령 이름 전체가 하나의 단어로 해석되지 않았는지 확인한다.
  • 자동화 도구가 만든 셸 명령은 실제 실행 셸(zsh·bash)을 기준으로 시험한다.

참고 자료