사이트 내 전체검색
[linux] 쉘 스크립트 (Shell Script) - (II)
로빈아빠
https://cmd.kr/server/334 URL이 복사되었습니다.

본문

쉘 스크립트 (Shell Script) - (II)

임종균 : hermes44@secsm.org / 서울대학교 컴퓨터공학과 /리눅스 프로그래머 

표준 입출력

이전 기사에서 표준 출력(stdout)으로 출력을 하기 위해서 echo 명령을 - 외부 프로그램으로 같은 역할을 하는 echo가 있지만 여기서의 echo는 쉘의 내장(built-in)명령이다. - 사용하였다. 그렇다면 표준 입력(stdin)으로 사용자 입력을 받을 수 있는 방법은? 표준 입력을 받아 변수에 저장을 해주는 read 명령이 있다.
 
$ cat ./stdio #!/bin/sh # # stdio: 표준 입력을 받아 표준 출력으로 표시한다. # echo -n “Type the filename: “ read filename if [ -e $filename ] then echo $filename exists. else echo $filename doesn\’t exist. fi $ ./stdio Type the filename: /dev/fd0 /dev/fd0 exists. $ ./stdio Type the filename: /dev/fd0 ./stdio ./stdio: [: /dev/fd0: binary operator expected /dev/fd0 ./stdio doesn’t exist.

stdio 예제에서 처럼 read는 표준 입력의 한 줄을 모두 filename이라는 변수에 넣는다. (한 줄은 엔터를 입력함으로서 끝난다.) 하지만 위와 같이 할 경우, 입력값이 여러 개라면 문제가 생길 수 있다. 두 개의 파일명을 공백으로 구분하여 입력할 경우 각각이 따로 저장되는 것이 아니라 하나의 filename 변수에 들어가기 때문에 if 조건식에서 실패를 한다. 이런 경우 가장 처음에 입력된 파일명만을 받아 처리하고 나머지는 무시하려면? read 다음에 변수를 여러 개를 명시하면 된다. 표준 입력은 공백 문자로 - 스페이스와 탭 - 구분이 되고 구분된 각각의 값들은 read 다음에 명시된 변수에 차례로 채워진다. 가장 마지막 변수에는 나머지 남아있는 입력값들이 모두 다 들어간다.
 
$ cat ./stdio2 #!/bin/sh # # stdio2: 표준 입력을 받아 표준 출력으로 표시한다. # echo -n “Type the filename: “ read filename1 filename2 dummy for fn in $filename1 $filename2 do if [ -e $fn ] then echo $fn exists. else echo $fn doesn\’t exist. fi done echo Ignore $dummy $ ./stdio2 Type the filename: /dev/hda1 /etc/bashrc /bin/sh /bin/ls /dev/hda1 exists. /etc/bashrc exists. Ignore /bin/sh /bin/ls

즉, stdio2의 예에서 공백으로 구분된 입력 /dev/hda1은 filename1에, /etc/bashrc는 filenam2에 /bin/sh /bin/ls는 dummy에 들어가게 되는 것이다.
암호를 입력받을 경우와 같이 잠시 키보드 입력이 화면에 표시되지 - 이렇게 입력이 화면에 출력되는 것을 echo라고 말한다. - 않게 하려면 stty 프로그램을 이용하여 echo를 없애면 된다. 후에 다시 복원을 해야한다. 스크립트가 echo를 다시 복원하지 못하고 비정상적으로 종료할 경우를 대비하여 반드시 trap 구문을 두어야 한다.
 
$ cat pswd #!/bin/sh # # pswd: 암호를 입력받는다. # trap ‘stty echo; exit’ 0 1 2 3 15 echo -n “Enter password: “ stty -echo read password stty echo echo echo “Your password is $password”


리다이렉션 (Redirection)

우리는 쉘 상에서 <, >, >>를 이용하여 표준 입출력을 리다이렉션(redirection)할 수 있다.
쉘 스크립트 상에서도 그와 같은 일이 가능하다. exec를 이용하는 것이다.
 
$ cat redirect #!/bin/sh # # redirect: 표준 입력을 파일로 리다이렉션한다. # temp=/tmp/delme$$ # $$는 현재 프로세스의 id값을 넘겨준다. echo “This is line1. This is line2. This is line3.” > $temp exec < $temp read line; echo $line read line; echo $line read line; echo $line $ ./redirect This is line1. This is line2. This is line3.

redirect 예제에서 exec 명령에 의해서 표준 입력이 /tmp/delme$$ 파일로 리다이렉션되어 read 명령은 그 파일에서 한 줄씩 입력을 받게 된다. 본 쉘의 리다이렉션에서 각 표준 입력, 표준 출력, 표준 에러는 파일 디스크립터 0, 1, 2에 해당한다. 그 외에 3~9까지 디스크립터를 사용할 수 있다.
 
