섹션 5. 비교와 루프문

 

리눅스 서버를 자동화하여 관리하기 위한 쉘 스크립트(Shell Script) 심화 강좌를 정리합니다.
출처 : inflearn

1. 조건문(if..else.fi)

linux:/home/shkim$ if true; then
> echo true
> else
> echo false
> fi
true
linux:/home/shkim$ if true; then echo true; else echo false; fi # 한줄로도 표현 가능 줄바꿈은 세미콜론 처리.
true

if 문(IF Statement)

  • style #1
    if COMMANDS
    then OTHER COMMANDS
    fi
    
  • style #2
    if COMMANDS
    then 
    OTHER COMMANDS
    fi
    
  • style #3
    if COMMANDS;then 
    OTHER COMMANDS
    fi
    

2. [..] vs [[..]]

linux:/home/shkim$ tom="Tom janks"
linux:/home/shkim$ deniro="Robert Deniro"
linux:/home/shkim$ [ $tom > $deniro ] # 대괄호는 테스트로 사용함, [..] 안에서 >< 문자는 리다이렉트로 해석된다.
-bash: $deniro: ambiguous redirect
linux:/home/shkim$ 
linux:/home/shkim$ [[ $tom > $deniro ]]
linux:/home/shkim$ echo $? # $?는 직전 명령의 성공,실패를 확인할 때 사용 
0 # 참
linux:/home/shkim$ [ $tom = $deniro ] # ==> [ Tom hanks = Robert Deniro ] // 공백으로 인한 에러
-bash: [: too many arguments
linux:/home/shkim$ [ "$tom" = "$deniro" ]
linux:/home/shkim$ [ "Tom hanks" = "Robert Deniro" ]
linux:/home/shkim$ [[ $tom = $deniro ]]; echo $?
1
linux:/home/shkim$ [[ $tom > $deniro ]]; echo $?
0

3. 인용부호 사용시 주의사항

linux:/home/shkim$ VAR=; if [ $VAR = "" ];then echo true; else echo false;fi
-bash: [: =: unary operator expected
false

#  위의 내용을 해석하면 다음과 같음 [   = ""]
linux:/home/shkim$ VAR=; if [ "$VAR" = "" ];then echo true; else echo false;fi
true

#  인용부호를 사용하여 해결
linux:/home/shkim$ VAR=; if [[ $VAR = "" ]];then echo true; else echo false;fi
true

# [[ ]] 테스트 부호를 입력하여 인용문자 생략 할 수 있음.

4. 비교 메타 문자들

linux:/home/shkim/a$ touch hello.txt
linux:/home/shkim/a$ if [ ! -f "hello.txt.bak" ]; then
>  cp "hello.txt" "hello.txt.bak"
> fi
linux:/home/shkim/a$ ls
hello.txt  hello.txt.bak

# 현재 디렉토리에 hello.txt.bak 파일이 없으면 hello.txt 파일을 hello.txt.bak 복사본 생성
# -f // 파일의 존재여부 확인 할 때 사용
Name Description
-e FILE 파일이 있는 경우 True 입니다.
-f FILE 파일이 일반 파일인 경우 True 입니다.
-d FILE 파일이 디렉터리인 경우 True 입니다.
-h FILE 파일이 심볼 링크인 경우 True 입니다.
-p PIPE 파이프가 있는 경우 True 입니다.
-r FILE 사용자가 파일을 읽을 수 있는 경우 True 입니다.
-e FILE 파일이 있는 경우 True 입니다.
-s FILE 파일이 존재하며 비어 있지 않은 경우 True 입니다.
-t FD 터미널에서 FD가 열려 있는 경우 True 입니다.
-w FILE 사용자가 파일을 쓸 수 있는 경우 True 입니다.
linux:/home/shkim$ if (( $? )); 
>    then echo 'Please run using "bash" or "sh", but not "." or "source"' >&2; 
>    exit 1; 
>fi

# 앞선 명령어가 비정상 실행 일 경우 실행을 무효화 시키는 스크립트.
# 어떤 경로에 파일 목록이 있는지 확인
linux:/home/shkim$ if [[ $(ls -A) ]]; then
>     echo "there are files"
> else
>     echo "no files found"
> fi
there are files
linux:/home/shkim/shell_cmd$ cat sleep.sh 
#!/bin/bash

while true; do
  sleep 1;
done

linux:/home/shkim/shell_cmd$ ps
  PID TTY          TIME CMD
10386 pts/0    00:00:00 bash
11304 pts/0    00:00:00 sleep.sh
11417 pts/0    00:00:00 sleep
11423 pts/0    00:00:00 ps

linux:/home/shkim/shell_cmd$ result=`ps aux | grep -i "sleep.sh" | grep -v "grep" | wc -l`
linux:/home/shkim/shell_cmd$ if [ $result -ge 1 ] # 크거나 같다 (greater and equal)
> then
>     echo "script is running"
> else
>     echo "script is not running"
> fi
script is running
linux:/home/shkim/shell_cmd$ ps
  PID TTY          TIME CMD
10386 pts/0    00:00:00 bash
11575 pts/0    00:00:00 sleep.sh
12021 pts/0    00:00:00 sleep
12027 pts/0    00:00:00 ps
linux:/home/shkim/shell_cmd$ kill 11575 12021
-bash: kill: (12021) - No such process
[1]+  Terminated              ./sleep.sh
linux:/home/shkim/shell_cmd$ ps
  PID TTY          TIME CMD
10386 pts/0    00:00:00 bash
12077 pts/0    00:00:00 ps
linux:/home/shkim/shell_cmd$ result=`ps aux | grep -i "sleep.sh" | grep -v "grep" | wc -l`
linux:/home/shkim/shell_cmd$ if [ $result -ge 1 ]; then     echo "script is runngin"; else     echo "script is not running"; fi
script is not running

[ ] 을 이용한 TEST

Name Description
-x FILE 파일이 실행 가능한 경우 True 입니다.
-O FILE 파일이 사용자가 효과적으로 소유하는 경우 True 입니다.
-G FILE 파일이 그룹에 의해 효과적으로 소유되는 경우 가능한 경우 True 입니다.
FILE -nt FILE 첫 번째 파일이 두 번째 파일보다 최신이면 경우 True 입니다.
FILE -ot FILE 첫 번째 파일이 두 번째 파일보다 오래된 경우 True 입니다.
-z STRING 문자열이 비어 있으면 True 입니다.(길이가 0임)
-n STRING 문자열이 비어있지 않은 경우 True 입니다.(길이 0이 아님)
STRING = STRING 첫 번째 문자열이 두 번째 문자열과 동일한 경우 True 입니다.
STRING != STRING 첫 번째 문자열이 두 번째 문자열과 동일하지 않은 경우 True 입니다.
STRING < STRING 첫 번째 문자열이 두 번째 문자열보다 먼저 정렬되는 경우 True 입니다.
STRING > STRING 첫 번째 문자열이 두 번째 문자열 뒤에 정렬되는 경우 True 입니다.
EXPR -a EXPR 두 식이 모두 참이면 참입니다.(logical AND)
EXPR -o EXPR 두 식 중 하나가 참이면 참입니다.(logical OR)
! EXPR 표현식의 결과를 반전합니다.(logical NOT)
INT -eq INT 두 정수가 동일한 경우 True 입니다.
INT -ne INT 정수가 동일하지 않은 경우 True 입니다.
INT -lt INT 정수가 동일하지 않은 경우 True 입니다.
INT -gt INT 첫 번째 정수가 두 번째 정수보다 큰 경우 True 입니다.
INT -le INT 첫 번째 정수가 두 번째 정수보다 작거나 같으면 True 입니다.
INT -ge INT 첫 번째 정수가 두 번째 정수보다 크거나 같은 경우 True 입니다.
STRING =(or ==) PATTERN [ 과 같은 문자열 비교는 아니지만 패턴 일치가 수행됩니다. 문자열이 글로브 패턴과 일치하는 경우 Treu 입니다.
STRING != PATTERN [ 과 같은 문자열 비교는 아니지만 패턴 일치가 수행됩니다. 문자열이 글로브 패턴과 일치하지 않는 경우 True 입니다.
STRING =~ REGEX 문자열이 regex 패턴과 일차히는 경우 True 입니다.
(EXPR) 괄호를 사용하여 평가 우선 순위를 변경 할 수 있습니다.
EXPR && EXPR 테스트의 -a 연산자와 매우 유사하지만 첫 번째 표현식이 이미 거짓으로 판명되면 두 번째 표현식을 평가하지 않습니다.
EXPR || EXPR 테스트의 -o 연산자와 매우 유사하지만 첫 번째 표현식이 이미 사실인 경우 두 번째 표현식을 평가하지 않습니다.

[ ] 보다는 [[ ]]를 사용하는 것이 좋습니다.

실습(DRILL)

  • 스크립트 뒤에 전달인자가 있어야 실행되는 스크립트를 작성해 보세요.
#!/bin/bash

if [ -z "$1" ] ; then
    echo "usage: $0 directory"
    exit
fi
    echo "Have a nice day!"

6. while 루프

# 1초에 한번씩 Hello world 출력
linux:/home/shkim/a$ while true; do
> echo "Hello world"
> sleep 1
> done
Hello world
Hello world
Hello world
^C
linux:/home/shkim/a$ 
# 1초에 한번씩 비프음 발생
linux:/home/shkim/a$ while true; do
> echo -n -e "\a";
> sleep 1;
> done
linux:/home/shkim/a$ for no in `eval echo {0..$COUNT}`; do
> echo $no
> done
0
1
2
3
4
5
6
7
8
9
10
linux:/home/shkim/a$ 
linux:/home/shkim/a$ eval echo {0..$COUNT}
0 1 2 3 4 5 6 7 8 9 10
linux:/home/shkim/a$ 

실습(DRILL)

  • 1부터 100까지 더하는 스크립트를 작성해 보세요.
#!/bin/bash

sum=0
for num in {1..100}; do
    (( sum = sum+num ))
done 

echo "SUM=$sum"

7. for..in 루프

linux:/home/shkim$ classroom=(desk pen note chair book)
linux:/home/shkim$ echo ${classroom[@]}
desk pen note chair book

# ! 는 배열의 인덱스 참조 / 배열의 요소를 가져오는 것이 아니고 인덱스 값을 얻어오는 방법
linux:/home/shkim$ for i in ${!classroom[@]} ; do
>     if [ "${classroom[$i]}" = 'pen' ] ; then
>         classroom[$i]=''
>     fi
> done
linux:/home/shkim$ echo ${classroom[@]}
desk note chair book
linux:/home/shkim$ echo ${!classroom[@]}
0 1 2 3 4

실습(DRILL)

# 이미지 매직을 사용하여 locale 이미지 파일 만들기
# imagemagick 설치 후 진행
for locale in en es fr ja ko th vi zh-cn zh-tw; do convert -size 256x256 -background transparent -gravity Center -fill black -font arial.ttf -pointsize 240 label:${locale} ${locale}.png; done

8. for((;;)) 루프

linux:/home/shkim$ mystr="Hello world"
linux:/home/shkim$ for((i=0;i<${#mystr};i++)); do
>     c="${mystr:$i:1}"
>     echo "$c"
> done
H
e
l
l
o
 
w
o
r
l
d

9. 명령어(date)

linux:/home/shkim$ date +"%Y-%m-%d"
2021-10-01
linux:/home/shkim$ date +"%Y/%m/%d"
2021/10/01
linux:/home/shkim$ date +"%Y-%m-%d %r"
2021-10-01 12:30:58 AM
linux:/home/shkim$ date +"%Y-%m-%d %H:%M"
2021-10-01 00:31
linux:/home/shkim$ date +"%Y-%m-%d %H:%M %A"
2021-10-01 00:31 Friday
linux:/home/shkim$ date "+DATE: %Y-%m-%d%nTIME: %H:%M:%S"
DATE: 2021-10-01
TIME: 00:32:39

실습(DRILL)

# command line 시계만들기
#!/bin/bash

while true; do
    TIME=$(date "+DATE: %Y-%m-%d %H:%M:%S")
    echo -ne "$TIME\r"
    sleep 1
done

10. 루프문과 glob

  • 오류 1
linux:/home/shkim/mydir$ ll
total 0
-rw-rw-r-- 1 shkim shkim 0 Oct 17 21:18 For Whom the Bell Tolls.mp3
-rw-rw-r-- 1 shkim shkim 0 Oct 17 21:19 Gone With the Wind.mp3
-rw-rw-r-- 1 shkim shkim 0 Oct 17 21:19 old man and ?the sea.mp3
linux:/home/shkim/mydir$ for file in $(ls *.mp3);do
> rm "$file"
> done
rm: cannot remove ‘For’: No such file or directory
rm: cannot remove ‘Whom’: No such file or directory
rm: cannot remove ‘the’: No such file or directory
rm: cannot remove ‘Bell’: No such file or directory
rm: cannot remove ‘Tolls.mp3’: No such file or directory
rm: cannot remove ‘Gone’: No such file or directory
rm: cannot remove ‘With’: No such file or directory
rm: cannot remove ‘the’: No such file or directory
rm: cannot remove ‘Wind.mp3’: No such file or directory
rm: cannot remove ‘old’: No such file or directory
rm: cannot remove ‘man’: No such file or directory
rm: cannot remove ‘and’: No such file or directory
rm: cannot remove ‘?the’: No such file or directory
rm: cannot remove ‘sea.mp3’: No such file or directory

# 실패원인 : 파일에 띄워쓰기가 들어가 각각의 분리된 값이 $file 변수에 대입됨.
  • 오류 2
linux:/home/shkim/mydir$ for file in "$(ls *.mp3)"; do
> rm "$file";
> done
rm: cannot remove ‘For Whom the Bell Tolls.mp3\nGone With the Wind.mp3\nold man and ?the sea.mp3’: No such file or directory

# 실패원인 : 전체파일이 하나의 문자열로 취급됨.
  • 성공
linux:/home/shkim/mydir$ ls
For Whom the Bell Tolls.mp3  Gone With the Wind.mp3  old man and ?the sea.mp3
linux:/home/shkim/mydir$ for file in *.mp3 ; do # 인용문자가 필요치 않음.
> rm "$file" # 인용문자를 꼭 해줘야한다.
> done
linux:/home/shkim/mydir$ ls
linux:/home/shkim/mydir$

# 쉴스크립트에서 인용부호를 사용해야 할 때와 사용하지 않아야 할때를 구분할 줄 아는 것은 매우 중요합니다.
linux:/home/shkim/mydir$ for file in "*.mp3" ; do
> rm "$file"
> done
rm: cannot remove ‘*.mp3’: No such file or directory
linux:/home/shkim/mydir$ ls

# * 를 사용할 때는 " " 인용 부호를 사용하면 안된다.

실습(DRILL)

  • for 문을 이용하여 /shell_cmd/images 파일의 백업본을 만들어보세요.
for file in *; do cp ${file}{,.bak}; done

11. 명령어(seq)

linux:/home/shkim$ seq 1 10
1
2
3
4
5
6
7
8
9
10
linux:/home/shkim$ seq 0 2 10
0
2
4
6
8
10
linux:/home/shkim$ seq -s, 1 1 10
1,2,3,4,5,6,7,8,9,10
linux:/home/shkim$ seq -s, 1 2 10
1,3,5,7,9
linux:/home/shkim$ seq 10 -1 1
10
9
8
7
6
5
4
3
2
1

실습(DRILL)

: seq 와 printf 그리고 for을 이용하여 다음처럼 출력하도록 프로그램 하세요.

001 002 003 004 005 006 007 008 009 010
#!/bin/bash
for num in $(seq 1 10) ; do
    printf "%03d\t" $num
done
echo

12. case

linux:/home/shkim$ read -p "Enter any string: "
Enter any string: abc
linux:/home/shkim$ case $REPLY in 
> +([[:digit:]]) ) echo "digits" ;;
> *) echo "not digits" ;;
> esac
not digits
linux:/home/shkim$ read -p "Enter any string: "
Enter any string: 123
linux:/home/shkim$ case $REPLY in  +([[:digit:]]) ) echo "digits" ;; *) echo "not digits" ;; esac
digits

실습(DRILL)

: 사용자에게 Y/N 입력을 받아 종료하는 스크립트를 만들어보세요.

#!/bin/bash

read -s -n 1 -p "You really want to exit ?" response

case $response in
    y|Y) echo "YES";;
    n|N) echo "NO";;
    *) kill -SIGKILL $$;;
