셸 스크립팅 심화 가이드

셸 프로그래밍

기본 정보 확인

셸 버전 확인 및 전환

사용 가능한 셸 목록 확인: cat /etc/shells 파일에 나열된 셸 경로를 직접 입력하여 다른 셸로 전환 가능 현재 셸 환경 변수 확인: cat $SHELL

셸 실행 모드

  • bash 스크립트.shsh 스크립트.sh는 하위 셸에서 실행
  • ./스크립트.shsource 스크립트.sh는 현재 셸에서 실행

혼동되기 쉬운 문법

$( )와 (백틱)

두 가지 모두 명령 치환(command substitution)에 사용됩니다. 괄호 안의 명령을 먼저 실행하고 그 결과를 새 명령어의 일부로 치환합니다. $( )는 모든 유닉스 시스템에서 지원되지 않을 수 있지만, 백틱은 모든 시스템에서 지원됩니다.

${ }

변수 값 추출에 사용됩니다. 일반적으로 $변수${변수}는 차이가 없지만, ${ }는 변수명의 정확한 범위를 지정할 때 유용합니다.

$(( ))

정수 연산에 사용됩니다. $(( )) 내의 변수명은 앞에 $ 기호를 붙여도 되고 붙이지 않아도 됩니다. 다른 진법을 10진수로 변환하여 표시할 수도 있습니다. 사용 예:

