스크립트가 분명히 존재하고 실행 권한도 있는데 실행하면 파일이 없다는 오류가 난다.
Caused by: java.io.IOException: Cannot run program "/opt/app/bin/decoder.sh"
(in directory "/opt/app/bin"): error=2, No such file or directory
$ ls -l /opt/app/bin/decoder.sh
-rwxr-xr-x 1 app app 3834 May 27 13:46 /opt/app/bin/decoder.sh
error=2 는 ENOENT 이고 리눅스의 execve(2) 가 돌려준 값이다. 커널은 스크립트를 실행할 때 첫 줄의 #! 뒤에 적힌 인터프리터를 먼저 연다. 그 인터프리터가 없으면 커널은 실행 대상인 스크립트가 아니라 인터프리터에 대해 ENOENT 를 돌려주는데, 오류 메시지에는 스크립트 경로만 찍히므로 스크립트가 없는 것처럼 보인다.
첫 줄을 본다.
head -1 /opt/app/bin/decoder.sh
#!/bin/ksh
그 인터프리터가 실제로 있는지 본다.
ls -l /bin/ksh
command -v ksh
셸에서 직접 실행해 보면 더 분명한 메시지가 나온다.
$ /opt/app/bin/decoder.sh
bash: /opt/app/bin/decoder.sh: /bin/ksh: bad interpreter: No such file or directory
인터프리터 미설치 — 위 경우다. ksh · zsh · python 처럼 기본 설치가 아닌 셸을 shebang 에 쓴 스크립트를 최소 설치 서버에 올렸을 때 나온다.
경로 차이 — 같은 인터프리터라도 배포판마다 경로가 다르다. #!/usr/bin/python 은 RHEL 8 이상에서 존재하지 않고(/usr/bin/python3 만 있다), #!/bin/bash 는 일부 컨테이너 이미지(Alpine 등)에 없다. 이식성이 필요하면 #!/usr/bin/env bash 형태를 쓴다.
CRLF 줄 끝 — Windows 에서 만든 스크립트를 그대로 올리면 첫 줄이 #!/bin/bash\r 이 되어 커널이 /bin/bash\r 라는 이름을 찾는다. 파일 자체는 멀쩡해 보이므로 가장 헷갈린다.
file /opt/app/bin/decoder.sh
head -1 /opt/app/bin/decoder.sh | od -c | head -2
sed -i 's/\r$//' /opt/app/bin/decoder.sh
인터프리터를 설치하거나, 스크립트를 이미 있는 셸로 바꾼다.
# RHEL 8 / Rocky 8 계열
sudo dnf install ksh
# RHEL 9 / Rocky 9 계열 — AppStream 에 없으면 EPEL 을 켠다
sudo dnf install epel-release
sudo dnf install ksh
바꿔도 되는 스크립트라면 shebang 을 #!/bin/sh 나 #!/bin/bash 로 고치는 편이 간단하지만, ksh 전용 문법(typeset -A, print -u2, ${.sh.*} 등)을 쓰고 있으면 그대로 동작하지 않는다. 벤더가 제공한 스크립트는 고치지 말고 인터프리터를 설치한다.
리포지토리가 아예 동작하지 않아 설치가 안 되는 서버라면 저장소 설정부터 본다. /etc/yum.repos.d/ 의 .repo 파일이 HTML 오류 문서로 덮여 있거나 baseurl 이 죽어 있는 경우가 있다.
cat /etc/yum.repos.d/*.repo | head -40
dnf repolist