공식 문서: GNU Bash Reference Manual, ShellCheck
Linux shell이란?
Shell은 사용자가 입력한 command를 해석하고 operating system에 실행을 요청하는 program이다.
Linux에서 terminal을 열고 다음 command를 입력할 때 실제로 command를 해석하는 것이 shell이다.
pwd
ls -al
mkdir project
cd project
자주 사용하는 shell은 다음과 같다.
| Shell | 특징 |
|---|---|
sh | 전통적인 Unix shell interface |
bash | Linux에서 널리 사용하는 shell |
zsh | macOS 기본 shell, plugin과 customization이 다양함 |
fish | 편리한 자동 완성과 interactive 기능 제공 |
현재 사용 중인 login shell은 다음 command로 확인할 수 있다.
echo "$SHELL"
Shell script는 terminal에서 실행하던 여러 command를 파일에 저장해 반복 작업을 자동화하는 program이다.
첫 shell script 작성
hello.sh 파일을 만든다.
#!/usr/bin/env bash
echo "Hello, Linux!"
pwd
date
첫 줄의 #!를 shebang이라고 한다.
이 파일을 어떤 interpreter로 실행할지 지정한다.
#!/usr/bin/env bash
Bash 기능을 사용하는 script라면 interpreter를 Bash로 명확하게 지정한다.
실행 권한을 추가한다.
chmod +x hello.sh
Script를 실행한다.
./hello.sh
실행 권한 없이 Bash에 파일을 전달해 실행할 수도 있다.
bash hello.sh
두 방식의 차이는 다음과 같다.
| 실행 방법 | Interpreter 선택 |
|---|---|
./hello.sh | Shebang에 지정된 interpreter 사용 |
bash hello.sh | 현재 command에서 Bash를 직접 지정 |
Comment 작성
# 뒤의 내용은 comment로 처리된다.
#!/usr/bin/env bash
# 현재 시간을 출력한다.
date
Shebang도 #으로 시작하지만 첫 줄의 #!는 특별한 의미를 가진다.
변수
Shell 변수는 이름=값 형식으로 만든다.
name="br4c3"
project_dir="$HOME/projects/blog"
변수를 대입할 때 = 앞뒤에 공백을 넣지 않는다.
# 올바른 작성
name="br4c3"
# 잘못된 작성
name = "br4c3"
변수의 값은 $name 또는 ${name}으로 사용한다.
echo "$name"
echo "User: ${name}"
echo "Project: ${project_dir}"
문자열 안에서는 변수 범위를 명확하게 하기 위해 ${name} 형태가 편리하다.
filename="report"
echo "${filename}_2026.txt"
따옴표
Shell에서 따옴표는 변수 확장과 공백 처리에 영향을 준다.
name="Linux"
echo "$name"
echo '$name'
출력은 다음과 같다.
Linux
$name
| 문법 | 동작 |
|---|---|
"$name" | 변수 값을 확장함 |
'$name' | 문자 그대로 사용함 |
$name | word splitting과 globbing이 발생할 수 있음 |
File path나 사용자 입력을 담은 변수는 특별한 이유가 없다면 "$variable"처럼 double quote로 감싼다.
file="my report.txt"
cat "$file"
Quote를 생략하면 my와 report.txt를 서로 다른 argument로 처리할 수 있다.
Command 결과를 변수에 저장
$(command)를 사용하면 command의 출력을 변수에 저장할 수 있다.
current_date=$(date +%Y-%m-%d)
current_user=$(whoami)
echo "Date: $current_date"
echo "User: $current_user"
예전 backtick 문법보다 $(...)를 사용하는 것이 읽기 쉽고 중첩하기도 편하다.
kernel_version=$(uname -r)
사용자 입력
read command로 값을 입력받는다.
#!/usr/bin/env bash
read -r -p "Enter your name: " name
echo "Hello, $name"
-r option은 backslash를 특별하게 처리하지 않고 입력 그대로 읽도록 한다.
Password처럼 입력 내용을 화면에 표시하지 않으려면 -s를 사용한다.
read -r -s -p "Password: " password
echo
Script argument
Script를 실행할 때 전달한 값은 positional parameter로 사용할 수 있다.
./greet.sh Alice developer
#!/usr/bin/env bash
echo "Script: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Argument count: $#"
echo "All arguments: $@"
주요 parameter는 다음과 같다.
| Parameter | 의미 |
|---|---|
$0 | Script 이름 |
$1, $2 | 첫 번째, 두 번째 argument |
$# | Argument 개수 |
"$@" | 모든 argument를 각각 유지 |
$? | 직전에 실행한 command의 exit status |
모든 argument를 순서대로 처리할 때는 "$@"를 사용한다.
for arg in "$@"; do
echo "$arg"
done
조건문
Bash에서는 if, elif, else, fi로 조건문을 작성한다.
#!/usr/bin/env bash
file_path="$1"
if [[ -f "$file_path" ]]; then
echo "File exists: $file_path"
elif [[ -d "$file_path" ]]; then
echo "Directory exists: $file_path"
else
echo "Path does not exist: $file_path"
fi
자주 사용하는 file 조건은 다음과 같다.
| 조건 | 의미 |
|---|---|
-e "$path" | Path가 존재함 |
-f "$path" | 일반 file임 |
-d "$path" | Directory임 |
-r "$path" | 읽을 수 있음 |
-w "$path" | 쓸 수 있음 |
-x "$path" | 실행할 수 있음 |
문자열을 비교한다.
if [[ "$name" == "admin" ]]; then
echo "Administrator"
fi
숫자를 비교한다.
count=10
if (( count >= 5 )); then
echo "count is at least 5"
fi
반복문
for
목록을 순서대로 처리한다.
for name in Alice Bob Charlie; do
echo "Hello, $name"
done
Directory의 .log file을 처리한다.
for file in ./*.log; do
[[ -e "$file" ]] || continue
echo "Log file: $file"
done
while
조건이 참인 동안 반복한다.
count=1
while (( count <= 3 )); do
echo "$count"
((count++))
done
File을 한 줄씩 읽는다.
while IFS= read -r line; do
echo "$line"
done < input.txt
함수
반복해서 사용하는 command를 함수로 묶을 수 있다.
log() {
local message="$1"
echo "[$(date +%H:%M:%S)] $message"
}
log "Build started"
log "Build finished"
함수 안의 local 변수는 함수 내부에서만 사용된다.
greet() {
local name="$1"
echo "Hello, $name"
}
greet "Linux"
Exit status
Linux command는 종료할 때 숫자 status를 반환한다.
0: 성공
0이 아닌 값: 실패 또는 다른 상태
직전 command의 status는 $?로 확인한다.
ls /tmp
echo "$?"
if는 command의 출력 문자열이 아니라 exit status를 기준으로 판단한다.
if ping -c 1 example.com > /dev/null 2>&1; then
echo "Network is reachable"
else
echo "Network is unavailable"
fi
Script를 특정 status로 종료한다.
exit 0
Error 상황에서는 1 같은 non-zero 값을 반환할 수 있다.
if [[ $# -eq 0 ]]; then
echo "Usage: $0 <file>" >&2
exit 1
fi
Redirection과 pipe
출력 redirection
Command 출력을 file에 저장한다.
ls -al > files.txt
>는 file을 새로 쓰고, >>는 뒤에 추가한다.
echo "start" > app.log
echo "finish" >> app.log
Error 출력은 file descriptor 2를 사용한다.
command 2> error.log
Standard output과 error를 모두 저장한다.
command > output.log 2>&1
Pipe
앞 command의 출력을 뒤 command의 입력으로 전달한다.
ps aux | rg python
find . -name "*.log" | sort
du -sh ./* | sort -h
Pipe는 여러 작은 command를 연결해 하나의 처리 흐름을 만드는 Linux shell의 핵심 기능이다.
안전한 기본 option
Bash script 시작 부분에서 다음 option을 사용할 수 있다.
set -Eeuo pipefail
| Option | 의미 |
|---|---|
-e | Command가 실패하면 script 종료 |
-u | 정의하지 않은 변수를 사용하면 오류 |
-o pipefail | Pipe 중간 command의 실패도 반영 |
-E | ERR trap을 함수 등에도 상속 |
작은 자동화 script에서는 실수를 빨리 발견하는 데 도움이 된다. 다만 실패를 직접 처리해야 하는 command에는 조건문을 사용한다.
if ! mkdir -p "$target_dir"; then
echo "Failed to create directory: $target_dir" >&2
exit 1
fi
간단한 backup script
다음은 지정한 directory를 날짜가 포함된 archive로 만드는 예제다.
#!/usr/bin/env bash
set -Eeuo pipefail
if [[ $# -ne 2 ]]; then
echo "Usage: $0 <source-directory> <backup-directory>" >&2
exit 1
fi
source_dir="$1"
backup_dir="$2"
timestamp=$(date +%Y%m%d-%H%M%S)
if [[ ! -d "$source_dir" ]]; then
echo "Source directory does not exist: $source_dir" >&2
exit 1
fi
mkdir -p "$backup_dir"
archive_name="backup-${timestamp}.tar.gz"
archive_path="${backup_dir}/${archive_name}"
tar -czf "$archive_path" -C "$source_dir" .
echo "Backup created: $archive_path"
실행 권한을 추가한다.
chmod +x backup.sh
실행한다.
./backup.sh ~/projects ~/backups
이 예제에는 다음 개념이 함께 사용된다.
Shebang
Strict mode
Script arguments
Condition
Variables and quoting
Command substitution
Exit status
문법 검사
실행하지 않고 Bash 문법만 확인한다.
bash -n backup.sh
ShellCheck를 설치하면 quoting, 변수 사용, portability와 관련된 문제를 정적으로 검사할 수 있다.
Ubuntu:
sudo apt install shellcheck
macOS:
brew install shellcheck
검사한다.
shellcheck backup.sh
자동 formatting이 필요하다면 shfmt도 사용할 수 있다.
정리
Linux shell script의 기본 흐름은 다음과 같다.
1. Shebang으로 interpreter 지정
2. Terminal command를 파일에 작성
3. chmod +x로 실행 권한 추가
4. 변수와 path는 double quote로 보호
5. if, for, while, function으로 흐름 구성
6. Exit status로 성공과 실패 판단
7. Redirection과 pipe로 command 연결
8. bash -n과 ShellCheck로 검사
처음에는 terminal에서 반복해서 입력하는 command를 .sh 파일로 옮기는 것부터 시작하면 된다.
Script가 길어지면 함수로 역할을 나누고, argument 검사와 error 처리를 추가하면 재사용하기 쉬운 자동화 도구가 된다.