문서 읽는 데 39분 · A11

A-11: bash 스크립팅 ① — 첫 스크립트와 변수

목차 23
전체 12강 중 11강 · 리눅스
난이도 · 입문

ℹ️서버·배포에서 쓰는 리눅스 셸과 명령어 기본기. 백엔드·인프라로 가기 전에 익혀두면 든든해요.

안녕하세요, 홍순구입니다. 드디어 이 과목의 마지막 여정, bash 스크립팅에 도착했어요. 지난 시간까지 우리는 파일을 다루고, 권한을 이해하고, 텍스트를 가공하고, 프로세스를 들여다보고, 원격 서버에 들어가 파일을 주고받고 방화벽을 여닫는 것까지 손에 익혔습니다. 명령을 정말 많이 배웠죠.

그런데 그 명령들을 매번 손으로 하나씩 치자니 번거롭지 않던가요? 같은 점검을 매일 아침 반복하고, 같은 백업을 매주 돌리고. 사람이 하면 빠뜨리기도 하고 지치기도 합니다. 오늘부터 배우는 스크립트가 바로 그 반복을 대신 맡아줘요.

"스크립트"라는 말이 갑자기 프로그래밍처럼 들려서 겁날 수 있어요. 안심하세요. 스크립트는 여러분이 지금까지 친 명령을 그대로 파일에 적어 저장해둔 것뿐입니다. 새로운 문법은 거의 없어요. 그 명령들을 엮을 약간의 접착제 — 변수, 조건, 반복 — 만 더하면 됩니다. 그 접착제도 천천히 하나씩 손에 익혀갈 거예요.

분량이 넉넉해서 두 번에 걸쳐 진행해요. 오늘(A-11)은 스크립트를 만들고 실행하는 법, 그리고 변수에 값을 담아 다루는 기본기까지. 다음 시간(A-12)에 조건·반복·함수로 진짜 자동화를 완성합니다. 오늘 우리가 걸어갈 길을 먼저 그려둘게요.

