셸 프로그래밍
기본 정보 확인
셸 버전 확인 및 전환
사용 가능한 셸 목록 확인: cat /etc/shells
파일에 나열된 셸 경로를 직접 입력하여 다른 셸로 전환 가능
현재 셸 환경 변수 확인: cat $SHELL
셸 실행 모드
bash 스크립트.sh와sh 스크립트.sh는 하위 셸에서 실행./스크립트.sh와source 스크립트.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)
$[]
$(( ))와 동일하게 정수 연산에 사용됩니다.
$(( )) 사용을 권장합니다.
변수 할당
변수 할당 방식
- 직접 할당:
변수=값 - 명령 결과 할당:
ip=ps -ef|grep "mysql"`` - 대화형 할당:
read -t(타임아웃 설정),-s(입력 숨김),-p(프롬프트 메시지)
read -t 10 -s -p "비밀번호 입력: " password
- 스크립트 위치 매개변수:
$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 set과declare: 모든 변수(전역 및 지역) 출력env: 전역 변수만 표시unset 변수명: 변수나 함수 삭제export: 환경 변수 값 표시 및 설정readonly 변수명: 변수를 현재 셸에만 적용
환경 변수 파일 로드 순서
- 로그인 시 비대화형 모드:
/etc/bashrc시스템 시작 시 실행~/.bashrc사용자 로그인 및 새 셸 시 실행
- 로그인 시 대화형 모드:
/etc/profile사용자 로그인 시 실행~/.bash_profile사용자 로그인 시 한 번만 실행
- 파일이 없으면
~/.bash_login,~/.profile순서로 확인
매개변수 전달
find의 -exec와 xargs를 사용한 매개변수 전달:
# 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))
산술 연산자
연산 방식
$(( ))사용:
echo $((1+2))
echo $((2*2))
echo $((2/2))
echo $((2**3))
let명령 사용:
let a=5+3
let a+=2 # a = a + 2
expr명령 사용:
expr 1 + 2
expr 1 \* 2 # *는 이스케이프 필요
bc명령 사용 (소수점 지원):
echo "10/3" | bc -l
echo "scale=3;6/4" | bc
$[]사용:
echo $[1+3]
echo $[2*3]
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