리눅스 쉘 스크립트
- 리눅스 쉘 스크립트(Shell Script)
- 개발자와 데이터 엔지니어에게 ‘반복 업무로부터의 자유’를 선사하는 가장 강력한 무기
- 단순히 명령어를 나열하는 것을 넘어, 시스템을 자동화하고 복잡한 데이터 파이프라인을 구축함
1. 쉘 스크립트 개요
- 리눅스 쉘(Bash, Zsh 등)에서 실행되도록 작성된 인터프리터 방식의 프로그램
리눅스 커맨드라인에서 하나씩 입력하던 명령어들을 하나의 파일에 모아 순차적으로 실행하게 만든 일종의 ‘명령어 묶음’
- 의의와 목적
- 업무 자동화
- 수백 개의 로그 파일을 분석하거나, 매일 특정 시간에 DB를 백업하는 등 반복적인 작업을 자동화
- 환경 구성의 일관성
- 서버를 새로 세팅할 때 필요한 패키지 설치 및 환경 설정을 스크립트 하나로 완벽하게 복제할 수 있음
- 데이터 파이프라인의 접착제
- 서로 다른 언어(Python, Java 등)나 툴(Spark, Kafka) 사이에서 데이터를 전달하고 흐름을 제어하는 ‘접착제’ 역할 수행
- 업무 자동화
2. 쉘 스크립트 작성 방법과 학습 방향성
- 기본 작성 규칙
- 확장자: 통상적으로
.sh사용 - Shebang: 파일의 최상단에
#!/bin/bash를 작성하여 이 파일을 실행할 인터프리터를 지정 - 실행 권한: 작성 후
chmod +x script.sh명령으로 실행 권한을 부여해야 함
- 확장자: 통상적으로
- 학습 방향성
- 변수와 매개변수: 데이터를 동적으로 처리하는 법을 익힘
- 제어문 (If, For, While): 조건에 따라 동작을 분기하고 반복하는 법을 학습
- Exit Code: 앞선 작업의 성공/실패 여부를 판단하여 안정적인 스크립트를 작성
3. 기초 예제
- 쉘 스크립트는 “내가 터미널에 치던 명령어를 순서대로 기록하고, 상황에 따라 판단(if)을 내리게 하는 것”에서 시작함
- 리눅스 쉘 스크립트 공부를 시작할 때 가장 좋은 방법은 “변수, 입력, 조건문, 출력”이라는 프로그래밍의 4대 요소를 한꺼번에 다루는 실용적인 예제를 만들어보는 것
3.1 Welcome & System Checker” 스크립트
- 실행 시 사용자의 이름을 묻고, 입력된 이름에 따라 인사를 건넨 뒤 현재 서버의 날짜와 디스크 사용량을 알려주는 ‘나만의 시스템 안내 가이드’ 스크립트
- 코드 작성
nano hello.sh또는vi hello.sh명령어로 파일을 열고 아래의 내용을 입력
#//file: "hello.sh" #!/bin/bash # 1. 변수 설정 및 입력받기 echo "안녕하세요! 성함이 어떻게 되시나요?" read user_name # 2. 조건문을 활용한 맞춤형 인사 if [ "$user_name" = "admin" ]; then message="관리자님, 환영합니다. 시스템을 점검하겠습니다." else message="$user_name 님, 반갑습니다! 현재 시스템 정보입니다." fi # 3. 결과 출력 (변수 사용) echo "------------------------------------------" echo "$message" echo "오늘의 날짜: $(date)" echo "현재 접속 계정: $(whoami)" # 4. 시스템 명령어 실행 (디스크 사용량 확인) echo "------------------------------------------" echo "현재 디스크 사용량(핵심 요약):" df -h | grep '^/dev/' echo "------------------------------------------" echo "스크립트 실행이 완료되었습니다." - 💡 상세 설명 (핵심 포인트)
- Shebang (#!/bin/bash)
- 스크립트의 맨 첫 줄에 위치함
- 이 파일이 어떤 해석기(Shell)를 통해 실행될지 지정하는 약속
/bin/bash는 “이 파일은 bash 쉘로 실행해라”라는 의미
- read 명령어와 변수
read user_name: 사용자가 키보드로 입력한 값을user_name이라는 바구니(변수)에 넣기$user_name: 변수에 담긴 내용을 꺼내 쓸 때는 이름 앞에$기호를 붙임
- 조건문 (if [ condition ]; then … fi)
- 리눅스 쉘에서 조건문은
[와]사이에 공백이 반드시 있어야 함 =: 문자열이 같은지 비교fi:if문의 끝을 알리는 표시 (if를 거꾸로 쓴 것)
- 리눅스 쉘에서 조건문은
- 명령어 치환 ($(command))
$(date):- 텍스트 중간에 리눅스 명령어의 실행 결과를 그대로 넣고 싶을 때 사용
date명령어가 실행된 결과값이 그 자리에 쏙 들어감
**파이프라인과 필터 (df -h grep …)** df -h: 디스크 용량을 사람이 보기 편하게(Human-readable) 출력| grep '^/dev/': 전체 출력 결과 중 실제 물리 장치인/dev/로 시작하는 줄만 골라서 보여줌
- Shebang (#!/bin/bash)
- 스크립트 실행 방법
스크립트를 만든 후 바로 실행하면 “Permission denied”라는 에러가 뜸 🡲 실행 권한을 주어야 함
실행 권한 부여:
chmod +x hello.sh스크립트 실행:
./hello.sh
3.2 반복문 (For Loop): 여러 파일 일괄 처리
- 데이터 엔지니어링에서 가장 흔한 작업은 특정 폴더 내의 수많은 파일을 하나씩 순회하며 처리하는 것
for문을 사용해서 특정 디렉토리에 있는 파일 목록을 하나씩 출력해보기- 학습 포인트 🡲 대량의 데이터를 자동 처리하는 ‘루프’의 원리 이해
- 코드 작성:
- 로그 파일 확장자 일괄 변경
#//file: "loop.sh" #!/bin/bash # 현재 디렉토리의 모든 .txt 파일을 찾아서 처리 echo "파일 변환 작업을 시작합니다..." for file in *.txt; do # 파일이 실제로 존재하는지 체크 (파일이 없을 경우 대비) if [ -e "$file" ]; then filename=$(basename "$file" .txt) mv "$file" "${filename}.log" echo "$file -> ${filename}.log 변환 완료" fi done echo "모든 작업이 끝났습니다." - 💡 상세 설명 (핵심 포인트)
for file in *.txt; do:- 현재 디렉토리에서
.txt로 끝나는 모든 파일명을 - 하나씩 꺼내어
file이라는 변수에 담고 반복
- 현재 디렉토리에서
basename "$file" .txt- 파일명에서 경로와 확장자(
.txt)를 떼어내고 - 순수 이름만 추출
- 파일명에서 경로와 확장자(
${filename}.log- 변수명 뒤에 바로 문자를 붙일 때는
{}를 사용하여 변수의 범위를 명확히 지정하는 것이 안전함
- 변수명 뒤에 바로 문자를 붙일 때는
3.3 숫자 계산 (Arithmetic): 산술 연산 처리
- 리눅스 쉘은 기본적으로 모든 것을 ‘문자열’로 취급함 🡲 따라서 숫자를 계산할 때는 특수한 문법이 필요함
- 사용자가 입력한 두 숫자를 더해서 출력해보기 (쉘 스크립트의 산술 연산
(( ... ))공부) - 학습 포인트 🡲 텍스트와 숫자를 구분하여 다루는 ‘데이터 타입’의 개념 익히기
- 코드 작성:
- 간단한 평균 점수 계산기
#//file: "arithmetic.sh" #!/bin/bash echo "수학 점수를 입력하세요:" read math echo "영어 점수를 입력하세요:" read eng # (( )) 내부에서 산술 연산을 수행 (( sum = math + eng )) # 소수점 계산을 위해 bc(Basic Calculator) 도구 사용 avg=$(echo "scale=2; $sum / 2" | bc) echo "--------------------------" echo "총점: $sum" echo "평균: $avg" - 💡 상세 설명 (핵심 포인트)
(( ... )):- 쉘에서 정수형 산술 연산을 수행할 때 사용
- 이 안에서는 변수 앞에
$를 붙이지 않아도 인식이 가능함
bc(Basic Calculator):- 쉘 자체는 소수점 계산에 약함
scale=2는 소수점 둘째 자리까지 표시하라는 설정- 파이프(
|)를 통해 계산식을bc명령어로 전달하여 정밀한 값을 얻음
3.4 에러 처리 (Error Handling): 안정적인 스크립트
- 실무용 스크립트는 예상치 못한 상황(파일 없음, 권한 부족 등)에서 갑자기 멈추지 않고 적절한 안내를 제공해야 함
- 만약 사용자가 이름을 입력하지 않고 엔터를 치면 “이름을 입력해주세요”라고 다시 묻는 로직 추가하기
- 학습 포인트 🡲 어떤 상황에서도 시스템이 안전하게 동작하게 만드는 ‘예외 처리’ 마인드 확립
- 코드 작성:
- 디렉토리 접근 및 파일 확인
#//file: "error_handling.sh" #!/bin/bash TARGET_DIR="./data_folder" # 1. 디렉토리 존재 여부 확인 if [ ! -d "$TARGET_DIR" ]; then echo "에러: $TARGET_DIR 디렉토리가 존재하지 않습니다." >&2 exit 1 fi # 2. 이동 시도 및 성공 여부 확인 cd "$TARGET_DIR" || { echo "에러: 디렉토리로 이동할 수 없습니다."; exit 1; } # 3. 파일 목록 확인 files=$(ls) if [ -z "$files" ]; then echo "주의: 디렉토리가 비어 있습니다." else echo "현재 파일 목록:" echo "$files" fi - 💡 상세 설명 (핵심 포인트)
>&2:- 에러 메시지를 ‘표준 에러(stderr)’ 스트림으로 보냄
- 이는 로그를 남길 때 일반 출력과 에러를 분리하기 위함
exit 1:- 스크립트를 즉시 종료하며 ‘비정상 종료(1)’ 상태 코드를 반환
- 외부 시스템(Cron, Jenkins 등)에서 이 코드를 보고 작업 실패를 감지
||(OR 연산자):- 앞의 명령어(
cd)가 실패했을 때만 뒤의 중괄호({ }) 내용을 실행하라는 의미 - 매우 직관적인 에러 처리 방식
- 앞의 명령어(
-z "$files":- 변수가 비어있는지(Zero length) 확인하는 옵션
4. 핵심 실무 예제 및 상세 설명
- 예제 1: 데이터 백업 및 로그 관리 (데이터 엔지니어 기초)
- 특정 디렉토리의 로그 파일을 압축하여 날짜별로 저장하고, 오래된 파일은 삭제하는 스크립트
#//file: "backup_log.sh" #!/bin/bash # 1. 변수 설정 SOURCE_DIR="/var/log/myapp" BACKUP_DIR="/home/user/backups" DATE=$(date +%Y-%m-%d) BACKUP_FILE="log_backup_$DATE.tar.gz" # 2. 백업 디렉토리가 없으면 생성 if [ ! -d "$BACKUP_DIR" ]; then mkdir -p "$BACKUP_DIR" echo "백업 디렉토리를 생성했습니다: $BACKUP_DIR" fi # 3. 로그 파일 압축 (표준 에러는 에러 로그 파일로 따로 저장) tar -czf "$BACKUP_DIR/$BACKUP_FILE" "$SOURCE_DIR"/*.log 2> "$BACKUP_DIR/error.log" # 4. 결과 확인 (Exit Code 사용) if [ $? -eq 0 ]; then echo "백업 성공: $BACKUP_FILE" else echo "백업 실패! 에러 로그를 확인하세요." exit 1 fi # 5. 7일이 지난 백업 파일은 자동 삭제 (관리 효율성) find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +7 -delete- 💡 상세 설명:
DATE=$(date +%Y-%m-%d): 시스템 날짜를 변수에 할당하여 매일 다른 파일명 생성if [ ! -d ... ]: 디렉토리 존재 여부를 체크하는 조건문$?: 마지막으로 실행된 명령어의 결과 코드. 0은 성공, 그 외는 실패를 의미find ... -delete: 시스템 디스크 용량이 가득 차는 것을 방지하는 실무 필수 로직
- 예제 2: 서비스 상태 모니터링 (개발 및 운영 기초)
- 특정 프로세스가 살아있는지 확인하고, 죽어있다면 자동으로 재시작하는 스크립트
#//file: "monitoring.sh" #!/bin/bash PROCESS_NAME="nginx" # 프로세스 개수를 세어 확인 COUNT=$(ps -ef | grep -v "grep" | grep "$PROCESS_NAME" | wc -l) if [ $COUNT -gt 0 ]; then echo "$(date): $PROCESS_NAME 서비스가 정상 작동 중입니다." else echo "$(date): $PROCESS_NAME 서비스가 중단되었습니다. 재시작을 시도합니다." sudo systemctl start "$PROCESS_NAME" # 재시작 후 결과 알림 (가상 시나리오: 슬랙 전송 등) if [ $? -eq 0 ]; then echo "서비스 재시작 성공" else echo "서비스 재시작 실패! 관리자의 확인이 필요합니다." fi fi- 💡 상세 설명:
ps -ef | grep ...: 실행 중인 프로세스 목록에서 특정 이름 검색grep -v "grep":grep명령어 자체도 프로세스 목록에 나타나기 때문에 이를 결과에서 제외함wc -l: 출력된 결과의 줄 수를 세어 프로세스 존재 여부를 숫자로 반환
- 쉘 스크립트 실력 향상을 위한 조언
- 한 줄씩 실행해보기:
- 스크립트 파일로 만들기 전에 터미널에서 명령어를 직접 입력하며 결과 확인
- 공통 기능은 함수화:
- 반복되는 코드는
function_name() { ... }형태로 묶어 재사용성 향상- 주석의 생활화:
- 쉘 스크립트는 시간이 지나면 본인이 짠 코드도 이해하기 어려울 수 있음
- 각 로직의 이유를 반드시 주석(
#)으로 남길것