텍스트
  [첫걸음]  왜 스크립트인가    shebang(#!/bin/bash)    실행하는 두 가지 길
  [데이터]  변수에 값 담기    따옴표의 함정    환경변수와 PATH
  [입력]    스크립트에 값 건네기 (위치 인자 $1 · read)

지금까지 모은 재료들을 드디어 하나의 요리로 묶는 시간입니다. 편하게 따라오세요.

💡 오늘 수업의 핵심 — "명령을 파일에 적어 한 번에 실행하는 게 스크립트다"

셸 명령을 파일에 차례로 적어두면, 셸이 위에서 아래로 한 줄씩 읽어 실행해줍니다. 그 파일이 스크립트예요. 오늘은 #!/bin/bash로 시작하는 첫 스크립트를 만들어 실행하고, 변수에 값을 담아 꺼내 쓰고, 바깥에서 값을 건네받는 것 — 여기까지가 목표입니다.

🎯 학습 목표

  • 셸 스크립트가 무엇인지 이해하고, shebang(#!/bin/bash)으로 첫 스크립트를 만들어 실행한다
  • 변수에 값을 담고 꺼내며, 따옴표의 함정을 피하고, 환경변수와 PATH·.bashrc의 관계를 이해한다
  • 위치 인자($1)와 read로 스크립트에 바깥 값을 건네는 법을 익힌다

Step 1: "왜 스크립트인가 — 명령을 파일에 적는다"

지난 시간(A-4)에 긴 파이프라인을 짧은 별명(alias)으로 줄였던 것 기억나세요? alias는 한 줄짜리 명령에 별명을 붙이는 거였죠. 그런데 여러 줄의 명령을, 정해진 순서대로, 매번 똑같이 실행하고 싶다면? 그땐 별명으로는 부족해요. 명령들을 파일 하나에 적어두면 됩니다. 그게 바로 셸 스크립트예요.

스크립트는 거창한 게 아니에요. 평소 터미널에 치던 명령을 위에서 아래로 적어둔 텍스트 파일입니다. 그 파일을 실행하면, 셸이 첫 줄부터 마지막 줄까지 한 줄씩 읽어서, 마치 여러분이 손으로 친 것처럼 차례로 실행해줍니다.

텍스트
  파일 hello.sh 안에 명령을 차례로 적어둔다
  ┌──────────────────────────────
  │ #!/bin/bash            1번 줄: 이 파일을 bash로 실행하라는 표시(셔뱅)
  │ echo "안녕하세요..."    2번 줄: 화면에 글자를 출력하는 명령
  └──────────────────────────────
   실행하면  셸이 위에서 아래로 한 줄씩 읽어 그대로 실행

연습 디렉토리에서 첫 스크립트를 만들어볼게요. 파일 이름은 hello.sh로 하겠습니다. 스크립트 파일은 보통 .sh 확장자를 붙여요(셸 스크립트라는 뜻이죠). 내용은 딱 두 줄입니다.

Bash
#!/bin/bash
echo "안녕하세요, 첫 셸 스크립트입니다!"

두 번째 줄은 익숙하죠. 지금까지 써온 echo(화면에 글자를 출력하는 명령)예요. 핵심은 첫 줄입니다. #!/bin/bash — 이걸 셔뱅(shebang)이라고 불러요. "이 파일을 실행할 때는 /bin/bash라는 프로그램한테 맡겨라"라고 맨 윗줄에 적어두는 표시입니다.

왜 이게 필요할까요? 같은 텍스트 파일이라도 안에 담긴 게 bash 명령일 수도, 파이썬 코드일 수도 있잖아요. 셔뱅은 "이 파일은 bash로 읽어달라"고 운영체제에 알려주는 이름표예요. 그래서 스크립트의 첫 줄은 거의 항상 셔뱅으로 시작합니다.

이제 실행해봅시다. 가장 단순한 방법은 bash라는 명령에 파일 이름을 건네는 거예요.

Bash
bash hello.sh

그러면 이렇게 출력됩니다.

텍스트
안녕하세요, 첫 셸 스크립트입니다!

방금 여러분은 첫 스크립트를 실행한 거예요. bash hello.sh는 "bash야, 이 파일을 읽어서 실행해줘"라는 뜻이라, 파일에 실행 권한이 없어도 곧바로 돌아갑니다. 현업에서도 스크립트를 빠르게 한 번 돌려볼 땐 이렇게 bash 파일명으로 자주 실행해요.

💡 한 줄 정리

스크립트는 셸 명령을 위에서 아래로 적어둔 텍스트 파일이고, 첫 줄의 셔뱅(#!/bin/bash)이 "이 파일을 bash로 실행하라"고 알려준다. bash 파일명으로 곧바로 실행할 수 있다.

🙋 학생 질문 — "튜터님, #!로 시작하면 그냥 주석 아니에요? #은 주석 기호라고 배웠는데요."

좋은 관찰이에요. 맞아요, bash에서 #으로 시작하는 줄은 주석(설명을 적어두는, 실행되지 않는 줄)이에요. 그래서 셔뱅도 bash 입장에서는 그냥 주석이라 무시됩니다.

그런데 셔뱅이 특별한 건 bash가 아니라 운영체제(커널)가 읽기 때문이에요. 여러분이 ./hello.sh처럼 파일을 직접 실행하면, 커널이 파일의 맨 첫 두 글자를 살핍니다. 그 두 글자가 #!이면 "아, 뒤에 적힌 프로그램으로 이 파일을 실행하라는 뜻이구나" 하고 /bin/bash를 불러서 파일을 넘겨줘요.

정리하면 #!/bin/bash는 두 얼굴을 가져요. 커널에게는 "이 파일을 bash로 실행하라"는 특별한 지시, bash에게는 그냥 무시해도 되는 주석. 그래서 충돌 없이 맨 윗줄에 얌전히 들어앉아 있는 거예요. (다음 Step에서 ./hello.sh로 직접 실행해보면 이 동작이 눈에 보입니다.)


Step 2: "실행하는 두 가지 길 — 실행 권한과 ./"

방금은 bash hello.sh로 실행했어요. 그런데 현업에서 스크립트는 보통 ./hello.sh처럼 파일을 직접 가리켜 실행합니다. 이 두 방법이 어떻게 다른지, 그리고 왜 ./를 붙이는지 짚고 갈게요. 여기서 지난 시간(A-3)에 배운 권한이 다시 등장합니다.

먼저 그냥 파일을 직접 실행해볼게요.

Bash
./hello.sh

그러면 반갑지 않은 메시지가 나옵니다.

텍스트
bash: ./hello.sh: Permission denied

또 그 권한 거부 메시지죠. 이번엔 당황하지 않아요. A-3에서 배운 대로 ls -l로 권한 10글자를 읽어봅시다.

Bash
ls -l hello.sh
텍스트
-rw-r--r-- 1 alice alice 67 ... hello.sh

rw-r--r--, 어디에도 실행을 뜻하는 x가 없네요. "아, 실행 권한이 꺼져 있어서 막혔구나"가 바로 보입니다. 파일을 직접 실행하려면 그 파일에 실행 권한이 있어야 하거든요. 권한을 켜주면 되겠죠. A-3에서 배운 chmod를 씁니다.

Bash
chmod +x hello.sh

다시 권한을 확인해볼게요.

Bash
ls -l hello.sh
텍스트
-rwxr-xr-x 1 alice alice 67 ... hello.sh

이제 x가 켜졌어요(rwxr-xr-x). 다시 직접 실행해봅시다.

Bash
./hello.sh
텍스트
안녕하세요, 첫 셸 스크립트입니다!

이번엔 잘 돌아갑니다. 현업에서 스크립트를 새로 짜서 서버에 올리면 거의 항상 chmod +x 스크립트.sh부터 친다고 A-3에서 얘기했었죠. 바로 이 자동 실행 파일을 두고 한 말이었어요.

그런데 한 가지 의문이 남아요. 왜 hello.sh라고만 치면 안 되고 앞에 ./를 붙여야 할까요? 한번 ./ 없이 쳐보면 이렇게 나옵니다.

Bash
hello.sh
텍스트
bash: hello.sh: command not found

"명령을 못 찾겠다"는 거예요. lschmod 같은 명령은 그냥 이름만 쳐도 실행되는데 말이죠. 이유는 이래요. 셸은 명령 이름만 받으면 정해진 폴더 목록만 뒤져서 그 명령을 찾습니다. 이 목록을 PATH라고 불러요(Step 4에서 제대로 파헤칩니다). 그런데 우리가 방금 만든 hello.sh는 그 목록에 없는, 지금 이 폴더에 있는 파일이거든요. 그래서 "그런 명령 없는데?" 하고 못 찾는 거예요.

./는 "다른 데 찾지 말고, 바로 지금 이 폴더(.)에 있는 그 파일"이라고 콕 집어주는 표시입니다. .은 A-1에서 배운 "현재 디렉토리"를 가리키는 기호였죠. 그래서 ./hello.sh는 "지금 여기 있는 hello.sh를 실행해줘"라는 뜻이 됩니다.

텍스트
  hello.sh          PATH 목록의 폴더들만 뒤짐    없음    command not found
  ./hello.sh        "지금 이 폴더"를 콕 집음      찾음    실행 (단 x 권한 필요)
  bash hello.sh     bash에게 직접 넘김            찾음    실행 (x 권한 불필요)

💡 한 줄 정리

./hello.sh로 직접 실행하려면 chmod +x로 실행 권한을 켜야 한다. ./를 붙이는 이유는 셸이 명령을 PATH 목록에서만 찾기 때문에, "지금 이 폴더에 있는 파일"이라고 콕 집어주기 위해서다.

🙋 학생 질문 — "튜터님, 그럼 bash hello.sh./hello.sh 중 뭘 써야 하나요?"

둘 다 같은 스크립트를 실행하지만 결이 조금 달라요.

bash hello.sh는 "bash야, 이 파일 읽어서 실행해"라고 인터프리터를 직접 지정하는 방식이에요. 파일에 실행 권한이 없어도 돌아가고, 첫 줄의 셔뱅도 무시돼요(이미 bash로 실행하라고 못 박았으니까요). 그냥 한 번 빠르게 돌려볼 때 편합니다.

./hello.sh는 "이 파일을 알아서 실행해"라고 파일에 맡기는 방식이에요. 이때는 실행 권한(x)이 꼭 있어야 하고, 첫 줄의 셔뱅을 보고 "아 bash로 실행하라는 거구나" 하고 인터프리터를 정합니다. 그래서 셔뱅이 진짜로 일을 하는 건 이 방식일 때예요.

실무에서는 한 번 만들어 계속 쓰는 스크립트라면 chmod +x 한 번 해두고 ./로 실행하는 쪽이 자연스러워요. 매번 bash를 앞에 붙이지 않아도 되고, 셔뱅 덕분에 "이 파일은 bash 스크립트"라는 게 파일 안에 적혀 있으니까요. 처음 짠 스크립트를 빠르게 테스트할 땐 bash 파일명, 완성해서 계속 쓸 땐 chmod +x./ — 이렇게 기억하면 편합니다.


Step 3: "데이터를 담는 그릇 — 변수와 따옴표의 함정"

지금까지 만든 스크립트는 늘 똑같은 인사만 했어요. 그런데 실제 스크립트는 이름, 날짜, 파일 경로처럼 그때그때 다른 값을 다뤄야 하죠. 그 값을 담아두고 꺼내 쓰는 그릇이 변수(variable)예요.

변수를 만드는 법은 간단합니다. 이름과 값을 =로 잇기만 하면 돼요. 꺼낼 때는 이름 앞에 $를 붙입니다.

Bash
name="홍순구"
echo "안녕하세요, $name 님"
텍스트
안녕하세요, 홍순구 님

name이라는 그릇에 "홍순구"를 담았고, $name으로 그 값을 꺼내 문장에 끼워 넣었어요. 그런데 여기서 입문자가 가장 많이 걸려 넘어지는 함정이 둘 있습니다. 하나씩 짚을게요.

첫 번째 함정은 = 양옆의 공백이에요. 보기 좋으라고 name = "홍순구"처럼 띄어 쓰면 안 됩니다. 한번 그렇게 해보면 이렇게 혼나요.

Bash
name = "홍순구"
텍스트
bash: name: command not found

왜 그럴까요? 셸은 공백을 만나면 거기서 단어를 끊어 읽어요. 그래서 name을 변수가 아니라 실행할 명령으로 착각하는 거예요. 변수를 만들 때는 = 양옆에 공백 없이 딱 붙여 씁니다. 사소해 보이지만 신입이 스크립트를 짜며 가장 먼저 부딪히는 벽이에요.

두 번째 함정은 따옴표예요. 큰따옴표와 작은따옴표가 변수를 다르게 대합니다.

Bash
echo "큰따옴표 안의 $name"
echo '작은따옴표 안의 $name'
텍스트
큰따옴표 안의 홍순구
작은따옴표 안의 $name

큰따옴표(")는 안에 든 $name을 값으로 풀어주고, 작은따옴표(')는 글자 그대로 묶어버려요. 정리하면 이래요.

텍스트
  "..."  큰따옴표     변수($name)를 값으로 풀어줌     홍순구
  '...'  작은따옴표    글자 그대로, 변수도 안 풀림     $name

그럼 변수는 어떤 따옴표로 꺼내는 게 좋을까요? 거의 항상 큰따옴표로 감싸는 게 안전합니다. 값에 공백이 들어 있을 때 따옴표로 감싸지 않으면 셸이 그 공백에서 값을 쪼개버려, 파일 경로 같은 걸 다룰 때 사고가 나거든요. "변수를 꺼낼 땐 "$name"처럼 큰따옴표로 감싼다"를 습관으로 들이면 두고두고 편해요.

마지막으로 아주 자주 쓰는 기술 하나. 변수에는 글자뿐 아니라 명령의 실행 결과도 담을 수 있어요. $(명령)으로 감싸면 그 명령을 실행한 결과가 변수로 들어갑니다.

Bash
today=$(date +%Y-%m-%d)
echo "오늘은 $today 입니다"
텍스트
오늘은 2026-06-17 입니다

date 명령의 출력을 today에 담은 거예요. 이걸 명령 치환(command substitution)이라고 불러요. 백업 파일 이름에 날짜를 붙이는(backup-2026-06-17.tar.gz 같은) 자동화에서 정말 단골로 쓰입니다. 다음 시간 실전 스크립트에서 다시 만날 거예요.

💡 한 줄 정리

변수는 이름=값으로 만들고(= 양옆 공백 금지) $이름으로 꺼낸다. 꺼낼 땐 "$이름"처럼 큰따옴표로 감싸는 게 안전하고, $(명령)으로 명령의 실행 결과를 변수에 담을 수 있다.

🙋 학생 질문 — "튜터님, 변수를 그냥 $name으로 쓰면 되지, 왜 굳이 "$name"처럼 감싸요?"

값에 공백이 없을 땐 사실 둘 다 똑같이 동작해요. 그래서 처음엔 따옴표가 괜한 수고처럼 느껴질 수 있어요. 차이가 드러나는 건 값에 공백이 들어갈 때예요.

예를 들어 파일 이름이 내 사진.jpg처럼 공백을 품고 있다고 해봐요. path="내 사진.jpg"로 담아두고 rm $path라고 따옴표 없이 쓰면, 셸은 공백에서 값을 쪼개 rm 내 사진.jpg, 즉 "사진.jpg 두 개를 지워라"로 읽어버려요. 엉뚱한 파일을 건드리거나 에러가 나죠. rm "$path"로 감싸면 통째로 한 파일 이름으로 다뤄집니다.

그래서 실무에서는 "변수를 꺼낼 땐 일단 큰따옴표로 감싼다"를 거의 반사적으로 합니다. 처음엔 의식적으로 챙겨야 하지만, 공백이 든 파일·경로를 다루다 한 번 데어보면 금방 손에 익어요. 안전한 쪽으로 습관을 들이는 게 이득입니다.


Step 4: "어디서나 통하는 메모 — 환경변수와 .bashrc"

방금 만든 변수는 지금 이 터미널에서만 통하는 메모예요. 터미널을 닫으면 사라지고, 스크립트를 실행하면(그 스크립트는 별도의 자식 프로세스로 도니까) 그 안에서는 안 보이기도 합니다. 직접 확인해볼게요.

Bash
myvar="hello"
bash -c 'echo "자식이 본 myvar=[$myvar]"'
텍스트
자식이 본 myvar=[]

bash -c '...'는 새 셸(자식 프로세스)을 하나 띄워 명령을 실행해요. 그 자식 입장에서는 myvar가 비어 있죠. 부모 셸에서 만든 평범한 변수(셸 변수)는 자식에게 물려주지 않거든요. 자식에게도 물려주려면 export로 "이건 환경변수다"라고 선언해야 합니다.

Bash
export myvar
bash -c 'echo "자식이 본 myvar=[$myvar]"'
텍스트
자식이 본 myvar=[hello]

이제 자식도 myvar를 봅니다. 비유하면 셸 변수는 내 책상 위 메모라 나만 보고, 환경변수는 사무실 게시판에 붙인 메모라 들어오는 사람 누구나 봐요. 그래서 어디서나 통해야 하는 값은 export로 환경변수로 만듭니다.

가장 유명한 환경변수가 바로 Step 2에서 만난 PATH예요. "셸이 명령을 찾아다니는 폴더 목록"이라고 했었죠. 한번 들여다봅시다.

Bash
echo "$PATH"
텍스트
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

콜론(:)으로 구분된 폴더들이 보이죠. lschmod./ 없이도 실행되는 건, 그 명령 파일들이 이 목록 안의 폴더(/bin 등)에 있기 때문이에요. 그래서 내가 만든 스크립트도 이 목록에 든 폴더에 두거나, 목록에 내 폴더를 더해주면 ./ 없이 이름만으로 실행할 수 있어요.

Bash
export PATH="$PATH:$HOME/bin"

기존 PATH($PATH)에 내 홈의 bin 폴더를 콜론으로 이어 붙인 거예요. 그런데 이렇게 export로 바꾼 값은 지금 이 터미널에서만 유효해요. 터미널을 닫으면 원래대로 돌아갑니다. A-3에서 umaskalias를 다룰 때 "영구적으로 바꾸려면 설정 파일에 적어야 하는데 그건 나중에"라고 미뤄뒀던 그 이야기를 이제 풀 차례예요.

그 설정 파일이 홈 디렉토리의 .bashrc입니다. 새 터미널을 열 때마다 bash가 자동으로 읽어 실행하는 셸 설정 파일이에요. 여기에 export PATH="$PATH:$HOME/bin"이나 A-4에서 배운 alias toperr='...' 같은 줄을 적어두면, 터미널을 새로 열 때마다 자동으로 적용됩니다. 매번 손으로 다시 치지 않아도 되는 거죠.

텍스트
  새 터미널 열기
       │  bash 가 자동으로 ~/.bashrc 를 읽어 실행
       
  .bashrc 안의 export PATH=... · alias ... 가 적용된 상태로 시작

⚠️ 한 가지 안심 겸 주의를 드릴게요. .bashrc를 잘못 건드리면(특히 PATH를 실수로 덮어쓰면) 명령이 갑자기 안 먹는 일이 생겨요. 신입이 한 번씩 겪는 일이죠. 그래도 무서워할 건 없어요. 고치기 전에 cp ~/.bashrc ~/.bashrc.bak으로 백업해두면, 문제가 생겨도 되돌릴 수 있으니까요. A-6에서 익힌 vim이나 nano로 조심히 열어 한 줄씩 추가하는 게 안전합니다.

💡 한 줄 정리

export로 선언한 변수(환경변수)는 자식 프로세스에도 전달된다. PATH는 셸이 명령을 찾는 폴더 목록이고, export로 바꾼 값은 터미널을 닫으면 사라지므로 영구히 적용하려면 .bashrc에 적는다(고치기 전 백업).

🙋 학생 질문 — "튜터님, 그럼 스크립트 안에서 만든 변수도 export 안 하면 못 쓰나요?"

아니에요, 그건 괜찮아요. 한 스크립트 안에서 만든 변수는 그 스크립트 안에서는 export 없이도 얼마든지 쓸 수 있어요. export가 필요한 건 "다른 프로그램(자식 프로세스)에 그 값을 물려주고 싶을 때"뿐이에요.

예를 들어 스크립트 안에서 name="홍순구"라고 만들고 같은 스크립트 안에서 echo "$name"으로 꺼내는 건 전혀 문제없어요. 그런데 그 스크립트가 또 다른 스크립트를 호출하면서 name 값을 넘겨주고 싶다면, 그때 export name이 필요한 거죠.

정리하면 이래요. 같은 셸(또는 같은 스크립트) 안에서 쓸 거면 평범한 변수로 충분하고, 자식 프로세스에까지 건네야 할 때만 export로 환경변수를 만든다. 대부분의 입문 스크립트는 자기 안에서만 변수를 쓰니 export 없이도 잘 돌아갑니다.


Step 5: "스크립트에 값 건네기 — 위치 인자와 read"

지금까지는 값을 스크립트 안에 직접 적었어요(name="홍순구"). 그런데 실행할 때마다 다른 값을 주고 싶으면 어떻게 할까요? 두 가지 길이 있어요. 실행할 때 인자로 건네거나, 실행 중에 물어봐서 받거나.

먼저 위치 인자예요. ls -l 파일처럼 명령 뒤에 값을 붙여 건네듯, 내 스크립트도 뒤에 붙인 값을 받을 수 있어요. 스크립트 안에서는 $1, $2로 순서대로 꺼냅니다. greet.sh를 이렇게 만들어볼게요.

Bash
#!/bin/bash
echo "스크립트 이름: $0"
echo "첫 번째 인자: $1"
echo "두 번째 인자: $2"
echo "전체 인자: $@"
echo "인자 개수: $#"

실행 권한을 주고(chmod +x greet.sh) 이름 두 개를 붙여 실행하면 이렇게 나와요.

Bash
./greet.sh 홍순구 카리나
텍스트
스크립트 이름: ./greet.sh
첫 번째 인자: 홍순구
두 번째 인자: 카리나
전체 인자: 홍순구 카리나
인자 개수: 2

$1은 첫 번째, $2는 두 번째 인자고, $0은 스크립트 이름 자신, $@는 인자 전체, $#는 인자 개수예요. 정리하면 이렇게 대응됩니다.

텍스트
  ./greet.sh  홍순구  카리나
    $0          $1      $2
  ($@ = 홍순구 카리나 전체  ·  $# = 2 인자 개수)

위치 인자는 실행할 때 값을 미리 다 건네는 방식이라, 사람 손을 안 거치는 자동화에 잘 맞아요. 반대로 실행 중에 사람에게 직접 물어보고 싶을 때는 read를 씁니다.

Bash
#!/bin/bash
read -p "이름을 입력하세요: " username
echo "안녕하세요, $username 님!"

read -p "..."는 안내 문구를 보여주고 사용자가 한 줄 입력할 때까지 기다렸다가, 그 값을 username 변수에 담아요. 실행하면 이런 식으로 대화하듯 동작합니다.

텍스트
이름을 입력하세요: 장원영
안녕하세요, 장원영 님!

위치 인자와 read는 쓰임이 갈려요. 위치 인자는 실행 순간 값을 다 주니 자동화(나중에 배울 예약 실행)에 좋고, read는 실행 중 사람에게 물으니 대화형 도구에 좋아요. 둘 다 "스크립트 바깥의 값을 안으로 들이는" 통로라는 점은 같습니다.

자, 이걸로 오늘의 기본기가 다 모였어요. 값을 담는 그릇(변수)을 만들고, 어디서나 통하게 하고(환경변수), 바깥에서 값을 건네받는 것(인자·read)까지. 다음 시간엔 이 값들을 가지고 판단하고(if) 반복하고(for) 함수로 묶어, 드디어 진짜 자동화 스크립트를 완성합니다.

💡 한 줄 정리

스크립트에 바깥 값을 건네는 두 가지 길이 있다. 위치 인자($1·$2·$@·$#)는 실행할 때 미리 건네 자동화에 좋고, read는 실행 중 사용자에게 물어 대화형에 좋다.

🙋 학생 질문 — "튜터님, 위치 인자랑 read 중에 뭘 써야 하나요?"

"사람이 직접 칠 거냐, 자동으로 돌 거냐"로 가르면 쉬워요.

스크립트를 사람이 매번 다른 값으로 실행할 거라면 위치 인자가 편해요. ./backup.sh /home/alice처럼 한 줄에 값까지 같이 쳐서 끝내니 빠르고, 무엇보다 사람이 지켜보지 않아도 돼요. 그래서 다음 시간에 배울 "정해진 시각에 자동으로 도는 작업"에는 위치 인자(또는 스크립트 안에 적어둔 값)를 씁니다.

반대로 read는 실행 도중에 멈춰 서서 사람의 입력을 기다려요. 그래서 사람이 앞에 앉아 대화하듯 쓰는 도구(예: "정말 삭제할까요? y/n")엔 좋지만, 자동으로 도는 스크립트에 넣으면 아무도 답을 안 해줘서 영영 멈춰 있게 돼요. 자동화 스크립트에 read를 넣으면 안 되는 이유죠.

정리하면 자동화엔 위치 인자, 사람과 대화하는 도구엔 read. 둘을 섞어 "인자가 있으면 그걸 쓰고, 없으면 물어본다"처럼 만들 수도 있는데, 그건 다음 시간 조건문을 배운 뒤에 자연스럽게 할 수 있어요.


마무리

오늘은 이 과목의 마지막 여정, bash 스크립팅의 절반을 걸었어요. 명령을 파일에 적어 실행하는 법부터, 변수에 값을 담고 따옴표의 함정을 피하고, 환경변수로 어디서나 통하게 하고, 바깥에서 값을 건네받는 것까지. 다음 시간에 흐름 제어를 더하면 진짜 자동화가 완성됩니다. 핵심을 셋으로 정리할게요.

오늘 배운 핵심 세 가지

💡 하나 — 스크립트는 셸 명령을 위에서 아래로 적어둔 텍스트 파일이고, 첫 줄의 셔뱅(#!/bin/bash)이 "이 파일을 bash로 실행하라"고 알린다. 빠르게 돌릴 땐 bash 파일명, 계속 쓸 땐 chmod +x./파일명으로 실행한다. ./를 붙이는 건 셸이 PATH 목록에서만 명령을 찾기 때문이다.

💡 — 변수는 이름=값(= 양옆 공백 금지)으로 만들고 "$이름"으로 꺼낸다. 큰따옴표는 변수를 풀어주고 작은따옴표는 글자 그대로 묶으며, $(명령)으로 명령의 결과를 변수에 담는다. export한 환경변수는 자식 프로세스에도 전달되고, 영구 적용은 .bashrc에 적는다.

💡 — 스크립트 바깥의 값은 위치 인자($1·$2·$@·$#)로 실행할 때 건네거나, read로 실행 중에 물어 받는다. 자동화엔 위치 인자, 대화형엔 read다.

다음 시간 예고

다음 시간은 이 과목의 진짜 마지막, bash 스크립팅 ②예요. 오늘 모은 재료(변수·인자)를 가지고 드디어 흐름을 제어합니다. 조건에 따라 갈라지는 if, 여러 갈래를 깔끔히 나누는 case, 같은 일을 반복하는 for·while, 그리고 명령 묶음을 이름 붙여 재사용하는 함수까지요. 마지막엔 지금까지 이 과목에서 배운 모든 명령 — scp·curl·ufw·cron까지 — 을 한 스크립트로 엮어, "매일 밤 로그를 백업하고 디스크를 점검하고 서비스가 살아있는지 확인하는" 실전 자동화 스크립트를 직접 완성할 거예요. 이 과목의 피날레, 다음 시간에 만나요.


과제

오늘 과제는 전부 여러분 자신의 리눅스 환경(WSL2·VM·맥 터미널)에서 직접 스크립트를 만들어 실행해보는 손 실습이에요. 위험한 명령은 없으니 편하게, 안 되면 bash 파일명으로 먼저 돌려보며 천천히 해보세요.

[기초] 첫 스크립트 만들어 실행 권한 주고 직접 실행하기

연습 디렉토리에 myinfo.sh라는 스크립트를 만들어, 셔뱅(#!/bin/bash)으로 시작하고 echo로 자기소개 두세 줄을 출력하게 하세요. 그런 다음 bash myinfo.sh로 한 번 돌려보고, chmod +x로 실행 권한을 준 뒤 ./myinfo.sh로도 실행해보세요. ls -l로 권한이 어떻게 바뀌었는지 직접 확인하면 더 좋아요.

[응용] 인자와 명령 치환으로 인사 스크립트 만들기

hello.sh를 만들어, 위치 인자로 이름을 하나 받아($1) "안녕하세요, ○○님! 오늘은 △△입니다"처럼 출력하게 하세요. 날짜 부분(△△)은 $(date +%Y-%m-%d)로 채웁니다. ./hello.sh 홍순구처럼 실행해 결과를 확인하고, 변수를 꺼낼 때 큰따옴표로 감쌌는지도 점검해보세요.

[심화] 내 스크립트를 ./ 없이 이름만으로 실행되게 만들기

홈 디렉토리에 bin이라는 폴더를 만들고(mkdir ~/bin), 거기에 앞서 만든 스크립트를 옮긴 뒤, PATH에 그 폴더를 추가(export PATH="$PATH:$HOME/bin")해서 ./ 없이 이름만으로 실행되는지 확인해보세요. 그런 다음 이 설정이 새 터미널에서도 유지되려면 어디에 적어야 하는지 생각해보고, .bashrc에 한 줄 추가해보세요(고치기 전 백업은 필수예요).


생각해볼 주제

1. 변수를 꺼낼 때 큰따옴표로 감싸는 습관은 왜 안전할까

name="홍순구"처럼 공백 없는 값일 때는 $name이든 "$name"이든 결과가 같아 보입니다. 그런데도 실무에서는 거의 항상 "$name"으로 감싸라고 합니다. 값에 공백이 들어가는 상황(예: 파일 이름 내 사진.jpg)을 떠올리며, 따옴표 없이 썼을 때 어떤 사고가 날 수 있을지 구체적으로 상상해보세요.

2. 셸 변수와 환경변수의 경계 — 왜 export라는 구분이 존재할까

평범한 셸 변수는 자식 프로세스에 전달되지 않고, export한 환경변수만 전달됩니다. 모든 변수를 자동으로 자식에게 물려주면 편할 텐데, 왜 굳이 이렇게 나눠뒀을까요? 한 터미널에서 수십 개의 변수를 만들며 작업하는 상황을 떠올리며, "전부 물려주면" 어떤 문제가 생길지 생각해보세요.

3. 자동으로 도는 스크립트에 read를 넣으면 어떻게 될까

read는 사람의 입력을 기다립니다. 그런데 다음 시간에 배울 "매일 새벽 3시에 사람 없이 자동으로 도는 스크립트"에 read가 들어 있다면 어떤 일이 벌어질까요? 자동화 스크립트가 바깥에서 값을 받아야 할 때, read 대신 무엇을 써야 하는지와 함께 생각해보세요.

✅ 예시 답안정답 보기

과제와 생각해볼 주제의 예시답안입니다. 스크립트는 글로 보면 간단해 보여도, 직접 파일을 만들어 실행해보고 한 번 막혀봐야 손에 익어요. 출력의 날짜·경로는 여러분 환경에 따라 다른 게 당연하니, 값보다 흐름이 맞는지를 보면 됩니다. 안 되면 bash 파일명으로 먼저 돌려보며 천천히 해보세요.


🎯 [과제 1 예시답안] 첫 스크립트 만들어 실행 권한 주고 직접 실행하기

채점 포인트

확인 항목 무엇을 보면 되는가
셔뱅 파일 첫 줄이 #!/bin/bash인가
출력 echo로 자기소개 두세 줄을 출력했는가
두 방법 실행 bash myinfo.sh./myinfo.sh 둘 다 실행해봤는가
권한 변화 chmod +x 전후 ls -l에서 x가 켜지는 걸 확인했는가

풀이 예시

먼저 myinfo.sh를 이렇게 만듭니다.

Bash
#!/bin/bash
echo "이름: 김민지"
echo "오늘 배운 것: bash 스크립트 만들기"
echo "한마디: 터미널이 점점 편해진다!"

실행 권한 없이 먼저 bash로 돌려보고, 그다음 chmod +x로 권한을 준 뒤 ./로 실행해봅니다.

Bash
$ bash myinfo.sh
이름: 김민지
오늘 배운 것: bash 스크립트 만들기
한마디: 터미널이 점점 편해진다!

$ ./myinfo.sh
bash: ./myinfo.sh: Permission denied

$ chmod +x myinfo.sh
$ ls -l myinfo.sh
-rwxr-xr-x 1 minji minji 95 ... myinfo.sh

$ ./myinfo.sh
이름: 김민지
오늘 배운 것: bash 스크립트 만들기
한마디: 터미널이 점점 편해진다!

bash myinfo.sh는 실행 권한이 없어도 곧바로 돌아가요. 반면 ./myinfo.sh는 처음엔 Permission denied로 막혔다가, chmod +xx를 켜준 뒤에야 실행됩니다. ls -l의 맨 앞 10글자가 rw-r--r--에서 rwxr-xr-x로 바뀐 게 보이죠.

💡 튜터의 한마디: 처음 스크립트를 짜면 거의 누구나 ./파일명에서 Permission denied를 한 번 만나요. 당황하지 말고 "아, chmod +x 안 했구나" 하면 됩니다. 현업에서 스크립트를 서버에 올리면 가장 먼저 치는 게 chmod +x라, 이 한 쌍(chmod +x./실행)은 곧 손에 붙어요.


🎯 [과제 2 예시답안] 인자와 명령 치환으로 인사 스크립트 만들기

채점 포인트

확인 항목 무엇을 보면 되는가
위치 인자 이름을 $1로 받았는가
큰따옴표 변수를 "$name"처럼 큰따옴표로 감쌌는가
명령 치환 날짜를 $(date +%Y-%m-%d)로 채웠는가
실행 확인 ./hello.sh 홍순구로 결과를 확인했는가

풀이 예시

Bash
#!/bin/bash
name="$1"
today=$(date +%Y-%m-%d)
echo "안녕하세요, ${name}님! 오늘은 ${today}입니다."
Bash
$ chmod +x hello.sh
$ ./hello.sh 홍순구
안녕하세요, 홍순구님! 오늘은 2026-06-17입니다.

$1로 명령 뒤에 붙인 이름을 받아 name에 담고, $(date +%Y-%m-%d)로 오늘 날짜를 today에 담았어요. 변수를 꺼낼 때 ${name}처럼 중괄호로 감싼 건 뒤에 이라는 글자가 바로 붙어서예요. "$name님"이라고 쓰면 셸이 name님이라는 변수를 찾으려 하거든요. 그래서 변수 이름의 경계가 헷갈릴 땐 ${name}으로 또렷이 구분해줍니다.

💡 튜터의 한마디: $(date)로 날짜를 끼워 넣는 이 패턴은 실전에서 정말 자주 써요. 백업 파일 이름을 backup-$(date +%Y-%m-%d).tar.gz처럼 지으면, 매일 돌려도 날짜별로 차곡차곡 다른 이름으로 쌓이죠. 다음 시간 실전 스크립트에서 바로 이렇게 씁니다.


🎯 [과제 3 예시답안] 내 스크립트를 ./ 없이 이름만으로 실행되게 만들기

채점 포인트

확인 항목 무엇을 보면 되는가
bin 폴더 mkdir ~/bin으로 폴더를 만들고 스크립트를 옮겼는가
PATH 추가 export PATH="$PATH:$HOME/bin"으로 목록에 더했는가
실행 확인 ./ 없이 이름만으로 실행되는 걸 확인했는가
영속화 이해 새 터미널에서도 유지하려면 .bashrc에 적어야 함을 이해했는가

풀이 예시

Bash
$ mkdir ~/bin
$ mv hello.sh ~/bin/
$ hello.sh 홍순구
bash: hello.sh: command not found

$ export PATH="$PATH:$HOME/bin"
$ hello.sh 홍순구
안녕하세요, 홍순구님! 오늘은 2026-06-17입니다.

~/bin으로 옮긴 직후엔 아직 command not found예요. ~/binPATH 목록에 없으니까요. export PATH="$PATH:$HOME/bin"로 기존 목록($PATH) 뒤에 내 폴더를 이어 붙이고 나면, 이제 ./ 없이 이름만으로 실행됩니다.

그런데 이 설정은 지금 터미널에서만 유효해요. 새 터미널을 열면 사라지죠. 영구히 유지하려면 홈의 .bashrc에 같은 줄을 적어둡니다. 고치기 전 백업은 필수예요.

Bash
$ cp ~/.bashrc ~/.bashrc.bak
$ echo 'export PATH="$PATH:$HOME/bin"' >> ~/.bashrc

>>는 A-4에서 배운 "파일 끝에 이어 붙이는" 리다이렉션이죠. 이제 새 터미널을 열 때마다 bash가 .bashrc를 읽어 이 줄을 자동으로 실행하니, 항상 ~/binPATH에 들어간 상태로 시작합니다.

💡 튜터의 한마디: 내가 자주 쓰는 스크립트를 ~/bin에 모아두고 PATH에 걸어두는 건 현업 개발자들이 거의 다 하는 습관이에요. deploy, backup 같은 이름만 치면 어디서든 내 스크립트가 돌죠. 다만 .bashrcPATH 줄을 실수로 export PATH="$HOME/bin"처럼 기존 $PATH 없이 덮어쓰면 ls조차 안 먹는 사태가 나니, 항상 $PATH:를 앞에 살려두세요. 백업이 있으면 그래도 안심이고요.


🤔 [생각해볼 주제 1] 변수를 꺼낼 때 큰따옴표로 감싸는 습관은 왜 안전할까

문제 상황 요약

name="홍순구"처럼 공백 없는 값일 때는 $name이든 "$name"이든 결과가 같아 보입니다. 그런데도 실무에서는 거의 항상 "$name"으로 감싸라고 합니다. 값에 공백이 들어가면 무슨 일이 벌어지는지가 핵심이에요.

튜터의 가이드 및 해설

셸은 변수를 펼친 뒤, 그 결과에 공백이 있으면 거기서 단어를 쪼개요. 이걸 "단어 분리(word splitting)"라고 해요. 평소엔 고맙게 쓰이는 동작인데, 공백이 든 값을 다룰 때 사고로 이어집니다.

파일 이름이 내 사진.jpg처럼 공백을 품었다고 해봅시다.

Bash
path="내 사진.jpg"
rm $path          # 따옴표 없음 → rm 내 사진.jpg 로 쪼개짐

이러면 셸은 rm에게 사진.jpg라는 두 개의 인자를 건넨 걸로 읽어요. "라는 파일과 사진.jpg라는 파일을 지워라"가 되는 거죠. 정작 지우려던 내 사진.jpg는 그대로 남고, 엉뚱한 이름의 파일을 건드리거나 에러가 납니다. rm "$path"로 감싸면 공백째 한 덩어리로 다뤄져 내 사진.jpg 하나만 정확히 가리켜요.

그래서 실무의 습관은 단순해요. "변수를 꺼낼 땐 일단 큰따옴표". 공백이 없을 땐 손해도 없고, 공백이 있을 땐 사고를 막아주니, 안 감쌀 이유가 없는 거죠. 파일 경로를 다루는 스크립트일수록 이 습관이 안전망이 됩니다.

💡 핵심을 한마디로

따옴표 없는 $var는 값의 공백에서 쪼개진다. "$var"로 감싸면 공백이 든 값도 한 덩어리로 안전하게 다뤄지므로, 변수는 꺼낼 때 항상 큰따옴표로 감싸는 게 안전한 기본값이다.


🤔 [생각해볼 주제 2] 셸 변수와 환경변수의 경계 — 왜 export라는 구분이 존재할까

문제 상황 요약

평범한 셸 변수는 자식 프로세스에 전달되지 않고, export한 환경변수만 전달됩니다. 모든 변수를 자동으로 자식에게 물려주면 편할 텐데, 왜 굳이 나눠뒀을까요?

튜터의 가이드 및 해설

한 터미널에서 작업하다 보면 임시 변수를 정말 많이 만들어요. i, tmp, count, name… 잠깐 쓰고 버리는 것들이죠. 만약 이 모든 변수가 실행하는 모든 자식 프로세스로 자동으로 흘러 들어간다면 어떻게 될까요?

자식 프로그램 입장에서는 자기가 만들지도 않은 수십 개의 변수가 환경에 떠다니는 셈이에요. 어쩌다 내 임시 변수 이름이 그 프로그램이 의미 있게 쓰는 환경변수 이름과 겹치기라도 하면, 프로그램이 엉뚱하게 동작할 수 있어요. 한쪽의 사소한 메모가 다른 쪽의 중요한 설정을 덮어버리는 거죠.

그래서 셸은 기본을 "안 물려준다"로 두고, 정말 자식에게 건네야 하는 값만 export로 명시적으로 골라 내보내게 했어요. 덕분에 내 셸의 임시 변수들은 내 셸 안에만 머물고, 자식에게 전해지는 건 내가 의도한 환경변수뿐입니다. PATH처럼 어디서나 통해야 하는 것만 환경변수로, 나머지는 셸 안에만. 이 경계가 프로세스들이 서로의 변수에 오염되지 않게 지켜줍니다.

💡 핵심을 한마디로

기본을 "전달 안 함"으로 두고 export한 것만 자식에게 넘기는 건, 셸의 임시 변수들이 자식 프로세스의 환경을 오염시키지 않게 하는 격리 장치다. 어디서나 통해야 하는 값만 골라 환경변수로 내보낸다.


🤔 [생각해볼 주제 3] 자동으로 도는 스크립트에 read를 넣으면 어떻게 될까

문제 상황 요약

read는 사람의 입력을 기다립니다. 그런데 "매일 새벽 3시에 사람 없이 자동으로 도는 스크립트"에 read가 들어 있다면 무슨 일이 벌어질까요?

튜터의 가이드 및 해설

read를 만나면 스크립트는 거기서 멈춰 서서 누군가 한 줄 입력해줄 때까지 기다려요. 사람이 앞에 앉아 있을 땐 자연스럽지만, 새벽 3시에 자동으로 도는 스크립트에는 답해줄 사람이 없죠. 그래서 스크립트는 그 자리에서 영영 멈춰 있게 됩니다. 백업도 안 되고, 다음 줄로 넘어가지도 못한 채로요.

이게 자동화 스크립트에 read를 넣으면 안 되는 이유예요. 사람 없이 도는 스크립트가 바깥 값을 받아야 한다면, 답은 read가 아니라 다른 통로입니다. 가장 흔한 건 위치 인자($1)로 실행할 때 값을 미리 다 건네는 거예요. 아니면 환경변수로 미리 깔아두거나, 그것도 아니면 스크립트 안에 값을 직접 적어두죠. 어느 쪽이든 "실행 시점에 값이 이미 정해져 있어서, 도중에 사람을 기다릴 필요가 없다"는 게 공통점이에요.

다음 시간에 배울 예약 실행(정해진 시각에 자동으로 도는 작업)이 바로 이 상황이에요. 그래서 자동화용 스크립트는 위치 인자나 환경변수로 값을 받게 설계하고, read는 사람과 대화하는 도구에만 남겨둡니다.

💡 핵심을 한마디로

read는 입력을 기다리며 멈추므로, 사람이 없는 자동 실행 환경에선 스크립트가 영영 멈춰 선다. 자동화 스크립트는 값을 위치 인자·환경변수·내부 변수로 미리 정해두고, read는 대화형 도구에만 쓴다.

전체 목록 리눅스