스크립트의 현재 디렉터리는 호출한 쪽의 디렉터리다. 스크립트 파일이 있는 위치가 아니다. cron 이나 systemd 에서 부르면 / 나 사용자 홈이 되므로, 상대 경로로 파일을 읽는 스크립트는 여기서 깨진다.
가장 흔한 처리는 스크립트 자신이 있는 디렉터리로 옮겨 가는 것이다.
#!/bin/bash
set -euo pipefail
cd "$(dirname "$(readlink -f "$0")")" || exit 1
./run_job.sh
readlink -f 를 거치면 심볼릭 링크로 호출돼도 실제 경로를 잡는다. cd 뒤의 || exit 1 은 이동에 실패했을 때 엉뚱한 디렉터리에서 작업이 이어지는 것을 막는다.
고정된 작업 디렉터리가 따로 있다면 그 경로를 직접 쓴다.
cd /opt/batch/daily || exit 1
스크립트를 고치지 않고 디렉터리만 바꿔 실행하려면 서브셸로 감싼다. 괄호 안의 cd 는 밖으로 새어 나가지 않는다.
(cd /opt/batch/daily && ./run_job.sh)
pushd · popd 는 대화형에서 오갈 때 편하지만, 스크립트에서는 중간에 실패하면 popd 에 닿지 못해 디렉터리가 어긋난 채 남는다. 서브셸 쪽이 안전하다.
systemd 서비스라면 유닛 파일에 적는다.
[Service]
WorkingDirectory=/opt/batch/daily
ExecStart=/opt/batch/daily/run_job.sh
cron 은 WorkingDirectory 에 해당하는 것이 없으므로 스크립트 안에서 cd 하거나 크론 줄에 cd 를 넣는다.
0 3 * * * cd /opt/batch/daily && ./run_job.sh >> /var/log/daily.log 2>&1
히어독을 쓰면 명령 하나로 여러 줄을 써 넣을 수 있다.
cat > a.sh <<'EOF'
#!/bin/bash
/bin/uptime
/opt/app/bin/start
EOF
chmod +x a.sh
구분자를 작은따옴표로 감싸는 것(<<'EOF') 이 중요하다. 감싸면 내용 안의 $VAR 와 `cmd` 가 그대로 기록되고, 감싸지 않으면 히어독을 쓰는 시점에 셸이 치환한다. 스크립트나 설정 파일을 만들 때는 대개 그대로 남기고 싶으므로 감싼다.
값을 치환해 넣고 싶을 때만 따옴표를 뺀다.
cat > app.conf <<EOF
host=${DB_HOST}
port=${DB_PORT}
EOF
이어 붙이려면 >> 를 쓴다. 들여쓴 히어독이 필요하면 <<- 를 쓰는데, 이때 제거되는 것은 탭뿐이고 공백은 남는다.
sudo 와 함께 쓸 때는 리다이렉션이 sudo 가 아닌 현재 셸에서 일어나므로 권한이 필요한 경로에는 tee 를 끼운다.
sudo tee /etc/profile.d/app.sh > /dev/null <<'EOF'
export APP_HOME=/opt/app
EOF