우분투 · 리눅스 입문 — 21편 (고급)

리눅스 셸 스크립트 작성법 — 변수·조건·반복·함수

지금까지 터미널에 친 명령들을 파일 하나에 모아 "한 번에 실행"하게 만드는 게 셸 스크립트입니다. 자동화의 출발점이에요.

2026년 5월 13일 · 약 8분 · 26편 입문 시리즈 21편

backup.sh 파일에 shebang·변수·if·for 로 명령들을 묶어둔 터미널 화면 일러스트 — 셸 스크립트 작성을 상징

20편(cron)에서 "정해진 때에 명령을 자동 실행"하는 법을 배웠죠. 그런데 그 '명령'이 한 줄이 아니라 "디렉토리 만들고 → 파일 복사하고 → 압축하고 → 로그 남기고…" 여러 줄이면? 그걸 파일 하나에 묶어 이름 붙인 게 셸 스크립트입니다. 여기서부터가 "고급" — 명령을 아는 것에서, 명령을 조립하는 것으로 넘어갑니다.

먼저: 1~12편(터미널·파일·텍스트·권한·apt·파이프)을 봤다면 충분합니다. 편집은 8편의 nano나 13편의 vim 둘 중 편한 걸로. 모든 예제는 빈 폴더에서 안전하게 연습할 수 있는 것들입니다. 이번 편은 — ① 첫 .sh 파일 만들고 실행 → ② 변수·입력 → ③ if·for·while → ④ 함수·인자·종료코드.

첫 .sh 파일 만들고 실행하기

파일 하나 만들어봅니다. nano hello.sh 로 열어 이렇게 적으세요:

#!/bin/bash # 위 줄(shebang)은 "이 파일을 bash 로 실행해라"는 표시 — 항상 1번 줄에 # # 으로 시작하는 줄은 주석(실행 안 됨) echo "안녕, 셸 스크립트!" echo "오늘은 $(date +%Y-%m-%d)" # $(명령) 은 그 명령의 결과로 바뀜

저장하고 나와서, 실행 권한을 준 다음(9편의 chmod 기억나죠?) 돌립니다:

$ chmod +x hello.sh # 실행 가능하게 $ ./hello.sh # 같은 폴더의 스크립트는 ./ 를 붙여서 실행 안녕, 셸 스크립트! 오늘은 2026-05-13

./ 를 붙이는 이유는 12편에서 본 PATH 때문 — 현재 폴더는 보통 PATH에 없어서 "여기 있는 거 실행해"라고 명시해줘야 합니다. 또는 bash hello.sh 처럼 실행해도 됩니다(이러면 chmod +x 없어도 OK).

변수와 입력 — 값을 담고 받기

변수는 등호 양옆에 공백 없이 이름=값, 쓸 땐 앞에 $. 이게 셸의 가장 흔한 초보 함정이에요(x = 5 는 에러 — x=5).

name="준성" count=3 echo "$name 님, 항목 ${count}개" # ${} 는 변수 경계가 헷갈릴 때 today=$(date +%F) # 명령 결과를 변수에 echo "오늘: $today / 홈: $HOME" # $HOME 같은 환경변수도 그대로 씀 read -p "이름을 입력하세요: " who # 사용자 입력을 who 에 담기 echo "반가워요, $who!"
따옴표는 거의 항상 큰따옴표로: echo $name 처럼 인용 안 하면 값에 공백이 있을 때 단어가 쪼개집니다("홍 길동" 길동 두 개로). echo "$name" 처럼 큰따옴표로 감싸면 통째로 한 덩어리 + $변수·$(명령)은 그대로 치환됩니다. 작은따옴표 '...' 는 치환도 안 함(글자 그대로). 헷갈리면 큰따옴표가 정답.

흐름 제어 — 조건문과 반복문

조건문: if [ 조건 ]; then ... fi. 대괄호 안팎에 공백 필수([ "$x" = "5" ][ 도, ] 도 공백). 숫자 비교는 -eq -ne -lt -le -gt -ge, 문자열은 = !=, 파일은 -f(파일 존재) -d(디렉토리) -e(존재) !(부정).

if [ ! -d "$HOME/backup" ]; then mkdir -p "$HOME/backup" echo "backup 폴더 만듦" elif [ -f "$HOME/backup/old.tar" ]; then echo "이미 백업이 있음" else echo "폴더는 있는데 백업 파일은 없음" fi # && 와 || 로 짧게: 앞이 성공하면 && 뒤 실행, 실패하면 || 뒤 실행 mkdir -p logs && echo "ok" || echo "실패"