echo $((2#110))  # 2진수 110을 10진수로 변환 (6)
echo $((16#2a))  # 16진수 2a를 10진수로 변환 (42)
echo $((8#11))   # 8진수 11을 10진수로 변환 (9)

$[]

$(( ))와 동일하게 정수 연산에 사용됩니다. $(( )) 사용을 권장합니다.

변수 할당

변수 할당 방식

  1. 직접 할당: 변수=값
  2. 명령 결과 할당: ip=ps -ef|grep "mysql"``
  3. 대화형 할당: read -t (타임아웃 설정), -s (입력 숨김), -p (프롬프트 메시지)
read -t 10 -s -p "비밀번호 입력: " password
  1. 스크립트 위치 매개변수: $1, $2, $3

따옴표의 차이

  • "" (약한 인용): 공백을 구분자로 인식하지 않음
  • '' (강한 인용): 모든 특수 문자의 의미를 무시
  • ` `$(): 명령을 먼저 실행하고 결과를 치환
name="test"
echo "$name"  # test 출력
echo '$name'  # $name 그대로 출력

위치 변수

$*$@의 차이:

  • 따옴표로 감싸지지 않을 때: 두 변수 모두 각 매개변수를 전체 데이터로 취급
  • 따옴표로 감싸일 때:
  • $*: 모든 데이터를 하나의 데이터로 취급
  • $@: 각 데이터를 별개로 취급
#!/bin/bash
echo '특수 변수 $0 $1 $2 테스트'
echo '결과: '$0 $1 $2

echo "##############################"
echo '특수 변수 $# 테스트'
echo '결과: '$#

echo "##############################"
echo '특수 변수 $* 테스트'
echo '결과: '$*
echo "##############################"
echo '특수 변수 $@ 테스트'
echo '결과: '$@

변수 조작

# 문자열 길이
${#변수}

# 부분 문자열 추출
${변수:시작위치:길이}

# 패턴 제거
${변수#패턴}  # 가장 짧은 패턴 일치 부분 제거 (앞에서)
${변수##패턴} # 가장 긴 패턴 일치 부분 제거 (앞에서)
${변수%패턴}  # 가장 짧은 패턴 일치 부분 제거 (뒤에서)
${변수%%패턴} # 가장 긴 패턴 일치 부분 제거 (뒤에서)

# 패턴 치환
${변수/패턴/치환문자}  # 첫 번째 일치 패턴 치환
${변수//패턴/치환문자} # 모든 일치 패턴 치환

변수 확장 기능

# 변수가 비어 있을 경우 word 반환
${변수:-word}

# 변수가 비어 있을 경우 word로 변수 설정 후 반환
${변수:=word}

# 변수가 비어 있을 경우 word를 에러 메시지로 출력
${변수:?word}

# 변수가 비어 있지 않을 경우 word 반환
${변수:+word}

환경 변수 설정

환경 변수는 일반적으로 export 내장 명령으로 내보낸 변수로, 셸의 실행 환경을 정의합니다.

  • 사용자 개인 설정 파일: ~/.bash_profile, ~/.bashrc
  • 전역 설정 파일: /etc/profile, /etc/bashrc
  • setdeclare: 모든 변수(전역 및 지역) 출력
  • env: 전역 변수만 표시
  • unset 변수명: 변수나 함수 삭제
  • export: 환경 변수 값 표시 및 설정
  • readonly 변수명: 변수를 현재 셸에만 적용

환경 변수 파일 로드 순서

  • 로그인 시 비대화형 모드:
  1. /etc/bashrc 시스템 시작 시 실행
  2. ~/.bashrc 사용자 로그인 및 새 셸 시 실행
  • 로그인 시 대화형 모드:
  1. /etc/profile 사용자 로그인 시 실행
  2. ~/.bash_profile 사용자 로그인 시 한 번만 실행
  • 파일이 없으면 ~/.bash_login, ~/.profile 순서로 확인

매개변수 전달

find-execxargs를 사용한 매개변수 전달:

# find 기본 사용법
find /경로/검색 -type f -mtime +20 -name "*.log"

# -exec 사용
find /경로/검색 -type f -mtime +20 -name "*.log" -exec 명령 {} \;

# xargs 사용
find /경로/검색 -type f -mtime +20 -name "*.log" | xargs -I {} 명령 {}

# 특수 파일명 처리
find /경로/검색 -type f -mtime +20 -name "*.log" -print0 | xargs -0 -I {} 명령 {}

리디렉션

기본 개념

  • 0, 1, 2: 각각 stdin, stdout, stderr를 의미
  • >: 출력 덮어쓰기
  • >>: 출력 추가
  • <: 입력 리디렉션
  • <<: 여러 줄 입력 (Here Document)

Here Document 사용 예

cat <<EOF > 파일명
첫 번째 줄
두 번째 줄
세 번째 줄
EOF

# 여러 줄 입력에서 탭 문자 무시
cat <<-EOF > 파일명
    탭이 있는 줄
일반 줄
EOF

셸 내장 명령

echo

-n: 줄바꿈 없이 출력
-e: 특수 문자 해석
echo -e "줄바꿈: \n탭: \t"

eval

인수를 받아 명령으로 실행

eval "cd /opt; ls -l"

exec

하위 프로세스를 생성하지 않고 명령을 실행하고 종료

exec date  # 실행 후 셸 종료

프로세스 목록과 하위 셸 환경

하위 셸

소괄호 ()는 하위 셸을 생성합니다:

(cd /home; ls)

하위 셸 중첩 실행

하위 셸을 사용하여 병렬 처리 구현:

(ls; echo $BASH_SUBSHELL; (cd /var; ls; echo $BASH_SUBSHELL))

산술 연산자

연산 방식

  1. $(( )) 사용:
echo $((1+2))
echo $((2*2))
echo $((2/2))
echo $((2**3))
  1. let 명령 사용:
let a=5+3
let a+=2  # a = a + 2
  1. expr 명령 사용:
expr 1 + 2
expr 1 \* 2  # *는 이스케이프 필요
  1. bc 명령 사용 (소수점 지원):
echo "10/3" | bc -l
echo "scale=3;6/4" | bc
  1. $[] 사용:
echo $[1+3]
echo $[2*3]
  1. awk 사용:
awk 'BEGIN{print 1+3, 2*3, 10/3}'
awk -v x=3 -v y=4 'BEGIN{print x+y}'

조건 테스트 문

테스트 문 유형

  • 파일 관련 표현식
  • 숫자 비교
  • 문자열 비교
  • 논리 연산 (AND, OR, NOT)

테스트 문 형식

[ 조건 ]
[[ 조건 ]]  # 확장된 테스트 (정규식 지원)

파일 관련 테스트

옵션 의미
-d 디렉토리 존재 여부
-f 파일 존재 여부
-e 존재 여부
-r 읽기 권한 여부
-w 쓰기 권한 여부
-x 실행 권한 여부
-s 파일 크기가 0보다 큰지 여부

숫자 비교

옵션 의미
-eq 같음
-ne 같지 않음
-gt
-ge 크거나 같음
-lt 작음
-le 작거나 같음

문자열 비교

[ "$str1" = "$str2" ]  # 문자열이 같은지 확인
[ "$str1" != "$str2" ] # 문자열이 다른지 확인
[ -z "$str" ]          # 문자열이 비어 있는지 확인
[ -n "$str" ]          # 문자열이 비어 있지 않은지 확인

조건문 예시

#!/bin/bash
read -p "두 개의 숫자를 입력하세요: " num1 num2

if [ $# -ne 2 ]; then
    echo "두 개의 숫자를 입력해야 합니다."
    exit 1
fi

if [[ $num1 =~ ^[0-9]+$ ]] && [[ $num2 =~ ^[0-9]+$ ]]; then
    echo "합산 결과: $((num1 + num2))"
else
    echo "숫자가 아닌 값이 입력되었습니다."
    exit 2
fi

문자열 처리

문자열 비주얼라이제이션

# 문자열 길이
${#string}

# 부분 문자열 추출
${string:2:4}  # 2번째 위치부터 4개 문자

# 패턴 제거
${string#pattern}   # 가장 짧은 패턴 일치 부분 제거 (앞)
${string##pattern}  # 가장 긴 패턴 일치 부분 제거 (앞)
${string%pattern}   # 가장 짧은 패턴 일치 부분 제거 (뒤)
${string%%pattern} # 가장 긴 패턴 일치 부분 제거 (뒤)

# 패턴 치환
${string/pattern/replacement}  # 첫 번째 일치 패턴 치환
${string//pattern/replacement} # 모든 일치 패턴 치환

배열

배열 기본 문법

# 배열 선언
arr=(값1 값2 값3)
arr=([0]=첫번째 [2]=세번째)

# 배열 요소 추가
arr[0]="새로운값"
arr+=(추가값1 추가값2)

# 배열 요소 접근
echo ${arr[0]}      # 첫 번째 요소
echo ${arr[@]}      # 모든 요소
echo ${#arr[@]}     # 배열 크기
echo ${!arr[@]}     # 배열 인덱스

연관 배열 (해시)

# 연관 배열 선언
declare -A assoc_arr
assoc_arr=([이름]="홍길동" [나이]=25 [성별]="남성")

# 값 추가 및 변경
assoc_arr["직업"]="개발자"

# 값 접근
echo ${assoc_arr["이름"]}
echo ${assoc_arr[@]}

루프 구조

for 루프

# 범위 기반 for 루프
for i in {1..10}; do
    echo "숫자: $i"
done

# 파일 목록 순회
for file in *.txt; do
    echo "파일: $file"
done

# C 스타일 for 루프
for ((i=0; i<10; i++)); do
    echo "카운트: $i"
done

while 루프

# 기본 while 루프
count=0
while [ $count -lt 5 ]; do
    echo "카운트: $count"
    ((count++))
done

# 파일 읽기 while 루프
while read line; do
    echo "라인: $line"
done < 파일명.txt

루프 제어

  • break: 루프 종료
  • continue: 다음 반복으로 이동
  • shift: 매개변수 왼쪽으로 이동
# break 예시
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        break
    fi
    echo $i
done

# shift 예시
sum=0
while [ $# -gt 0 ]; do
    sum=$((sum + $1))
    shift
done
echo "합계: $sum"

if 문

기본 if 구조

if [ 조건 ]; then
    # 조건이 참일 때 실행
elif [ 조건 ]; then
    # 다른 조건이 참일 때 실행
else
    # 모든 조건이 거짓일 때 실행
fi

중첩 if 예시

#!/bin/bash
read -p "사용자 이름 입력: " username

if id "$username" &>/dev/null; then
    echo "$username 사용자가 이미 존재합니다."
else
    echo "$username 사용자 생성 중..."
    read -s -p "비밀번호 입력: " password
    if [ ${#password} -ge 8 ]; then
        useradd "$username"
        echo "$password" | passwd "$username" --stdin
        echo "$username 사용자 생성 완료"
    else
        echo "비밀번호는 8자 이상이어야 합니다."
    fi
fi

case 문

기본 case 구조

case 변수 in
    패턴1)
        # 패턴1에 일치할 때 실행
        ;;
    패턴2)
        # 패턴2에 일치할 때 실행
        ;;
    *)
        # 어떤 패턴에도 일치하지 않을 때 실행
        ;;
esac

case 문 예시

#!/bin/bash
read -p "서버 유형 선택 (1: 웹, 2: 데이터베이스, 그 외: 종료): " choice

case $choice in
    1)
        echo "웹 서버 시작 중..."
        systemctl start nginx
        ;;
    2)
        echo "데이터베이스 서버 시작 중..."
        systemctl start mysql
        ;;
    *)
        echo "종료합니다."
        exit
        ;;
esac

함수

함수 정의

# 방법 1
함수명() {
    # 함수 내용
}

# 방법 2
function 함수명 {
    # 함수 내용
}

함수 호출 및 매개변수 전달

# 함수 정의
add_numbers() {
    local sum=$(( $1 + $2 ))
    echo "합계: $sum"
}

# 함수 호출
add_numbers 5 3

# 배열을 매개변수로 전달
process_array() {
    local arr=("$@")
    for item in "${arr[@]}"; do
        echo "처리 중: $item"
    done
}

numbers=(1 2 3 4 5)
process_array "${numbers[@]}"

함수 반환값

# return 사용 (0-255 정수)
check_file() {
    if [ -f "$1" ]; then
        return 0
    else
        return 1
    fi
}

# echo 사용 (임의의 값 반환)
calculate_factorial() {
    local num=$1
    local result=1
    for ((i=1; i<=num; i++)); do
        result=$((result * i))
    done
    echo $result
}

텍스트 처리 도구

sed (스트림 편집기)

# 특정 행 삭제
sed '3d' 파일.txt

# 패턴 치환
sed 's/old/new/g' 파일.txt

# 특정 패턴이 있는 행 삭제
sed '/^#/d' 파일.txt

# 여러 명령 실행
sed -e 's/old1/new1/' -e 's/old2/new2/' 파일.txt

# 파일 직접 수정
sed -i 's/old/new/g' 파일.txt

awk (텍스트 처리 도구)

# 필드 출력
awk -F: '{print $1, $3}' /etc/passwd

# 조건에 따른 필터링
awk -F: '$3 >= 1000 {print $1}' /etc/passwd

# 계산 수행
awk '{sum += $1} END {print "합계:", sum}' 숫자파일.txt

# 정규식 사용
awk '/^root/ {print $1}' /etc/passwd

# 함수 사용
function print_name(name) {
    print "이름: " name
}
BEGIN { print_name("홍길동") }

grep (텍스트 검색)

# 기본 검색
grep "패턴" 파일.txt

# 대소문자 무시
grep -i "패턴" 파일.txt

# 정규식 사용
grep -E "패턴1|패턴2" 파일.txt

# 일치하는 행 번호 출력
grep -n "패턴" 파일.txt

# 일치하지 않는 행 출력
grep -v "패턴" 파일.txt

# 컨텍스트 라인 출력
grep -C 2 "패턴" 파일.txt

실용 스크립트 예제

로그 기록 함수

function log_command() {
    local cmd="$*"
    eval "$cmd"
    local status=$?
    
    if [ $status -eq 0 ]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') 성공: $cmd" | tee -a /var/log/command.log
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') 실패: $cmd" | tee -a /var/log/command.log
    fi
}

# 사용 예시
log_command systemctl restart nginx

문자열 포함 확인

function contains() {
    local string="$1"
    local substring="$2"
    
    if [[ $string == *"$substring"* ]]; then
        return 0  # 포함됨
    else
        return 1  # 포함되지 않음
    fi
}

# 사용 예시
if contains "hello world" "world"; then
    echo "문자열에 'world'가 포함되어 있습니다."
else
    echo "문자열에 'world'가 포함되어 있지 않습니다."
fi

명령 존재 여부 확인

# 방법 1: command 사용
if command -v python3 &>/dev/null; then
    echo "Python3가 설치되어 있습니다."
else
    echo "Python3가 설치되어 있지 않습니다."
fi

# 방법 2: type 사용
if type python3 &>/dev/null; then
    echo "Python3가 설치되어 있습니다."
else
    echo "Python3가 설치되어 있지 않습니다."
fi

배치 파일명 변경

#!/bin/bash
# 확장자 변경 스크립트

# .txt 파일을 .md로 변경
for file in *.txt; do
    if [ -f "$file" ]; then
        mv "$file" "${file%.txt}.md"
        echo "$file -> ${file%.txt}.md"
    fi
done

# 파일명에서 특정 단어 변경
for file in *.md; do
    if [ -f "$file" ]; then
        newname=$(echo "$file" | sed 's/old/new/')
        mv "$file" "$newname"
        echo "$file -> $newname"
    fi
done

태그: 셸스크립트 bash 리눅스 자동화 sed

7월 9일 19:21에 게시됨