Shell 스크립팅 실전 가이드 | Bash, Zsh, PowerShell 비교
이 글의 핵심
Shell 스크립팅 실전 가이드에 대해 정리한 개발 블로그 글입니다. > TL;DR: Bash, Zsh, PowerShell의 차이를 이해하고, Shell 스크립트로 반복 작업을 자동화하는 방법을 배웁니다. 실무에서 바로 쓸 수 있는 패턴과 예제를 제공합니다. 이 글을 읽으면: - ✅… 개념과 예제 코드를 단계적으로 다루며, 실무·학습에 참고할 수 있도록 구성했습니다. 관련 키워드:…
🎯 이 글을 읽으면 (읽는 시간: 25분)
TL;DR: Bash, Zsh, PowerShell의 차이를 이해하고, Shell 스크립트로 반복 작업을 자동화하는 방법을 배웁니다. 실무에서 바로 쓸 수 있는 패턴과 예제를 제공합니다. 이 글을 읽으면:
- ✅ Bash, Zsh, PowerShell 차이점과 선택 기준 이해
- ✅ Shell 스크립트 작성 및 디버깅 능력 습득
- ✅ 배포 자동화, 백업 스크립트 등 실전 패턴 마스터
- ✅ 프로덕션 환경에서 안전한 스크립트 작성법 습득 실무 활용:
- 🔥 CI/CD 파이프라인 자동화
- 🔥 서버 배포 및 관리 스크립트
- 🔥 로그 분석 및 모니터링
- 🔥 백업 및 복구 자동화 난이도: 중급 | 실습 예제: 20개 | 프로덕션 레벨
Shell 스크립트는 반복 작업을 자동화하는 가장 빠른 방법이다. 이 글에서는 Bash, Zsh, PowerShell의 차이점과 실무에서 자주 쓰는 패턴을 다룬다.
사전 지식 (초보자를 위한 기초)
1. Shell이란?
Shell은 사용자와 운영체제를 연결하는 명령어 해석기다. 사용자가 입력한 명령어를 운영체제가 이해할 수 있는 형태로 변환하고, 결과를 다시 사용자에게 보여준다.
예를 들어 ls 명령어를 입력하면, Shell이 이를 해석해서 운영체제에 파일 목록 조회를 요청하고, 결과를 터미널에 출력한다.
GUI vs CLI:
GUI (Graphical User Interface):
- 마우스 클릭
- 아이콘, 창
- 직관적, 느림
CLI (Command Line Interface):
- 키보드 입력
- 텍스트 명령어
- 빠름, 자동화 가능
2. 터미널 vs Shell
터미널 (Terminal)
- Shell을 실행하는 프로그램
- 예: iTerm2, Windows Terminal, GNOME Terminal Shell
- 명령어를 해석하는 프로그램
- 예: Bash, Zsh, PowerShell
┌─────────────────────────────────┐
│ Terminal (창) │
│ ┌───────────────────────────┐ │
│ │ Shell (명령어 해석기) │ │
│ │ $ ls │ │
│ │ file1.txt file2.txt │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
3. 기본 명령어
# 현재 디렉토리 확인
pwd
# /home/user
# 파일 목록
ls
# file1.txt file2.txt folder/
# 디렉토리 이동
cd folder
# 파일 내용 보기
cat file1.txt
# 파일 복사
cp file1.txt file2.txt
# 파일 이동/이름 변경
mv file1.txt renamed.txt
# 파일 삭제
rm file1.txt
# 디렉토리 생성
mkdir new_folder
# 명령어 도움말
man ls
1. Shell이란?
Shell의 역할
1. 명령어 해석
$ echo "Hello"
→ echo 프로그램 실행
2. 파이프라인
$ cat file.txt | grep "error" | wc -l
→ 3개 명령어 연결
3. 리다이렉션
$ ls > output.txt
→ 출력을 파일로 저장
4. 변수 및 제어 구조
$ for i in 1 2 3; do echo $i; done
→ 반복문
5. 스크립트 실행
$ ./script.sh
→ 여러 명령어를 한 번에
Shell의 종류
Unix Shell 계열:
- sh (Bourne Shell, 1979)
- bash (Bourne Again Shell, 1989) ← 가장 보편적
- zsh (Z Shell, 1990) ← 최신 기능
- fish (Friendly Interactive Shell, 2005)
Windows:
- cmd.exe (Command Prompt, 1981)
- PowerShell (2006) ← 현대적, 강력함
기타:
- dash (Debian Almquist Shell) ← 빠름
- ksh (Korn Shell)
- tcsh (TENEX C Shell)
2. Bash 기초
Bash란?
Bash (Bourne Again Shell)는 Linux·macOS에서 흔히 쓰이는 기본 셸이다.
# Bash 버전 확인
bash --version
# GNU bash, version 5.2.15
# 현재 Shell 확인
echo $SHELL
# /bin/bash
기본 문법
변수 - Shell의 핵심 메커니즘:
# 변수 선언 (공백 없이!)
name="Alice"
age=25
# ⚠️ 흔한 실수: 공백 넣기
name = "Alice" # ❌ 에러: 'name' 명령어를 '=' 인자와 함께 실행
# Shell은 공백을 구분자로 사용:
# name = "Alice"
# ↑ ↑ ↑
# 명령어 인자1 인자2
# ✅ 올바른 방법
name="Alice" # 공백 없이
변수 참조의 다양한 방식:
# 기본 참조
echo "Name: $name, Age: $age"
# Name: Alice, Age: 25
# 중괄호 사용 (권장)
echo "Name: ${name}, Age: ${age}"
# 왜 중괄호?
file="report"
echo "$file.txt" # ❌ report.txt (변수: file.txt를 찾음)
echo "${file}.txt" # ✅ report.txt (변수: file)
# 기본값 설정
echo "${undefined:-default}" # undefined가 없으면 "default"
echo "${undefined:=default}" # undefined에 "default" 할당
echo "${undefined:?error}" # undefined가 없으면 에러 발생
# 변수 존재 확인
echo "${name:+exists}" # name이 있으면 "exists", 없으면 빈 문자열
명령어 치환 (Command Substitution):
# 명령어 결과 저장 - 최신 문법
current_date=$(date)
echo "Today: $current_date"
# Today: Fri Apr 17 10:30:00 KST 2026
# 레거시 문법 (백틱)
current_date=`date` # ❌ 가독성 떨어짐, 중첩 어려움
# 명령어 치환의 내부 동작:
# 1. Shell이 $(...) 내부 명령어 실행
# 2. 새로운 서브셸(subshell) 생성
# 3. 명령어 출력을 문자열로 캡처
# 4. 변수에 할당
# 서브셸 예제
count=1
(count=2; echo "Inside subshell: $count") # 2
echo "Outside: $count" # 1 (원본 유지)
# () 안의 변수 변경은 부모 셸에 영향 없음
환경 변수 vs 셸 변수:
# 셸 변수 (현재 셸에만)
local_var="value"
bash -c 'echo $local_var' # 빈 출력 (자식 프로세스는 모름)
# 환경 변수 (자식 프로세스에 전달)
export PATH="/usr/local/bin:$PATH"
bash -c 'echo $PATH' # ✅ 출력됨
# 환경 변수의 내부 동작:
# 1. export로 표시된 변수는 환경 배열에 추가
# 2. fork() 시 자식 프로세스에 복사
# 3. 자식은 부모의 환경 변수를 읽기만 가능 (수정 불가)
# PATH 환경 변수 이해
echo $PATH
# /usr/local/bin:/usr/bin:/bin
# Shell이 명령어를 찾는 순서:
# 1. /usr/local/bin/
# 2. /usr/bin/
# 3. /bin/
# PATH 추가 (앞에)
export PATH="/my/custom/bin:$PATH"
# /my/custom/bin이 우선 검색됨
# PATH 추가 (뒤에)
export PATH="$PATH:/my/custom/bin"
# 기존 경로 우선, /my/custom/bin은 마지막
특수 변수:
# $0: 스크립트 이름
echo $0 # /bin/bash 또는 스크립트 파일명
# $1, $2, ...: 위치 매개변수
# script.sh arg1 arg2
echo $1 # arg1
echo $2 # arg2
# $#: 인자 개수
echo $# # 2
# $@: 모든 인자 (각각 개별 단어)
for arg in "$@"; do
echo $arg
done
# $*: 모든 인자 (하나의 문자열)
echo "$*" # arg1 arg2
# $?: 마지막 명령어의 종료 상태
ls /tmp
echo $? # 0 (성공)
ls /nonexistent
echo $? # 2 (실패)
# $$: 현재 셸의 PID
echo $$ # 12345
# $!: 백그라운드 프로세스의 PID
sleep 100 &
echo $! # 12346
변수 타입과 배열:
# Bash는 기본적으로 문자열
number="123"
echo $number # 123 (문자열)
# 산술 연산 (정수만 가능)
result=$((10 + 20))
echo $result # 30
result=$((10 / 3))
echo $result # 3 (정수 나눗셈)
# 배열
fruits=("apple" "banana" "cherry")
echo ${fruits[0]} # apple
echo ${fruits[@]} # 모든 요소
echo ${#fruits[@]} # 배열 크기: 3
# 배열 순회
for fruit in "${fruits[@]}"; do
echo $fruit
done
# 연관 배열 (Bash 4.0+)
declare -A colors
colors[red]="#FF0000"
colors[green]="#00FF00"
echo ${colors[red]} # #FF0000
조건문:
# if 문
if [ "$age" -gt 18 ]; then
echo "Adult"
else
echo "Minor"
fi
# 파일 존재 확인
if [ -f "file.txt" ]; then
echo "File exists"
fi
# 디렉토리 존재 확인
if [ -d "folder" ]; then
echo "Directory exists"
fi
# 문자열 비교
if [ "$name" = "Alice" ]; then
echo "Hello Alice"
fi
# 숫자 비교
# -eq (equal), -ne (not equal)
# -gt (greater than), -lt (less than)
# -ge (greater or equal), -le (less or equal)
반복문:
# for 문
for i in 1 2 3 4 5; do
echo "Number: $i"
done
# 범위
for i in {1..10}; do
echo $i
done
# 파일 순회
for file in *.txt; do
echo "Processing $file"
cat "$file"
done
# while 문
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
count=$((count + 1))
done
함수:
# 함수 정의
greet() {
local name=$1 # 첫 번째 인자
echo "Hello, $name!"
}
# 함수 호출
greet "Alice"
# Hello, Alice!
# 반환값
add() {
local result=$(($1 + $2))
echo $result
}
sum=$(add 10 20)
echo "Sum: $sum"
# Sum: 30
3. 파이프와 리다이렉션 - Unix 철학의 핵심
파이프 (|) - 프로세스 간 통신의 마법
파이프의 내부 동작:
# 간단한 예제
cat log.txt | grep "error"
# 내부에서 일어나는 일:
# 1. Shell이 파이프(pipe) 생성: pipe()
# - 읽기 파일 디스크립터 (fd[0])
# - 쓰기 파일 디스크립터 (fd[1])
#
# 2. 첫 번째 프로세스 (cat) 생성: fork()
# - stdout(fd 1)을 파이프의 쓰기 끝(fd[1])으로 리다이렉트
# - 파일 읽기 끝(fd[0]) 닫기
#
# 3. 두 번째 프로세스 (grep) 생성: fork()
# - stdin(fd 0)을 파이프의 읽기 끝(fd[0])으로 리다이렉트
# - 파일 쓰기 끝(fd[1]) 닫기
#
# 4. 부모 Shell은 파이프 양쪽 끝 모두 닫기
#
# 5. 데이터 흐름:
# cat → [pipe buffer] → grep
#
# 파이프 버퍼: 커널 메모리 (보통 64KB)
# 버퍼가 가득 차면 cat은 블록됨
# grep이 읽으면 cat 재개
파이프 체인의 효율성:
# 비효율적 (임시 파일 사용)
cat log.txt > tmp1.txt
grep "error" tmp1.txt > tmp2.txt
sort tmp2.txt > tmp3.txt
uniq tmp3.txt > result.txt
rm tmp1.txt tmp2.txt tmp3.txt
# 문제:
# - 디스크 I/O 느림
# - 임시 파일 관리 번거로움
# 효율적 (파이프 사용)
cat log.txt | grep "error" | sort | uniq > result.txt
# 장점:
# - 메모리 버퍼 사용 (빠름)
# - 병렬 실행 (각 프로세스 동시 실행)
# - 첫 번째 출력이 나오면 즉시 다음 명령으로 전달
실전 파이프 패턴:
# 1. 파일에서 "error" 찾기
cat log.txt | grep "error"
# 더 나은 방법:
grep "error" log.txt # cat 불필요 (grep이 직접 파일 읽기)
# 2. 중복 제거 후 정렬
cat names.txt | sort | uniq
# sort는 먼저 정렬해야 uniq가 작동:
# uniq는 연속된 중복만 제거
# 예: "a b a" → "a b a" (중복 제거 안 됨)
# 예: "a a b" → "a b" (중복 제거됨)
# 3. 상위 10개 프로세스 (CPU 사용률)
ps aux | sort -k3 -r | head -10
# ps aux: 모든 프로세스
# sort -k3 -r: 3번째 컬럼(CPU) 기준 역순 정렬
# head -10: 상위 10개만
# 4. 파일 개수 세기
ls | wc -l
# wc -l: line count
# 주의: 파일명에 공백이나 줄바꿈이 있으면 부정확
# 더 나은 방법:
find . -maxdepth 1 -type f | wc -l
# 5. JSON 파싱 (jq 사용)
curl https://api.example.com/users | jq '.[] | .name'
# jq: JSON 처리 전용 도구
# .[]: 배열 각 요소
# .name: name 필드 추출
# 6. 로그 분석 실전
cat access.log |
grep "404" | # 404 에러만
awk '{print $1}' | # IP 주소 추출
sort | # 정렬
uniq -c | # 개수 세기
sort -rn | # 개수 역순 정렬
head -10 # 상위 10개
# 결과: 404 에러를 가장 많이 발생시킨 IP 10개
리다이렉션 - 파일 디스크립터 조작
파일 디스크립터의 이해:
# 표준 파일 디스크립터:
# 0: stdin (표준 입력)
# 1: stdout (표준 출력)
# 2: stderr (표준 에러)
# 프로세스가 시작될 때:
# fd 0 → 키보드 (또는 파이프)
# fd 1 → 터미널 (또는 파이프)
# fd 2 → 터미널
# ls 명령어 예시:
ls /tmp # stdout → 터미널 (파일 목록 출력)
ls /xyz # stderr → 터미널 ("No such file" 에러)
출력 리다이렉션 상세:
# 1. stdout 리다이렉션 (덮어쓰기)
echo "Hello" > output.txt
# 내부 동작:
# - output.txt 파일을 쓰기 모드로 열기
# - fd 1(stdout)을 파일로 리다이렉트
# - echo 실행 → 출력이 파일로 감
# 2. stdout 리다이렉션 (추가)
echo "World" >> output.txt
# >> 는 O_APPEND 플래그로 열기
# 파일 끝에 추가
# 3. stderr 리다이렉션
command 2> error.log
# 2>: fd 2(stderr)를 error.log로 리다이렉트
# stdout은 여전히 터미널로 출력
# 4. stdout과 stderr 모두 리다이렉트
command > output.log 2>&1
# 순서 중요!
# 1) 1> output.log (stdout → output.log)
# 2) 2>&1 (stderr → stdout이 가리키는 곳, 즉 output.log)
# ❌ 잘못된 순서:
command 2>&1 > output.log
# 1) 2>&1 (stderr → 현재 stdout, 즉 터미널)
# 2) > output.log (stdout → output.log)
# 결과: stderr는 터미널, stdout만 파일
# 최신 Bash 문법 (간결):
command &> output.log # stdout + stderr
command &>> output.log # stdout + stderr (추가)
# 5. /dev/null로 버리기 (Black Hole)
command > /dev/null 2>&1
# /dev/null: 특수 파일, 쓰기는 무시, 읽기는 EOF
# 사용 예: 출력을 완전히 무시하고 싶을 때
# 6. 출력 분리
command > stdout.log 2> stderr.log
# stdout과 stderr를 다른 파일로
입력 리다이렉션:
# 1. 파일에서 입력 받기
mysql -u root -p < schema.sql
# < : stdin을 파일로 리다이렉트
# mysql은 키보드 대신 schema.sql에서 SQL 읽기
# 2. Here Document (여러 줄 입력)
cat << EOF > config.txt
server {
listen 80;
server_name example.com;
}
EOF
# 내부 동작:
# 1. Shell이 EOF까지의 모든 줄을 임시 버퍼에 저장
# 2. cat의 stdin을 버퍼로 리다이렉트
# 3. cat이 읽고 stdout으로 출력
# 4. stdout은 config.txt로 리다이렉트
# Here Document 활용:
ssh user@server << 'ENDSSH'
cd /var/www
git pull
sudo systemctl restart nginx
ENDSSH
# 'ENDSSH': 따옴표로 감싸면 변수 치환 안 함
# 3. Here String (한 줄 입력)
grep "pattern" <<< "This is a test string"
# <<<: 문자열을 stdin으로 전달
# 동등한 명령:
echo "This is a test string" | grep "pattern"
고급 리다이렉션 기법:
# 1. 파일 디스크립터 3+ 사용
exec 3> output.txt # fd 3을 파일로
echo "Hello" >&3 # fd 3에 쓰기
exec 3>&- # fd 3 닫기
# 2. 파일 디스크립터 복사
exec 3>&1 # fd 3 = 현재 stdout 복사
exec 1> output.txt # stdout을 파일로 변경
echo "To file" # 파일로 출력
exec 1>&3 # stdout 복원
exec 3>&- # fd 3 닫기
echo "To terminal" # 터미널로 출력
# 3. 양방향 리다이렉션 (프로세스 치환)
diff <(ls dir1) <(ls dir2)
# <(...): 명령어 출력을 임시 파일처럼 사용
# 내부 동작:
# 1. /dev/fd/63 같은 임시 파일 디스크립터 생성
# 2. 백그라운드에서 명령어 실행
# 3. 출력을 파이프로 연결
# 4. 출력 tee (화면 + 파일 동시)
command | tee output.txt
# tee: stdout을 복제
# - 하나는 터미널로 (화면에 출력)
# - 하나는 파일로
command | tee -a output.txt # 추가 모드
리다이렉션 성능 고려사항:
# ❌ 비효율적: 파일을 반복해서 열기
for i in {1..1000}; do
echo "Line $i" >> output.txt # 1000번 파일 열기/닫기
done
# ✅ 효율적: 파일을 한 번만 열기
{
for i in {1..1000}; do
echo "Line $i"
done
} > output.txt # 1번만 파일 열기
# 또는
for i in {1..1000}; do
echo "Line $i"
done > output.txt
4. Zsh와 Oh My Zsh
Zsh란?
Zsh (Z Shell)는 Bash를 호환하면서 확장이 많은 대안 셸이다. (macOS Catalina부터 기본) Bash vs Zsh:
Bash:
- 표준, 호환성 높음
- 기본 기능
Zsh:
- 강력한 자동 완성
- 테마 및 플러그인
- 더 나은 히스토리
- 스펠링 교정
Zsh 설치
# macOS (기본 설치됨)
chsh -s /bin/zsh
# Ubuntu/Debian
sudo apt install zsh
chsh -s $(which zsh)
# 로그아웃 후 재로그인
Oh My Zsh 설치
Oh My Zsh는 Zsh 설정을 묶어 둔 프레임워크다.
# 설치
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# 설치 후 ~/.zshrc 파일 생성됨
Oh My Zsh 테마
# ~/.zshrc 편집
nano ~/.zshrc
# 테마 변경
ZSH_THEME="robbyrussell" # 기본
ZSH_THEME="agnoster" # 인기
ZSH_THEME="powerlevel10k/powerlevel10k" # 최고 인기
# 적용
source ~/.zshrc
Powerlevel10k 설치 (추천):
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
# ~/.zshrc
ZSH_THEME="powerlevel10k/powerlevel10k"
# 설정 마법사
p10k configure
Oh My Zsh 플러그인
# ~/.zshrc
plugins=(
git # Git 단축키
zsh-autosuggestions # 명령어 자동 제안
zsh-syntax-highlighting # 문법 하이라이팅
docker # Docker 자동 완성
kubectl # Kubernetes 자동 완성
npm # npm 자동 완성
)
# zsh-autosuggestions 설치
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
# zsh-syntax-highlighting 설치
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
# 적용
source ~/.zshrc
Zsh 강력한 기능
1) 자동 완성:
# 디렉토리 이동 (Tab으로 자동 완성)
cd /u/l/b # Tab → /usr/local/bin
# 명령어 옵션 자동 완성
git co # Tab → git checkout, git commit, ...
# 파일 이름 자동 완성 (대소문자 무시)
ls doc # Tab → Documents/
2) 히스토리 검색:
# Ctrl+R: 히스토리 검색
# 입력: git
# → 이전에 실행한 git 명령어 표시
# 히스토리 공유 (여러 터미널)
setopt share_history
3) 스펠링 교정:
$ gti status
# zsh: correct 'gti' to 'git' [nyae]? y
# → git status 실행
5. PowerShell 기초
PowerShell이란?
PowerShell은 Windows에서 널리 쓰이는 객체 중심 셸이다. cmd.exe vs PowerShell:
cmd.exe (옛날):
- 텍스트 기반
- 제한적인 기능
- 레거시
PowerShell (현대):
- 객체 기반
- .NET 통합
- 강력한 스크립팅
- 크로스 플랫폼 (Linux/macOS 지원)
PowerShell 설치
# Windows (기본 설치됨)
# PowerShell 7 (최신 버전) 설치
winget install Microsoft.PowerShell
# macOS
brew install powershell/tap/powershell
# Ubuntu
sudo snap install powershell --classic
# 실행
pwsh
PowerShell 기본 명령어
Cmdlet (Command-let):
# 동사-명사 형식
Get-Process # 프로세스 목록
Get-Service # 서비스 목록
Get-ChildItem # 파일 목록 (ls 별칭)
Set-Location # 디렉토리 이동 (cd 별칭)
Copy-Item # 파일 복사 (cp 별칭)
Remove-Item # 파일 삭제 (rm 별칭)
# 별칭 확인
Get-Alias ls
# CommandType Name
# ----------- ----
# Alias ls -> Get-ChildItem
객체 기반 파이프라인:
# Bash (텍스트 기반)
ps aux | grep chrome | awk '{print $2}'
# PowerShell (객체 기반)
Get-Process | Where-Object {$_.Name -like "*chrome*"} | Select-Object Id
# 더 간단하게
Get-Process chrome | Select-Object Id, CPU, WorkingSet
PowerShell 변수
# 변수 선언
$name = "Alice"
$age = 25
# 변수 사용
Write-Host "Name: $name, Age: $age"
# 배열
$fruits = @("Apple", "Banana", "Cherry")
$fruits[0] # Apple
# 해시테이블
$user = @{
Name = "Alice"
Age = 25
Email = "[email protected]"
}
$user.Name # Alice
$user[Age] # 25
6. Shell 스크립트 작성
Bash 스크립트
기본 구조:
#!/bin/bash
# Shebang: 어떤 Shell로 실행할지 지정
# 스크립트 설명
# 작성자: Alice
# 날짜: 2026-03-31
# 에러 발생 시 중단
set -e
# 변수
NAME="World"
# 함수
greet() {
echo "Hello, $1!"
}
# 실행
greet "$NAME"
실행 방법:
# 실행 권한 부여
chmod +x script.sh
# 실행
./script.sh
# 또는
bash script.sh
실전 예제 1: 백업 스크립트
#!/bin/bash
# 설정
SOURCE_DIR="/home/user/documents"
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_$DATE.tar.gz"
# 백업 디렉토리 생성
mkdir -p "$BACKUP_DIR"
# 압축 백업
echo "Starting backup..."
tar -czf "$BACKUP_DIR/$BACKUP_FILE" "$SOURCE_DIR"
# 결과 확인
if [ $? -eq 0 ]; then
echo "Backup successful: $BACKUP_FILE"
# 7일 이상 된 백업 삭제
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "Old backups cleaned"
else
echo "Backup failed!"
exit 1
fi
실전 예제 2: 로그 분석
#!/bin/bash
LOG_FILE="/var/log/nginx/access.log"
echo "=== Nginx 로그 분석 ==="
echo
# 총 요청 수
total_requests=$(wc -l < "$LOG_FILE")
echo "총 요청 수: $total_requests"
# 상위 10개 IP
echo
echo "상위 10개 IP:"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
# 상위 10개 URL
echo
echo "상위 10개 URL:"
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
# HTTP 상태 코드 분포
echo
echo "HTTP 상태 코드:"
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -rn
# 시간대별 요청 수
echo
echo "시간대별 요청 수:"
awk '{print $4}' "$LOG_FILE" | cut -d: -f2 | sort | uniq -c
실전 예제 3: 배포 자동화
#!/bin/bash
set -e # 에러 시 중단
PROJECT_DIR="/var/www/myapp"
BRANCH="main"
echo "🚀 배포 시작..."
# 1. Git Pull
echo "📥 최신 코드 가져오기..."
cd "$PROJECT_DIR"
git fetch origin
git reset --hard origin/$BRANCH
# 2. 의존성 설치
echo "📦 의존성 설치..."
npm ci --production
# 3. 빌드
echo "🔨 빌드..."
npm run build
# 4. 서비스 재시작
echo "🔄 서비스 재시작..."
pm2 restart myapp
# 5. 헬스 체크
echo "🏥 헬스 체크..."
sleep 5
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health)
if [ "$response" = "200" ]; then
echo "✅ 배포 성공!"
else
echo "❌ 배포 실패! (HTTP $response)"
# 롤백
git reset --hard HEAD~1
npm ci --production
npm run build
pm2 restart myapp
exit 1
fi
7. PowerShell 스크립트
기본 문법
# 변수
$name = "Alice"
$age = 25
# 조건문
if ($age -gt 18) {
Write-Host "Adult"
} else {
Write-Host "Minor"
}
# 반복문
foreach ($i in 1..5) {
Write-Host "Number: $i"
}
# 파일 순회
Get-ChildItem *.txt | ForEach-Object {
Write-Host "Processing $($_.Name)"
Get-Content $_.FullName
}
# 함수
function Greet {
param($Name)
Write-Host "Hello, $Name!"
}
Greet -Name "Alice"
실전 예제 1: 파일 정리
#!/usr/bin/env pwsh
# 30일 이상 된 로그 파일 삭제
$logDir = "C:\Logs"
$daysOld = 30
Get-ChildItem -Path $logDir -Filter "*.log" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$daysOld) } |
ForEach-Object {
Write-Host "Deleting: $($_.Name)"
Remove-Item $_.FullName -Force
}
Write-Host "Cleanup complete!"
실전 예제 2: 시스템 모니터링
#!/usr/bin/env pwsh
# CPU 사용률
$cpu = Get-Counter '\Processor(_Total)\% Processor Time' |
Select-Object -ExpandProperty CounterSamples |
Select-Object -ExpandProperty CookedValue
Write-Host "CPU Usage: $([math]::Round($cpu, 2))%"
# 메모리 사용률
$os = Get-CimInstance Win32_OperatingSystem
$totalMemory = $os.TotalVisibleMemorySize / 1MB
$freeMemory = $os.FreePhysicalMemory / 1MB
$usedMemory = $totalMemory - $freeMemory
$memoryPercent = ($usedMemory / $totalMemory) * 100
Write-Host "Memory Usage: $([math]::Round($memoryPercent, 2))% ($([math]::Round($usedMemory, 2))GB / $([math]::Round($totalMemory, 2))GB)"
# 디스크 사용률
Get-PSDrive -PSProvider FileSystem |
Where-Object { $_.Used -ne $null } |
ForEach-Object {
$usedGB = $_.Used / 1GB
$freeGB = $_.Free / 1GB
$totalGB = $usedGB + $freeGB
$percent = ($usedGB / $totalGB) * 100
Write-Host "$($_.Name): $([math]::Round($percent, 2))% ($([math]::Round($usedGB, 2))GB / $([math]::Round($totalGB, 2))GB)"
}
# 상위 10개 프로세스 (메모리)
Get-Process |
Sort-Object WorkingSet -Descending |
Select-Object -First 10 Name, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSet / 1MB, 2)}} |
Format-Table -AutoSize
8. 실전 자동화 예제
예제 1: Git 자동화
Bash:
#!/bin/bash
# Git 저장소 일괄 업데이트
REPOS_DIR="$HOME/projects"
for repo in "$REPOS_DIR"/*; do
if [ -d "$repo/.git" ]; then
echo "Updating $(basename $repo)..."
cd "$repo"
# 현재 브랜치 확인
branch=$(git branch --show-current)
# Pull
git pull origin "$branch"
# 변경사항 확인
if [ -n "$(git status --porcelain)" ]; then
echo " ⚠️ Uncommitted changes"
else
echo " ✅ Up to date"
fi
echo
fi
done
예제 2: 서버 헬스 체크
#!/bin/bash
# 서버 목록
SERVERS=(
"https://api.example.com/health"
"https://web.example.com/health"
"https://admin.example.com/health"
)
echo "=== Server Health Check ==="
echo "Time: $(date)"
echo
for server in "${SERVERS[@]}"; do
response=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$server")
if [ "$response" = "200" ]; then
echo "✅ $server - OK"
else
echo "❌ $server - FAILED (HTTP $response)"
# Slack 알림 (선택)
# curl -X POST https://hooks.slack.com/....\
# -d "{\"text\":\"Server down: $server\"}"
fi
done
예제 3: 데이터베이스 백업
#!/bin/bash
# 설정
DB_HOST="localhost"
DB_USER="admin"
DB_NAME="mydb"
BACKUP_DIR="/backup/db"
DATE=$(date +%Y%m%d_%H%M%S)
# 백업 디렉토리 생성
mkdir -p "$BACKUP_DIR"
# PostgreSQL 백업
echo "Starting database backup..."
pg_dump -h "$DB_HOST" -U "$DB_USER" "$DB_NAME" | gzip > "$BACKUP_DIR/backup_$DATE.sql.gz"
# 결과 확인
if [ $? -eq 0 ]; then
echo "✅ Backup successful: backup_$DATE.sql.gz"
# 파일 크기 확인
size=$(du -h "$BACKUP_DIR/backup_$DATE.sql.gz" | cut -f1)
echo "Size: $size"
# 30일 이상 된 백업 삭제
find "$BACKUP_DIR" -name "backup_*.sql.gz" -mtime +30 -delete
# S3 업로드 (선택)
# aws s3 cp "$BACKUP_DIR/backup_$DATE.sql.gz" s3://my-bucket/backups/
else
echo "❌ Backup failed!"
exit 1
fi
9. 유용한 명령어 모음
파일 및 디렉토리
# 파일 찾기
find . -name "*.log"
find . -type f -mtime +7 # 7일 이상 된 파일
# 파일 내용 검색
grep -r "error" ./logs
grep -i "warning" file.txt # 대소문자 무시
# 디스크 사용량
df -h
du -sh *
# 파일 권한 변경
chmod 755 script.sh
chmod +x script.sh
# 소유자 변경
chown user:group file.txt
프로세스 관리
프로세스 실행의 내부 메커니즘:
Shell이 명령어를 실행하는 과정:
1. 명령어 입력:
$ ls -la /home
2. Shell 파싱:
- 명령어: ls
- 인자: ["-la", "/home"]
- 리다이렉션: 없음
- 파이프: 없음
3. fork() 시스템 콜:
Shell 프로세스 복제
Parent (Shell): Child (복제본):
PID: 1234 PID: 5678
계속 대기 다음 단계 진행
메모리 구조 (Copy-on-Write):
┌─────────────────┐ ┌─────────────────┐
│ Shell Memory │ │ Child Memory │
│ (공유, 읽기만) │ ←→ │ (필요시 복사) │
└─────────────────┘ └─────────────────┘
4. execve() 시스템 콜 (Child에서):
Child 프로세스를 ls 프로그램으로 교체
Before: After:
Child (bash 복제) → Child (ls 프로그램)
PID: 5678 PID: 5678 (유지)
메모리: bash 코드 메모리: ls 코드
execve("/bin/ls", ["-la", "/home"], environ)
5. ls 프로그램 실행:
- 시스템 콜: openat("/home"), getdents64(), ...
- 파일 목록 읽기
- 포맷팅 (컬러, 권한 등)
- stdout에 출력
6. exit() 시스템 콜:
ls 프로세스 종료
Exit Code: 0 (성공)
7. wait() 시스템 콜 (Parent에서):
Shell이 Child 종료 대기
Exit Code 수신
$? = 0 # 마지막 명령어 종료 코드
프로세스 계층:
systemd (PID 1)
└─ bash (PID 1234) ← Shell
└─ ls (PID 5678) ← 명령어
(완료 후 사라짐)
백그라운드 실행의 내부 동작:
Foreground (기본):
$ sleep 10
Shell: wait(child_pid) ← 10초 동안 대기
User: (입력 불가)
Background (&):
$ sleep 10 &
[1] 5678
Shell:
- Child PID 기록 (Job Table)
- wait() 호출 안 함
- 즉시 프롬프트 반환
User: (다른 명령어 입력 가능)
Job Table:
Job ID PID Status Command
[1] 5678 Running sleep 10
SIGCHLD 시그널:
Child 종료 시 Parent에게 알림
Shell이 SIGCHLD 받으면:
- Job Table 업데이트
- "[1]+ Done sleep 10" 출력
Signal (시그널) 메커니즘:
주요 시그널:
SIGTERM (15):
정상 종료 요청
kill <PID> (기본)
프로그램 동작:
1. SIGTERM 수신
2. 정리 작업:
- 열린 파일 닫기
- 네트워크 연결 종료
- 임시 파일 삭제
3. exit(0)
SIGKILL (9):
강제 종료 (막을 수 없음)
kill -9 <PID>
커널이 즉시 프로세스 종료
정리 작업 없음
→ 데이터 손실 위험
SIGINT (2):
Ctrl+C
인터럽트 요청
프로그램이 핸들러 등록 가능:
trap 'cleanup; exit' INT
SIGHUP (1):
터미널 연결 끊김
SSH 세션 종료 시
nohup으로 무시:
nohup ./task.sh &
→ SIGHUP 무시, 계속 실행
SIGSTOP (19), SIGCONT (18):
일시정지 / 재개
Ctrl+Z: SIGSTOP
bg / fg: SIGCONT
Signal 전달 과정:
User: kill -TERM 5678
↓
Kernel: 시그널 큐에 SIGTERM 추가
↓
Scheduler: 프로세스 5678 실행 시
↓
Process 5678: signal_handler() 실행
↓
Handler: cleanup() → exit(0)
프로세스 명령어:
# 프로세스 목록 (상세)
ps aux
# a: 모든 사용자
# u: 사용자 이름 표시
# x: 터미널 없는 프로세스도 표시
출력:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 169564 13140 ? Ss Apr01 0:05 /sbin/init
john 5678 2.5 1.2 450120 98304 pts/0 S+ 10:30 0:03 node server.js
컬럼 의미:
- PID: Process ID
- %CPU: CPU 사용률
- %MEM: 메모리 사용률
- VSZ: 가상 메모리 크기 (KB)
- RSS: 실제 메모리 크기 (KB)
- TTY: 연결된 터미널 (?: 없음)
- STAT: 상태
* S: Sleeping (대기 중)
* R: Running (실행 중)
* Z: Zombie (종료됨, Parent가 wait() 안 함)
* T: sTopped (일시정지)
* +: Foreground
* <: 높은 우선순위
# 프로세스 트리
pstree -p
systemd(1)─┬─sshd(1234)───sshd(5678)───bash(5679)───node(5680)
├─nginx(2345)─┬─nginx(2346)
│ └─nginx(2347)
└─docker(3456)
# 특정 프로세스 찾기
pgrep -a nginx
2345 nginx: master
2346 nginx: worker
2347 nginx: worker
# 프로세스 종료
kill <PID> # SIGTERM (15) - 정상 종료
kill -9 <PID> # SIGKILL (9) - 강제 종료
kill -STOP <PID> # SIGSTOP (19) - 일시정지
kill -CONT <PID> # SIGCONT (18) - 재개
killall nginx # 이름으로 종료 (모든 nginx)
pkill -f "node.*server" # 패턴 매칭
# 백그라운드 실행
./long_running_task.sh &
[1] 5678 # Job ID와 PID
# nohup (로그아웃 후에도 실행)
nohup ./task.sh > output.log 2>&1 &
# SIGHUP 무시
# stdout/stderr → output.log
# 작업 목록
jobs
[1]+ Running ./task.sh &
[2]- Stopped vim file.txt
# 작업 제어
fg %1 # Job 1을 Foreground로
bg %2 # Job 2를 Background로
kill %1 # Job 1 종료
파이프 (Pipe)의 내부 동작:
명령어: ls -la | grep txt | wc -l
Shell이 실행하는 과정:
1. 파이프 생성 (pipe() 시스템 콜):
Pipe 1: [read_fd1, write_fd1]
Pipe 2: [read_fd2, write_fd2]
커널 메모리에 버퍼 생성 (보통 64KB)
2. 프로세스 생성 (3개):
ls -la:
fork() → Child 1 (PID 5678)
dup2(write_fd1, STDOUT) ← stdout을 파이프로
close(read_fd1, write_fd1)
execve("/bin/ls", ["-la"])
grep txt:
fork() → Child 2 (PID 5679)
dup2(read_fd1, STDIN) ← stdin을 파이프에서
dup2(write_fd2, STDOUT) ← stdout을 파이프로
close(...)
execve("/bin/grep", ["txt"])
wc -l:
fork() → Child 3 (PID 5680)
dup2(read_fd2, STDIN) ← stdin을 파이프에서
close(...)
execve("/usr/bin/wc", ["-l"])
3. 데이터 흐름:
ls (5678):
파일 목록 생성
→ write(STDOUT)
→ Pipe 1 Buffer
grep (5679):
read(STDIN) ← Pipe 1
"txt" 포함 줄만 필터링
→ write(STDOUT)
→ Pipe 2 Buffer
wc (5680):
read(STDIN) ← Pipe 2
줄 수 카운트
→ write(STDOUT) → Terminal
출력: 3
4. 프로세스 종료:
ls → EOF → Pipe 1 close
grep → EOF 감지 → Pipe 2 close
wc → EOF 감지 → 출력 후 종료
5. Shell 복귀:
wait(5678)
wait(5679)
wait(5680)
모두 완료 → 프롬프트 반환
파이프 버퍼:
- 크기: 64KB (Linux), 8KB (macOS)
- 버퍼 가득 차면: write() 블록
- 버퍼 비면: read() 블록
- 비동기 I/O (Producer-Consumer 패턴)
예시:
ls 출력: 100KB
→ 64KB 쓰기 → 블록 (grep 읽을 때까지)
→ grep 64KB 읽기 → ls 계속 쓰기 (36KB)
확인:
ulimit -p # 파이프 버퍼 크기
cat /proc/sys/fs/pipe-max-size # 최대 크기
리다이렉션의 내부 동작:
명령어: echo "Hello" > file.txt
1. Shell이 처리:
open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644)
→ fd = 3
2. 파일 디스크립터 복제:
dup2(3, STDOUT_FILENO) # fd 3 → stdout (fd 1)
프로세스 파일 디스크립터 테이블:
0: STDIN → /dev/pts/0 (terminal)
1: STDOUT → file.txt ← 변경됨!
2: STDERR → /dev/pts/0 (terminal)
3. echo 실행:
write(STDOUT, "Hello\n", 6)
→ file.txt에 쓰임
4. 종료 후 Shell 복구:
dup2(saved_stdout, STDOUT)
리다이렉션 종류:
> : 덮어쓰기 (O_TRUNC)
>> : 추가 (O_APPEND)
< : 입력
2> : stderr 리다이렉션
&> : stdout + stderr
2>&1: stderr → stdout
예시:
command > out.txt 2> err.txt
stdout: out.txt (fd 3)
stderr: err.txt (fd 4)
command &> all.txt
stdout: all.txt (fd 3)
stderr: dup2(3, 2) ← stdout과 같은 곳
command 2>&1 | tee log.txt
stderr → stdout
stdout → 파이프 → tee
tee → 화면 + log.txt
# 프로세스 목록 (상세)
ps aux
ps aux | grep nginx
# 프로세스 종료
kill <PID> # SIGTERM (15)
kill -9 <PID> # SIGKILL (9) - 강제
killall nginx # 이름으로 종료
pkill -f "pattern" # 패턴 매칭
# 백그라운드 실행
./long_running_task.sh &
# nohup (로그아웃 후에도 실행)
nohup ./task.sh > output.log 2>&1 &
# 작업 목록
jobs
# 백그라운드 → 포그라운드
fg %1
네트워크
# 포트 확인
netstat -tulpn
ss -tulpn # 더 빠름
# 특정 포트 사용 프로세스
lsof -i :8080
# HTTP 요청
curl https://api.example.com
curl -X POST -H "Content-Type: application/json" -d '{"key":"value"}' https://api.example.com
# 다운로드
wget https://example.com/file.zip
# DNS 조회
dig example.com
nslookup example.com
# 핑
ping -c 4 google.com
# 경로 추적
traceroute google.com
시스템 정보
# CPU 정보
lscpu
cat /proc/cpuinfo
# 메모리 정보
free -h
cat /proc/meminfo
# 디스크 정보
lsblk
fdisk -l
# OS 정보
uname -a
cat /etc/os-release
# 시스템 부하
uptime
top
htop # 더 보기 좋음
10. 고급 Shell 기법
명령어 치환
# $() 사용 (권장)
current_date=$(date +%Y-%m-%d)
echo "Today: $current_date"
# 백틱 (옛날 방식)
current_date=`date +%Y-%m-%d`
# 중첩 가능
files_count=$(ls $(pwd) | wc -l)
배열
# 배열 선언
fruits=("Apple" "Banana" "Cherry")
# 접근
echo ${fruits[0]} # Apple
echo ${fruits[@]} # 모든 요소
echo ${#fruits[@]} # 배열 크기
# 순회
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
# 추가
fruits+=("Date")
문자열 처리
text="Hello World"
# 길이
echo ${#text} # 11
# 부분 문자열
echo ${text:0:5} # Hello
echo ${text:6} # World
# 치환
echo ${text/World/Bash} # Hello Bash
echo ${text//o/0} # Hell0 W0rld (모든 o)
# 대소문자 변환
echo ${text^^} # HELLO WORLD (대문자)
echo ${text,,} # hello world (소문자)
# 기본값
echo ${name:-"Guest"} # name이 없으면 "Guest"
에러 처리
#!/bin/bash
# 에러 발생 시 중단
set -e
# 에러 발생 시 함수 호출
trap 'echo "Error on line $LINENO"' ERR
# 종료 시 정리
cleanup() {
echo "Cleaning up..."
rm -f /tmp/temp_file
}
trap cleanup EXIT
# 명령어 실행
if ! command -v git &> /dev/null; then
echo "Git is not installed"
exit 1
fi
# 명령어 성공 여부 확인
if git pull; then
echo "Pull successful"
else
echo "Pull failed"
exit 1
fi
11. Shell 비교
문법 비교
변수:
# Bash/Zsh
name="Alice"
echo $name
# PowerShell
$name = "Alice"
Write-Host $name
조건문:
# Bash/Zsh
if [ "$age" -gt 18 ]; then
echo "Adult"
fi
# PowerShell
if ($age -gt 18) {
Write-Host "Adult"
}
반복문:
# Bash/Zsh
for i in {1..5}; do
echo $i
done
# PowerShell
foreach ($i in 1..5) {
Write-Host $i
}
파이프:
# Bash/Zsh (텍스트)
ps aux | grep nginx
# PowerShell (객체)
Get-Process | Where-Object {$_.Name -eq "nginx"}
성능 비교
스크립트 실행 속도 (1000번 반복, 대략적인 예):
dash: 0.5초 (가장 빠른 편)
bash: 1.2초
zsh: 1.5초
PowerShell: 3.0초
대화형 사용 (자동 완성, 플러그인):
zsh, PowerShell이 손이 많이 간다. bash는 중간. dash는 대화형보다 스크립트용에 가깝다.
기능 비교
| 기능 | Bash | Zsh | PowerShell |
|---|---|---|---|
| 자동 완성 | 기본 | 강력 | 강력 |
| 플러그인 | 제한적 | 풍부 | 풍부 |
| 객체 파이프 | 없음 | 없음 | 있음 |
| 크로스 플랫폼 | Linux/Mac | Linux/Mac | All |
| 스크립트 속도 | 빠름 | 보통 | 느림 |
| 학습 곡선 | 낮음 | 중간 | 높음 |
| .NET 통합 | 없음 | 없음 | 있음 |
12. 개발 환경 설정
.bashrc / .zshrc 추천 설정
# ~/.bashrc 또는 ~/.zshrc
# 별칭 (Alias)
alias ll='ls -lah'
alias la='ls -A'
alias l='ls -CF'
alias ..='cd ..'
alias ...='cd ../..'
alias gs='git status'
alias ga='git add'
alias gc='git commit'
alias gp='git push'
alias gpl='git pull'
alias gd='git diff'
alias gl='git log --oneline --graph --decorate'
# 함수
mkcd() {
mkdir -p "$1" && cd "$1"
}
extract() {
if [ -f "$1" ]; then
case "$1" in
*.tar.gz) tar xzf "$1" ;;
*.tar.bz2) tar xjf "$1" ;;
*.zip) unzip "$1" ;;
*.rar) unrar x "$1" ;;
*) echo "Unknown format" ;;
esac
fi
}
# 환경 변수
export EDITOR=vim
export VISUAL=vim
export LANG=en_US.UTF-8
# PATH 추가
export PATH="$HOME/bin:$PATH"
export PATH="/usr/local/bin:$PATH"
# 히스토리 설정
export HISTSIZE=10000
export HISTFILESIZE=20000
export HISTCONTROL=ignoredups:erasedups
# 프롬프트 커스터마이징
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
# 자동 완성
if [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
PowerShell 프로필
# $PROFILE 파일 편집
notepad $PROFILE
# 별칭
Set-Alias ll Get-ChildItem
Set-Alias g git
# 함수
function mkcd {
param($Path)
New-Item -ItemType Directory -Path $Path -Force
Set-Location $Path
}
function gs { git status }
function ga { git add $args }
function gc { git commit -m $args }
function gp { git push }
# 프롬프트 커스터마이징
function prompt {
$location = Get-Location
Write-Host "PS " -NoNewline -ForegroundColor Green
Write-Host "$location" -NoNewline -ForegroundColor Blue
return "> "
}
# 모듈 자동 로드
Import-Module posh-git # Git 통합
Import-Module PSReadLine # 향상된 입력
# PSReadLine 설정
Set-PSReadLineOption -PredictionSource History
Set-PSReadLineOption -PredictionViewStyle ListView
13. 디버깅
Bash 디버깅
# 디버그 모드 실행
bash -x script.sh
# 스크립트 내에서 활성화
set -x # 디버그 시작
# ....코드 ...
set +x # 디버그 종료
# 출력 예시:
# + echo 'Hello'
# Hello
# + name=Alice
# + echo 'Name: Alice'
# Name: Alice
디버깅 옵션:
#!/bin/bash
set -e # 에러 시 즉시 종료
set -u # 미정의 변수 사용 시 에러
set -o pipefail # 파이프라인 에러 감지
set -x # 디버그 모드
# 또는 한 줄로
set -euxo pipefail
PowerShell 디버깅
# 디버그 모드
Set-PSDebug -Trace 1 # 명령어 추적
Set-PSDebug -Trace 2 # 변수 할당도 추적
# 중단점
Set-PSBreakpoint -Script script.ps1 -Line 10
# 단계별 실행
Set-PSDebug -Step
# 디버그 해제
Set-PSDebug -Off
14. 크로스 플랫폼 스크립트
플랫폼 감지
#!/bin/bash
# OS 감지
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
echo "Linux"
package_manager="apt"
elif [[ "$OSTYPE" == "darwin"* ]]; then
echo "macOS"
package_manager="brew"
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
echo "Windows"
package_manager="choco"
fi
# 패키지 설치
case "$package_manager" in
apt)
sudo apt update
sudo apt install -y git
;;
brew)
brew install git
;;
choco)
choco install git -y
;;
esac
PowerShell 크로스 플랫폼
# OS 감지
if ($IsLinux) {
Write-Host "Running on Linux"
$packageManager = "apt"
} elseif ($IsMacOS) {
Write-Host "Running on macOS"
$packageManager = "brew"
} elseif ($IsWindows) {
Write-Host "Running on Windows"
$packageManager = "choco"
}
# 경로 구분자
$separator = [System.IO.Path]::DirectorySeparatorChar
$path = "folder$($separator)file.txt"
15. 보안 모범 사례
안전한 스크립트 작성
#!/bin/bash
# 1. 엄격 모드
set -euo pipefail
# 2. 입력 검증
if [ $# -ne 1 ]; then
echo "Usage: $0 <filename>"
exit 1
fi
filename="$1"
# 3. 경로 검증
if [[ "$filename" != *.txt ]]; then
echo "Only .txt files allowed"
exit 1
fi
# 4. 변수 인용 (공백 처리)
if [ -f "$filename" ]; then # 인용 필수!
cat "$filename"
fi
# 5. 임시 파일 안전하게 생성
temp_file=$(mktemp)
trap "rm -f $temp_file" EXIT
# 6. 비밀번호 입력 (숨김)
read -s -p "Password: " password
echo
비밀 정보 관리
# ❌ 스크립트에 하드코딩
DB_PASSWORD="secret123"
# ✅ 환경 변수
export DB_PASSWORD="secret123"
./script.sh
# ✅ .env 파일
# .env
DB_PASSWORD=secret123
# script.sh
source .env
echo $DB_PASSWORD
# ⚠️ .env를 .gitignore에 추가!
# ✅ 안전한 입력
read -s -p "Database password: " DB_PASSWORD
echo
16. 성능 최적화
빠른 스크립트 작성
# ❌ 느림 (외부 명령어 반복 호출)
for file in *.txt; do
lines=$(wc -l < "$file")
echo "$file: $lines lines"
done
# ✅ 빠름 (한 번에 처리)
wc -l *.txt
# ❌ 느림 (파이프 과다 사용)
cat file.txt | grep "error" | grep "critical" | wc -l
# ✅ 빠름 (grep 한 번에)
grep "error.*critical" file.txt | wc -l
# ✅ 더 빠름 (grep -c)
grep -c "error.*critical" file.txt
병렬 처리
# 순차 처리 (느림)
for file in *.jpg; do
convert "$file" -resize 800x600 "resized_$file"
done
# 병렬 처리 (빠름)
for file in *.jpg; do
convert "$file" -resize 800x600 "resized_$file" &
done
wait # 모든 백그라운드 작업 완료 대기
# GNU parallel 사용 (가장 빠름)
parallel convert {} -resize 800x600 resized_{} ::: *.jpg
17. 실전 도구
fzf (퍼지 파인더)
# 설치
# macOS
brew install fzf
# Ubuntu
sudo apt install fzf
# 사용
# Ctrl+R: 명령어 히스토리 검색
# Ctrl+T: 파일 검색
# Alt+C: 디렉토리 검색
# 스크립트에서 사용
selected_file=$(find . -type f | fzf)
echo "Selected: $selected_file"
ripgrep (빠른 검색)
# 설치
brew install ripgrep # macOS
sudo apt install ripgrep # Ubuntu
# 사용 (grep보다 훨씬 빠름)
rg "error" ./logs
rg -i "warning" # 대소문자 무시
rg -t js "function" # JavaScript 파일만
rg "TODO" --stats # 통계 포함
bat (cat 개선)
# 설치
brew install bat # macOS
sudo apt install bat # Ubuntu
# 사용 (문법 하이라이팅)
bat file.js
bat --style=numbers,changes file.js
exa (ls 개선)
# 설치
brew install exa # macOS
sudo apt install exa # Ubuntu
# 사용
exa -l # 자세히
exa -T # 트리 구조
exa -l --git # Git 상태 포함
18. CI/CD에서 Shell 활용
GitHub Actions
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: |
npm ci
npm run build
- name: Deploy
run: |
#!/bin/bash
set -e
echo "Deploying to production..."
# SSH로 서버 접속 및 배포
ssh user@server << 'EOF'
cd /var/www/app
git pull
npm ci --production
npm run build
pm2 restart app
EOF
echo "Deploy complete!"
GitLab CI
deploy:
stage: deploy
script:
- |
#!/bin/bash
set -euxo pipefail
# Docker 이미지 빌드
docker build -t myapp:$CI_COMMIT_SHA .
# Docker Hub 푸시
docker push myapp:$CI_COMMIT_SHA
# Kubernetes 배포
kubectl set image deployment/myapp \
myapp=myapp:$CI_COMMIT_SHA
only:
- main
19. Shell 스크립트 테스트
ShellCheck (정적 분석)
# 설치
brew install shellcheck # macOS
sudo apt install shellcheck # Ubuntu
# 사용
shellcheck script.sh
# 출력 예시:
# In script.sh line 5:
# if [ $name = "Alice" ]; then
# ^-- SC2086: Double quote to prevent globbing
Bats (테스트 프레임워크)
# 설치
npm install -g bats
# test.bats
#!/usr/bin/env bats
@test "addition" {
result=$(echo $((2 + 2)))
[ "$result" -eq 4 ]
}
@test "file exists" {
touch /tmp/test_file
[ -f /tmp/test_file ]
rm /tmp/test_file
}
# 실행
bats test.bats
20. 실전 팁
1회성 명령어 vs 스크립트
# 1회성: 명령어 직접 실행
docker ps -a | grep Exited | awk '{print $1}' | xargs docker rm
# 반복 사용: 함수로 만들기
# ~/.bashrc
docker-clean() {
docker ps -a | grep Exited | awk '{print $1}' | xargs docker rm
}
# 사용
docker-clean
진행 상황 표시
#!/bin/bash
files=(*.jpg)
total=${#files[@]}
current=0
for file in "${files[@]}"; do
current=$((current + 1))
# 진행률 계산
percent=$((current * 100 / total))
# 진행 바 표시
printf "\rProcessing: [%-50s] %d%%" \
$(printf '#%.0s' $(seq 1 $((percent / 2)))) \
$percent
# 실제 작업
convert "$file" -resize 800x600 "resized_$file"
done
echo
echo "Complete!"
색상 출력
#!/bin/bash
# 색상 코드
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 사용
echo -e "${GREEN}✅ Success${NC}"
echo -e "${RED}❌ Error${NC}"
echo -e "${YELLOW}⚠️ Warning${NC}"
echo -e "${BLUE}ℹ️ Info${NC}"
# 함수로 만들기
success() { echo -e "${GREEN}✅ $1${NC}"; }
error() { echo -e "${RED}❌ $1${NC}"; }
warning() { echo -e "${YELLOW}⚠️ $1${NC}"; }
info() { echo -e "${BLUE}ℹ️ $1${NC}"; }
# 사용
success "Build complete"
error "Build failed"
21. 실전 프로젝트
프로젝트 1: 개발 환경 셋업 스크립트
#!/bin/bash
set -e
echo "🚀 개발 환경 셋업 시작..."
# OS 감지
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
OS="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
OS="macos"
else
echo "Unsupported OS"
exit 1
fi
# 패키지 매니저 업데이트
echo "📦 패키지 매니저 업데이트..."
if [ "$OS" = "linux" ]; then
sudo apt update && sudo apt upgrade -y
elif [ "$OS" = "macos" ]; then
brew update && brew upgrade
fi
# Git 설치
echo "📥 Git 설치..."
if ! command -v git &> /dev/null; then
if [ "$OS" = "linux" ]; then
sudo apt install -y git
elif [ "$OS" = "macos" ]; then
brew install git
fi
fi
# Node.js 설치
echo "📥 Node.js 설치..."
if ! command -v node &> /dev/null; then
if [ "$OS" = "linux" ]; then
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs
elif [ "$OS" = "macos" ]; then
brew install node
fi
fi
# Docker 설치
echo "🐳 Docker 설치..."
if ! command -v docker &> /dev/null; then
if [ "$OS" = "linux" ]; then
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
elif [ "$OS" = "macos" ]; then
echo "Please install Docker Desktop manually"
fi
fi
# VSCode 설치
echo "💻 VSCode 설치..."
if ! command -v code &> /dev/null; then
if [ "$OS" = "linux" ]; then
sudo snap install code --classic
elif [ "$OS" = "macos" ]; then
brew install --cask visual-studio-code
fi
fi
# Git 설정
echo "⚙️ Git 설정..."
read -p "Git username: " git_username
read -p "Git email: " git_email
git config --global user.name "$git_username"
git config --global user.email "$git_email"
git config --global init.defaultBranch main
# SSH 키 생성
if [ ! -f ~/.ssh/id_ed25519 ]; then
echo "🔑 SSH 키 생성..."
ssh-keygen -t ed25519 -C "$git_email" -f ~/.ssh/id_ed25519 -N ""
echo "SSH 공개 키:"
cat ~/.ssh/id_ed25519.pub
fi
echo
echo "✅ 개발 환경 셋업 완료!"
echo
echo "설치된 도구:"
echo "- Git: $(git --version)"
echo "- Node.js: $(node --version)"
echo "- npm: $(npm --version)"
echo "- Docker: $(docker --version 2>/dev/null || echo 'Not installed')"
echo "- VSCode: $(code --version 2>/dev/null | head -1 || echo 'Not installed')"
프로젝트 2: 로그 모니터링
#!/bin/bash
LOG_FILE="/var/log/nginx/access.log"
ALERT_EMAIL="[email protected]"
ERROR_THRESHOLD=100
# 실시간 로그 모니터링
tail -f "$LOG_FILE" | while read line; do
# 에러 감지
if echo "$line" | grep -q "500\|502\|503"; then
echo "⚠️ Error detected: $line"
# 에러 카운트
error_count=$(grep -c "50[0-3]" "$LOG_FILE")
# 임계값 초과 시 알림
if [ "$error_count" -gt "$ERROR_THRESHOLD" ]; then
echo "🚨 Too many errors! Sending alert..."
# 이메일 알림
echo "Error count: $error_count" | mail -s "Server Alert" "$ALERT_EMAIL"
# Slack 알림
curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
-H 'Content-Type: application/json' \
-d "{\"text\":\"🚨 Server errors: $error_count\"}"
fi
fi
done
프로젝트 3: 서버 배포 스크립트
#!/bin/bash
set -euo pipefail
# 색상
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# 함수
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 설정
APP_NAME="myapp"
APP_DIR="/var/www/$APP_NAME"
BRANCH="main"
BACKUP_DIR="/backup/$APP_NAME"
# 백업
backup() {
log_info "Creating backup..."
local backup_file="$BACKUP_DIR/backup_$(date +%Y%m%d_%H%M%S).tar.gz"
mkdir -p "$BACKUP_DIR"
tar -czf "$backup_file" -C "$APP_DIR" .
log_info "Backup created: $backup_file"
}
# 배포
deploy() {
log_info "Starting deployment..."
cd "$APP_DIR"
# 1. 백업
backup
# 2. Git Pull
log_info "Pulling latest code..."
git fetch origin
git reset --hard origin/$BRANCH
# 3. 의존성 설치
log_info "Installing dependencies..."
npm ci --production
# 4. 빌드
log_info "Building..."
npm run build
# 5. 데이터베이스 마이그레이션
log_info "Running migrations..."
npm run migrate
# 6. 서비스 재시작
log_info "Restarting service..."
pm2 restart "$APP_NAME"
# 7. 헬스 체크
log_info "Health check..."
sleep 5
local response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health)
if [ "$response" = "200" ]; then
log_info "✅ Deployment successful!"
else
log_error "❌ Health check failed (HTTP $response)"
rollback
exit 1
fi
}
# 롤백
rollback() {
log_warn "Rolling back..."
# 최신 백업 찾기
local latest_backup=$(ls -t "$BACKUP_DIR"/backup_*.tar.gz | head -1)
if [ -z "$latest_backup" ]; then
log_error "No backup found!"
exit 1
fi
log_info "Restoring from: $latest_backup"
cd "$APP_DIR"
tar -xzf "$latest_backup"
npm ci --production
pm2 restart "$APP_NAME"
log_info "Rollback complete"
}
# 메인
case "${1:-}" in
deploy)
deploy
;;
rollback)
rollback
;;
backup)
backup
;;
*)
echo "Usage: $0 {deploy|rollback|backup}"
exit 1
;;
esac
22. PowerShell 고급 기능
객체 파이프라인
# 프로세스 정보 (객체)
Get-Process |
Where-Object { $_.CPU -gt 10 } |
Sort-Object CPU -Descending |
Select-Object -First 10 Name, CPU, WorkingSet |
Format-Table -AutoSize
# 파일 크기 합계
Get-ChildItem -Recurse |
Measure-Object -Property Length -Sum |
Select-Object @{Name="TotalGB";Expression={[math]::Round($_.Sum / 1GB, 2)}}
# CSV 처리
Import-Csv users.csv |
Where-Object { $_.Age -gt 18 } |
Select-Object Name, Email |
Export-Csv adults.csv -NoTypeInformation
원격 실행
# 원격 서버에서 명령어 실행
Invoke-Command -ComputerName Server01 -ScriptBlock {
Get-Service | Where-Object { $_.Status -eq "Running" }
}
# 여러 서버
$servers = @("Server01", "Server02", "Server03")
Invoke-Command -ComputerName $servers -ScriptBlock {
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5
}
# 자격 증명
$cred = Get-Credential
Invoke-Command -ComputerName Server01 -Credential $cred -ScriptBlock {
Restart-Service IIS
}
모듈 및 패키지
# 모듈 검색
Find-Module -Name "*Azure*"
# 모듈 설치
Install-Module -Name Az -Scope CurrentUser
# 모듈 가져오기
Import-Module Az
# 설치된 모듈 목록
Get-Module -ListAvailable
# 명령어 검색
Get-Command -Module Az
23. 트러블슈팅
Bash 문제 해결
1) “Permission denied”
# 원인: 실행 권한 없음
# 해결:
chmod +x script.sh
2) “command not found”
# 원인: PATH에 없음
# 해결:
export PATH="/usr/local/bin:$PATH"
# 또는 전체 경로 사용
/usr/local/bin/mycommand
3) “No such file or directory”
# 원인: 공백 처리 안됨
# ❌ 잘못된 코드
file=my file.txt
cat $file # cat이 "my"와 "file.txt"를 별도 파일로 인식
# ✅ 올바른 코드
file="my file.txt"
cat "$file" # 인용 필수!
PowerShell 문제 해결
1) “실행 정책” 에러
# 원인: 스크립트 실행 정책
# 해결:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# 확인
Get-ExecutionPolicy
2) “명령어를 찾을 수 없습니다”
# 원인: 모듈 미설치
# 해결:
Install-Module -Name ModuleName
# 또는 PATH 추가
$env:PATH += ";C:\MyTools"
FAQ
Q1. Bash와 Zsh 중 무엇을 쓰나?
서버 스크립트·CI는 Bash가 기본값이 되는 경우가 많다. 로컬 터미널에서 자동 완성·플러그인을 쓰고 싶으면 Zsh로 가는 사람이 많다. macOS는 기본이 Zsh다.
Q2. PowerShell을 Linux에서도 쓰나?
PowerShell Core(pwsh)는 크로스 플랫폼이다. 다만 Linux 서버·문서·예제는 여전히 Bash 쪽이 압도적으로 많다.
Q3. Shell과 Python은 어떻게 나누나?
짧은 파이프라인·배포 스크립트·크론은 셸이 가볍다. 데이터 가공·복잡한 분기·라이브러리가 많으면 Python이 나을 때가 많다.
Q4. 무엇부터 익혀야 하나?
Linux를 쓸 거면 Bash는 피하기 어렵다. 그다음 로컬 환경에 Zsh를 얹거나, Windows 관리면 PowerShell을 병행하면 된다.
요약
핵심 정리
Bash:
- Linux/macOS 기본 Shell
- 스크립트 작성에 최적
- 높은 호환성 Zsh:
- Bash 호환 + 강력한 기능
- 자동 완성, 플러그인
- 대화형 사용에 최적 PowerShell:
- Windows 현대적 Shell
- 객체 기반 파이프라인
- .NET 통합 선택 가이드:
- Linux 서버 → Bash
- 개발 환경 → Zsh
- Windows 관리 → PowerShell
- CI/CD → Bash (호환성)
필수 명령어
# 파일
ls, cd, pwd, cp, mv, rm, mkdir, touch, cat
# 검색
find, grep, awk, sed
# 프로세스
ps, top, kill, jobs, bg, fg
# 네트워크
curl, wget, netstat, ss, ping
# 시스템
df, du, free, uname, uptime
# 압축
tar, gzip, zip, unzip
학습 로드맵
기본 명령·파이프·리다이렉션을 먼저 손에 익히고, 변수·조건·함수로 스크립트를 짜 본다. 그다음 배포·로그·헬스체크처럼 실제로 돌아가는 걸 하나씩 붙이면 된다. fzf·ripgrep 같은 도구는 반복 작업이 느껴질 때 넣어도 늦지 않다.
다음 글 추천
- Linux 명령어 모음
- Shell 스크립트 디버깅 가이드
- CI/CD Shell 스크립트 모음
키워드: Shell, Bash, Zsh, PowerShell, Terminal, Linux, Script, 쉘, 터미널, 자동화, CLI
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「Shell 스크립팅 실전 가이드 | Bash, Zsh, PowerShell 비교」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「Shell 스크립팅 실전 가이드 | Bash, Zsh, PowerShell 비교」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Bun Shell 완벽 가이드 — 크로스 플랫폼 스크립팅
- IO 다중화 select epoll 완벽 가이드 | I/O 멀티플렉싱 실전
- 개발자를 위한 리눅스·맥 명령어 실전 가이드 | 네트워크·파일·프로세스·디버깅
이 글에서 다루는 키워드 (관련 검색어)
Shell, Bash, Zsh, PowerShell, Linux, Terminal, Script, 쉘, 터미널, 자동화 등으로 검색하시면 이 글이 도움이 됩니다.