반복문: for 는 목록을 하나씩, while 은 조건이 참인 동안. break 로 중단, continue 로 다음 차례.

for f in *.log; do # 현재 폴더의 .log 파일들을 하나씩 f 에 echo "처리 중: $f" cp "$f" "$HOME/backup/" done for i in {1..5}; do echo "$i 번째"; done # {1..5} = 1 2 3 4 5 n=1 while [ "$n" -le 3 ]; do echo "카운트 $n" n=$((n + 1)) # $(( )) 안에서 산술 연산 done

함수·인자·종료코드 — 그리고 정리

함수이름() { ... } 로 정의, 호출은 그냥 이름. 함수 안에서 $1 $2함수에 넘긴 인자입니다. 스크립트 자체에 넘긴 인자$1 $2 …, 전부는 $@, 개수는 $#, 스크립트 이름은 $0.

log() { # 함수 정의 echo "[$(date +%T)] $1" # 함수에 넘어온 첫 인자 = $1 } backup_one() { cp "$1" "$2" && log "$1 → $2 완료" } # 스크립트 인자 받기: ./myscript.sh a.txt /tmp → $1=a.txt $2=/tmp if [ "$#" -lt 2 ]; then echo "사용법: $0 <원본> <대상폴더>" exit 1 # 0 = 성공, 0 아님 = 실패. 인자 부족이면 1 로 종료 fi backup_one "$1" "$2" log "끝. 종료코드 직전 값: $?" # $? = 직전 명령의 종료코드 exit 0

종료코드(exit code)는 "이 스크립트가 잘 끝났나"를 다음 사람(혹은 cron, 혹은 다른 스크립트)에게 알려주는 신호입니다 — 0이면 성공, 그 외는 실패. 직전 명령의 종료코드는 $? 에 들어 있어서, if 명령; then … 처럼 명령을 그대로 조건으로 쓸 수도 있어요(0이면 참 취급).

초보가 거의 다 당하는 셋:x = 5 — 등호 양옆 공백 금지(x=5). ② [ $x = 5 ] 에서 $x 가 비어 있으면 에러 — 변수는 큰따옴표로 [ "$x" = "5" ]. ③ [] 양옆 공백 빼먹기 — [ "$x" -gt 3 ]. 이 셋만 조심하면 첫 스크립트는 거의 굴러갑니다.

시리즈 흐름

  1. 1~12편 입문·기초 ✔   13~20편 중급(vim·프로세스·디스크·네트워크·ufw·SSH·systemd·cron) ✔
  2. 21편 — 셸 스크립트 작성법 (이 글) ✔ — "고급" 묶음 시작
  3. 22편 — 안전한 셸 스크립트(set -euo pipefail·trap·shellcheck)
  4. 23편~ — grep·sed·awk / 로그·모니터링 / 서버 보안 하드닝 / 도커 입문

정리하면 — 셸 스크립트는 "외운 명령들을 파일에 모아, 변수로 유연하게, if·for로 상황에 맞게, 함수로 깔끔하게, exit로 결과를 알리는" 것입니다. 작은 것부터 시작하세요: 자주 치는 명령 두세 개를 .sh 파일에 넣고 chmod +x → 실행. 그게 첫 자동화입니다. 22편에서는 이 스크립트가 조용히 망가지지 않게 만드는 법(set -euo pipefail 등 베스트 프랙티스)을 다룹니다.

참고: 본 글은 bash(우분투 기본 셸) 기준이며 2026년 5월 13일에 작성되었습니다. 일부 문법은 sh/dash(POSIX)와 다르니, 스크립트 첫 줄을 #!/bin/bash로 명시하세요.

우분투·리눅스 입문 시리즈

명령을 아는 단계에서 명령을 조립하는 단계로 — "고급" 묶음 첫 편입니다. 22편 "안전한 셸 스크립트"로 이어집니다.

다음 편은 junai.ai/blog 에서 — 26편까지 차례로 올라옵니다.

© 2026 JUNAI · 우분투·리눅스 입문 시리즈 21편 · 본 글은 2026년 5월 13일 기준으로 작성되었습니다.

© 2026 주나이테크(주) @JUNAITECH