본문 바로가기

Learning/└◆Shell Scripts

[Shell Scripting] 본(Born) 쉘 특징

5 장 본쉘 특징

 

 

---------------------

1. 쉘 환경

2. 명령행

3. 메타문자

4. 파일이름 치환

5. 변수

6. 인용부호

7. 명령어 치환

8. 함수

9. 리다이렉션

10. 파이프

11. here 문서

---------------------

 

쉘의 특성

     리다이렉션(Redirection)

     파이프(pipe)

     셀 자체의 기능(bash function)

     변수(Variable)

     메타캐릭터(Metacharacter)

     히스토리(History)

     환경파일(Environment Files)

      

리다이렉션(Redirection)    방향재설정

     fd(file description, 파일기술자)   열린 파일을 구분할때 사용하는 기호 (ex) vi file1 <-- 운영체제가 번호 할당 fd5)

     -------------------------

     표준입력 [stdin (keyboard)]  

     표준출력 [stdout(screen)]    미리 예약 되어 있는 fd값 : fd0,fd1,fd2 다른 프로세스에 할당 할 수 없다.

     표준에러 [stderr(screen)]

     -------------------------


fd0 -> shell -> fd1

              -> fd2


원본파일을 3개 열면 무엇으로 구분할까?

=>파일을 열면 fd 값을 할당해준다. 예로 int fd=open(...) 같이 int형 fd에 fd값을 넣는것.


# ls /var /nodir

# cat    지정이 없으면 하나만쳐도 두개씩 나오는 이유

hello    stdin 지정이 없으니 키보드로 입력받고

hello    stdout 지정이 없어서 키보드로 출력한다.

linux

linux

<CTRL + D>

 

# cat < /etc/passwd   (< : 입력 redirection) 키보드 대신 /etc/passwd로 입력을 받는다.   (키보드입력 파일출력)

# cat > file1         (> : 출력 redirection) 정상적인 출력결과를 file1에다 집어넣어라.  (파일입력 파일출력)

hello

linux

<CTRL + D>

# cat file1

 

입력 리다이렉션(Redirection stdin)

     # CMD < file1

     # CMD 0< file1

     (X) # CMD << file1 (EX: Here Documentation)

 

     # cat < /etc/passwd

1.   # mail -s "Test Mail" root@example.com < report.txt   명령어로 report파일을 메일로 전송 report.txt = mail text body

 

2.   # mail root

     Subject: Test Mail

     This is a test.

     <.> or <CTRL + D>

     #

 

 

출력 리다이렉션(Redirection stdout) 

정상적인 메시지 지정없으면 모니터에 출력하는데 다른쪽에 출력할때 사용 방향재설정

     # CMD > file1

     # CMD 1> file1

     # CMD >> file1

     # CMD 1>> file1

 

     # ls -l > file.list

     # cat file.list


 

echo => 모니터 출력   

     # echo 1111 > file1

     # echo 2222 > file1

     # cat file1

     # echo 3333 >> file1

     # cat file1

      

     [TERM1] # tty

               /dev/pts/3

     [TERM2] # echo hello redirectioin > /dev/pts/3

 

     # cat dic1.txt dic2.txt > dic3.txt


에러 리다이렉션(Redirection stderr)  에러메시지를 다른쪽에 출력할때 사용, 생략하면 1번하고 동일해져서 생략불가능

     # CMD 2> file1

     # CMD 2>> file1

 

     # ls -l /test /nodir

     정상 출력

     에러 출력 


     # ls -l /test /nodir > file1

     에러 출력

     file1 => 정상 출력  


     # ls -l /test /nodir 2> file2

     정상 출력

     file2 => 에러 출력 


     # ls -l /test /nodir > file1 2> file2

     file1 => 정상 출력

     file2 => 에러 출력 

 

     # ls -l /test /nodir > file1 2>&1

     file1 => 정상 출력 , 에러 출력

&1은 fd값을 나타낸다. (형식-&뒤에 숫자) fd1이 file1로 들어가니깐 에러메시지가 나오면 같은 파일 file1에 저장하는 방법

 

키워드 지정된 것 외에는 함수를 끌어 사용해야 한다.

