UNIX Shell Programming

#############################
# 오류 발생 시 스크립트 실행
# 중지 또는 무시하고 진행
#############################
set -e
set +e

#############################
# 변수 사용 방법 (Variable)
#############################
▣ 위치 매개 변수(Positional Parameters)
$0 : 실행된 스크립트 이름
$1 : $1 $2 $3...${10}인자 순서대로 번호가 부여된다. 10번째부터는 "{}"감싸줘야 함
$* : 전체 인자 값
$@ : 전체 인자 값($* 동일하지만 쌍따옴표로 변수를 감싸면 다른 결과 나옴)
$# : 매개 변수의 총 개수

▣ 특수 매개 변수(Special Parameters)
$$ : 현재 스크립트의 PID
$? : 최근에 실행된 명령어, 함수, 스크립트 자식의 종료 상태
$! : 최근에 실행한 백그라운드(비동기) 명령의 PID
$- : 현재 옵션 플래그
$_ : 지난 명령의 마지막 인자로 설정된 특수 변수

▣ 매개 변수 확장(Parameter Expansion)
${변수} : $변수와 동일하지만 {} 사용해야만 동작하는 것들이 있음(예: echo ${string})
${변수:위치} : 위치 다음부터 문자열 추출(예: echo ${string:4})
${변수:위치:길이} : 위치 다음부터 지정한 길이 만큼의 문자열 추출(예: echo ${string:4:3})
${변수:-단어} : 변수 미선언 혹은 NULL일때 기본값 지정, 위치 매개 변수는 사용 불가(예: echo ${string:-HELLO})
${변수-단어}  : 변수 미선언시만 기본값 지정, 위치 매개 변수는 사용 불가(예: echo ${string-HELLO})
${변수:=단어} : 변수 미선언 혹은 NULL일때 기본값 지정, 위치 매개 변수 사용 가능(예: echo ${string:=HELLO})
${변수=단어}  : 변수 미선언시만 기본값 지정, 위치 매개 변수 사용 가능(예: echo ${string=HELLO})
${변수:?단어} : 변수 미선언 혹은 NULL일때 단어 출력 후 스크립트 종료,(예: echo ${string:?HELLO})
${변수?단어}  : 변수 미선언시만 단어 출력 후 스크립트 종료(예: echo ${string?HELLO})
${변수:+단어} : 변수 선언시만 단어 사용(예: echo ${string:+HELLO})
${변수+단어}  : 변수 선언 혹은 NULL일때 단어 사용(예: echo ${string+HELLO})
${#변수}      : 문자열 길이(예: echo ${#string})
${변수#단어}  : 변수의 앞부분부터 짧게 일치한 단어 삭제(예: echo ${string#a*b})
${변수##단어} : 변수의 앞부분부터 길게 일치한 단어 삭제(예: echo ${string##a*b})
${변수%단어}  : 변수의 뒷부분부터 짧게 일치한 단어 삭제(예: echo ${string%b*c})
${변수%%단어} : 변수의 뒷부분부터 길게 일치한 단어 삭제(예: echo ${string%%b*c})
${변수/찾는단어/변경단어}  : 처음 일치한 단어를 변경(예: echo ${string/abc/HELLO})
${변수//찾는단어/변경단어} : 일치하는 모든 단어를 변경(예: echo ${string//abc/HELLO})
${변수/#찾는단어/변경단어} : 앞부분이 일치하면 변경(예: echo ${string/#abc/HELLO})
${변수/%찾는단어/변경단어} : 뒷부분이 일치하면 변경(예: echo ${string/%abc/HELLO})
${!단어*}, ${!단어@} : 선언된 변수중에서 단어가 포함된 변수 명 추출(예: echo ${!string*}, echo ${!string@})

#############################
# 인수 처리
# 인수가 2개 미만이면 오류 처리
##############################
if [ $# -ne 2 ]; then
  echo "Usage: $0 <param1> <param2>"
  exit -1
else
  echo "param1: $1"
  echo "param2: $2"
fi

#############################
# 텍스트 파일에서
# 중복된 줄만 찾기
#############################
sort mapping.txt | uniq -d

#############################
# 변수 내에 문자열 존재 체크
#############################
AA="sftp,telnet"

if [[ "${AA}" = *"tel"* ]]; then
 echo found.
fi

#############################
# 질문
#############################
while true; do
  echo
  read -p "이 작업을 정말 진행하시겠습니까? (y/n) " YN_ANSWER
    case ${YN_ANSWER} in
      [Yy]* ) break;;
      [Nn]* ) exit 1;;
      * ) echo "Please answer y or n.";;
  esac
done

#############################
# 스크립트에서 설정파일 읽기
#############################
THIS_IS_CONFIG_FILE="./init.conf"
echo "###########################################################"
echo "# Read the ${THIS_IS_CONFIG_FILE} file..."
echo "###########################################################"
if [ -f ${THIS_IS_CONFIG_FILE} ]; then
  source ${THIS_IS_CONFIG_FILE}
else
  echo "${THIS_IS_CONFIG_FILE} can't be read."
  exit 1
fi

echo ${TIMESTAMP_STR}
----------------------------------
▣▣▣ init.conf ▣▣▣
TIMESTAMP_STR=`date "+%Y%m%d_%H%M%S"`
CURRENT_DIR=`pwd -P`

#############################
# PAUSE
#############################
read -p "Press any key to continue..."
read -t 5 -p "I am going to wait for 5 seconds only..."

#############################
# 명령어 실행 성공여부 확인
#############################
grep "${USER_ID}" /etc/sudoers
if [ $? -eq 0 ];
then
  echo "문자열이 존재 합니다."
else
  echo "문자열이 존재하지 않습니다."
fi

#############################
# 파일명을 정렬하여
# 최신 파일의 파일명 추출
#############################
TMP_FILENAME1=`ls -1 -t /backup/*_backup.tar.gz | head -1`
FILENAME_TAR_GZ=`basename ${TMP_FILENAME1}`

#############################
# 문자열 분리 및 파싱
#############################
#!/bin/bash
TEXT_FILE="1.txt"

#-----------------------------------------------
# 문자열이 포함되었는지 확인 (있으면1, 없으면0)
#-----------------------------------------------
func_strstr() {
  [ "${1#*$2*}" = "$1" ] && return 0
  return 1
}

while read READ_STRING
do
  #---------------------------------------------
  # ;을 분리자로 토큰 분리
  #---------------------------------------------
  TOKENARR1=(${READ_STRING//;/ })

  TOKEN0=${TOKENARR1[0]}
  TOKEN1=${TOKENARR1[1]}
  TOKEN2=${TOKENARR1[2]}

  if [ ${TOKEN0} == "-" ]
  then
    TOKEN0=${OLD_TOKEN0}
  fi
  if [ ${TOKEN1} == "-" ]
  then
    TOKEN1=${OLD_TOKEN1}
  fi

  #---------------------------------------------
  # TOKEN2에 ,가 포함되어 있으면 다시 토큰 분리
  # 후 처리
  #---------------------------------------------
  func_strstr ${TOKEN2} ","
  if [ $? -eq 1 ];
  then
    TOKENARR2=(${TOKEN2//,/ })

    COUNT2=0
    while [ ! -z ${TOKENARR2[${COUNT2}]} ]
    do
      echo "COMMA: ${TOKEN0} ; ${TOKEN1} ; ${TOKENARR2[${COUNT2}]}"
      COUNT2=$(($COUNT2+1))
    done
  else
    echo "${TOKEN0} ; ${TOKEN1} ; ${TOKEN2}"
  fi

  OLD_TOKEN0=${TOKEN0}
  OLD_TOKEN1=${TOKEN1}
done < ${TEXT_FILE}

#############################
# 배열 사용 예제 (1차원 배열만 지원)
#############################
# 배열 변수 사용은 반드시 괄호를 사용해야 한다.(예: ${array[1]})
# 배열의 크기 지정없이 배열 변수로 선언
# 참고: 'declare -a' 명령으로 선언하지 않아도 배열 변수 사용 가능함
declare -a array

# 4개의 배열 값 지정
array=("hello" "test" "array" "world")

# 기존 배열에 1개의 배열 값 추가(순차적으로 입력할 필요 없음)
array[4]="variable"

# 기존 배열 전체에 1개의 배열 값을 추가하여 배열 저장(배열 복사 시 사용)
array=(${array[@]} "string")

# 배열 루핑, ${loop1}이 배열값
for loop1 in "${array[@]}"
do
  echo ${loop1}
done

# 위에서 지정한 배열 출력
echo "hello world 출력: ${array[0]} ${array[3]}"
echo "배열 전체 출력: ${array[@]}"
echo "배열 전체 개수 출력: ${#array[@]}"

printf "배열 출력: %s\n" ${array[@]}

# 배열 특정 요소만 지우기
unset array[4]
echo "배열 전체 출력: ${array[@]}"

# 배열 전체 지우기
unset array
echo "배열 전체 출력: ${array[@]}"

#############################
# 함수 사용 예제 (Function)
#############################
string_test1() {
  echo "string test 1"
}

string_test2() {
  echo "string test 2"
  echo "개별 인수: ${1} ${2} ${3} ${4}"
  echo "전체 인수: ${@}"
}
string_test1
string_test2 "param-1" "param 2" "param_3" "param 4"

#############################
# 텍스트 파일 읽기
#############################
while read READ_STRING
do
  STRLEN=`echo -n $READ_STRING | wc -c`
  if [ ${STRLEN=} -gt 20 ]
  then
    echo ${READ_STRING}
  fi
done < ${TEXT_FILE}

#############################
# case문 예제
#############################
case "$1" in
  "start")
    func_start
    ;;
  "stop")
    func_stop
    ;;
  "status")
    status anacron
    ;;
  "restart")
    func_stop
    func_start
    ;;
  "condrestart")
    if test "x`pidof anacron`" != x; then
      stop
      start
    fi
    ;;
  *)
    echo $"Usage: $0 {start|stop|restart|condrestart|status}"
    exit 1
esac

#############################
# if문 예제
#############################
if [ conditons ]
then
  조건 만족시 실행하고자 하는 문장
elif [ conditons ]
  두번째 조건 만족시 실행 문장
else
  조건 불만족시 실행하고자 하는 문장
fi

if [ $1 -gt 100 ]
then
  echo Hey that\'s a large number.
  pwd
fi

if [ ${value} -eq 0 ]; then
  echo value is 0
fi

if [ ${test} -gt 2 -a ${test} -le 7 ]
then
  echo test greater then 2 and lower then 7
fi

if [ "$opt" == 'test' ] || [ "$opt" == 'aaa' ]
then
  echo the value is ...
fi

[ -z ] : 문자열의 길이가 0이면 참
[ -n ] : 문자열의 길이가 0이 아니면 참
[ string ] : 문자열이 NULL이 아니면 참
[ string1 = string2 ] : 문자열이 동일하면 참
[ string1 != string2 ] : 문자열이 다르면 참
[ -eq ] : 값이 같으면 참
[ -ne ] : 값이 다르면 참
[ -gt ] : 값1 > 값2
[ -ge ] : 값1 >= 값2
[ -lt ] : 값1 < 값2
[ -le ] : 값1 <= 값2
[ -a ] : &&연산과 동일 and 연산
[ -o ] : ||연산과 동일 or 연산
[ -d ] : 파일이 디렉토리면 참
[ -e ] : 파일이 있으면 참
[ -L ] : 파일이 심볼릭 링크면 참
[ -r ] : 파일이 읽기 가능하면 참
[ -s ] : 파일의 크기가 0 보다 크면 참
[ -w ] : 파일이 쓰기 가능하면 참
[ -x ] : 파일이 실행 가능하면 참
[ 파일1 -nt 파일2 ] : 파일1이 파일2보다 최신파일이면 참
[ 파일1 -ot 파일2 ] : 파일1이 파일2보다 이전파일이면 참
[ 파일1 -ef 파일2 ] : 파일1이 파일2랑 같은 파일이면 참
[ -r 파일명 ] : 해당 파일이 읽기 가능하면 참
[ -w 파일명 ] : 해당 파일이 쓰기 가능하면 참
[ -x 파일명 ] : 해당 파일이 실행 가능하면 참
[ -s 파일명 ] : 해당 파일의 사이즈가 제로 이상이면 참
[ -d 파일명 ] : 해당 파일이 디렉토리면 참
[ -f 파일명 ] : 해당 파일이 보통 파일이면 참
[ -h 파일명 ] : 해당 파일이 링크 파일이면 참

#############################
# while문 예제
#############################
while [ $i -lt 3 ]
do
  echo $i
  i=$(($i+1))
done

#############################
# for문 예제
#############################
for i in 1 2 3 4 5
do
   echo "Welcome $i times"
done
-----
for i in {1..5}
do
   echo "Welcome $i times"
done
-----
for (( ; ; ))
do
   echo "infinite loops [ hit CTRL+C to stop]"
done
-----
for file in /etc/*
do
  if [ "${file}" == "/etc/resolv.conf" ]
    then
      countNameservers=$(grep -c nameserver /etc/resolv.conf)
      echo "Total  ${countNameservers} nameservers defined in ${file}"
      break
  fi
done

#############################
# 파일명과 확장자 추출
#############################
NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`

FILE=test.tar.gz
% echo ${FILE%%.*}
test
% echo ${FILE%.*}
test.tar
% echo ${FILE#*.}
tar.gz
% echo ${FILE##*.}
gz

#############################
# find (파일명 검색)
#############################
find . -name "*.c" -print
↑: *.c 파일 찾기

find . -name "*.c" -print
↑: *.c 파일을 찾아서 .c$로 확장자 변경

find . -atime +30 -print
↑: 읽은지 30일 이상된 파일 찾기

find . -mtime -1 -print
↑: 24시간 내 수정된 파일 찾기

find . -type f -name "*.c" -newer ttt.rc -print
↑: *c 파일 중 ttt.rc 수정 이후에 수정된 파일 찾기

find . -atime +7 -size +20480 -print
↑: 접근한지 7일 이상이며 10MB이상 파일(size unix:512B)

find . -name "*.bak" -print -exec rm -f {} \;
↑: *.bak 파일을 찾아서 출력하고 삭제한다. {}는 찾은 파일명이고 \;는 명령의 끝을 명시함

find . -depth -name "*.c" -print -exec sh -c 'mv "$1" "${1%.c}.c$"' _ {} \;
↑: 모든 .c 파일을 찾아 .c$로 확장자만 변경한다.

find . -print -exec touch -t "201506171200.00" {} \;
find /opt -name "*" -exec touch -t "202101010000.00" {} \;
↑: 파일 및 디렉토리의 시간 변경

# ${OLDLOG_EXPDATE}일 이상 경과된 로그를 삭제한다.
OLDLOG_EXPDATE=3
find "/var/log" -mtime +${OLDLOG_EXPDATE} -name "*.log" -type f -exec rm -f {} \;

# 보안성 심의를 위한 불필요한 파일 검색
find /opt/gate1/www \( -name "*sample*" -o -name "*demo*" -o -name "*tutorial*" -o -name "*test*" -o -name "*manual*" -o -name "*example*" -o -name "*bak*" -o -name "*backup*" -o -name "*_[0-9][0-9][0-9]*" \) -print

#############################
# sed (stream editor: 파일 내의 문자열 조작)
#############################
☞기본문법 : sed -i 's/찾는문자열/바꿀문자열/g' 입력파일
(.*[]^$&/\ 문자는 메타문자라 앞에 \을 두어 구분한다.)

sed -i -e 's/Index/색인/g' -e 's/Contents/차례/g' test1.txt
↑: test1.txt 에서 "Index"을 "색인"으로 "Contents"를 "차례"로 바꾸어 test2.txt에 저장

sed -i 's/\/usr\/local/\/usr/g' test1.txt
↑: /usr/local을 /usr로 바꾼다.

sed -i 's/192.168.1/10.10.10/g' test1.txt
↑: 192.168.1을 10.10.10으로 바꾼다.

sed -i '/^ *$/d' test1.txt
↑: 공백만 있는 라인 삭제 (주의:^와 *사이에 공백 있음)

sed -i 's/[[:blank:]]*$/g' test.txt
↑: 줄끝 공백과 탭 제거

sed -i 's/\t/        /g' tab-file.txt
↑: 모든 탭을 공백으로 치환

sed -i 's/^        /\t/g' test.txt
↑: 맨 앞에 공백만 탭으로 치환 (Makefile 용)

sed -i 's/<[^>]*>//g' test.html
↑: test.html에서 HTML 태그 삭제

sed -n '/[Ll]ove/p' test1.txt
↑: Love or love를 포함하는 라인만 출력

sed -e '/#/d' test1.txt > test2.txt
↑: test1.txt의 내용에서 #으로 시작되는 행을 모두 제거 (주석 제거)

sed -e '1,10d' test1.txt > test2.txt
↑: 1~10행 삭제

sed -i 's/^M$//' test1.txt
↑: DOS 개행문자를 UNIX 개행문자로 변경 (^M은 "Ctrl+V" 누르고 "Ctrl+M')

#############################
# 특정 디렉토리의 마운트 여부 체크
#############################
is_mounted() {
  mount | awk -v MOUNT_DIR="${1}" '{ if ($3 == MOUNT_DIR) { print 1 } } ENDFILE{ exit -1 }'
}
MOUNT_OK=$(is_mounted ${STORAGE_MOUNT_DIR})
if [ -z ${MOUNT_OK} ] || [ ${MOUNT_OK} != "1" ]
then
  echo "ERROR: ${STORAGE_MOUNT_DIR} is not mounted."
  exit 2
fi

#############################
# touch (파일 시간 변경)
#############################
# 파일 시간(Access, Modify) 변경
touch -a -m -t 202101010000.00 file1

# 파일 날짜 확인
stat file1

# 파일 날짜 일괄 변경
find /mnt/0_tmp -exec touch -a -m -t 202101010000.00 {} \;

#############################
# grep (g/re/p: 문자열 검색)
#############################
-i : 영문의 대소문자를 구별하지 않는다.
-v : pattern을 포함하지 않는 라인을 출력한다.
-n : 검색 결과의 각 행의 선두에 행 번호를 넣는다(first line is 1).
-l : 파일명만 출력한다.
-c : 패턴과 일치하는 라인의 개수만 출력한다.
-r : 하위 디렉토리까지 검색한다.

grep -i "홍길동|김철수|박순희" ./*
현재 디렉토리의 모든 파일에서 홍길동 or 김철수 or 박순희를 검색한다.

grep -rn "Allow" /etc/*
/etc 밑의 모든 파일에서 Allow 문자열을 검색하고 행번호를 출력한다.

#############################
# ln (link: 파일 or 디렉토리 링크)
#############################
ln -s <원본파일> <심볼릭링크파일>

ex) ln -s /bin/ls dir

심볼릭 링크는 원본이 삭제되면 사용불가하나 하드링크는 사용가능.

#############################
# tail (파일 내용 끝부분 부터 열람)
#############################
tail -f /var/log/messages
↑: /var/log/messages를 실시간으로 출력(모니터링)

#############################
# Shell profile reload
#############################
. ~/.profile
. ~/.bash_profile
. /etc/profile

#############################
# ksh(AIX) Shell Auto Completion
#############################
-자동완성 : ESC+\ or ESC+ESC
-편집 (vi 모드)
ESC 를 누르면 vi 모드로 들어가고 k,j 를 이용하여 커서 이동.
※ 환경변수에 vi 모드로 설정되어 있어야 함.
EDITOR=vi; export EDITOR

#############################
# diff (파일/디렉토리 비교해서 patch 파일 만듦)
#############################
diff -urN dir.orig dir.work > dir.patch
(dir.orig는 원본, dir.work는 작업본)
서로 비교해서 다른점을 dir.patch에 넣는다.

#############################
# patch
#############################
patch -p0 < dir.patch
(p0는 p1, p2 등으로 바꿀 수 있으며 대상 경로가 한단계씩 줄어든다.)

#############################
# zip
#############################
zip -r -9 dir.zip dir

#############################
# zip
#############################
zip -r -9 dir.zip dir

#############################
# 입출력 리다이렉션
#############################
명령 >& 파일명 : 명령이 실행된 표준 출력의 결과와 에러를 파일로 출력
명령 >! 파일명 : 파일의 존재 유무와 상관없이 생성하고 명령이 실행된 표준 출력의 결과를 파일로 출력
명령 >&! 파일명 : 파일의 존재 유무와 상관없이 생성하고 명령이 실행된 표준 출력의 결과와 에러를 파일로 출력
명령A | 명령B : 명령A의 출력을 명령B 입력으로 사용하여 실행
명령A |& 명령B : 명령A의 출력과 에러를 명령 B의 입력으로 사용하여 실행

■ 파일 디스크립터
표준입력:0 표준출력:1 표준에러:2

#############################
# 시스템 IP주소 알아내기
#############################
get_system_ip_addr() {
  SYSTEM_IP_ADDR=$(hostname -I | awk '{print $1}')
  if [ -z ${SYSTEM_IP_ADDR} ]
  then
    echo "ERROR: Unknown IP Address."
    exit
  fi
  echo ${SYSTEM_IP_ADDR}
}

#############################
# 시스템 인터페이스명 알아내기
#############################
get_system_if_name() {
  SYSTEM_IF_NAME=$(ls -1 /sys/class/net/ | awk '{ if ( $1 != "lo") print $1 }' | head -n 1)
  if [ -z ${SYSTEM_IF_NAME} ]
  then
    echo "ERROR: Unknown interface name."
    exit
  fi
  echo ${SYSTEM_IF_NAME}
}

위로 스크롤