셸 스크립팅 기초

ShellBeginner
지금 연습하기

소개

셸 스크립트는 명령을 파일에 저장해 작업을 일관되게 반복 수행할 수 있도록 합니다. 신뢰할 수 있는 초보자용 스크립트는 사용할 인터프리터를 명확히 지정하고, 변수 확장 결과를 인용하며, 작업을 수행하기 전에 입력을 검증하고, 다른 명령에서도 해석할 수 있는 종료 상태를 반환합니다.

이 실습에서는 작은 Bash 스크립트를 단계적으로 작성합니다. Bash 를 통해 스크립트를 실행하고 실행 파일로 직접 실행하는 방법을 익히며, 변수와 위치 인수 사용, 대화형 입력 읽기, if와 파일 테스트를 활용한 분기, 종료 코드 선택, for를 이용한 반복, 그리고 이러한 개념을 간단한 로그 요약기에 결합하는 방법을 실습합니다.

첫 번째 스크립트 만들고 실행하기

이 단계에서는 Bash 스크립트를 만들고, 인터프리터를 통해 실행한 다음, 파일을 직접 실행할 수 있도록 설정합니다.

실습 작업 공간으로 이동합니다.

cd /home/labex/project/shell-lab

here-document 를 사용해 hello.sh를 만듭니다. 첫 번째 줄은 파일을 직접 실행할 때 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 스크립트를 만들고 실행했으며, 셸뱅과 권한을 사용하고, 인용된 변수를 확장하고, 위치 입력과 대화형 입력을 읽고, 의미 있는 종료 상태를 반환했습니다. 또한 if/elif/else를 사용한 분기, 파일과 디렉터리 테스트, for를 이용한 로그 순회, 그리고 이러한 개념을 재사용 가능한 요약기에 결합하는 방법을 익혔습니다.

이러한 기초를 바탕으로 이후의 자동화 작업을 더 쉽게 읽고, 더 안전하게 다시 실행하며, 다른 명령이나 예약 작업에서도 결과를 쉽게 판단할 수 있습니다.