소개
셸 스크립트는 명령을 파일에 저장하므로 작업을 일관되게 반복할 수 있습니다. 신뢰할 수 있는 초급 스크립트는 사용할 인터프리터를 명확히 지정하고, 변수 확장을 인용하며, 작업을 수행하기 전에 입력을 검증하고, 다른 명령이 해석할 수 있는 종료 상태를 반환합니다.
이 실습에서는 작은 Bash 스크립트를 단계적으로 작성합니다. Bash 를 통해 스크립트를 실행하고 실행 파일로 직접 실행합니다. 변수와 위치 인수를 사용하고, 대화형 입력을 읽고, if와 파일 테스트로 분기하고, 종료 코드를 선택하고, for를 사용해 반복합니다. 마지막으로 이러한 개념을 간단한 로그 요약 스크립트에 결합합니다.
첫 번째 스크립트 만들기 및 실행
이 단계에서는 Bash 스크립트를 만들고, 인터프리터를 통해 실행한 다음, 파일을 직접 실행할 수 있도록 설정합니다.
실습 작업 디렉터리로 이동합니다.
cd /home/labex/project/shell-lab
here-document 를 사용해 hello.sh를 만듭니다. 첫 번째 줄은 shebang 이며, 파일을 직접 실행할 때 사용할 Bash 를 지정합니다.
cat > hello.sh <<'EOF'
#!/bin/bash
echo "Hello from a Bash script"
pwd
EOF
실행하기 전에 파일 내용을 확인합니다.
cat hello.sh
파일을 Bash 에 명시적으로 전달합니다. 실행 권한을 추가하기 전에도 이 방식으로 실행할 수 있습니다.
bash hello.sh
실행 권한을 추가하고 파일 모드를 확인합니다.
chmod u+x hello.sh
ls -l hello.sh
현재 디렉터리에서 파일을 실행합니다. ./는 경로를 지정합니다. 셸은 일반적으로 현재 디렉터리를 자동으로 검색하지 않습니다.
./hello.sh
변수와 안전한 인용 사용
이 단계에서는 변수를 할당하고 확장합니다. 또한 큰따옴표가 공백을 포함한 값을 하나의 값으로 보호하는 이유를 확인합니다.
두 개의 변수를 사용하는 스크립트를 만듭니다.
cat > variables.sh <<'EOF'
#!/bin/bash
course="Linux for Noobs"
workspace="/home/labex/project/shell-lab"
echo "Course: $course"
echo "Workspace: $workspace"
printf 'Quoted argument count: %s\n' "$#"
printf 'First argument: %s\n' "${1:-not provided}"
EOF
셸에서 변수를 할당할 때 = 양옆에 공백을 넣지 않습니다. 큰따옴표를 사용하면 $course가 확장되면서도 Linux for Noobs가 하나의 값으로 유지됩니다.
공백을 포함한 인수 하나를 전달해 스크립트를 실행합니다.
bash variables.sh "beginner learner"
특수 변수 $#는 위치 인수의 개수이고, $1은 첫 번째 인수입니다. ${1:-not provided}는 첫 번째 인수가 없을 때 대체값을 제공합니다.
확인할 수 있도록 출력을 저장합니다.
bash variables.sh "beginner learner" > variable-output.txt
cat variable-output.txt
위치 인수 검증
이 단계에서는 인수를 필수로 지정하고, 사용법 안내를 출력하며, 실패와 성공에 서로 다른 종료 상태를 반환합니다.
greet-argument.sh를 만듭니다.
cat > greet-argument.sh <<'EOF'
#!/bin/bash
if [ "$#" -ne 1 ]; then
echo "Usage: $0 NAME" >&2
exit 2
fi
name=$1
echo "Hello, $name"
exit 0
EOF
테스트 명령 [ ... ]는 인수 개수가 1 과 같지 않은지 확인합니다. >&2는 사용법 안내를 표준 오류로 보내고, exit 2는 잘못된 사용법을 보고합니다.
인수 없이 실행한 다음, 바로 $?를 출력합니다. $?는 이전 명령의 종료 상태입니다.
bash greet-argument.sh
echo "missing argument status: $?"
예상되는 상태는 2입니다. 이번에는 올바르게 실행합니다.
bash greet-argument.sh "Ada Lovelace"
echo "valid argument status: $?"
성공한 인사말을 저장합니다.
bash greet-argument.sh "Ada Lovelace" > argument-greeting.txt
대화형 입력 읽기
이 단계에서는 사용자에게 입력을 요청하고, 한 줄을 안전하게 읽고, 빈 입력을 처리합니다.
ask-name.sh를 만듭니다.
cat > ask-name.sh <<'EOF'
#!/bin/bash
read -r -p "Enter your name: " name
if [ -z "$name" ]; then
echo "A name is required" >&2
exit 1
fi
echo "Welcome, $name"
EOF
read -r은 백슬래시를 특별하게 처리하지 않고 한 줄을 읽습니다. -p는 프롬프트를 표시하고, -z는 인용된 값이 비어 있는지 확인합니다.
스크립트를 실행합니다.
bash ask-name.sh
프롬프트가 표시되면 다음을 입력합니다.
Grace Hopper
Welcome, Grace Hopper가 표시되어야 합니다. 동일한 스크립트에 입력 한 줄을 파이프해 결과를 재현 가능한 파일로 저장할 수도 있습니다.
printf 'Grace Hopper\n' | bash ask-name.sh > interactive-greeting.txt
cat interactive-greeting.txt
파일 테스트로 분기하기
이 단계에서는 if, elif, else와 파일 테스트를 사용해 경로의 유형을 분류합니다.
classify-path.sh를 만듭니다.
cat > classify-path.sh <<'EOF'
#!/bin/bash
path=${1:-}
if [ -z "$path" ]; then
echo "Usage: $0 PATH" >&2
exit 2
elif [ -d "$path" ]; then
echo "directory: $path"
elif [ -f "$path" ]; then
echo "file: $path"
else
echo "missing: $path" >&2
exit 1
fi
exit 0
EOF
-d는 디렉터리인지 테스트하고, -f는 일반 파일인지 테스트합니다. 처리 순서가 중요합니다. 경로 유형을 확인하기 전에 잘못된 사용법을 먼저 처리합니다.
세 가지 경로 결과를 모두 확인합니다.
bash classify-path.sh logs
bash classify-path.sh notes.txt
bash classify-path.sh missing.txt
echo "missing path status: $?"
성공한 결과를 하나의 파일에 이어서 저장합니다. &&는 첫 번째 명령이 성공한 경우에만 두 번째 명령을 실행합니다.
bash classify-path.sh logs > path-results.txt && bash classify-path.sh notes.txt >> path-results.txt
cat path-results.txt
For 루프로 여러 파일 처리
이 단계에서는 로그 파일을 반복해서 처리하고 각 파일에서 조건에 맞는 레코드 수를 셉니다.
count-errors.sh를 만듭니다.
cat > count-errors.sh <<'EOF'
#!/bin/bash
log_dir=${1:-logs}
if [ ! -d "$log_dir" ]; then
echo "Log directory not found: $log_dir" >&2
exit 1
fi
for log_file in "$log_dir"/*.log; do
error_count=$(grep -c '^ERROR ' "$log_file" || true)
printf '%s errors=%s\n' "$(basename "$log_file")" "$error_count"
done
EOF
반복할 때마다 루프는 일치하는 경로 하나를 log_file에 할당합니다. "$log_file"처럼 인용하면 공백이 포함된 경로를 안전하게 처리할 수 있고, basename은 디렉터리 접두사를 제거합니다. grep -c는 일치하는 줄 수를 셉니다. || true는 일치하는 항목이 없을 때 더 큰 엄격한 작업 흐름이 중단되지 않도록 합니다.
루프를 실행하고 출력을 저장합니다.
bash count-errors.sh logs | tee error-counts.txt
각 .log 파일에 대한 결과가 하나씩 표시되어야 합니다.
검증, 루프, 종료 상태 결합
이 단계에서는 디렉터리를 검증하고 로그를 요약하며 성공 또는 실패를 명확히 보고하는 재사용 가능한 스크립트를 작성합니다.
summarize-logs.sh를 만듭니다.
cat > summarize-logs.sh <<'EOF'
#!/bin/bash
input_dir=${1:-}
output_file=${2:-}
if [ -z "$input_dir" ] || [ -z "$output_file" ]; then
echo "Usage: $0 INPUT_DIR OUTPUT_FILE" >&2
exit 2
fi
if [ ! -d "$input_dir" ]; then
echo "Input directory not found: $input_dir" >&2
exit 1
fi
: > "$output_file"
for log_file in "$input_dir"/*.log; do
lines=$(wc -l < "$log_file")
errors=$(grep -c '^ERROR ' "$log_file" || true)
printf '%s lines=%s errors=%s\n' "$(basename "$log_file")" "$lines" "$errors" >> "$output_file"
done
echo "Wrote summary to $output_file"
exit 0
EOF
: > "$output_file" 명령은 루프를 시작하기 전에 대상 파일을 안전하게 만들거나 비웁니다. 필수 인수 두 개를 모두 지정해 스크립트를 실행합니다.
chmod u+x summarize-logs.sh
./summarize-logs.sh logs log-summary.txt
echo "summary status: $?"
cat log-summary.txt
존재하지 않는 디렉터리를 지정해 실패 경로를 테스트합니다.
./summarize-logs.sh missing log-summary.txt
echo "missing directory status: $?"
성공 상태는 0이고, 존재하지 않는 디렉터리의 상태는 1이어야 합니다.
요약
Bash 스크립트를 만들고 실행했으며, shebang 과 권한을 사용하고, 인용된 변수를 확장하고, 위치 인수와 대화형 입력을 읽고, 의미 있는 종료 상태를 반환했습니다. 또한 if/elif/else로 분기하고, 파일과 디렉터리를 테스트하고, for로 로그를 반복 처리했으며, 이러한 개념을 재사용 가능한 요약 스크립트에 결합했습니다.
이러한 기초를 익히면 이후의 자동화를 더 쉽게 읽고 안전하게 다시 실행할 수 있으며, 다른 명령이나 예약 작업도 결과를 더 쉽게 판단할 수 있습니다.



