개요
리눅스 시스템에서 반복적인 작업을 자동화하기 위해 crontab
을 자주 사용하는데요.
그런데 터미널에서 직접 실행하면 잘 동작하던 스크립트가 crontab
에 등록하면 제대로 동작하는 않는 경우가 종종 발생합니다.
특히 .bashrc
파일에 정의해 둔 환경 변수를 스크립트가 읽지 못하는 문제가 대표적입니다.
이번 포스팅에서는 왜 그러한 문제가 발생하는지, 그리고 어떻게 해결할 수 있는지 에 대해 자세하게 알아보겠습니다.
문제 상황: 터미널에서는 OK, Crontab에서는 NG?
많은 리눅스 사용자들이 터미널 환경을 자신에게 맞게 설정하기 위해 .bashrc
파일에 환경 변수나 alias 등을 정의해서 사용하죠?
예를 들어, 다음과 같이 특정 경로를 환경 변수로 설정했다고 가정해봅시다.
# ~/.bashrc 파일 내용 예시
export MY_APP_HOME="/opt/my_application"
export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
그리고 이 변수를 사용하는 쉘 스크립트(my_script.sh
)를 작성해볼게요.
#!/usr/bin/bash
echo "애플리케이션 경로: $MY_APP_HOME"
echo "자바 경로: $JAVA_HOME"
# ... 그 외의 스크립트 로직 ...
터미널에서 ./my_script.sh
를 실행하면 .bashrc
에 정의된 MY_APP_HOME
과 JAVA_HOME
변수가 정상적으로 출력됩니다.
하지만 이 스크립트를 crontab
에 등록해서 실행하면, 해당 변수들이 빈 값으로 나오거나 스크립트가 오류를 내뱉는 것을 보게 됩니다.
왜 그러는걸까요?
원인: 쉘 환경의 차이 (대화형 vs 비대화형)
이 문제의 핵심 원인은 쉘(Shell)이 실행되는 환경의 차이에 있습니다.
1. 대화형 쉘(Interactive Shell)
사용자가 터미널을 열거나 SSH로 접속할 때 실행되는 쉘입니다.
이 환경에서는 보통 로그인 시 .bash_profile
이나 .profile
이 먼저 실행되고, 새 터미널 창을 열 때는 .bashrc
파일이 실행되어 사용자가 정의한 환경 변수, 함수, alias 등이 로드됩니다.
즉, 우리가 터미널에서 명령어를 입력하는 환경입니다.
2. 비대화형 쉘(Non-interacive Shell)
스크립트 실행이나 crontab
작업처럼 사용자와 직접 상호작용하지 않는 환경에서 실행되는 쉘입니다.
중요한 점은, crontab
은 기본적으로 비대화형, 비로그인 쉘 환경에서 작업을 수행하며, 이 환경에서는 .bashrc
파일을 자동으로 읽어오지 않는다는 점입니다!
따라서 .bashrc
에만 정의된 환경 변수는 crontab
으로 실행되는 스크립트에게는 존재하지 않는 변수가 되어버리는거죠!
해결 방법: crontab에서도 환경 변수 사용하기
당연하게도 이 문제를 해결할 수 있는 몇 가지 방법이 있겠죠?
방법 1: 스크립트 내에서 .bashrc
직접 로드하기 (Source)
스크립트 시작 부분에서 source
명령어를 사용하여 .bashrc
파일을 명시적으로 읽어오도록 하는거죠!
#!/bin/bash
# 사용자 홈 디렉토리의 .bashrc 파일을 로드
if [ -f "$HOME/.bashrc" ]; then
source "$HOME/.bashrc"
else
echo -e "사용자의 .bashrc 파일을 불러올 수 없습니다! 스크립트가 종료됩니다."
exit 1
fi
# 이제 .bashrc 변수 사용 가능
echo "애플리케이션 경로: $MY_APP_HOME"
echo "자바 경로: $JAVA_HOME"
# ... 나머지 스크립트 로직 ...
장점
- 비교적 간단하게 적용할 수 있습니다
주의
.bashrc
에는 터미널 프롬프트 설정(PS1
)이나echo
문처럼 대화형 세션에만 적합한 명령어가 있을 수 있어요!- 이런 명령어들이 비대화형 환경에서 예기치 않은 문제를 일으킬 수 있으므로 주의해야 해요!
방법2: crontab
항목에 변수 직접 정의
crontab -e
명령으로 편집기를 열고, 스크립트 실행 명령어 앞에 필요한 환경 변수를 직접 명시해주면 됩니다!
# 매일 새벽 1시에 스크립트 실행
0 1 * * * MY_APP_HOME="/opt/my_application" JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64" /path/to/my_script.sh
장점
- 스크립트 파일을 수정할 필요가 없죠!
crontab
작업에 필요한 환경 변수만 명확하게 지정할 수 있으니까요.
단점
- 변수가 많거나 여러 작업에서 공통으로 사용될 경우 중복 정의가 발생하겠죠?
방법3: crontab
에서 Bash 쉘과 source
명령 함께 사용하기
crontab
명령 실행 시 bash -c
를 사용하여 명령 문자열을 전달하고, 그 안에서 .bashrc
를 source
한 후 스크립트를 실행하는거에요!
# 매일 새벽 1시에 bash 쉘로 .bashrc 로드 후 스크립트 실행
0 1 * * * bash -c 'source $HOME/.bashrc && /path/to/my_script.sh'
장점
- 스크립트 수정 없이
.bashrc
의 환경을 가져올 수 있겠죠.
주의
- 방법1과 마찬가지로
.bashrc
내용에 따라 부작용이 발생할 수 있어요!
방법 4: 필요한 환경 변수만 별도 파일로 분리하기 (권장)
스크립트 실행에 꼭 필요한 환경 변수들을 별도의 파일(예: $HOME/.common.env
)로 분리하여 관리하는 것이 가장 깔끔하고 안전한 방법일 수 있어요.
1. 환경 변수 파일 생성 ($HOME/.common.env
)
export MY_APP_HOME="/opt/my_application"
export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
2. .bashrc
에서 이 파일 로드 (대화형 쉘을 위해)
# ~/.bashrc 끝에 추가
if [ -f "$HOME/.common_env" ]; then
source "$HOME/.common_env"
fi
3. 스크립트에서 이 파일 로드 (my_script.sh
)
#!/bin/bash
# 공통 환경 변수 파일 로드
if [ -f "$HOME/.common_env" ]; then
source "$HOME/.common_env"
fi
echo "애플리케이션 경로: $MY_APP_HOME"
echo "자바 경로: $JAVA_HOME"
# ... 나머지 스크립트 로직 ...
4. crontab 설정은 그대로 유지
0 1 * * * /path/to/my_script.sh
장점
- 환경 변수 관리가 용이하고, 대화형/비대화형 환경 모두에서 일관성 유지가 가능해요!
.bashrc
의 다른 설정과 분리되어 안전한 것도 덤!
단점
- 스크립트별로 관리해야 할 파일이 늘어난다는 정도?
추가 팁!
안정적인 Crontab 작업을 위해 아래 팁을 참고해보세요!
절대 경로 사용하기
crontab
작업은 실행 경로가 보장되지 않으므로, 스크립트 내에서 사용하는 명령어(예:java
,python
)나 파일 경로는 가급적/usr/bin/java
,/usr/bin/python
와 같이 절대 경로를 사용하는 것을 추천합니다!
로그 남기기
crontab
작업의 성공/실패 여부나 출력 내용을 확인하기가 어렵기 때문에 표준 출력(stdout
)과 표준 에러(stderr
)를 파일로 리다이렉션하여 로그를 남기면 디버깅에 큰 도움이 되겠죠?