sh 와 bash
sh 와 bash
Unix 계열 시스템에서 가장 자주 만나는 셸은 bash 입니다. 그리고 그 뿌리에는 sh (Bourne shell) 가 있습니다.
1. sh 와 bash 의 관계
- sh (Bourne shell) — Stephen Bourne 이 1977 년 Unix V7 을 위해 만든 셸. 이후 POSIX 가 표준화한 sh 의 기준.
- bash (Bourne Again Shell) — Brian Fox 가 1989 년 GNU 프로젝트로 만든 sh 호환 셸. POSIX sh 의 상위 집합으로, 배열·연관 배열·
[[ ]]같은 확장.
/bin/sh 는 시스템마다 가리키는 실체가 다릅니다. 일부 Linux 배포판은 dash (가벼운 POSIX sh) 로, 다른 배포판은 bash 의 sh 호환 모드로 연결. macOS 는 /bin/sh 가 bash 의 sh 모드.
같은 스크립트라도 #!/bin/sh 로 시작하면 POSIX 범위만 허용되고, #!/bin/bash 또는 #!/usr/bin/env bash 로 시작하면 bash 확장을 쓸 수 있습니다.
2. shebang 과 실행 권한
스크립트 파일의 첫 줄 #! 는 shebang. 커널은 실행 시 이 줄을 보고 어떤 인터프리터로 파일을 돌릴지 결정합니다.
#!/usr/bin/env bash
echo "hello"
/usr/bin/env bash 형태는 bash 의 절대 경로가 시스템마다 다를 때 (/bin/bash vs /usr/local/bin/bash) 유연하게 찾아 실행해 줍니다.
| 작업 | macOS · Linux | Windows (Git Bash · WSL) |
|---|---|---|
| 권한 부여 | chmod +x script.sh |
(WSL · Git Bash 안에서 동일) |
| 실행 | ./script.sh |
./script.sh 또는 bash script.sh |
| 직접 인터프리터 호출 | bash script.sh |
bash script.sh |
bash script.sh 처럼 인터프리터를 직접 부르면 실행 비트가 없어도 동작.
3. 변수와 조건문
NAME="world"
echo "hello, $NAME"
echo "hello, ${NAME}!"
= 양옆에 공백을 두면 안 됩니다. NAME = "world" 는 명령 실행으로 해석되어 에러.
if [ -f config.json ]; then
echo "found"
elif [ -d config ]; then
echo "directory"
else
echo "not found"
fi
bash 에서는 [[ ... ]] 도 가능. [[ ]] 는 단어 분리·글로빙을 하지 않아 안전하지만 POSIX sh 에는 없습니다.
if [[ "$NAME" == "world" ]]; then
echo "match"
fi
4. 반복과 함수
for f in *.txt; do
echo "$f"
done
i=0
while [ $i -lt 5 ]; do
echo $i
i=$((i + 1))
done
greet() {
local name="$1"
echo "hello, $name"
}
greet "alice"
local 은 bash 확장이며 POSIX sh 에는 없습니다. POSIX 범위에서는 모든 변수가 전역.
5. 파이프와 리다이렉션
ls -la | grep "\.md$" # 표준출력을 다음 명령으로
echo "log entry" >> app.log # append
command 2> errors.txt # 표준에러 리다이렉션
command > out.txt 2>&1 # 둘 다 한 곳으로
command 2>/dev/null # 에러 버리기
6. set -euo pipefail
스크립트 첫머리에 자주 등장하는 한 줄.
| 옵션 | 의미 |
|---|---|
-e |
명령이 비-0 으로 종료되면 즉시 스크립트 종료. |
-u |
정의되지 않은 변수를 참조하면 에러. |
-o pipefail |
파이프 내 어느 단계라도 실패하면 전체 실패. |
이 셋이 없으면 중간 명령이 실패해도 스크립트가 계속 진행되어 망가진 상태로 끝나기 쉽습니다. 다만 -e 는 일부 합법적인 비-0 반환 (예: grep 이 매칭 없을 때 1) 을 에러로 잡으므로, 그 부분은 || true 로 명시 처리.
#!/usr/bin/env bash
set -euo pipefail
count=$(grep -c "ERROR" app.log || true)
echo "errors: $count"
7. dash vs bash 비호환 함정
Debian · Ubuntu 의 /bin/sh 가 dash 인 점은 자주 문제가 됩니다. bash 에서 잘 돌던 스크립트가 dash 에서 깨지는 대표 사례:
[[ ... ]]는 dash 에 없음.[ ... ]만.- 배열 (
arr=(a b c)) 은 dash 에 없음. function name() { }의function키워드는 dash 에 없음.name() { }만.<<<(here-string) 은 dash 에 없음.==비교는 POSIX 가 아님.=만 안전.local은 dash 에 있긴 하나 POSIX 표준은 아님.
스크립트가 bash 확장을 쓰면 shebang 을 #!/usr/bin/env bash 로 분명히. POSIX 범위에 머물 의도라면 #!/bin/sh 로 적고 dash 에서 한 번 검증.
8. 양 환경에서의 호출
# macOS · Linux
#!/usr/bin/env bash
set -euo pipefail
chmod +x deploy.sh
./deploy.sh
# Windows (Git Bash · WSL)
bash deploy.sh
# 또는 WSL
wsl bash deploy.sh
cmd.exe 나 PowerShell 에서 직접 .sh 를 더블클릭하거나 호출하면 동작하지 않습니다. 셸 자체가 bash 가 아니기 때문.
9. 자주 걸리는 자리
변수 인용 빠뜨리기 — rm $FILE 에서 $FILE 이 비어 있으면 rm 만 실행되어 에러로 끝나지만, 공백이 들어 있으면 의도하지 않은 파일을 지움. 항상 "$FILE" 로 인용.
== vs = — [[ ]] 안에서는 둘 다 동작하지만 [ ] 에서는 = 만 POSIX.
수치 비교에 > · < 사용 — [ $a > $b ] 는 비교가 아니라 리다이렉션. -gt · -lt 를 사용.
윈도우 줄바꿈 (CRLF) 으로 저장된 .sh 가 \r 때문에 실행 실패 — dos2unix 또는 git 의 text eol=lf 설정.
set -e 를 쓴 상태에서 command || true 를 빠뜨려 의도와 다르게 종료.
하고픈 말
shebang + set -euo pipefail + 변수 인용 + eol=lf 네 자리가 정착된 스크립트는 OS 차이를 거의 안 드러냅니다. 깐깐한 dash 환경에서 한 번 돌려 보면 호환성 사고를 미리 잡을 수 있어요. ShellCheck 도 좋은 출발점.
Next
- powershell-basics
- cmd-and-bat
GNU Bash Reference Manual · POSIX Shell · BashGuide · ShellCheck · Dash 매뉴얼 을 참고합니다.