esac

13. getopts

  • 하이픈 (-)으로 전달인자를 전달받아 처리하는 방식
linux:/home/shkim/shell_cmd$ vi Anaconda2-2.4.1-MacOSX-x86_64.sh 
while getopts "bfhp:z:" x; do
    case "$x" in
        h)
            echo "usage: $0 [options]

Installs Anaconda2 2.4.1

    -b           run install in batch mode (without manual intervention),
                 it is expected the license terms are agreed upon
    -f           no error if install prefix already exists
    -h           print this help message and exit
    -p PREFIX    install prefix, defaults to $PREFIX

linux:/home/shkim/shell_cmd$ Anaconda2-2.4.1-MacOSX-x86_64.sh -b -f # 이런 식으로 사용하는 것을 의미

14. select

linux:/home/shkim/shell_cmd$ movies=("Avengers" "Matrix" "Titanic")
linux:/home/shkim/shell_cmd$ PS3="Please select your favorite movie: "
linux:/home/shkim/shell_cmd$ select movie in ${movies[@]}
> do
>     echo "$movie selected"
> done
1) Avengers
2) Matrix
3) Titanic
Please select your favorite movie: 1
Avengers selected
Please select your favorite movie: 2
Matrix selected
Please select your favorite movie: 3
Titanic selected
Please select your favorite movie: ^C
linux:/home/shkim/shell_cmd$ 
#!/bin/bash

movies=("Avengers" "Matrix" "Titanic", "None")
PS3="Please select your favorite movie: "

select movie in ${movies[@]} ; do
    case $movie in 
        "None") echo "My favorite movie is not on the list. quit ";break;;
        *) echo "$movie selected";;
    esac
done
inux:/home/shkim/shell_cmd$ ./select.sh 
1) Avengers
2) Matrix
3) Titanic,
4) none
Please select your favorite movie: 1
Avengers selected
Please select your favorite movie: 2
Matrix selected
Please select your favorite movie: 3
Titanic, selected
Please select your favorite movie: 4
none selected
Please select your favorite movie: 5
 selected
Please select your favorite movie: ^C