Bash Programming Introduction Howto Mike G mikkey at dyamo.com.ar Thu Jul 27 09:36:18 ART 2000 ---- 이 글은 당신이 초중급 셸 스크립트 프로그래밍을 시작하는 것을 돕고자 한다. 고도의 문서화를 하려고 하는 게 아니다(제목을 보라). 나는 전문가도 고급 셸 프로그래머도 아니다. 다는 내가 좀 더 배웠고 다른 사람들에게 쓸모가 있을 것이기에 이 글을 쓰기로 결정했다. 어떤 피드백도 - 특히 패치 형태 - 환영한다. ---- [[TableOfContents]] == 소개 == === 최신판 구하기 === http://en.tldp.org/HOWTO/Bash-Prog-Intro-HOWTO.html === 요구사항 === GNU/Linux 명령행에 익숙하고, 기본 프로그램개념이 있다면 도움이 될 것이다. 이 글은 프로그래밍의 소개가 아니므로, 많은 기본 개념을 설명한다(적어도 시도한다) === 이 문서의 사용 === 이 문서는 다음 상황에 쓸모 있게 하려 하였다. * 당신이 프로그램에 관해 개념이 있고 당신이 어떤 셸 스크립트를 시작하기를 원한다. * 당신은 셸 프로그래밍에 대해 모호한 개념을 가지고 있고 어떤 참고자료를 원한다. * 당신은 셸 스크립트와 당신 자신의 셸 스크립트를 작성하기 시작하는 것에 대한 의견을 보기를 원한다. * 당신은 DOS / Windows 로 부터 이주하려고 하거나 이주했고, 일괄처리를 만들기를 원한다. * 당신은 완전한 컴맹이고 가능한한 모든 하우투를 읽으려고 한다. == 매우 간단한 스크립트 == === 전통적인 hello world 스트립트 === {{{ #!/bin/bash echo Hello World }}} 이 스크립트는 딱 두 줄로 되어 있다. 첫 줄은 프로그램이 파일을 실행시키기 위해 사용할 시스템을 가리킨다. 둘째 줄은 이 스크립트에 의해 수행되는 행동인데, 그것은 터미널에 'Hello World'를 출력한다. 만약 당신이 ./hello.sh: Command not found. 와 같은 것을 보게 된다면 아마도 첫 줄이 잘못되었을 것일텐데, bash가 어디 있는지에 대해 또는 어떻게 당신이 이 줄을 써야 하는지 보기 위해 'bash를 찾는 것'을 하게 될 것이다. === 매우 간단한 백업 스크립트 === {{{ #!/bin/bash tar -cZf /var/my-backup.tgz /home/me/ }}} 이 스크립트에서는 터미널에 메시지를 출력하는 대신 사용자 홈디렉토리의 타르볼을 만든다. 이것은 사용하려고 하는 게 아니고, 좀더 쓸모 있는 백업 스크립트가 이 문서에서 나중에 나온다. == 리디렉션의 모든 것 == === 이론과 빠른 참조 === 파일 지시자는 stdin, stdout, stderr 세 개가 있다. 기본적으로 당신은 1. stdout에서 파일로 1. stderr에서 파일로 1. stdout에서 stderr로 1. stderr에서 stdout로 1. stderr와 stdout 에서 파일로 1. stderr와 stdout 에서 stdout로 1. stderr와 stdout 에서 stderr로 리디렉트할 수 있다. 1은 stdout을 2는 stderr를 나타낸다. 이것을 보기 위한 작은 주석: less 명령어로 당신은stdout (이것은 버퍼에 남음) 과 stderr가 화면에 출력되는 것을 볼 것이지만, 버퍼를 검색하려고 시도하면 지워진다. === 예: stdout 에서 file === 이것은 프로그램의 출력을 파일에 쓸 것이다. {{{ ls -l > ls-l.txt }}} 여기서 ls-l.txt 이라는 파일이 생성될 것이고, 그 파일은 당신이 ls -l를 실행했을 때 화면에 보게 되는 내용을 포함할 것이다. === 예: stderr 에서 file === 이것은 프로그램의 stderr 출력을 파일에 쓸 것이다. {{{ grep da * 2> grep-errors.txt }}} 여기서 'grep-errors.txt'이라는 파일이 생성되고 당신은 'grep da *'명령의 stderr 출력 부분을 볼 것이다. === 예: stdout 에서 stderr === 이것은 프로그램의 stderr출력을 stdout대신 같은 파일지시자에 쓸 것이다. {{{ grep da * 1>&2 }}} 여기서, 명령의 stdout 부분은 stderr로 보내지고, 당신은 다른 방법을 알게 될 것이다. === 예: stderr 에서 stdout === 이것은 프로그램의 stderr출력글 stdout 대신 같은 파일지시자에 쓸 것이다. {{{ grep * 2>&1 }}} 여기서 이 명령의 stderr 부분은 stdout 로 보내지고, 당신이 less로 파이프하면, 당신은 보통 '사라지는'(그것들이 stderr에 써진 것 처럼) 줄이 현재는 유지되는 것을 볼 것이다.(왜냐면 그것은 stdout에 있으므로) === 예: stderr 와 stdout 에서 file === 이것은 프로그램의 모든 출력을 파일에 저장할 것이다. 이것은 당신이 절대로 조용히 명령이 진행되기를 원하면 때때로 cron 요소에 적합하다. {{{ rm -f $(find / -name core) &> /dev/null }}} 이것은(cron 요소에서 생각하면) 모든 디렉토리의 core 라는 모른 파일을 지울 것이다. 당신은 당신이 그 출력을 지운다면 어떤 명령이 실행되는지 분명히 알고 있어야 한다. == 파이프 == 이 절은 파이프를 어떻게 쓰는지 매우 간단하고 실용적인 방법을 설명하고, 왜 당신이 그것을 원할 것인지 설명할 것이다. === 그것은 무엇인가 왜 당신은 그것을 사용하기를 원할 것인가 === 파이프는 당신이 프로그램의 출력을 다른 프로그램의 입력으로 사용하게 한다. === 예: sed와 간단한 파이프 === 이것은 파이프를 사용하는 간단한 방법이다. {{{ ls -l | sed -e "s/[aeio]/u/g" }}} 여기서, 아래의 상황이 발생한다. 처음에 ls -l 명령이 실행되고, 그것의 출력이 인쇄되는 대신 프로그램에 보내진다(파이프된다). 결국, 그것이 해야 하는 것을 인쇄된다. === 예: 다른 ls -l *.txt === 아마, 이것은 ls -l *.txt 을 실행하는 좀더 다른 방법일 것이지만, 파이프를 묘사하려는 것이지 이런 리스팅 문제를 풀려는 것이 아니다. {{{ ls -l | grep "\.txt$" }}} 여기서, ls -l 프로그램의 출력이 grep 프로그램에 보내져 결국 정규표현식 "\.txt$" 에 맞는 행을 출력할 것이다. == 변수 == 당신은 다른 프로그램 언어처럼 변수를 사용할 수 있다. 자료형은 없다. bash의 변수는 숫자, 문자, 문자열을 포함할 수 있다. 변수를 선언할 필요는 없으며, 값을 참조에 할당하기만 하면 변수를 생성한다. === 예: 변수를 사용한 Hello World! === {{{ #!/bin/bash STR="Hello World!" echo $STR }}} 둘 째 줄은 STR이라는 변수를 생성하고 "Hello World!"을 할당한다. 그러면 이 변수의 값은 앞에 $를 붙여서 되찾을 수 있다. 만약 $ 기호를 사용하지 않는다면, 그 프로그램의 출력은 다를 것이고, 거의 당신이 원하는 바가 아닐 것이다. === 예: 매우 간단한 백업 스크립트(조금 나아진 것) === {{{ #!/bin/bash OF=/var/my-backup-$(date +%Y%m%d).tgz tar -cZf $OF /home/me/ }}} 이 스크립트는 다른 것을 도입한다. 우선, 둘째 줄의 변수 생성과 할당에 익숙해져야 한다. 식 '$(date +%Y%m%d)'을 주목하라. 스크립트를 실행하면 괄호 안에 있는 명령을 실행하고 출력을 잡을 것이다. 이 스크립트 안에서, 출력 파일 이름은 매일 달라질 것이고, date 명령의 형식 변환(+%Y%m%d) 때문이다. 예가 더 있다. echo ls echo $(ls) === 지역 변수 === 지역 변수는 local 키워드를 사용하여 생성할 수 있다. {{{ #!/bin/bash HELLO=Hello function hello { local HELLO=World echo $HELLO } echo $HELLO hello echo $HELLO }}} 이 예는 어떻게 지역 변수를 사용하는지 보여준다. == 조건문 == 조건문은 어떤 행동을 수행할지 하지 않을지 결정하게 하고, 이 결정은 식에 의해 이루어진다. === 시시한 이론 === 조건문은 여러 형식이 있다. 가장 기본적인 형식은 if 식 then 문장인데 여기서 문장은 식이 참일 때만 실행된다. '2<1'은 거짓으로 평가 되고 '2>1'은 참으로 평가된다. 조건문은 다음과 같이 다른 형식이 될 수도 있다. if 식 then 문장1 else 문장2. 여기서 식이 참이면 문장1이 실행되고, 그렇지 않으면 문장2가 실행된다. 또 다른 형식의 조건문은 if 식1 then 문장1 else if 식2 then 문장2 else 문장3 이다. 이 형식에서는 else if 식2 then 문장2 가 추가된 것 뿐인데, 그것은 식2가 참일 때 문장2를 실행하게 한다. 나머지는 당신이 생각하는 것과 같다. 구문에 대한 말: bash에서 if 구문의 기본은 아래와 같다. {{{ if [ 식 ]; then 식이 참일 때의 코드 fi }}} === 예: 기본 조건문 if ... then === {{{ #!/bin/bash if [ "foo" = "foo" ]; then echo expression evaluated as true fi }}} 이 코드는 괄호 안의 식이 참이면 then 다음에서 fi 전까지의 코드를 실행한다. === 예: 기본 조건문 if ... then ... else === {{{ #!/bin/bash if [ "foo" = "foo" ]; then echo expression evaluated as true else echo expression evaluated as false fi }}} === 변수를 가진 조건문 === {{{ #!/bin/bash T1="foo" T2="bar" if [ "$T1" = "$T2" ]; then echo expression evaluated as true else echo expression evaluated as false fi }}} == for, while, until == 이 절에서는 for, while, until 에 대해 찾을 수 있다. for는 다른 프로그래밍 언어와 다르다. 기본적으로, 한 문자열 안에 있는 단어의 열을 반복하게 한다. while은 만약 제어 식이 참일 때 코드를 실행하고, 거짓일 때(또는 실행되는 코드에서 명시적인 break를 찾았을 때)는 끝난다. until은 제어코드가 거짓으로 평가 되는 동안 실행돠는 것만 제외하고 while과 거의 같다. 만약 당신이 while과 until이 매우 비슷하다는 걸 눈치챘다면 당신은 제대로 본 것이다. === for 예 === {{{ #!/bin/bash for i in $( ls ); do echo item: $i done }}} 둘째 줄에서 i를 변수로 선언하고 $(ls)안에 포함된 다른 값을 얻는다. 세째 줄은 필요하면 더 길어질 수도 있다. 즉 done 이전에 더 많은 줄이 있을 수 있다. done 은 $i의 값이 사용되는 것이 끝났고 $i가 새로운 값을 얻을 수 있음을 가리킨다. === C 비슷한 for === fiesh가 이 형식을 더하는 것을 제안했다. C/Perl 과 비슷한 for 이다. {{{ #!/bin/bash for i in `seq 1 10`; do echo $i done }}} === while 예 === {{{ #!/bin/bash COUNTER=0 while [ $COUNTER -lt 10 ]; do echo The counter is $COUNTER let COUNTER=COUNTER+1 done }}} 이 스크립트는 잘 알려진(C, Pascal, perl, 등) for 구조를 흉내낸다. === until 예 === {{{ #!/bin/bash COUNTER=20 until [ $COUNTER -lt 10 ]; do echo COUNTER $COUNTER let COUNTER-=1 done }}} == 함수 == 거의 모든 프로그램 언어에서 처럼, 당신은 좀더 논리적인 방법으로 코드의 조각들을 묶거나 재귀의 기술을 실행하기 위해 함수를 사용할 수 있다. 함수를 선언하는 것은 대략 내_함수 {내_코드} 으로 쓰는 것이다. 함수를 호출하는 것은 다른 프로그램처럼 당신이 그 이름을 쓰는 것이다. === 함수 예 === {{{ #!/bin/bash function quit { exit } function hello { echo Hello! } hello quit echo foo }}} 둘째 - 네째 줄은 quit 함수를 포함한다. 다섯째 - 일곱째줄은 hello 함수를 포함한다. 만약 이 스크립트가 무엇을 하는지 잘 모르겠으면 꼭 해 보시오. 함수는 특정한 순서로 선언될 필요는 없다. 스크립트를 실행시키면 우선 다음을 주목하라. 함수 hello가 호출되면 quit 함수가 호출되고, 프로그램은 열째 줄에 도달하지 못한다. === 매개변수를 가진 함수 예 === {{{ #!/bin/bash function quit { exit } function e { echo $1 } e Hello e World quit echo foo }}} 이 스크립트는 첫번째 것과 거의 같다. 주된 차이는 함수 e이다. 이 함수는 받은 매개변수를 출력한다. 함수에서는 매개변수는 스크립트에 주어진 매개변수와 같은 방법으로 다루어진다. == 사용자 인터페이스 == === 간단한 메뉴를 위한 select 사용 === {{{ #!/bin/bash OPTIONS="Hello Quit" select opt in $OPTIONS; do if [ "$opt" = "Quit" ]; then echo done exit elif [ "$opt" = "Hello" ]; then echo Hello World else clear echo bad option fi done }}} 당신이 이 스크립트를 실행하면 이것이 테스트기반메뉴에 대한 프로그래며의 꿈이라는 것을 볼 것이다. 당신은 이것이 단순히 $OPTIONS 안의 각각의 단어를 도는 것만이 아니라는 것 말고는 for구조와 비슷하다. 이것은 사용자 응답을 기다린다. === 명령행 사용하기 === {{{ #!/bin/bash if [ -z "$1" ]; then echo usage: $0 directory exit fi SRCD=$1 TGTD="/var/backups/" OF=home-$(date +%Y%m%d).tgz tar -cZf $TGTD$OF $SRCD }}} 이 스크립트가 하는 것은 당신에게 명확할 것이다. 첫번째 조건문의 식은 프로그램이 매개변수($1)을 받았는지 체크하고 받지 않았으면 끝내고, 사용자에게 간단한 사용법 메시지를 보여준다. 나머지 부분은 명확하다. == 기타 == === read로 사용자 입력 읽기 === 여러 경우에 당신은 사용자가 어떤 입력에 응답하기를 원할것이고, 이것을 수행하는 방법은 여러가지가 있다. 그 중 한 방법이 이것이다. {{{ #!/bin/bash echo Please, enter your name read NAME echo "Hi $NAME!" }}} 변화를 주어 여러개의 값은 얻을 수 있고 이 예제가 그것을 명확히 할 것이다. {{{ #!/bin/bash echo Please, enter your firstname and lastname read FN LN echo "Hi! $LN, $FN !" }}} === 산술 평가 === 명령행(또는 셸)에서 이것을 시도해 보라. {{{ echo 1 + 1 }}} 당신이 2를 기대했다면 기대가 어긋날 것이다. 당신은 BASH가 어떤 수를 계산하기를 원한다면 어떻게 해야 할까? 답이 이것이다. {{{ echo $((1+1)) }}} 이것은 좀 더 논리적인 출력을 만들 것이다. 이것은 산술식을 계산한 것이다. 당신은 이것을 이렇게 할 수 있다. {{{ echo $[1+1] }}} 당신이 분수를 사용하기 원한다면, 또는 수학 또는 당신이 그것을 원하기만 한다면, 당신은 산술식을 계산하기 위해 bc를 사용할 수 있다. 만약 {{{echo $[3/4]}}}을 실행한다면, 0을 반환할 것이다. 왜냐하면, bash는 답할때 정수만을 사용하기 때문이다. {{{echo 3/4|bc -l}}}을 실행하면 0.75를 반환할 것이다. === bash 찾기 === mike로부터의 메시지에서 (Thanks to 를 보라) 당신은 항상 #!/bin/bash .. 를 사용했다.어디에 bash가 있는지 찾는 방법을 보여주었으면 한다. 'locate bash'를 권장하지만, 모든 기계가 locate를 가지고 있는 건 아니다. / 디렉토리에서 'find ./ -name bash' 를 하면 대개의 경우 될 것이다. 점검해볼 위치 추천 ls -l /bin/bash ls -l /sbin/bash ls -l /usr/local/bin/bash ls -l /usr/bin/bash ls -l /usr/sbin/bash ls -l /usr/local/sbin/bash 'which bash'를 시도해 볼 수도 있다. === 프로그램의 반환값 얻기 === bash에서, 프로그램의 반환값은 특정한 변수 $? 에 저장된다. 이것은 어떻게 프로그램의 반환값을 어떻게 잡는지 묘사한다. 나는 디렉토리 데이터가 존재하지 않는다고 가정했다.(이 또한 mike가 제안했다) {{{ #!/bin/bash cd /dada &> /dev/null echo rv: $? cd $(pwd) &> /dev/null echo rv: $? }}} === 명령 출력 잡기 === 이 작은 스크립트는 모든 데이터베이스의 테이블은 보여준다.(MySQL이 설치되었다고 가정한다). 또한, 올바른 사용자와 패스워드를 사용하여'mysql'을 사용한다고 가정한다. {{{ #!/bin/bash DBS=`mysql -uroot -e"show databases"` for b in $DBS ; do mysql -uroot -e"show tables from $b" done }}} === 여러 소스파일 === 당신은 여러개의 명령소스로 여러개의 파일을 사용할 수 있다. == Tables == === 문자열 비교 연산자 === (1) s1 = s2 (2) s1 != s2 (3) s1 < s2 (4) s1 > s2 (5) -n s1 (6) -z s1 (1) s1 matches s2 (2) s1 does not match s2 (3) __TO-DO__ (4) __TO-DO__ (5) s1 (하나 이상의 문자 포함) (6) s1 is null === 문자열 비교 예 === 두 문자열 비교 {{{ #!/bin/bash S1='string' S2='String' if [ $S1=$S2 ]; then echo "S1('$S1') is not equal to S2('$S2')" fi if [ $S1=$S1 ]; then echo "S1('$S1') is equal to S1('$S1')" fi }}} 나는 if [ $1 = $2 ]를 사용하는 것을 참조하기 위해 Andreas Beck이 보낸 메일을 여기서 인용했다. 이것은 $S1 또는 $S2 가 비었을 때는 그다지 좋은 생각은 아닌데 그럴 경우 parse 에러가 날 것이다. x$1=x$2 또는 "$1"="$2" 가 낫다. === 산술 연산자 === + - * / % (나머지) === 산술 관계 연산자 === -lt (<) -gt (>) -le (<=) -ge (>=) -eq (==) -ne (!=) C 프로그래머는 이 연산자를 해당하는 괄호에 있는 것에 대응시킬 수 있겠다. === 쓸만한 명령어 === 이 절은 Kees가 다시 썼다. 이 명령어들은 대개 완전한 프로그램 언어를 포함한다. 이 명령어들은 단지 기본만 설명한다. 좀 더 자세한 설명을 위해서는 각 명령어의 man 페이지를 보라. sed (stream editor) sed 는 비대화형 편집기다. 화면에서 커서를 옮겨 파일을 변경하는 대신 명령 편집 스크립트로 sed를 사용하여 편집할 파일의 이름을 추가할 수 있다. sed를 필터로 설명할 수도 있다. 몇 개의 예를 보자 {{{ $sed 's/to_be_replaced/replaced/g' /tmp/dummy }}} sed는 /tmp/dummy 파일을 읽어서 'to_be_replaced'를 'replaced'로 바꾼다. 결과는 stdout(대개 콘솔)로 보내지지만 위의 줄 끝에 '> capture'를 덧붙여서 sed가 출력을 파일 capture로 보내게 할 수도 있다. {{{ $sed 12, 18d /tmp/dummy }}} sed는 12째에서 18째까지를 제외한 행을 보여준다. 원본 파일이 이 명령어에 의해 바뀌지는 않는다. awk (데이터파일 다루기, 텍스트 추출 및 처리) AWK의 여러가지 구현이 존재한다(가장 많이 알려진 인터프리터는 GNU의 gawk 와 'new awk' mawk.) 원칙은 간단하다. awk는 패턴을 훑고 해당되는 모든 패턴에 어떤 행동이 수행된다. 다시, 아래 행을 포함하는 파일을 만들었다. {{{ test123 test tteesstt }}} {{{ $ awk '/test/ {print}' /tmp/dummy }}} awk가 찾는 패턴은 'test'이고 /tmp/dummy 안에서 문자열 'test'를 가지고 있는 행을 찾았을 때 수행하는 것은 그 행을 출력하는 것이다. {{{ test123 test }}} {{{ $ awk '/test/ {i=i+1} END {print i}' /tmp/dummy }}} 3 당신이 여러 패턴을 찾을 때는, 당신은 '-f file.awk'를 가지고 따옴표사이의 텍스트를 치환해야 하며 그러면 모든 패턴과 'file.awk'의 행동을 넣을 수 있다. grep (검색 패턴에 해당하는 행을 출력) 우리는 이전의 장에서 이미 꽤 많은 그룹 명령을 봤고, 그것은 패턴에 해당하는 행을 보여준다. 그러나 grep 은 더 많은 것을 할 수 있다. {{{ $ grep "look for this" /var/log/messages -c }}} 12 문자열 "look for this"는 파일 /var/log/messages에서 12번 발견되었다. [그렇다 이 예는 속임수다 /var/log/messages는 꼬집어졌다 :) ] wc (행, 단어, 바이트 세기) 다음의 예에서 우리는 출력이 우리가 기대하는 것이 아님을 보게 된다. 더미 파일, 이 예에서 사용된 것처럼 다음의 텍스트를 포함한다. "bash introduction howto test file" {{{ $ wc --words --lines --bytes /tmp/dummy }}} 2 5 34 /tmp/dummy Wc doesn't care about the parameter order. Wc always prints them in a standard order, which is, as you can see: . 우리는 파라메터의 순서에 대해는 신경쓰지 않는다. 우리는 언제나 그것을 표준 순서로 출력한다. 그건, 당신이 보는 바와 같다. sort (텍스트 파일의 정렬) 이번엔 더미 파일이 다음의 문자열을 포함하고 있다. {{{ b c a }}} {{{ $ sort /tmp/dummy }}} 출력은 이렇게 나온다. {{{ a b c }}} 명령은 그렇게 쉽지만은 않다 :) bc (계산기 프로그램 언어) bc는 명령행에서의 (파일로부터 입력받은, 리디렉터나 파이프가 아니라) 뿐 아니라 사용자 인터페이스에서도 계산을 수행한다. 아래의 예는 이런 명령 몇 개를 보여준다. I start bc using the -q parameter to avoid a welcome message. 나는 환영 메시지를 피하기 위해 -q 파라메터를 썼다. {{{ $ bc -q }}} 1 == 5 0 0.05 == 0.05 1 5 != 5 0 2 ^ 8 256 sqrt(9) 3 while (i != 9) { i = i + 1; print i } 123456789 quit tput (터미널 또는 terminfo 데이터베이스질의 초기화) A little demonstration of tput's capabilities: tput의 능력의 예 {{{ $ tput cup 10 4 }}} The prompt appears at (y10,x4). 프롬프트가 (y10,x4)에 나타난다. {{{ $ tput reset }}} 화면을 깨끗이 하고 프롬프트가 (y1,x1)에 나타난다. (y0,x0)이 왼쪽 위 구석임을 주목하라. {{{ $ tput cols }}} 80 x방향에 가능한 문자수를 보여준다. (적어도) 이들 프로그램에 익숙해지길 권한다. 명령행에서 당신이 진짜 마술을 하게 할 작은 프로그램들이 어머어마하게 많다. 몇 예제는 man 페이지나 fag에서 가져왔다.