쉘스크립트는 전부다 명령어를 끌어다 사용한다. 일반 API는 그 기능만 동작하지만 명령어는 옵션을 주면 다른식으로 동작하기 때문에 굉장히 많은 용도로 사용가능하다.


     # script.sh > file.log 2>&1               <= 정상적인 메시지와 에러메시지를 한꺼번에 담아줘야 적절한 로그기능이 가능

     $ find / -name core -type f 2>/dev/null   <= 관리가 권한이 아니라 열어볼 수 없는 파일이 많이 생긴다. null처리(에러출력삭제)

     # ./configure --prefix=/usr/local/apache2 >apache.log 2>&1  <=점검하는 중요한 과정이라 점검에서 오류가 나면 문제가 커진다. 정상출력과 에러메시지를 저장하여 파일로 생성, 이 파일을 분석하면 도움이 된다.







파이프(Pipe)

     # CMD | CMD

 

     # ps -ef

     # more /etc/passwd

     # ps -ef | more

 

     # ps -ef | more           # ps -ef > ps.txt

             효율적              # more ps.txt


     # CMD | more

     # ps -ef | more

     # ps -ef | grep xinetd         => Pipe의 정의 때문에 입력값을 지정안해줘도 된다.

 

[참고] CMD | tee file1  =>tee명령어 유용하다. file1에도 저장하고 모니터에도 출력한다.

     # cal > cal.txt

     # cal | tee cal2.txt

 

여러개의 터미널(화면)을 공유하는 경우

[참고] script CMD    

     # cd /test

     # script -a file.log    => Sub shell이 뜨고 여기서 작업한 명령어 출력값을 file.log로 저장된다.

     # ls ; date ; cal

     # exit

     # cat file.log

=> 선배들 컴퓨터 몰래 script실행해서 어떤작업했는지 확인도 가능하다..!

 

[TERM1]

     # tty

     /dev/pts/3

[TERM2]

     # tty

     /dev/pts/4

[TERM3]

     # script -a /dev/null | tee /dev/pts/3 | tee /dev/pts/4

     # CMD

     # exit

=> Sub shell생성하여 넘기면 tee에서 모니터와 pts/2에 출력하고 넘긴것을 tee가 받고 다시 모니터와 pts/4에 출력

 

모니터링 구문을 사용하는 경우(로그 생성)

# while true

> do

> clear

> echo "-------------'date'----------------"

> df -h

> sleep 2

> done

-------------'date'----------------

Filesystem    Type    Size  Used Avail Use% Mounted on

/dev/mapper/VolGroup00-LogVol00

              ext3     37G  3.4G   32G  10% /

/dev/sda1     ext3     99M   13M   82M  14% /boot

tmpfs        tmpfs    506M     0  506M   0% /dev/shm

-------------'date'----------------

Filesystem    Type    Size  Used Avail Use% Mounted on

/dev/mapper/VolGroup00-LogVol00

              ext3     37G  3.4G   32G  10% /

/dev/sda1     ext3     99M   13M   82M  14% /boot

tmpfs        tmpfs    506M     0  506M   0% /dev/shm

-------------'date'----------------

...(중략)....



# while true
> do
> ps -ef | grep httpd | wc -l | tee -a file.log
> sleep 2
> done
10
10
10
=>tee -a는 append모드이며, 80포트에 몇명씩 붙어있는지 보는 예제

 

# ls -l /test /nodir | tee file2.log 

# ls -l /test /nodir | tee file2.log 2>&1 (X)

# ls -l /test /nodir 2>&1 | tee file2.log

 

 

쉘 자체의 기능(bash shell function)

     # set -o      기능목록 출력

     # set -o vi   vi 기능 off

     # set +o vi   vi 기능 on

 

 