$ cat fredirect #!/bin/sh # # fredirct: 동적인 리다이렉션 변경 # outfile=fredirect.out exec 3<&1 # 표준 출력과 3번 파일 디스크립터를 일치시킨다. # 표준 출력을 3번 파일 디스크립터에 저장해두는 역할 /bin/rm -f $outfile while echo -n “Enter command or CTRL-D to quit: “ read cmd do exec >> $outfile # 표준 출력을 fredirect.out 파일로 리다이렉션 echo $cmd exec >&3 # 파일로 변환된 것은 다시 표준 출력으로 복원 done $ ./fredirect Enter command or CTRL-D to quit: 21 Enter command or CTRL-D to quit: 234 Enter command or CTRL-D to quit: 2 Enter command or CTRL-D to quit: sjf Enter command or CTRL-D to quit: sd Enter command or CTRL-D to quit: kd Enter command or CTRL-D to quit: $ cat fredirect.out 21 234 2 sjf sd kd

while, until, for, if, case 구문에서 가장 마지막 줄에 - done, fi, esac 뒤에 - 리다이렉션을 지정하여 그 구문 내에서의 입출력만을 변경할 수 있다. 그 구문을 빠져나올 때에는 원래대로 복원이 된다.
 
$ cat bredirect #!/bin/sh # # bredirct: 특정 구문에서만 리다이렉션을 한다. # for arg do echo $arg done > bredirect.out 2> /tmp/bredirect.err # 에러를 표준 에러로 출력하고 임시 에러 파일을 지운다. if [ -s bredirect.err ] then /bin/cat /tmp/bredirect.err 1>&2 fi /bin/rm -f /tmp/bredirect.err $ ./bredirect 1 2 3 45 $ cat ./bredirect.out 1 2 3 45


경로명

쉘 스크립트를 이용하여서 파일을 다루다보면 경로명을 조작해야할 필요가 있다. 보통 경로명은 ‘디렉토리명/파일명’ 형식으로 되어 있다. 이렇게 사용자의 입력이나 환경 변수 등을 통해서 얻어진 경로명에서 디렉토리명와 파일명을 분리해주는 basename과 dirname이라는 프로그램이 있다.

basename <경로명>

은 경로명에서 파일명만을 넘겨준다. 이 때 파일이 실제로 존재할 필요는 없다.

basename <경로명> <확장자>

은 파일명에서 지정한 확장자 부분을 없앤다.

dirname <경로명>

은 경로명에서 디렉토리 부분만을 넘겨준다.
 
$ basename /home/httpd/index.html index.html $ basename /home/httpd/index.html .html index $ dirname /home/httpd/index.html /home/httpd


수식 계산 : expr

스크립트에서는 기본적인 모든 값들을 문자열로 처리하기 때문에 숫자값을 이용한 수식 계산을 하기 위해서는 expr 프로그램을 이용해야 한다.

expr 인자1 연산자 인자2 [연산자 인자3 ...]

인자는 숫자값이나 문자열이 될 수 있고, 연산자는 수식 연산자, 관계 연산자, 논리 연산자 세 가지 종류가 있다. 인자와 연산자 사이에는 반드시 공백으로 구분되어야만 한다. expr의 결과값이 0이 아니거나 null이 아니면 종료 상태값은 0이다. 0이나 null일 경우에는 1이고 수식이 유효하지 않을 경우는 2이다.

지원하는 수식 연산자는 +, -, *, /, %가 있고, 연산 순위는 일반적인 순위를 따른다. 연산 순위 변경을 위한 괄호 또한 사용할 수 있다. *, (, )는 쉘에서 특별히 사용되기 때문에 수식에서 사용하려면 \와 함께 쓰여야 한다. 수식 연산자가 올 때의 인자는 정수값이 되어야만 한다. 수식 계산의 결과값이 출력된다.
 
$ expr 3 + 5 \* 2 13 $ expr \( 3 + 5 \) \* 2 16 $ echo $1 1 $ expr $i + 1 2


지원하는 관계 연산자는 =, !=, >, >=, <, <= 이 있고, >와 <는 \과 함께 사용되어야 한다. 비교한 결과가 참이면 1을 출력하고, 거짓이면 0을 출력하다. 이 때 인자는 정수, 실수, 문자열이 모두 다 올 수 있다.
 
$ echo $USER hermes44 $ expr $USER = hermes44 1 $ expr 3 \> 5 0 $ expr 4.5 \<= 4.5 1

논리 연산자는 3가지가 있다. |, &, :이다. 각 OR, AND, 정규식 검색을 나타내며 |와 &는 와 함께 사용되어야 한다.

·|는 OR이다. 인자1과 인자2가 둘 다 0이 아니면 인자1을 출력하고 그렇지 않으면 0을    출력한다.
·&는 AND이다. 인자 1이 0이 아니면 인자 1을 출력하고, 그렇지 않으면 인자 2를 출력한다.
·:는 정규식(regular expression) 검색이다. 인자1에는 어떤 문자열이 오고, 인자2에는 정규식 이 온다. 인자1에서 인자2로 주어진 정규식에 해당하는 패턴을 찾는다.인자2를 \(와 \)로 괄호를 씌우면 패턴에 해당하는 인자1의 부분을 출력하고 괄호가 없을 때에는 패턴이 일치하는 회수를 출력한다.(정규식의 형식에 대한 사항은 이 기사의 범위를 넘어가기 때문에 생략한다).
 
