Run The Bridge

shell script master -5- 본문

Cloud/Linux

shell script master -5-

anfrhrl5555 2022. 1. 21. 00:21
728x90

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

if문은 'if 조건문; then fi'으로 이루어져 있다.

if true; then
echo true
else
echo false
fi

---

다음과 같이 한 줄로도 사용할 수 있다.

if true; then echo true; else echo false; fi
true

if문은 사용자의 용도에 따라 다양하게 사용되므로 천천히 배워보자.


1. [..] vs [[..]]

tom, deniro라는 변수에 값을 할당한다.

 

그 후 대괄호 비교를 사용해 두 문자열을 비교한다.

tom="Tom hanks"
deniro="Robert Deniro"
[ $tom > $deniro ]  
-bash: $deniro: ambiguous redirect  # 오류 발생

※ [..] 안에서 '<,>'문자는 redirect로 해석된다.

 

다음과 같은 방법으로 해결할 수 있다.

바로 대괄호 두 개를 사용해준다. 이러면 비교로 사용할 수 있다.

[[ $tom > $deniro ]]
echo $?
0

---

'=' 기호를 사용해도 오류가 나온다.

[ $tom = $deniro ]
-bash: [: too many arguments

다음과 같은 방법으로 해결할 수 있다. 인용부호("")를 사용해준다.

[ "$tom" = "$deniro" ]
echo $?
1

※ TIp. [[]]를 사용하면 ""를 생략해도 된다.


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

다음과 같은 명령어를 입력한다.

 

VAR에 빈 문자로 초기화하였다.

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

스크립트를 작성해서 돌려보면 $VAR에 어떤 값이 들어가는지 알 수 있다. 

 bash -x VAR.sh
+ VAR=
+ '[' = '' ']'
VAR.sh: line 3: [: =: unary operator expected
+ echo false
false

우리는 다음과 같은 방법으로 해결해 줄 수 있다. → 인용부호("")를 사용해준다.

VAR=; if [ "$VAR" = "" ];then echo true; else echo false;fi
true

스크립트를 수정하고 다시 돌려본다.  → 빈 문자가 입력되었다는 걸 알 수 있다.

bash -x VAR.sh
+ VAR=
+ '[' '' = '' ']'
+ echo true
true

위에서 마찬가지로 '[[]]'를 사용해서 생략 가능하다.

VAR=; if [[ $VAR = "" ]]; then echo true; else echo false; fi
true

3. 비교 메타 문자열

if [ ! -f "hello.txt.bak" ]; then  # hello.txt.bak라는 파일이 존재하지 않으면
> cp "hello.txt" "hello.txt.bak"  # hello.txt를 hello.txt.bak으로 복사한다.
> fi  # if문 종료

복사가 되었다는 것을 알 수 있다.

-rw-r--r--.  1 root root     6 Jan 23 23:02 hello.txt.bak

-f 이외에 사용하는 메타 문자는 다음과 같다.

Name Description
-e FILE 파일이 있는 경우 True
-f FILE 파일이 일반 파일인 경우 True
-d FILE 파일이 디렉터리인 경우 True
-h FILE 파일이 심볼 링크인 경우 True
-p FILE 파이프가 있는 경우 True
-r FILE 사용자가 파일을 읽을 수 있는 경우 True
-s FILE 파일이 존재하며 비어 있지 않은 경우 True
-t FD 터미널에서 FD가 열려 있는 경우 True
-w FILE 사용자가 파일을 쓸 수 있는 경우 True

---

예시를 들어보자.

 if (( $? )); then  # 먼저 사용한 조건이 거짓에 한해서
> echo 'Please run using "bash" or "sh", but not "." or "source"' >&2  # echo가 실행된다.
> exit 1
> fi

---

디렉터리에 파일이 존재하는지를 체크하는 if문 + 명령문을 if문안에 넣을 때는 $()로 묶어준다는 것도 알고 가자.

if [[ $(ls -A) ]]; then
>	 echo "there are files"
> else
>	 echo "no files found"
> fi
there are files

---

다음과 같은 sleep.sh를 실행한다.

#!/bin/bash

while true; do
  sleep 1;
done
./sleep.sh &  # 백그라운드로 실행
ps
   PID TTY          TIME CMD
 73865 pts/2    00:00:00 bash
116149 pts/2    00:00:00 sleep.sh
116217 pts/2    00:00:00 ps
result=`ps aux | grep -i "sleep.sh" | grep -v "grep" | wc -l`  # sleep.sh 파일이 동작하는지 확인하는 스크립트
if [ $result -ge 1 ]; then echo "script is running"; else echo "script is not running"; fi
script is running

다시 동작중인 sleep를 kill 명령어로 죽이고 다시 명령어를 입력해본다.

---

result에 변수값이 이미 할당되어 있어서 재입력해주고 if문을 실행한다.

result=`ps aux | grep -i "sleep.sh" | grep -v "grep" | wc -l`  # sleep.sh 파일이 동작하는지 확인하는 스크립트
if [ $result -ge 1 ]; then echo "script is running"; else echo "script is not running"; fi
script is not running

예상대로 실행하지 않다고 나오는 것을 볼 수 있다.

Name Description
-x FILE 파일이 실행 가능한 경우 True
-O FILE 파일이 사용자가 효과적으로 소유하는 경우 True
-G FILE 파일이 그룹에 의해 효과적으로 소유되는 경우 True
FILE -nt FILE 첫 번째 파일이 두 번째 파일보다 최신이면 True
FILE -ot FILE 첫 번째 파일이 두 번째 파일보다 오래된 경우 True
-z STRING 문자열이 비어 있으면 True(length =0 0)
-n STRING 문자열이 비어 있지 않은 경우 True
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
Name Description
STRING = (OR ==) PATTERN `[`과 같은 문자열 비교는 아니지만 패턴 일치가 수행된다.
문자열이 글로브 패턴과 일치하는 경우 True
STRING != PATTERN `[`과 같은 문자열 비교는 아니지만 패턴 일치가 수행된다.
문자열이 글로브 패턴과 일치하지 않은 경우 True
STRING =~ REGEX 문자열이 regex 패턴과 일치하는 경우 True
(EXPR) 괄호를 사용하여 평가 우선 순위를 변경할 수 있다.
EXPR && EXPR 테스트의 `-a` 연산자와 매우 유사하지만 첫 번째 표현식이 이미 거짓으로 판명되면 두 번쨰 표현식을 평가하지 않는다.
EXPR || EXPR 테스트의 `-o` 연산자와 매우 유사하지만 첫 번째 표현식이 이미 사실인 경우 두 번쨰 표현식을 평가하지 않는다.

4. 실습(DRILL)

사용자의 전달 인자를 체크하여 사용법을 출력하도록 하는 스크립트를 만들어본다.

 ./goodday.sh  # 전달인자 x
usage: ./goodday.sh directory

./goodday.sh dir  # 전달인자 o
Have a nice day!

---

간단하게 전달인자 1개를 보고, 그 값이 -z(zero)이면 if문, 아닐 시 else문을 출력한다.

#!/bin/bash

if [ -z $1 ];then
        echo "you need to input factor"
else
        echo "Your input factor ${1}"
fi

친절하게 어떤 인자를 입력했는지 알려준다 ㅎㅎ

./factor.sh
you need to input factor

./factor.sh test
Your input factor test

5. while 루프

while true; do done

---

hello world를 1초마다 찍는 while문

while true; do
> echo "hello world"
> sleep 1
> done
hello world
hello world
hello world
hello world

---

while문에서 잠깐 벗어났지만, eval을 사용하면 {}안에 $COUNT가 변수로 선언되어 동작한다.

for no in `eval echo {0..$COUNT}`; do
> echo $no
> done
0
1
2
3
4
5
6
7
8
9
10

---

중괄호 확장을 사용할 땐 위에서 배웠듯이 eval을 사용해야 한다.

 eval echo {0..$COUNT}
0 1 2 3 4 5 6 7 8 9 10

---


6. DRILL

1부터 10까지 더하는 함수를 만들어본다.

 

나는 약간 변형해서 사용자의 입력만큼 더하는 스크립트를 작성했다.

#!/bin/bash

result=0  # 빈 값 선언

read -p "your favorite number: "  # 사용자의 입력

for num in `eval echo {0..$REPLY}`  # 해당 값 만큼 for문 진행
do
        result=$(($num + $result))  # 더하기
done

echo $result  # 출력

하면서 추가로 배웠던 점이..물론 앞에서 했겠지만 

 

1. linux에서 변수를 할당할 때 띄어쓰기가 있으면 안된다.(result = 0, x)

 

2. linux 더하기는 $(($ + $))로 구성된다. 물론 다른방법이 존재하겠지만.

 

3. eval를 쓰고 {0..$REPLY)를 사용할 때 앞에 echo가 존재해야 한다.

 

 ./while_DRILL.sh
your favorite number: 5
15

./while_DRILL.sh
your favorite number: 10
55

7. for..in 루프

classroom=(desk pen note chair book)

---

배열 내용 출력

echo ${classroom[@]}
desk pen note chair book

---

배열에 pen이 들어가 있으면 공백으로 바꾸는 코드

for i in ${!classroom[@]};  # '!'를 사용하여 배열의 참조 인덱스 값을 가져온다.
do 
	if [ "${classroom[$i]}" = 'pen' ]; then
    	classroom[$i]='';     
    fi; 
done
echo ${classroom[@]}
desk note chair book

---

이 부분을 조금 더 파헤쳐보자.

일단 다시 배열을 선언해준다.

for i in ${!classroom[@]};

예상 했던대로 참조 인덱스 값이면 0, 1, 2, 3, 4 였다. 

파이썬의 len참수를 사용하면 0, len 길이만큼 가져올 수 있는데, linux의 length라고 봐도 될 듯 하다.

for i in ${!classroom[@]}; do
> echo $i
> done
0
1
2
3
4

8. 실습(DRILL)

imagemagick을 이용하여 PNG 파일을 만들어보세요.

 - 이미지 편집

 - 이미지 필터

 - 이미지 합성

PASS

 


9. for((;;)) 루프

c언어의 for loop문을 똑같이 사용할 수 있다.

for((i=0;i<${#mystr};i++)); do
>     c="${mystr:$i:1}"
>     echo "$c"
> done
H
e
l
l
o

W
o
r
l
d

---

#을 써서 띄어쓰기 포함 문자열의 length을 얻어올 수 있다.

echo ${#mystr}
11

---

mystr="Hello World"
       012345678910
echo ${mystr:1:1}  # 1번째 글자부터 1글자 출력
e

echo ${mystr:2:1}  # 2번째 글자부터 1글자 출력
l

echo ${mystr:2:2}  # 2번째 글자부터 2글자 출력
ll

echo ${mystr:2:3}  # 2번째 글자부터 글자 출력
llo

10. 명령어(date)

date +"%Y-%m-%d"  # 년, 월, 일 출력
2022-01-26

---

구분 기호는 자유롭게 사용하면 된다.

 date +"%Y/%m/%d"
2022/01/26

---

시간도 뽑아낼 수 있다.

date +"%Y-%m-%d %r"
2022-01-26 12:46:47 AM

---

요일도 뽑아 낼 수 있다.

date +"%Y-%m-%d %H:%M %A"
2022-01-26 00:47 Wednesday

---

다음과 같이 형식화된 출력도 가능

date "+DATE: %Y-%m-%d%nTIME: %H:%M:%S"
DATE: 2022-01-26
TIME: 00:48:13

13. 실습(DRILL)

커맨드라인 시계를 만들어보세요

 

나는 그냥 좀 이상하게 만들었다..

while true; do
        date "+DATE: %Y-%m-%d %H:%M:%S"
        sleep 1
        clear
done

---

변수에 넣어서 출력해야 되는 것 까진 생각했는데, 커맨드 라인을 보존하면서 출력하는게 어려웠다.

 

근데 강사님은 echo문을 써서 만들었다.

#/bin/bash

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

14. 루프 문과 glob

다음과 같이 해당 파일을 for문을 이용해 지워본다.

root@kube-master ~/shell_script/mydir # ll
total 0
-rw-r--r--. 1 root root 0 Jan  6 22:12 Gone
-rw-r--r--. 1 root root 0 Jan  6 22:12 Gone With the Wind.mp3
-rw-r--r--. 1 root root 0 Jan  6 22:12 the
-rw-r--r--. 1 root root 0 Jan  6 22:12 Wind.mp3
-rw-r--r--. 1 root root 0 Jan  6 22:12 With
for file in $(ls *.mp3); do
    rm "$file"
done

---

이게 강사님 영상에는 안 지워지는데, 버전에 따라 달라지나..? 삭제되었다

root@kube-master ~/shell_script/mydir # ll
total 0
-rw-r--r--. 1 root root 0 Jan  6 22:12 Gone With the Wind.mp3
-rw-r--r--. 1 root root 0 Jan  6 22:12 the
-rw-r--r--. 1 root root 0 Jan  6 22:12 Wind.mp3
-rw-r--r--. 1 root root 0 Jan  6 22:12 With

---

아래의 간단한 방법으로 삭제할 수 있다.

for file in *.mp3 do
	rm "$file"
done

---

아래 명령을 실행해 file에 어떤 값이 담기는지 확인한다.

for file in *.mp3
do
	echo ${file}
done
for file in *.mp3
> do
>     echo ${file}
> done
Gone With the Wind.mp3
Wind.mp3

15. 실습(DRILL)

for문을 사용해 현재 경로내 모든 파일의 백업파일을 만들어 보세요

ll
total 0
-rw-r--r--. 1 root root 0 Jan  6 22:16 Balloon.jpg
-rw-r--r--. 1 root root 0 Jan  6 22:16 Candy.jpg
-rw-r--r--. 1 root root 0 Jan  6 22:16 glob.gif
-rw-r--r--. 1 root root 0 Jan  6 22:16 settings_down.png
-rw-r--r--. 1 root root 0 Jan  6 22:16 settings_up.png
-rw-r--r--. 1 root root 0 Jan  6 22:16 shadingimage.tiff
-rw-r--r--. 1 root root 0 Jan  6 22:16 smaller.tiff

---

성공?

total 24
-rw-r--r--.  1 root root     0 Jan  6 22:16 settings_up.png
-rw-r--r--.  1 root root     0 Jan  6 22:16 settings_down.png
-rw-r--r--.  1 root root     0 Jan  6 22:16 glob.gif
-rw-r--r--.  1 root root     0 Jan  6 22:16 Candy.jpg
-rw-r--r--.  1 root root     0 Jan  6 22:16 Balloon.jpg
-rw-r--r--.  1 root root     0 Jan  6 22:16 smaller.tiff
-rw-r--r--.  1 root root     0 Jan  6 22:16 shadingimage.tiff
drwxr-xr-x. 14 root root  4096 Jan 26 01:09 ..
-rwxr-xr-x.  1 root root    94 Jan 26 01:24 bakcup.sh
-rw-r--r--.  1 root root     0 Jan 26 01:24 Balloon.jpg.bak
-rw-r--r--.  1 root root     0 Jan 26 01:24 Candy.jpg.bak
-rw-r--r--.  1 root root     0 Jan 26 01:24 settings_down.png.bak
-rw-r--r--.  1 root root     0 Jan 26 01:24 glob.gif.bak
-rw-r--r--.  1 root root     0 Jan 26 01:24 shadingimage.tiff.bak
-rw-r--r--.  1 root root     0 Jan 26 01:24 settings_up.png.bak
-rw-r--r--.  1 root root     0 Jan 26 01:24 smaller.tiff.bak
-rw-------.  1 root root 12288 Jan 26 01:24 .bakcup.sh.swp
drwxr-xr-x.  2 root root  4096 Jan 26 01:25 .

---

script는 다음과 같다.

 

위에서 알려주셨듯이 확장자 명이 다양해서 *.*로 모든 파일을 backup이라는 변수에 하나씩 넣고

 

특정 변수에 할당시킨다.

 

그리고 cp명령으로 복사하면 끗~

#!/bin/bash

for backup in *.*
do
        backup_file=$backup
        cp ${backup} ${backup_file}.bak
done

16. 명령어(seq)

seq(시크)라는 명령어는 순서를 출력하기 아주 좋은 명령어

seq 1 10
1
2
3
4
5
6
7
8
9
10

---

숫자를 2단계씩 건너뛸 수 있다. (파이썬에도 리스트를 출력할 때 인덱스 번호 컨트롤로 존재하죠? 0:10:2)

seq 0 2 10
0
2
4
6
8
10

---

-s(separate)를 사용해서 구분 기호 콤마(,)를 추가할 수 있다.

 seq -s, 1 1 10
1,2,3,4,5,6,7,8,9,10

---

숫자를 감소시키는 것 도 가능하다.

seq -s, 10 -1 1
10,9,8,7,6,5,4,3,2,1

17. 실습(DRILL)

seq와 printf 그리고 for을 이용하여 다음을 출력하세요

001		002		003		004		005		006		007		008		009		010

 

---

간단하게 해결할 수 있었다.

printf "%03d\t" 라는 유용한 기능이 정말 시간 단축을 많이 시켜 주었다.

#!/bin/bash

read -p "Your favorite number: "  # 변수를 선언하지 않으면 $REPLY에 들어 가는거 아시죠? ㅎㅎ

for i in `eval echo {1..$REPLY}`
do
        printf "%03d\t" $i
done

이렇게 사용자 입력에 맞게 출력이 가능하다.


18. case

read -p "enter any string: "  # 아무 글자나 입력합니다.

c언어의 case문과는 좀 다르게 헷갈린다.

 

')'을 기준으로 case를 적고 조건을 명시한다.

'*)'을 적어주면 'if else'문에서 else구문이라고 보면 된다.

마지막에는 'case'를 뒤짚은 'esac'를 적어주면 된다.

그리고 ';;' 2개 적는거 잊으면 안된다!

case $REPLY in
	+([[:digit:]]) ) echo "digits" ;;
    *) echo "not digits" ;;
esac

솔직히 이 예시보다..구글에서 더 간단한 예시를 보는게 이해가 빠를 듯 하다.


19. 실습(DRILL)

PASS
#!/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

20.  getopts(부족)

사용자로부터 전달받은 인자를 처리하기 위해 사용한다.

 

전달인자는 파이프를 통해서 넘겨받을 수 있다.

-b -h -f -p 1  # 모두 사용자 정의로 가능하다.

21.  selects

다음과 같은 배열을 선언한다.

movies=("Avengers" "Matrix" "Titanic")

---

PS3라는 변수를 생성한다.

PS3="Please select your favorite moive: "

---

select문은 for문이랑 매우 유사하다.

select movie in ${movies[@]}
> do
>     echo "$movie selected"
> done
1) Avengers
2) Matrix
3) Titanic

Please select your favorite moive: 1
Avengers selected

사용자에게 메뉴를 출력하고, 메뉴를 선택 받는다.

---

이번엔 마지막 배열에 "None"을 추가했다.

movies=("Avengers" "Matirx" "Titanic", "None")
PS3="Please select your favorite moive: "

추가로 case문을 사용하여 None을 선택하면 break을 추가할 수 있다.

select moive in ${movies[@]}
do
    case $movie in
      "None") echo " My favorite moive is not on the list. quit";break;;
      *) echo "$movie selected";;
    esac
done

아.....값이 제대로 안나온다..

 

→ 02. 03일 발견.. moive라고 오타가 났었다.... ㅋㅋ

내일 해결 해야겠다.. 넘 피곤혀..

 

다시 해보니 또 잘된다..

select movie in ${movies[@]}
> do
>     case $movie in
>       "None") echo "My Favorite moive is not on the list. quit";break;;
>       *) echo "$movie selected";;
>     esac
> done
1) Avengers
2) Matirx
3) Titanic,
4) None
Please select your favorite moive: 1
Avengers selected
Please select your favorite moive: 2
Matirx selected
Please select your favorite moive: 3
Titanic, selected
Please select your favorite moive: 4
My Favorite moive is not on the list. quit

감사합니다.

728x90

'Cloud > Linux' 카테고리의 다른 글

shell script master -7-  (0) 2022.01.27
shell script master -6-  (0) 2022.01.27
shell script master -4-  (0) 2022.01.13
i-node 실습  (0) 2022.01.12
shell script master -3-  (0) 2022.01.05
Comments