변수(Variable)***중요

     변수의 종류

     - 지역변수(# VAR=5)

     - 환경변수(# export VAR=5) (전역변수, export차이로 지역변수 환경변수를 나눔)

     - 특수변수($$, $?, $!, $*, $1, $2, ....) (뒤에 있는 것이 변수이름)

     변수 선언 방법(env/set)

          # VAR=5 (# export VAR=hello)  변수이름=값(=양쪽에 공백이 있으면 안된다.)

          # export VAR

          # echo $VAR (# printf $VAR)

          # unset VAR

    변수를 생성할때는 $를 붙히지 않고 변수를 사용할때는 $를 붙힌다.


      프로그램 내에서 사용했던 변수는 새로운 서브 쉘을 가지고 있는다.

변수를 해제하지 않아도 사용했던 프로그램을 종료하면 사라진다. 

 

변수의 export 의미 

          # ps

          # VAR1=5

          # echo $VAR1

          # bash

          ps

  PID TTY          TIME CMD

 7098 pts/4    00:00:00 bash

 8240 pts/4    00:00:00 bash

 8254 pts/4    00:00:00 ps

=> PID가 낮은것부터 할당되기 때문에 높은 것이 현재임을 알 수 있다.

          # echo $VAR1

          # exit

          # echo $VAR1

 

          # VAR2=10

          # export VAR2 (# export VAR2=10)

          # echo $VAR2

          # bash

          # echo $VAR2

          # exit

          # echo $VAR2

=> export하여서 Sub Shell에도 환경변수를 확인할 수 있다.

 


특수변수

          $$ , $!, $?

          $*, $#, $0, $1, $2, $3, ....

 

          $$ : 현재 쉘의 PID 번호 저장(EX: 임시 파일 생성, /tmp/.tmp.$$)

          $! : 바로 이전 수행된 백그라운드 프로세스의 PID 번호 저장 (bg프로그램을 kill 명령으로 종료할 때)

          $? : 바로 이전 수행된 명령어의 return value 저장(0 ~ 255)

값이0 정상 종료 /0이 아닌 값 : 비정상 종료

 

          $* : 모든 인자($* == $@)

          $# : 인자의 개수 (#의 숫자의 의미라서)

          $0 : 프로그램 이름

          $1 : 프로그램에 대한 첫번째 인자

          $2 : 프로그램에 대한 두번째 인자

          $3 : 프로그램에 대한 세번째 인자

임시파일 생성하는 경우 

          # echo $$

          # ps

          # touch /tmp/tmp.$$

          # ls -l /tmp/tmp.*

 

바로 이전에 수행된 백그라운드 프로세스를 종료하는 경우 

          # sleep 20 &

          # echo $!

          # sleep 200 &

          # kill $!

 

명령어의 정상 동작 유무 확인하는 경우

          # date

          # echo $?

          0        <= c언어와 다르게 0이 참이다

          # cd /nodir

          # echo $?

          1~255    <= Error인 경우 0이 아닌 1~255숫자 

          # llllssss

          # echo $?

 

# vi script.sh     값이 0이면 참 , 0이 아니면 거짓

---------------------------------

#!/bin/bash

 

NET=172.16.10

START=200

END=230

 

while [ $START -le $END ]

do

      ping -c 1 $NET.$START > /dev/null 2>&1

      if [ $? -eq 0 ] ; then

      echo "$NET.$START is alive"

      else

      echo "$NET.$START is die"

      fi

      START=`expr $START + 1`

done

 

arp -an | grep $NET | grep -v incomplete

---------------------------------





인자 변수 

# cd /test

# vi test.sh

-------------------------------

#!/bin/bash

 

echo $*        => 인자 전체인 file1 file2 출력

echo $#        => 인자의 갯수인 2 출력

 

echo $0        => 프로그램 이름 ./test.sh

echo $1        => 첫번째 인자 file1

echo $2        => 두번째 인자 file2

-------------------------------

# chmod 755 test.sh

# ./test.sh file1 file2




 

# alias pps='ps ef | head 1 ; ps ef | grep $1'

# pps xinetd

# pps java              => 첫번째 인자값이 변수안에 들어가서 수행된다.

# pps httpd


alias nstat='netstat -an | head -2; netstat-an | grep $1'

# nstat :21
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address               Foreign Address             State      
tcp          0         0 0.0.0.0:21                   0.0.0.0:*                     LISTEN      
# nstat :80
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address               Foreign Address             State      
tcp          0         0 0.0.0.0:80                   0.0.0.0:*                     LISTEN  


 

 

  

쉘 메타캐릭터(Shell Metacharacter)  의미가 있는 특이한 문자들


‘’, “”, ``, \, ;


     ‘’: 작은따옴표(single quotation)  이안을 해석하지 말아라! 문자열처리가 된다.

          # echo $SHELL

          /bin/bash


          # echo '$SHELL'

          $SHELL


          # echo hello linux

          # echo hello linux

          # echo 'hello linux'

 

     “”: 큰 따옴표(double quotation)  => $, ``(백쿼터), \ 이 세가지는 해석이 되고 나머지는 해석이 되지 않는다.

          # echo "$SHELL"

            /bin/bash

          # echo "$SHELL is /bin/bash."

         /bin/bash is /bin/bash.


      `` : 역 따옴표(back quotation) sh -> ksh -> zsh -> bash    => 쉘은 커맨드라고 생각하고 실행한다.

          # date

          (sh style) # echo `date`      Born Shell에서 나와서 기능을 포함하고 있어서 모두 동작한다.

             2015. 11. 06. (금) 12:41:42 KST


          (ksh style) # echo $(date)    Korn Shell에서 나와서 Bash는 기능을 포함하고 있어서 모두 동작한다.

          2015. 11. 06. (금) 12:41:42 KST


          # echo "`date` is time."

             2015. 11. 06. (금) 12:42:47 KST is time.



       \ : 백 슬래쉬(back slash) => 바로 뒤에 오는 것을 해석하지 말아라

          # echo $SHELL

          # echo \$SHELL   그냥 $기호가 된다.

          $SHELL


 

# find / -type f exec grep l Error {} \;         => 3가지 유형 전부 해석이 안되고 단순한 ; 이 되는 것이다.

# find / -type f exec grep l Error {} ';'

# find / -type f exec grep l Error {} ";"

 

# alias rm='rm -i'

# rm -r dir1

# \rm -r dir1


alias vi='usr/bin/vim' 으로 잘못된 alias 설정

# vi ~/.bashrc 하면 잘못된 vi alias가 실행되어 문제가 발생한다


이것을 해결하기 위해 

\vi ~/.bashrc 같은 방법을 사용한다


 

[참고] \CMD & CMD\                 \가 앞에오면 뒤에를 해석하지 말아라, \가 뒤에오면 명령어가 아직 끝나지 않았다는 뜻

# ./configure \                    Shell 이 \를 끝에서 만나면 아직 끝나지 않았구나 생각하고 다음줄을 해석한다.

> --prefix=/usr/local/apache2 \

> --options1=.... \

> --options2=....




 

 ; : 세미콜론(semicolon)

# date

# cal

# ls

# date ; cal ; ls

 

# ps ef | grep xinetd

# ps ef | head 1

# ps ef | head 1 ; ps ef | grep xinetd

# alias pps='ps ef | head 1 ; ps ef | grep $1'

 

# EDITOR=/usr/bin/vi ; export EDITOR

# export EDITOR=/usr/bin/vi

 

 


엘리어스(Alias)         ※=> '=' 양쪽에 공백이 있으면 안된다.

# alias ls='ls --color'

# alias ls

# unalias ls


함수

# a() { ls ; date ; cal ; }           => 함수 선언 방식

# a                                   => 함수 실행 방법

# typeset -f                          => 함수 확인 방법

# unset f a                          => 함수 해제 방법

[참고] CMD -> alias -> function -> script


환경파일(EX: bash)

/etc/profile

~/.bash_profile

~/.bashrc

 

■ 기타(Here Documentation) cat CMD + Here Documentation

 

cat << EOF 다음 명령어에 같은EOF를 쓸 때까지, EOF사이에 명령어를 모두 모니터에 출력

프로그램 작성시 사용.

 root[/test]#./test.sh
..
...
....
.....
root[/test]#cat test.sh
#!/bin/bash

echo ".."
echo "..."
echo "...."
echo "....."
root[/test]#

 

EOF = echo 구문을 대신하여 사용

 cat << EOF
        hello
hello
   hello
EOF

 

# vi test.sh

-------------------------------

#!/bin/bash

 

cat << __EOF >> /tmp/.file1.c             <= __EOF에 A라고 쓰면 아래 __EOF에 A가 들어오면 가운데 내용이 입력결과에 추가되는                                                                     형태, 키보드에서 치는 것을 대신하고 출력은 모니터에 한다.

#include <stdio.h>

main()

{

     printf("hello");

}

__EOF

 

gcc -o /tmp/.file1 /tmp/.file1.c

/tmp/.file1

-------------------------------




기본예제

# cat << EOF

>    hello linux

> hello linux

>            hello linux

> EOF

-------------------------------

   hello linux

hello linux

           hello linux

--------------------------
=> 

echo "   hello linux"

echo "hello linux"

echo "           hello linux"

 

와 같이 사용해야하는데 이것을 편리하게 사용할 수 있는 방법이다.

 

목록이나 긴 형태 장문에서 유용하게 사용할 수 있다.



 

기타(쉘 메타캐릭터 (연속))

그룹화(Grouping)

   ( ls ; pwd ; date ) > outputfile      <= 그룹형태로 묶어줘야 3개의 커멘트 실행결과가 들어간다.

 

      # mkdir /disk1 /disk2

      # cd /disk1

      # cp -p /etc/passwd file1

      # ln -s file1 file2

      # mkdir dir1

      # cp file1 file3 ; chown user01 file3 ; chmod 777 file3

      # touch .file1

      # ls -al

 

      # cp -r /disk1/* /disk2 (X)

      # ls -al /disk1

      # ls -al /disk2

 

      # rm -rf /disk2 ; mkdir /disk2

 

      # cd /disk1

      # tar cvf - . | (cd /disk2 ; tar xvf -)

 

      # find / \( -perm -4000 -o -perm -6000 \) -type f


조건부 실행

   sh  style) -o(OR), -a(AND)

   ksh style) &&, ||(double pipe)

   bash style) 당연히 두 방법 모두 사용 가능

 

   # cd /etc && ls -l passwd     아래 if구문을 짧게 사용할 수 있다.

      if cd /etc ; then        <- then 참일 때 / 거짓 일 때는 then이 수행되지 않음.

         ls -l passwd

      fi

   # [ -f /etc/passwd ] && echo "OK"

      if [ -f /etc/passwd ] ; then

         echo "OK"

      fi

   # [ $? -eq 0 ] && echo "OK"

      if [ $? -eq 0 ] ; then

         echo "OK"

      fi

   # [ -f /etc/passwd ] && echo "OK" || echo "Fail"   <= || 연산은 앞이 참이면 실행안되고, (&&가 먼저 수행된다. &&가 거짓일때 ||가 수행된다.)

                                                           거짓이면 뒤에 부분이 참/거짓을 판별해야 되기 때문에 실행된다.

      if [ -f /etc/passwd ] ; then                        || 는 &&와 || 둘중 하나라도 참이면 참이라고 해석한다. 따라서

         echo "OK"                                        &&가 참일시 ||는 수행되지 않는다.

      else

         echo "Fail"                                      &&가 거짓일시 ||는 반드시 수행되어야 한다.

      fi                                                   앞 명령어가 참이기 때문에 &&가 아니라면 ||이 참이된다.

 

   # cd /test ; rm -rf * (# cd /test ; rm -rf /test/*) <= 만약 test디렉토리가 없으면 이동하지 못하고 현재 폴더를 다지움