$ echo $1 1 $ expr $i \> 5 \| $i + 1 2 $ expr $i \< 5 \& $i + 1 1 $ export d=`date`; echo $d Mon Oct 10 00:53:04 KST 1999 $ expr “$d” : ‘.*’ 28 $ expr “$d” : ‘\(.*\)’ Mon Oct 18 00:53:04 KST 1999 $ expr “$d” : ‘[a-zA-Z]*’ 3 $ expr “$d” : ‘\([a-zA-Z]*\)’ Mon


여러 프로그램을 하나의 스크립트로 많은 스크립트를 작성을 하다 보면 다음과 같은 경우가 발생할 수 있다.
·같은 동작을 하는 다른 이름의 프로그램들. 예를 들어 문서 편집기인 ex, vi, view는 이름은 다르지만 다 같은 프로그램이다. 하지만 어떤 이름으로 실행하느냐에 따라 동작이 조금씩 다르다. ex는 줄 편집기 모드로 시작하고, vi는 화면 편집기 모드로 시작한다. view는 읽기 전용 모드로 파일을 연다.
·다른 프로그램이지만 공통되는 부분이 많다. 예를 들어 jdk패키지의 들어있는 java, javac, javadoc, jar, jdb, appletview 등은 모두 .javawrapper 스크립트에 링크되어 있다. .javawrapper는 자바 환경을 설정을 하고 해당하는 프로그램을 실행시켜 준다.
이와 같은 경우에 공통되는 부분을 하나로 묶어 주게 된다면 디스크 공간을 절약할 수 있을 뿐만 아니라, 코딩에 드는 노력도 줄여줄 수 있고, 각 공통되는 부분에 대한 일관성을 유지할 수가 있다. 쉘 스크립트에서는 공통되는 부분을 처리하고 실행한 프로그램의 이름을 살펴보아 각각에 해당하는 처리를 한다.
공통 부분에 대한 핵심 스크립트를 만든다. 각각의 프로그램은 핵심 스크립트에 대한 링크로 만든다. 그 스크립트에서는 공통된 부분을 처리하고 프로그램의 이름, $0를 보고서 각 프로그램에 대한 작업을 수행한다.
 
$ cat sc_core #!/bin/sh # # sc_core: 공통되는 부분을 처리하는 핵심부분 # echo Setup the Environments and Flags case “$0” in *sc1) echo Excute sc1 ;; *sc2) echo Excute sc2 ;; *) echo Invalid commnad ! exit 1 ;; esac $ ln -s sc_core sc1 $ ln -s sc_core sc2 $ ./sc1 Setup the Environments and Flags Excute sc1 $ ./sc2 Setup the Environments and Flags Excute sc2

실행한 파일이름을 찾을 때 sc_core 예제에서와 같이 ‘*파일명’ 패턴을 이용하는 방법도 있고 basename 프로그램을 사용할 수도 있다.
 
$ cat jc_core #!/bin/sh # # jc_core: basename을 이용한 또 다른 방법 # jcpath=’/usr/local/bin’ jcprogram=`basename $0` echo Setup the Environments and Flags case “$jcprogram” in jcc) echo Excute jcc ;; jdb) echo Excute jdb ;; *) echo “$jcpath/$jcprogram” ;; esac $ ln -s jc_core jcc $ ln -s jc_core jdb $ ln -s jc_core jzip $ ./jcc Setup the Environments and Flags Excute jcc $ ./jdb Setup the Environments and Flags Excute jdb $ ./jzip Setup the Environments and Flags /usr/local/bin/jzip


결론

스크립트를 작성하는 일이 다 끝이 났다면 이제 마지막으로 남은 것은 스크립트의 이름을 정하는 것이다. 어떤 이름을 짓던지 상관은 없지만 한 가지 주의할 것은 이미 있는 프로그램의 이름을 사용해서는 안 된다는 것이다. 우리가 정한 이름이 이미 존재하는 지는 다음과 같은 방법으로 찾아보면 된다.

man 1 <스크립트 이름> which <스크립트 이름> whereis <스크립트 이름> alias <스크립트 이름> type <스크립트 이름>

모든 가능성을 검사해 보고 없는 이름으로 정한다.

이렇게 스크립트의 작성이 끝이 났다. 이제는 반복적인 일을 간단히 처리해주는 나름대로의 스크립트는 작성할 수 있을 것이다. 부족한 부분이 있기는 하지만 그런 부분들은 많은 시도를 통해서 채득할 수 있을 것이라 생각한다. 또한 쉘 스크립트를 작성하기 위해서는 사용하는 쉘에 대한 이해도 필요하다. 다음 기사에서는 작성한 스크립트를 디버깅하는 방법을 알아보자.


·참·고·자·료·

·UNIX POWER TOOLS, 2ed. Jerry Peek, Tim O’Reilly, Mike Loukides. O’Reilly
·bash 매뉴얼 페이지

댓글목록

등록된 댓글이 없습니다.

1,139 (12/23P)

Search

Copyright © Cmd 명령어 3.128.255.103