cd /test가 거짓일시 뒤에 명령어가 그대로 수행한다 ( /test에 가기전 pwd에 모든 파일을 삭제)

   # cd /test && rm -rf * (# cd /test && rm -rf /test/*) <= test디렉토리로 이동할 수 있는 경우만 이동해서 삭제한다 *안전

cd /test가 거짓이면 뒤에 삭제 명령어가 수행 되지 않는다.


파일의 이름/경로(dirname/basename)

   # DIR1=/etc/sysconfig/network-scripts/ifcfg-eth0

   # dirname $DIR1   <= 파일을 제외한 경로 이름

   # basename $DIR1  <= 경로를 제외한 파일 이름

 

   # echo $(dirname $DIR1)$(basename $DIR1)   $() = ' '(백쿼터)동일하다

   /etc/sysconfig/network-scriptsifcfg-eth0   





val CMD + other CMD

) --> 

# cat script.sh

 

#!/bin/ksh

) --> 

USERNAME=root

(X) sed 's/$USERNAME/ROOT/g' /etc/group => ' '을 사용해서 $USERNAME스트링으로 인식한다.

(0) eval sed 's/$USERNAME/ROOT/g' /etc/group => $USERNAME에 root라고 넣고 또한번 더 해석한다.

 

# cat script.sh

 

#!/bin/ksh

) --> 

USERNAME=root

(0) sed "s/$USERNAME/ROOT/g" /etc/group => " "은 $기호를 인식한다.

 

) --> 

# A=5

# B=3

# echo $A

# echo $B

) --> 

# A=B

# B=3

# echo $A   ---> B

# echo $B   ---> 3

# echo $$A    (# echo $B)  --->엉뚱한 결론

# eval echo \$$A   ---> 두번 해석하여 3이라는 값이 나온다.

 

A=B

$A => B

$B => 3

\$$A => echo '$B'

eval \$$A => $B => 3

 

 

# ls -a -t -r
 # A='ls -a'
 # B='  -t -r'
 # echo $A
 # echo $B
 # `$A$B`   ----> ls -a -t -r (x) 쉘이 한번해석

 

 

ls -a -l -t -r

A='ls -a --color'

B='-t -r'

C=eval $A $B

$C