관리 메뉴

데브로맨스

【 배시 셸 】 Bash Shell Script 기초 본문

>
【 스크립트 】/Shell

【 배시 셸 】 Bash Shell Script 기초

데브로맨스 2020. 10. 2. 00:02
반응형

변수 linux bash shell script

 개념 linux bash shell script

배시 셸(bash shell)은 환경 변수 말고도 사용자가 직접 변수를 설정하고 사용할 수 있다. 배시 셸 프로그래밍 환경에서 변수는 기초이다. 변수는 문자열 형태를 띠고 그 값에 접근하기 위해서는 변수 명 앞에 달러 문자($)를 붙여서 다룬다. 변수 중에 환경 변수라고 하는 부류는 그 이름을 대문자로 붙이는 일이 일반적인 관례다. 변수를 처리하는 방법은 프로그래밍 언어마다 존재하는 고유한 특징이다. 배시와 다른 프로그래밍 언어의 변수 사용 방법 사이에서 구별되는 주요한 특징 중 하나는 배시가 문자열에 강한 비중을 둔다는 것이다.

 

 정의 linux bash shell script

  • Bash shell 변수명을 대문자로 사용할 때는 배시 셸(bash shell) 환경 변수와 중복되지 않아야 한다.
  • Bash shell 변수명에는 알파벳, 0부터 9까지 숫자, 밑줄 문자(_)를 사용할 수 있다.
  • Bash shell 변수 명의 첫 문자로 숫자가 올 수 없다.
  • Bash shell 변수명과 값 사이에 공백이 있으면 안 된다.
  • Bash shell 변수명은 최대 20 글자까지 가능하다.
  • Bash shell 변수명은 대소문자를 구분한다.
  • Bash shell 배열 변수에는 괄호가 필요하다.
  • Bash shell 변수를 자식 프로세서에서도 사용하려면 'export' 해야 한다.
  • Bash shell 변수 값으로 NULL 문자 할당이 불가능하다.
$ var=$(echo -en "AA\xBB")    # 문제 없음
$ var=$(echo -en "AA\x00BB")    # 경고 발생! (NULL 문자 저장 안됨)
  • NULL 문자는 pipe로 전달하거나 file로 저장이 가능한다.
$ sudo cat /dev/input/mice | od -tx1

 

 종류 및 통용 범위 linux bash shell script

배시 셸(bash shell) 변수의 종류에는 크게 지역 변수(local variable)와 환경 변수(environment variable) 두 가지가 있다. 그리고 특수 내장 변수(built-in variable)들도 있다. 변수의 통용 범위(scope)란 변수를 사용할 수 있는 공간을 의미한다.

 

【 지역 변수 】

  • 자신을 생성한 셸(shell)에서만 사용할 수 있다.
  • 지역 함수는 지역 변수를 생성시키는 데 사용할 수 있다.
    하지만 이런 지역 변수들은 자신을 정의한 함수 내에서만 사용될 수 있다.

환경 변수

  • 자신을 생성한 셸) 뿐만 아니라, 이로부터 파생되어 나온 자식 프로세스에서도 사용할 수 있다.

내장 변수

  • 하나의 문자로 사용되는 특수 변수들과 위치 매개변수(positional parameter)라는 것이 있다.

 

 선언 linux bash shell script

배시 셸(bash shell)은  변수를 생성하기 위해서는 declare, typeset 두 가지의 내장 명령을 사용한다. 이 두 가지는 거의 동일하며 일반적으로 declare 명령을 주로 사용한다.

【 지역 변수 설정 】

  • 지역 변수는 변수명에 값을 할당하여 설정하거나 declare 내장 함수를 사용하여 생성한다.

【 환경 변수 설정 】

지역 변수와 구별하기 위해 일반적으로 대문자로 정의하며, export 내장 명령을 사용하여 지역 변수를 전역 변수화할 수 있다.

 

 

 

함수 linux bash shell script

 개념 linux bash shell script

배시 셸(bash shell)은 여러 개의 명령들을 하나의 그룹으로 묶어서, 사용하기 쉬운 함수명을 이용해 같은 문맥에서 실행시키는 데 있다.

 정의 linux bash shell script

  • 첫 문자로 숫자가 올 수 없다.
  • 종료 상태 값 지정은   return  명으로 한다.
  • 자식 셸에서도 사용하려면  export  해야 한다.
  •  { ;}  또는  ( ) 를 이용해 만듬
    • { ;} : 현재 셸에서 실행된다.
    • ( ) : 서브 셸에서 실행된다.
  • 함수 매개변수 전달
    • 매개변수를 적지 않음.
    • 전달된 인수 값은 함수 내에서  $1 $2 $3 ...  와 같은 특수 변수에 자동으로 할당됨.
    • 함수는 자신의 매개변수 값에 대해 특별한 환경 변수를 사용하므로,
      스크립트 command-line의 매개변수를 함수에서 직접 사용할 수 없다.
  • 함수에서 변수 다루기
    • 전역 변수
      • 스크립트의 메인 부분에서 전역 변수를 정의했다면 함수 안에서 이 값을 검색할 수 있다.
      • 마찬가지로, 함수 안에서 전역 변수를 정의하면 스크립트의 메인 섹션에서 이 값을 검색할 수 있다.
    • 지역 변수
      • 함수에서 전역 변수를 사용하는 대신 함수 안에서 사용되는 모든 변수를 지역 변수로 선언할 수 있다.
      • 이를 위해서는, 변수 선언 앞에  local  키워드를 사용하면 된다.
    • 배열 변수
      • 함수 매개변수로 배열 변수를 쓰려면 함수는 배열 변수의 첫 번째 값만 가져온다. 이문제를 해결하기 위해서는 배열 변수를 개별 값으로 분해해서 변수를 함수 매개변수로 사용해야 한다. 함수 안에서 새로운 배열 변수에 모든 매개변수를 재구성할 수 있다.
  • 기본 종료 상태
    •  return  
      • 함수가 특정한 종료 상태를 돌려준다.
      • 함수가 완료되었을 때 될 수 있는 데로 빨리 값을 저장하기.
      • 종료 상태는 0 ~ 255 범위 안에 있어야 한다. 
    •  $? 
      • 함수가 실행된 후에 종료 상태를 확인하기 위해 사용하는 변수
      • 함수의 반환 값을 얻기 전에 다른 명령을 실행하면 함수의 반환 값을 읽어버리게 된다.
      • 반환 값
        • 0 : 성공
        • 1 : 실패

 

 

 

구조적 명령 linux bash shell script

 개념 linux bash shell script

모든 프로그램이 순차적으로 실행될 순 없다. 스크립트에 작성된 명령들 사이에 논리적 흐름을 제어할 필요가 있다. 조건에 따라 분기가 될 수 있도록 하는 유형의 명령들이 존재하며 이러한 명령들을 구조적 명령이라고 한다. 

if / else : 어떤 조건이 참이냐에 따라 구문을 실행한다.
for : 정해진 횟수만큼 구문을 실행한다.
while : 어떤 조건이 참인 동안 구문을 반복해서 실행한다.
until : 어떤 조건이 참일 때까지 구문을 반복해서 실행한다.
case : 어떤 변수 값에 따라 이에 해당하는 구문을 실행한다.
select : 사용자가 메뉴에서 하나를 고를 수 있게 한다.

 if-then-else-elif linux bash shell script

if 문의 명령이 종료 상태로 0을 반환하면 then 코드 블록을 실행하고, 그렇지 않으면 elif나 else 코드 블록을 실행한다. if-then-else-elif 구문의 문법은 다음과 같다. 

 if condition
 then
      statements
 fi




 if condition
 then
     statements
 [ else

    statements ]
 fi


 if condition; then
     
statements

 [ else
     statements ]
 fi



 if condition; then
      statements
 [ elif condition; then

      statements ]
 [ else
      statements ]
 fi

 

 예제 - if-then

$ vim example.sh

#!/bin/bash

if pwd; then
    echo -e "\nIt worked!"
fi

$ ./example

if 조건문의 pwd 명령 실행의 종료 상태가 0이면 if 문의 코드 블록을 실행한다.

 

 예제 - if-else

$ vim example.sh

#!/bin/bash

if BadCommand 2> /dev/null; then
    echo -e "\nIt worked!"
else
    echo -e "\nThis not a command.."
fi

$ ./example.sh

if 조건문의 유효하지 않은 명령어을 실행시키게 되면 종료 상태로 0이 아닌 값이 반환되며 else 문의 코드 블록을 실행한다. 

 

 예제 - if-elif

$ vim example.sh

#!/bin/bash

TEST_USER=NoSuchUser

if grep $TEST_USER /etc/passwd 2> /dev/null; then
    echo "The user $TEST_USER exists on the system."
elif ls -d /home/$TEST_USER 2> /dev/null; then
    echo "The user $TEST_USER does not exist on this system."
    echo "However, $TEST_USER has a directory."
else
    echo "The user $TEST_USER does not exist on this system."
    echo "And, $TEST_USER does not have a directory."
fi

$ ./example.sh

if 조건문의 조건문들 중에서 종료 상태의 반환 값이 0인 경우가 없다면 else 문의 코드 블록을 실행한다.

 

 테스트 명령linux bash shell script

명령의 종료 상태 코드 말고도 다른 조건을 평가하기 위해 만들어진 방법이다. 테스트 조건은 3가지 종류(숫자 비교, 문자열 비교, 파일 비교)의 조건을 평가할 수 있다. 테스트 명령의 문법은 아래와 같다.

 if test condition
 then
     statements
 fi
 if test condition; then
     statements
 fi

 if [ condition ]
 then
     statements
 fi
 if [ condition ]; then
      statements
 fi

여는 대괄호 뒤와 닫는 대괄호 앞에는 반드시 빈 칸이 있어야 한다.  

 정수 검사 연산자 (숫자 비교)  설명
 n1 -eq n2  n1과 n2가 같은지 검사
 n1 -ge n2  n1이 n2보다 크거나 같은지 검사
 n1 -gt n2  n1이 n2보다 큰지 검사
 n1 -le n2  n1이 n2보다 작거나 같은지 검사
 n1 -lt n2  n1이 n2보다 작은지 검사
 n1 -ne n2  n1과 n2가 같지 않은지 검사

 

 문자열 비교 연산자  설명
 str1 = str2  str1과 str2가 일치하는지 검사
 str1 != str2  str1과 str2가 일치하지 않는지 검사
 str1 < str2  str1이 str2보다 작은지 검사
 str1 > str2  str1이 str2보다 큰지 검사
 -n str1  str1이 NULL이 아닌지 검사 (길이가 0보다 큰지 검사)
 -z str1  str1이 NULL인지 검사 (길이가 0인지 검사)

 

 파일 비교 연산자  설명
 -d file  파일이 존재하고 디렉토리인지 검사
 -e file  파일 존재 여부 검사
 -f file  파일이 존재하고 파일인지 검사
 -r file  파일이 존재하고 읽을 수 있는지 검사
 -s file  파일이 존재하고 비어 있지 않은지 검사
 -w file  파일이 존재하고 기록할 수 있는지 검사
 -x file  파일이 존재하고 실행할 수 있는지 검사
 -O file  파일이 존재하고 현재 사용자가 소유한 것인지 검사
 -G file   파일이 존재하고 기본 그룹이 현재 사용자와 같은지 검사
 file1 -nt file2  file1이 file2보다 나중에 작성되었는지 검사
 file -ot file2  file1이 file2보다 이전에 작성되었는지 검사

 

 예제 - 숫자 비교

$ vim example.sh

#!/bin/bash

val1=10
val2=20

if [ $val1 -gt 5 ]; then
    echo "$val1 is greater than 5"
fi

if [ $val1 -eq $val2 ]; then
    echo "equal!"
else
    echo "different!"
fi

$ ./example.sh

 

 

 예제 - 부동소수점 비교

$ vim example.sh

#!/bin/bash

val=9.999

echo "Test value is $val"

if [ $val -gt 5 ]; then
    echo "Test value $val1 is greater than 5"
fi

$ ./example.sh

배시 셸은 정수만 처리할 수 있다. 조건 테스를 할 때는 부동 소수점을 사용할 수 없다.

 

 예제 - 문자열 일치 검사

$ vim example.sh

#!/bin/bash

TEST_USER=iamjy1005

if [ $USER=$TEST_USER ]; then
    echo "Welcome $TEST_USER"
fi

$ ./example.sh

 

 

 예제 - 문자열 데이터 포함 여부 평가

$ vim example.sh

#!/bin/bash

str1=string
str2=''

if [ -n $str1 ]; then
    echo "The string '$str1' is not empty"
else
    echo "The string '$str1' is empty"
fi

if [ -z $str2 ]; then
    echo "The string 'str2' is empty"
else
    echo "The string 'str2' is not empty"
fi

$ ./example.sh

 

 

 예제 - 디렉토리 존재 여부 확인

$ vim example.sh

#!/bin/bash

TEST_DIR=./test_dir

if [ -d $TEST_DIR ]; then
    echo "The $TEST_DIR directory exists"
    cd $TEST_DIR
else
    echo "The $TEST_DIR directory does not exist"
fi

 

 

 예제 - 파일 존재 여부 확인 (파일과 디렉토리 둘 다 적용된다)

$ vim example.sh

#!/bin/bash

LOCATION=$HOME
FILE_NAME="sential"

if [ -e $LOCATION ]; then
    echo "OK on the $LOCATION directory."
    echo "Now checking on the file, $FILE_NAME"
    
    if [ -e $LOCATION/$FILE_NAME ]; then
        echo "OK on the filename"
        echo "Updating current date..."
       
        date >> $LOCATION/$FILE_NAME
    else
        echo "File does not exist"
        echo "Nothing to update"
    fi
else
    echo "The $LOCATION directory does no exist."
    echo "Nothing to update"
fi

$ ./example.sh

 

 

 예제 - 정규 파일인지 확인

$ vim example.sh

#!/bin/bash

ITEM_NAME=$HOME

echo -e "\nThe item being checked: $ITEM_NAME\n"

if [ -e $ITEM_NAME ]; then
    echo "The item, $ITEM_NAME, does exist."
    echo "But is it a file?"
    
    if [ -f $ITEM_NAME ]; then
        echo "Yes, $ITEM_NAME is a file."
    else
        echo "No, $ITEM_NAME is not a file."
    fi
else
	echo "The item, $ITEM_NAME, does not exist."
    echo "Nothing to update"
fi

$ ./example.sh

 

 

 

 예제 - 파일 읽기 가능 여부 검사

$ vim example.sh

#!/bin/bash

PW_FILE=/etc/shadow

if [ -f $PW_FILE ]; then
	if [ -r $PW_FILE ]; then
    	tail $PW_FILE
    else
    	echo "Sorry, I am unable to read the $PW_FILE file"
    fi
else
	echo "Sorry, the file $PW_FILE does not exist"
fi

$ ./example.sh

 

 

 예제 - 빈 파일인지 검사

$ vim example.sh

#!/bin/bash

FILE_NAME=$HOME/sentinel

if [ -f $FILE_NAME ]; then
	if [ -s $FILE_NAME ]; then
    	echo "The $FILE_NAME file exists and has data in it."
        echo "Will not remove this file"
    else
    	echo "The $FILE_NAME file exists, but is empty."
        echo "Deleting empty file..."
        
        rm $FILE_NAME
    fi
else
	echo "FIle, $FILE_NAME, does not exist."

$ ./example.sh

 

 

 예제 - 파일 쓰기 가능 여부 검사

$ vim example.sh

#!/bin/bash

ITEM_NAME=$HOME/sentinel

echo -e "\nThe item being checked: $ITEM_NAME\n\n"

if [ -f $ITEM_NAME ]; then
	echo "Yes, $ITEM_NAME is a file."
    echo -e "But is it writable?\n\n"
    
    if [ -w $ITEM_NAME ]; then
    	echo "Writing current time to $ITEM_NAME"
        data +%H%M >> $ITEM_NAME
    else
    	echo "Unable to write to $ITEM_NAME"
    fi
else
	echo "No, $ITEM_NAME is not a file."
    
$ ./example.sh

 

 

 예제 - 파일 실행 가능 여부 검사

$ touch script.sh

$ vim example.sh

#!/bin/bash

TEST_FILE=script.sh

if [ -x ./$TEST_FILE ]; then
	echo "You can run the script: "
    ./$TEST_FILE1
else
	echo "Sorry, you are unable to execute the script $TEST_FILE"
fi

$ chmod +x script.sh
$ ./example.sh
$ chmod -x script.sh
$ ./example.sh

 

 

 예제 - 소유권 확인

$ vim example.sh

#!/bin/bash

if [ -O /etc/passwd ]; then
	echo "You are the owner of the /etc/passwd file"
else
	echo "Sorry, you are not the owner of the /etc/passwd file"
fi

 

 

 예제 - 기본 그룹 구성원 확인

$ touch test_file.txt

$ vim example.sh

#!/bin/bash

TEST_FILE=test_file.txt

if [ -G $TEST_FILE ]; then
	echo "You are in the same group as the file"
else
	echo "The file is not owned by your group"
fi

$ ./example.sh

사용자의 기본 그룹과 일치하면 성공이다. 이 검사는 기본 그룹만 검사하며 사용자가 속한 모든 그룹을 검사하는 것이 아니다.

 

 예제 - 파일 날짜 확인

$ touch file1.txt file2.txt
$ date > file1.txt; sleep 1; date > file2.txt
$ cat file1.txt file2.txt

$ vim example.sh

#!/bin/bash

TEST_FILE1=file1.txt
TEST_FILE2=file2.txt

if [ $TEST_FILE1 -nt $TEST_FILE2.txt ]; then
	echo "The $TEST_FILE1 is newer than $TEST_FILE2"
else
	echo "The $TEST_FILE2 is newer than $TEST_FILE1"
fi

if [ $TEST_FILE1 -ot $TEST_FILE2 ]; then
	echo "The $TEST_FILE1 File is older than the $TEST_FILE2 file"
fi

$ ./example.sh

 

 

 복합 테스트 linux bash shell script

부울 연산을 사용하면 여러 개의 테스트를 결합할 수 있다.

 

 [ condition ] && [ condition2 ]
 [ condition ]  | |  [ condition2 ]

 

 예제

$ mkdir test_dir
$ touch test_dir/test_file.txt

$ vim example.sh

#!/bin/bash

TEST_DIR=test_dir
TEST_FILE=test_file

if [ -d ./$TEST_DIR ] && [ -w ./$TEST_DIR/$TEST_FILE ]; then
	echo "The file exests and you can write to it"
else
	echo "I cannot write to the file"
fi

$ ./example.sh

 

 

 이중 괄호 사용하기 linux bash shell script

수학 표현식에 대한 고급 기능을 제공한다. 테스트 명령에서 쓰이는 표준 수학 연산자 이외에도 추가적으로 아래 표에 나와있는 연산자들을 사용할 수 있다.

 

 기호  설명
 val++  후위 증가
 val--  후위 감소
 ++val  전위 증가
 --val  전위 감소
 !  논리 부정
 ~  비트 부정
 **  지수화
 <<  비트를 왼쪽으로 시프트
 >>  비트를 오른쪽으로 시프트
 &  비트 단위 부울 AND
 |  비트 단위 부울 OR
 &&  논리 AND
 ||  논리 OR

 

 예제

$ vim example.sh

#!/bin/bash

val1=10

if (( $val1 ** 2 > 90 )); then
	(( val2 = $val1 ** 2 ))
    
	echo "The square of $val1 is $va2"
fi

$ ./example.sh

 

 

 이중 대괄호 사용하기 linux bash shell script

문자열 비교에 대한 고급 기능을 제공한다. 정규 표현식을 사용해 규칙을 정의하여 패턴을 대조할 수 있다.

 [[ expression ]]

 

 예제

$ vim example.sh

#!/bin/bash

if [[ $USER == i* ]]; then
	echo "Hello, $USER"
else
	echo "Sorry, I do not know you"
fi

$ ./example.sh

 

 

 case

변수의 값을 평가할 때 가능한 몇 가지 값 중 하나에 해당하는 곳의 코드 블록을 실행한다.

 

 예제

$ vim example.sh

#!/bin/bash

case $USER in
	iamjy1005 | louis)
		echo "Welcome, $USER"
        echo "Please enjoy your visit";;
    testing)
    	echo "Special testing account";;
    jessica)
    	echo "Do not forget to log off when you're done";;
    *)
    	echo "Sory, you are not allowed here";;
esac

$ ./example.sh

 

 

 for-loop linux bash shell script

이 반복문은 특정 조건이 만족될 때까지 일련의 명령을 되풀이 한다.

 for var in list
 do
    compound-statements (code block, commands)
 done

 

 예제 - 목록에서 값 읽기

for alpha in a b c d e f g
do
    echo The next alphabet is $alpha
done

for 문의 목록에 나열된 요소들을 하나씩 변수로 읽어온 후 echo 명령으로 변수에 저장된 데이터를 화면에 출력한다.

 

 예제 - 목록의 복잡한 값 읽기

$ vim example.sh

#!/bin/bash

for test in Nevada "New York" "New Mexico" "New Hampshire"
do
    echo "Now going to $test"
done

$ ./example.sh

데이터 값 사이에 빈 칸이 있다면 쌍따옴표로 감싸야 한다.

 

 예제 - 변수에서 목록 읽기

$ vim example.sh

#!/bin/bash

list="seoul daegu busan ulsan"
list=$list" incheon"

for state in $list
do
    echo "Have you ever visited $state?"
done

$ ./example.sh

변수에 저장된 쌍따옴표로 묶인 하나의 문자열 데이터를 for 문에서 IFS 환경 변수에 지정된 구분 기호를 기준으로 문자열을 분리시켜 요소 하나 하나를 되풀이하며 읽어온다.

 

 예제 - 필드 구분자 변경

$ vim states.txt

Nevada
North Dakota
Delaware
New York

$ vim example.sh

#!/bin/bash

file="states.txt"

IFS=$'\n'
for state in $(cat $file)
do
	echo "Visit beautiful $state"
done

$ ./example.sh

내부 필드 분리 구분자라고 불리는 IFS 환경 변수는 배시 셸(bash shell)이 필드 구분자로 사용하는 문자의 목록을 정의한다. 배시 셸(bash shell)의 기본 필드 구분 기호로는 빈 칸, , 줄바꿈 문자가 있다. 위 예제는 데이터에 포함된 빈 칸이나 탭은 무시하고 줄바꿈 문자로 필드 구분자로 인식하도록 하였다.

 

 예제 - 와일드 카드 사용하여 디렉토리 읽기

$ mkdir -p ~/test_dir/sub_dir
$ touch ~/test_dir/test_file.txt

$ vim example.sh

#!/bin/bash

for file in ~/test_dir/*
do
	if [ -d "$file" ]; then
    	echo "$file is a directory"
    elif [ -f "$file" ]; then
    	echo "$file is a file"
done

이 예제에서는 와일드 카드 문자를 이용한 문자 패턴과 일치하는 디렉토리 안의 파일이나 하위 디렉토리에 대한 목록을 만들어내고 하나씩 되풀이 하면서 디렉토리인지 파일인지를 검사한다. 파일명이나 디렉토리명에는 빈 칸이 포함될 수 있기 때문에 변수를 읽을 때 쌍따옴표로 변수를 묶지 않으면 오류가 발생한다.

 

 예제 - 와일드 카드 사용하여 디렉토리 읽기 (2)

$ mkdir -p ~/test_dir/sub_dir
$ touch ~/test_dir/test_file.txt
$ touch ~/test_dir/test_file.bin

$ vim example.sh

#!/bin/bash

for file in ~/test_dir/*.txt ~/test_dir/sub_dir
do
	if [ -d "$file" ]; then
    	echo "$file is a directory"
    elif [ -f "$file" ]; then
    	echo "$file is a file"
done

for 문의 목록에는 찾고자 하는 파일이나 디렉토리에 대한 문자열 패턴을 여러 개 나열하여 결합시킬 수 있다.

 

 while-loop linux bash shell script

이 반복문(loop)은 탈출 조건식(condition expression)을 우선적으로 평가(evaluation)한 후 코드 블록(code block) 실행 여부를 결정한다. 즉, 명령문(command)의 종료 상태(return value)가 참(0)인 동안만 코드 블록내의 일련의 명령들(commands)을 수행한다.

 while test condition expression (command)
 do
      compound-statements (code block, commands)
 done
 while [ condition expression (command) ]
 do
    compound-statements (code block, commands)
 done

 

 예제 - 기본적인 사용법

$ vim example.sh

#!/bin/bash

var=10
while [ $var -gt 0 ]
do
	echo $var
    var=$[ $var - 1 ]
done

$ ./example.sh

 

 

 

 예제 - 여러 조건식 결합하기

$ vim example.sh

#!/bin/bash

while echo $var
        [ $var -ge 0 ]
do
    echo "This is inside the loop"
    var=$[ $var - 1 ]
done

$ ./example.sh

여기서 주의할 점은, 여러 조건식들 중에 참이 아닌 경우가 발생하면 그 뒤의 조건식은 평가하지 않고 반복문을 빠져나간다는 점을 유의하고 조건식을 정의해야 한다. 예제에서는 var의 값이 -1이 되는 시점에 조건식들 중에서 echo 명령문의 종료 상태는 0이기 때문에 echo 명령문을 실행하고 다음 조건식을 평가하지만 0보다 작기 때문에 반복문을 중단하고 빠져나간다. 그리고 각 조건식은 별개의 줄에 있어야 한다.

 

 until-loop linux bash shell script

이 반복문(loop)은 while 반복문과는 정반대로 작동한다.

until test condition expression (command)
 do
      compound-statements (code block, commands)
 done
 until [condition expression (command) ]
 do
   compound-statements (code block, commands)
 done

 

 예제

$ vim example.sh

#!/bin/bash

var=100

until [ $var -eq 0 ]
do
	echo $var
    var=$[ $var - 25 ]
done

$ ./example.sh

이 예제는 var의 값이 0일 때까지 until 조건문의 코드 블록내의 일련의 명령문들을 실행한다. while 반복문의 주의 사항이 여기에도 똑같이 적용된다.

 

 loop control - break linux bash shell script

이 명령을 사용하면 반복문을 완전히 빠져나올 수 있으며 하나의 매개변수를 가진다. 매개변수의 역할은 중첩 반복문에서 상위 n 단계의 반복문까지 빠져 나갈 수 있도록 한다.

 break n

 

 예제 - 단일 반복문 빠져나가기

$ vim example.sh

#!/bin/bash

for var in 1 2 3 4 5 6 7 8 9 10
do
	if [ $var -eq 5 ]; then
    	break
    fi
    
    echo "Iteration number: $var"
done

$ ./example.sh

 

 

 예제 - 중첩 반복문 빠져나가기

$ vim example.sh

#!/bin/bash

for (( a = 1; a < 4; a++ ))
do
	echo "Outer loop: $a"
    
	for ((b = 1; b < 100; b++ ))
	do
		if [ $b -gt 4 ]; then
			break 2
		fi
        
		echo "    Inner loop: $b"
        
	done
done

 

 

 loop control - continue linux bash shell script

이 명령은 반복문을 완전히 종료하지 않고 continue 뒤의 나머지 명령문들을 건너띄고 반복을 계속한다. break와 마찬가지로 이 명령도 하나의 매개변수를 가진다. 매개변수의 역할은 속개할 상위 n 단계의 반복문을 지정할 수 있다.

 continue n

 

 예제

$ vim example.sh

#!/bin/bash

var=0

while echo "while iteration: $var"
        [ $var -lt 15 ]
do
	if [ $var -gt 5 ] && [ $var -lt 10 ]; then
		continue
	fi
    
	echo "    Inside iteration number: $var"
	var=$[ $var + 1 ]
done

$ ./example.sh

 

 

 예제 - 중첩 반복문에서 속개할 반복문 지정하기

$ vim example.sh

#!/bin/bash

for (( a = 1; a <= 5; a++ ))
do
	echo "Iteration $a:"
    
	for (( b = 1; b < 3; b++ ))
	do
		if [ $a -gt 2 ] && [ $a -lt 4 ]; then
			continue 2
		fi
        
		var=$[ $a * $b ]
		echo "    The result of $a * $b is $var"
	done
done

 

 

 

 

 

 사용자 입력 처리 linux bash shell script

 개념 linux bash shell script

배시 셸(bash shell)은 커맨드라인(Command-line Interface) 매개변수(명령 뒤에 추가되는 데이터 값), 커맨드 라인 옵션(명령의 동작에 변화를 주는 문자 값)을 포함하여 직접 키보드를 통한 입력을 읽을 수 있는 방법을 제공한다.

 

 기본 linux bash shell script

배시 셸(bash shell)에서 데이터를 전달하는 가장 기본적인 방법은 명령 줄(command-line) 매개변수(parameter)를 사용하는 것이다. 셸은 입력된 매개변수를 위치 매개변수(location parameter)라고 하는 특별한 변수에 할당한다. 위치 매개변수에는 숫자와 문자열을 사용할 수 있다. 매개변수로 문자열을 전달할 때 사용되는 따옴표는 데이터의 시작과 끝을 나타낸다.

 

위치 매개변수 범수: $1 ~ $9 (총 9개의 매개변수를 전달할 수 있다), $9 이후부터는 ${10} ... ${N} 과 같이 중괄호를 사용해야 한다.특수 매개변수 : $0, $#

  • $0 : 스크립트 이름
  • $# : 매개변수 개수

매개변수 전달하기 linux bash shell script

 예제 - 매개변수 전달하기

$ ./example.sh 1 5    # 스크립트에 매개변수 2개를 전달한다.

 

 

 매개변수 읽기 linux bash shell script

 예제 - 매개변수 읽기 (1)

$ vim example.sh

#!/bin/bash

factorial=1
for (( number = $1; number <= $2; number++ ))    # 명령 줄에서 두 개의 매개변수가 전달되었고, $1과 $2를 통해 읽어올 수 있다.
do
    factorial=$[ $factorial * $number ]
done
echo "The factorial of $1 is $factorial"

$ ./example.sh 1 5    # 두 개의 매개변수를 스크립트에 전달한다.

  

 

 예제 - 매개변수 읽기 (2)

$ vim example.sh

Hello $1, nice to meet you

$ ./example.sh Mr louis      # 빈 칸이 포함되어 의도와는 다르게 두 개의 인자로 전달된다.
$ ./example.sh 'Mr louis'    # 따옴표로 묶어 하나의 인자로 전달한다.
$ ./example.sh "Mr louis"    # 따옴표로 묶어 하나의 인자로 전달한다.

  

 

 예제 - 매개변수 읽기 (3)

$ vim example.sh

#!/bin/bash

sum=$[ $9 + ${10} + ${11} ]      # 열 번째 인자부터는 중괄호를 사용하여 변수에 접근해야 한다.
echo "9th argument is $9"    
echo "10th argument is ${10}"    
echo "11th argument is ${11}"
echo "SUM $sum"

$ ./example.sh 1 2 3 4 5 6 7 8 9 10 11

  

 

 예제 - 스크립트 이름 읽기 (1)

$ vim example.sh

#!/bin/bash

echo "The \$0 argument is $0"

$ ./example.sh

기본적으로 $0은 스크립트의 이름뿐만 아니라 전체 경로까지 포함하고 있다

 

 예제 - 스크립트 이름 읽기 (2)

$ vim example.sh

#!/bin/bash

name=$(basename $0)              # 전체 경로 중 스크립트 이름만 읽어와 name 변수에 저장한다.
echo "Script name is ${name}"

$ ./example.sh

basename을 이용해 스크립트 파일명만 얻어올 수 있다

 

 예제 - 매개변수 테스트하기

$ vim example.sh

#!/bin/bash

if [ -n "$1" ]    # 매개변수에 접근하기 전에 인자를 -n 옵션으로 평가한다.
then
    echo "Hello $1, nice to meet you"
else
    echo "Sorry, you did not identify yourself!"
fi

$ ./example.sh andy
$ ./example.sh        # 인자가 없어서 에러가 발생한다.

  

 

 특수한 매개변수 사용하기 linux bash shell script

 예제 - 매개변수 개수 확인 (1)

$ vim example.sh

#!/bin/bash

echo "There were $# parameters supplied."   # 명령줄에서 스크립트로 전달된 인자 개수를 출력한다.

$ ./example.sh

  

 

 예제 - 매개변수 개수 확인 (2)

$ vim example.sh

#!/bin/bash

if [ $# -ne 2 ]    # 인자 개수를 확인한다. (정확히 2개인지 아닌지)
then
    echo "Usage: example.sh a b"
else
    total=$[ $1 + $2 ]
    echo "Total is $total"
fi

$ ./example.sh    # 인자가 없다.
$ ./example 1     # 인자가 1개 부족하다.
$ ./example 1 2

  

 

 예제 - 매개변수 개수 확인 (3)

$ vim example.sh

#!/bin/bash

params=$#    # 인자 개수를 변수에 저장한다.

# echo "The last parameter was ${$#}    # 중괄호 안에는 달러 기호를 사용할 수 없다.
echo "The last parameter is $params     # 개수를 확인한다.
echo "The last parameter is ${!#}       # 여기서 !#은 값이 아니라 그냥 이름이다. 이름에 해당하는 변수의 값을 읽어온다.

$ ./example.sh a b c d    # 인자의 개수가 4개 이므로 ${!#}은 ${4}가 되어 4번째 인자의 값을 읽어온다.
$ ./example.sh            # 인자의 개수가 0개 이므로 ${!#}은 ${0}가 되어 스크립트 이름을 읽어온다.

 

 

 모든 인자를 한꺼번에 읽어들이기 linux bash shell script

$# 대신 $*와 $@ 변수로 모든 매개변수에 쉽게 접근할 수 있다.

  • $* : 전달되는 모든 인자를 하나의 매개변수로 다룬다.
  • $@ : 전달되는 모든 인자를 같은 문자열의 분리된 단어들로 가지고 있다.

 예제

$ vim example.sh

#!/bin/bash

cnt=1
for arg in "$*"    # 전달은 모든 인자를 하나의 인자로 취급한다.
do
    echo "\$* arguments #$cnt = $arg"
    cnt=$[ $cnt + 1 ]
done

cnt=1
for arg in "$@"    # 전달된 모든 인자를 분리된 인자들로 취급한다.
do
    echo "\$@ arguments #$@ = $arg"
    cnt=$[ $cnt + 1 ]
done

$ ./example.sh arg1 arg2 arg3 arg4

 

 

 시프트 기능 활용하기 linux bash shell script

배시 셸(bash shell)은 명령 줄 매개변수를 조작하기 위해 shift 명령을 제공한다. 이 명령은 각 매개변수의 값을 왼쪽으로 이동시킨다. 예를 들어 전달된 인자 3개를 shift로 이동시키면 $3 -> $2 ->$1 이렇게 $3의 값은 $2로 옮겨가고 $2이 값은 $1로 옮겨가고 $1의 값은 없어진다.

 

주의 사항: 값을 복사되는 것이 아니라 이동된다. 즉 인자가 가지고 있던 원래 값은 없어지거나 shift된 값으로 바뀐다.

 

 예제 (1)

$ vim example.sh

#!/bin/bash

cnt=1
while [ -n "$1" ]
do
    echo "Argument #$cnt = $1"
    cnt=$ [ $cnt + 1 ]
    shift    # 인자들의 값을 왼쪽으로 하나씩 이동시킨다.
done

$ ./example.sh

 

 

 예제 (2) - 여러 자리씩 한번에 시프트하기

$ vim example.sh

#!/bin/bash

echo "The original arguments: $*"
shift 2    # 인자들의 값을 왼쪽으로 한번에 두 자리씩 이동시킨다.
echo "Here's the new first argument: $1"

$ ./exmaple.sh 1 2 3 4 5

 

 

 옵션 처리하기 linux bash shell script

배시 셸(bash shell)의 명령행 옵션은 스크립트 실행 시 동작을 변경하기 위한 하나의 문자로 앞에 대시(-)가 붙는다. 명령줄 매개변수를 처리하는 것과 똑같은 방식으로 명령줄 옵션을 처리할 수 있다. 종종 옵션과 매개변수를 모두 사용해야 될 때가 있는데, 표준적인 방법으로는 특별한 문자 코드로 둘을 분리해서 옵션의 시작과 끝을 알려주는 것이다. 리눅스에서 이러한 특수 문자는 이중 대시(--)이다. 셸은 옵션 목록의 끝을 표시하기 위해 이중 대시(--)를 사용한다.

 

 예제 - 간단한 옵션 처리

$ vim example.sh

#!/bin/bash

while [ -n "$1" ]
do
    case "$1" in    # 인자가 옵션 형식을 가지는지 판단한다.
        -a) echo "Found the -a option" ;;
        -b) echo "Found the -b option" ;;
        -c) echo "Found the -c option" ;;
         *) echo "$1 is not an option" ;;
     esac
     shift    # 전달된 인자들의 값을 하나씩 왼쪽으로 이동시킴.
done

$ example.sh -a -b -c -d

 

 

 예제 - 매개변수에서 옵션 분리

$ vim example.sh

#!/bin/bash

while [ -n "$1" ]
do
    case "$1" in
        -a) echo "Found the -a option" ;;
        -a) echo "Found the -a option" ;;
        -a) echo "Found the -a option" ;;
        --) shift       # 이중 대시를 밀어내기 위해서 shift를 한번 더 시킨다.
            break ;;    # 이중 대시를 만나면 shift시키고 case문을 벗어난다.
         *) echo "$1 is not an option" ;;
     esac
     shift
done

cnt=1
for arg in $@    # 남아 있는 인자를 옵션이 아닌 값으로 처리할 수 있게 되었다.
do
    echo "Argument #$cnt = $arg
    cnt=$[ $cnt + 1 ]
done

$ ./example.sh

  

 

 예제 - 옵션 값 처리

$ vim example.sh

#!/bin/bash

while [ -n "$1" ]
do
    case "$1" in
        -a) echo "Found the -a option" ;;
        -b) arg="$2"
            echo "Found the -b option, with argument value $arg"
            shift ;;
        -c) echo "Found the -b option" ;;
        --) shift
            break ;;
         *) echo "$1 is not an option" ;;
    esac
    shift
done

cnt=1
for arg in "$@"
do
    echo "Argument #$cnt: $arg"
    cnt=$[ $cnt + 1 ]
done

$ ./example.sh -a -b arg -d

 

 

 옵션 처리하기 - getopt 명령 사용하기 linux bash shell script

배시 셸(bash shell)에서 이 명령은 명령행 인자를 옵션 처리하기 위해 명령행 매개변수의 목록을 받아서 적절한 형식으로 자동 변환한다.

이 명령의 형식은 다음과 같다.

getopt optstring parameters

optstring : 옵션으로 사용될 문자들로 구성된 문자열이다.

 

 예제 - getopt 명령 형식

$ getopt ab:cd -a -b arg1 -cd arg2 arg3

이 명령문은  ab:cd 형식을 옵션으로 정의하였다. b 뒤에 콜론(:)은 b 옵션은 매개변수를 필요로한다는 것을 나타낸다. 명령이 실행되면 주어진 매개변수 목록을 검사하고 optsting을 기준으로 각 요소를 분리한다. -cd 옵션은 자동으로 두 개의 개별 옵션으로 구분되었고 추가 매개변수를 분리하는 이중 대시(--)도 자동으로 들어간 것을 알 수 있다

 

 예제 - 스크립트에서 getopt 사용

$ vim example.sh

#!/bin/bash

set -- $(getopt -q abc:d "$@")    # set 명령문의 이중 대시(--) 의미는 명령행 매개변수를 getopt 명령문의 optstring 형식으로 대체하라는 것이다.

while [ -n "$1" ]
do
    case "$1" in
        -a) echo "Found the -a option" ;;
        -b) arg="$2"
            echo "Found the -b option, with argument value $arg"
            shift ;;
        -c) echo "Found the -c option" ;;
        --) shift
            break ;;
         *) echo "$1 is not an option" ;;
     esac
     shift
done

cnt=1
for arg in "$@"
do
    echo "Argument #$cnt: $arg"
    cnt=$[ $cnt + 1 ]
done

$ ./example.sh -abc

 

 

getopt 명령은 따옴표 안에 있는 빈 칸도 매개변수 구분자로 해석하기 때문에, 빈 칸과과 따옴표를 사용하는 매개변수 값을 처리하기에는 좋지 않다. 이 문제를 해결하기 위해서는 기능이 확장된 getopts 명령을 사용하면 된다.

 

 예제 - 스크립에서 getopts 사용 (1)

$ vim example.sh

#!/bin/bash

while getopts :ab:c opt
do
    case "$opt" in
        a) echo "Found the -a option" ;;
        b) echo "Found the -b option, with value $OPTARG" ;;
        c) echo "Found the -c option" ;;
        *) echo "Unknown option: $opt" ;;
    esac
done

$ ./example.sh -ab arg1 -c

 

 

 예제 - 스크립트에서 getopts 사용 (2)

$ vim example.sh

#!/bin/bash

while getopts :ab:cd opt
do
    case "$opt" in
        a) echo "Found the -a option" ;;
        b) echo "Found the -a option" ;;
        c) echo "Found the -a option" ;;
        d) echo "Found the -a option" ;;
        *) echo "Unkown option: $opt" ;;
    esac
done

shift $[ $OPTIND - 1 ]

cnt=1
for arg in "$@"
do
    echo "Argument $cnt: $arg"
    cnt=$[ $cnt + 1 ]
done

$ ./example.sh -a -b arg1 -d arg2 arg3 arg4

 

 

 사용자 입력 받기 linux bash shell script

배시 셸 스크립트(bash shell script)에서 표준 입력(키보드)이나 파일로부터 데이터를 입력 받기 위해서는 read 명령을 사용하면 된다. 입력받은 데이터는 변수에 저장한다.

 

 예제 - read 명령으로 데이터 입력 받기

$ vim example.sh

#!/bin/bash

echo -n "Enter your name: "    # -n 옵션은 출력 문자 끝에 줄바꿈 문자를 표시하지 않도록 지시한다.
read name                      # 표준 입력(키보드)에서 데이터를 입력 받아서 name에 저장한다.
echo "Hello! $name"

$ ./example.sh

 

 

 예제 - 프롬프트에 문자 출력

$ vim example.sh

#!/bin/bash

read -p "Please enter your age: " age    # -p 옵션을 사용해 입력을 받기 전 프롬프트에 출력할 문자열을 설정한다.
days=$[ $age * 365 ]
echo "That makes you over $days days old!"

$ ./example.sh

 

 

 예제 - read 명령을 통해 다수의 데이터를 입력 받기

$ vim example.sh

#!/bin/bash

read -p "Enter your name(first last): " first last    # 2개의 데이터를 입력받는다.
echo "Your name is $first, $last"

$ ./example.sh

read 명령은 공백이 포함된 입력 데이터 모두를 하나의 변수에 할당하거나 공백으로 구분된 각각의 데이터를 여러 변수에 저자장할 수 있다. 변수 개수가 부족하면 나머지 데이터는 마지막 변수에 할당된다.

 

 예제 - read 명령으로 입력 받은 데이터에 대한 변수를 지정 않을 시 특수 변수 사용

$ vim example.sh

#!/bin/bash

read -p "Enter your name: "    # 변수를 지정하지 않아서 입력 데이터를 REPLY라는 특수 변수에 저장한다.
echo "Hello $REPLY"

$ ./example.sh

아무런 변수를 지정하지 않으면, read 명령은 특수한 환경 변수인 REPLY에 입력 받은 모든 데이터를 저장한다.

 

 예제 - read 명령 입력 시 타이를 설정하여 입력 시간 초과 시 그냥 넘어가기

$ vim example.sh

#!/bin/bash

if read -t 5 -p "Please enter your name: " name    # -t 옵션으로 타이머를 설정하여 5초 이내에 입력이 없으면 넘어간다.
then
    echo "Hello $name"
else
    echo -e "\nSorry, too slow!"
fi

$ ./example.sh

 데이터 입력 여부와는 상관없이 스크립트가 계속 수행되어야 할 경우에 적당히 타이머를 설정한 후 사용하면 된다. 

 

 예제 - read 명령을 통해 입력 받을 글자 개수를 제한하기

$ vim example.sh

#!/bin/bash

read -n1 -p "Do you want to continue [Y/N]? " answer    # -n 옵션을 사용해서 한 글자만 입력받도록 한다.
case $answer in
    Y|y) echo -e "\n\nfine, continue on...\n" ;;
    N|n) echo -e "\n\nOK, goodbye\n" ;;
         exit ;;
esac

echo "This is the end of the script"

$ ./example.sh

 -n 옵션을 사용하면 입력 문자의 개수를 제한할 수 있다.  

 

 예제 - read 명령으로 입력을 받을 때 입력 데이터를 화면에 표시하지 않기

$ vim example.sh

#!/bin/bash

read -s -p "Enter your password: " pass        # 입력 데이터를 모니터에 표시하지 않는다.
echo -e "\n\nAre you sure of your password $pass? "

$ ./example.sh

-s 옵션은 read 명령에 입력 데이터를 표시 하지 않도록 한다.

 

 예제 - read 명령을 통해 파일에서 데이터 읽기

$ vim example.txt

This is 1st line.
This is 2nd line.
This is 3rd line.

$ vim example.sh

#!/bin/bash

cnt=1
cat example.txt | while read line    # cat 명령문의 결과를 파이프를 통해 read에 전달한 후 한 줄씩 읽는다.
do
    echo "Line $cnt: $line"
    cnt=$[ $cnt + 1 ]
done

echo "Finished processing the file"

$ ./example.sh

while 문은 read 명령이 0이 아닌 종료 상태를 돌려줄 때까지 루프를 돌며 파일에서 한 줄씩 데이터를 읽어온다.

 

 

 

리다이렉트 (입출력 방향 재지정) linux bash shell script

 리다이렉트 - 입출력다루기 linux bash shell script

프로세스는 한 번에 최대 9개의 파일 디스크립터를 열 수 있으며, 처음 3개의 파일 디스크립터(0, 1, 2)를 예약해 둔다.

 

STDIN : 셸의 표준 입력 (키보드)

STDOUT : 셸의 표준 출력 (모니터)

STDERR : 셸의 표준 오류 출력 (STDOUT과 같은 곳을 가리키지만, 서로 다른 파일 디스크립터를 가진다)

파일 디스크립터 약어 설명
0 STDIN 표준 입력
1 STDOUT 표준 출력
2 STDERR 표준 오류

 

 예제 - STDIN

$ cat
This is test line.

표준 입력이 키보드로부터 입력을 받아 들인다

 

 예제 - STDIN (2)

$ vim input_file
This is the 1st line.
This is the 2nd line.

$ cat < input_file    # 파일로부터 입력받도록 하기 위해 STDIN을 파일로 리다이렉트시켰다.
This is the 1st line.
This is the 2nd line.

STDIN의 입력 방향을 키보드가 아닌 파일로부터 한 줄씩 입력받을 수 있도록 리다이렉트 기호를 사용하여 표준 입력의 입력 방향을 키보드가 아닌 파일로 재지정하였다

 

 예제 - STDOUT

$ ls -l > out_file    # 출력 결과를 표준 출력이 아닌 파일로 전송하기 위해 out_file로 출력 방향을 재지정하였다.
$ cat out_file        # 출력 결과는 out_file에 기록되어 있다.

출력 결과를 파일에 저장하기 위해 리다이렉트 기호를 사용하여 STDOUT의 출력 방향을 모니터가 아닌 파일로 방향을 재지정하였다.

 

 예제 - STDOUT (2)

$ who >> users.log    # 출력 결과를 파일의 끝에 덧붙여가며 저장하기 위해 출력 방향을 파일로 재지정한다.
$ cat users.log

'>>' 기호를 사용하면 기존의 파일에 새로 덮어 쓰는 것이 아닌 파일의 끝에 덧붙인다

 

 예제 - STDERR

$ ls -al bad_file 2> out.log    # 명령문의 출력 결과에서 에러 메시지는 파일로 저장하기 위해 표준 오류 출력 방향을 파일로 재지정한다.
$ cat out.log

명령문의 출력 결과에서 에러 메시지는 파일로 저장하기 위해 표준 오류 출력 방향을 파일로 재지정하였다

 

 예제 - STDERR (2)

$ ls -al file1 file2 file3 bad_file 2> err.log 1> out.log    # 명령문의 출력 결과에서 에러 메시지와 정상 메시지를 파일로 저장되도록 파일로 출력 방향을 재지정한다.
$ cat err.log; cat out.log

명령문의 출력 결과에서 에러 메시지와 정상 메시지를 파일로 저장하기 위해 출력 방향을 파일로 재지정하였다

 

 리다이렉트 - 스크립트에서 출력 리다이렉트하기 linux bash shell script

적절한 파일 디스크립터를 리다이렉트하면 출력을 만들어 내는 스크립트에서 STDOUT 및 STDERR 파일 디스크립터를 여러 개의 장소로 전송하도록 만들 수 있다. 스크립트에서 출력을 리다이렉트하는 방법은 두 가지가 있다.

  • 각 줄을 임시로 리다이렉트하기
  • 스크립트에서 모든 명령을 지속적으로 리다이렉트하기

 예제 - 일시 다이렉트

$ vim example.sh

#!/bin/bash

echo "This is an error" >&2    # 이 명령문은 문자열 데이터를 표준 오류(STDERR, 모니터)로 보내도록 지시한다.
echo "This is normal output"   # 이 명령문은 문자열 데이터를 표준 출력(STDIN, 모니터)로 보내도록 지시한다.

$ ./example.sh
$ ./example.sh 2> err_log   # 이 명령문은 STDERR로 가는 모든 문자열 데이터가 다시 err_log로 리다이렉트한다.
$  cat err_log

기본적으로 리눅스는 STDERR 출력을 STDOUT으로 보낸다는 점을 기억하라. 결과를 보면 STDOUT을 사용하는 문자열 데이터는 모니터에 나타나는 반면  STDERR로 가는 echo 문의 문자열 데이터는 err_log 파일로 리다이렉트되었다

 

 예제 - 지속적으로 리다이렉트하기

$ vim example.sh

#!/bin/bash

OUT_FILE="out_file"

exec 1>${OUT_FILE}    # 스크립트가 실행되는 동안 특정한 파일 디스크립터를 리다이렉트 하도록 셸에게 지시한다.

echo "This is a test of redirecting all output"
echo "from a script to another file"
echo "without having to redirect every individual line"

$ ./example.sh
$ cat out_file

스크립트에 리다이렉트할 데이터의 양이 많다면, 명령문 마다 일일이 다 리다이렉트하는 일은 여간 지루하고 귀찮은 일이 아닐 수 없다. 대신 exec 명령을 사용하여 스크립트가 실행되는 동안 특정 파일 디스크립터의 리다이렉트 상태를 유지시킬 수 있다

 

 리다이렉트 - 스크립트에서 입력 리다이렉트하기 linux bash shell script

STDIN을 키보드로부터 다른 곳으로 리다이렉트할 때에도 사용할 수 있다. exec 명령은 STDIN을 리눅스 시스템의 파일로 리다이렉트할 수 있다.

exec 0< test_file

이 명령은 셸에게  표준 입력(STDIN from 키보드) 대신 test_file로부터 입력을 받을 것을 지시한다

 

 예제

$ vim example.sh

#!/bin/bash

TEST_FILE="input_file"

exec 0<${TEST_FILE}    # TEST_FILE에 표준 입력에 해당하는 파일 디스크립터 0을 리다이렉트한다.
count=1

while read line    # read 명령은 TEST_FILE에 리다이렉트된 FD 0으로부터 한 줄씩 데이터를 읽어온다.
do
    echo "Line #$count: $line"
    count=$[ $count + 1 ]
done

$ ./example.sh

 

 

 리다이렉트 - 사용자 정의 리다이렉트 만들기 linux bash shell script

스크립트에서 입출력 리다이렉트 하기 위해 기본 파일 디스크립터를 포함해서 파일 디스크립터를 총 9개 열수 있다. 3~ 8번 중 하나를 입출력 리다이렉트로 사용할 수 있다. 이들 파일 디스크립터 중 어느 것이든 파일에 할당할 수 있으며 스크립트에서 사용할 수도 있다. 표준 파일 디스크립터와 마찬가지로 사용자 정의 파일 디스크립터를 어떤 파일로 할당하면 다시 할당하기 전까지는 계속해서 리다이렉트가 유지된다. 표준 파일 디스크립터를 파일로 리다이렉트하기 전에 다른 파일 디스크립터에 저장해 놓으면 파일을 다 읽거나 출력했을 때 원래 장소(키보드, 모니터)로 복원할 수 있다. 그리고 입력과 출력 모두를 할 수 있는 파일 디스크립터를 열수 있는데, 주의해야할 점이 있다. 한 파일로 데이터를 읽고 쓸 때 셸은 데이터 접근이 어디에서 이루어지는지를 가리키는 내부 포인터를 유지한다. 모든 읽기 또는 쓰기 작업을 파일 포인터가 마지막으로 가리키고 있는 곳에서 이루어진다.

 

 예제 - 출력 파일 디스크립터 만들기

$ vim example.sh
#!/bin/bash

OUT_FILE="out_file"

exec 3>${OUT_FILE}    # OUT_FILE에 파일 디스크립터 3번을 할당  (다시 할당하기 전까지 이 리다이렉션은 유지되어 지속적으로 접근이 가능하다)

echo "This is sould display on the monitor"         # 출력 결과가 STDOUT(1)으로 전송된다.
echo "and this should be stored in the file" >&3    # 출력 결과가 OUT_FILE(3)로 전송된다.
echo "Then this should be back on the monitor"      # 출력 결과가 STDOUT(1)으로 전송된다.

$ ./example.sh

bash shell

 

 

 예제 - 파일 디스크립터를 리다이렉트하기

$ vim example.sh

#!/bin/bash

OUT_FILE="out_file"

exec 3>&1             # 사용자 정의 FD 3을 표준 출력 FD 1(STDOUT)으로 리다이렉션한다.
exec 1>${OUT_FILE}    # 표준 출력 FD 1번(STDOUT)을 OUT_FILE로 리다이렉션한다.

echo "This should store in the output file"    # 출력 결과가 OUT_FILE(FD_1)로 전송된다.
echo "along with this line"

exec 1>&3             # 표준 출력 FD 1번(OUT_FILE)의 원래 장소인 모니터로 리다이렉션한다.

echo "Now things should be back to normal"    # 출력 결과가 모니터로 전송된다.

$ ./example.sh

bash shell script

스크립트는 파일 디스크립터 3을 파일 디스크립터 1인 STDOUT으로 리다이렉트한다. 즉 파일 디스크립터 3으로 가는 모든 데이터는 모니터에 출력된다. 두 번째 exec 명령은 STDOUT을 파일로 리다이렉트한다. 셸은 이제 STDOUT으로 전송되는 출력을 출력 파일로 리다이렉트한다. 그러나 FD 3은 계속해서 STDOUT의 원래 장소인 모니터를 가리킨다. STDOUT이 리다이렉트 되었다고 해도 FD 3으로 출력 데이터를 보내면 모니터로 간다. 세 번째 exec 명령은 몇 가지 출력을 파일을 가리키고 있는 STDOUT에 보낸 후 스크립트는 STDOUT을 모니터로 설정되어 있는 FD 3으의 현재 장소로 리다이렉트한다. 이제 STDOUT은 우원래 장소인 모니터를 가리키게 된다

 

예제 -  입력 파일 디스크립터 만들기

$ vim example.sh

#!/bin/bash

INPUT_FILE="input_file"

exec 6<&0               # 표준 입력(STDIN, 0)을 사용자 정의 파일 디스크립터 6으로 리다이렉트한다.

exec 0<${INPUT_FILE}    # 표준 입력(STDIN, 0)을 INPUT_FILE로부터 읽어 오도록 리다이렉트한다.

count=1
while read line         # read는 INPUT_FILE로 리다이렉트된 표준 입력으로부터 한 줄씩 입력받는다.
do
    echo "Line #$count: $line"
    count=$[ $count + 1 ]
done

exec 0<&6               # 표준 입력(STDIN, 0)을 원래 장소(키보드)로 복원한다.

read -p "Are you done now? " answer
case $answer in
    Y|y) echo "Goodbye";;
    N|n) echo "Sorry, this is the end.";;
esac

bash shell script

이 예에서 파일 디스크립터 6이 표준 입력(STDIN)의 장소를 저장하는 사용된다. 그 다음, 스크립트는 STDIN을 파일에 리다이렉트한다. read 명령의 모든 입력은 현재 입력 파일로 리다이렉트된 STDIN에서 온다. 파일 안의 모든 줄을 읽어 들이고 나서 스크립트는 STDIN을 원래의 장소인 파일 디스크립터 6으로 리다이렉트해서 복원한다

 

예제 - 읽기/쓰기용 파일 디스크립터 만들기

$ vim test_file

This is 1st line.
This is 2sd line.
This is 3rd line.

$ vim example.sh

#!/bin/bash

TEST_FILE="test_file"

exec 3<>${TEST_FILE}    # 입출력 모두 TEST_FILE을 사용하도록 파일 디스크립터 3번을 할당한다.

read line <&3                      # TEST_FILE의 데이터를 한 줄 읽어 들인다.
echo "Read: $line"                 # 읽어 들인 데이터 한 줄을 출력한다.
echo "This is a test line " >&3    # 현재 파일 포인터가 가리키고 있는 위치에 데이터를 덮어 쓴다.

$ cat test_file

스크립트를 실행하면 첫 번째는 괜찮아 보인다. 출력 결과는 스트립트가 TEST_FILE 파일의 첫 번째 줄을 읽어 들였지만 TEST_FILE의 내용을 보면 파일에 쓴 데이터가 기존 데이터를 덮어 썼다. 스크립트 파일에 데이터를 기록할 때 그 시작 지점은 파일 포인터가 가리키는 곳이다. read 명령은 데이터의 첫 번째 줄을 읽었고, 따라서 파일 포인터는 데이터의 두 번째 줄 첫 번째 문자를 가리키는 상태가 되었다. 파일에 데이터를 출력하는 echo 문은 데이터를 파일 포인터의 현재 위치에 기록하므로 어떤 데이터가 있든 그 위치에 있던 데이터를 덮어 쓰게 된다

 

 예제 - 파일 디스크립터 닫기

$ vim example.sh

#!/bin/bash

TEST_FILE="test_file"

exec 3>${TEST_FILE}                       # TEST_FILE에 파일 디스크립터 3번을 할당한다.
echo "This is a test line of data" >&3    # TEST_FILE에 데이터를 기록한다.
exec 3>&-                                 # 파일 디스크립터 3을 닫는다.
echo "This won't work" >&3                # 파일 디스크립터 3을 닫은 후에는 접근이 불가하다. (오류를 출력하고 스크립트를 종료한다)
# echo "This won't work" >&3              # 계속 진행을 위해 주석 처리를 한다.

exec 3>${TEST_FILE}          # TEST_FILE에 파일 디스크립터 3번을 다시 할당한다. (기존 파일을 새 파일로 대체한다)
echo "This'll be bad" >&3    # 또 다른 데이터 문자열을 TEST_FILE에 기록한다.

$ ./example.sh

「 

 

 리다이렉트 - 열린 파일 디스크립터 나열하기 linux bash shell script

파일 디스크립터를 9개 까지만 쓸 수 있으므로 이들을 올바로 유지 관리하는 일은 별로 어렵지 않다고 생각할 수 있지만, 때때로 어떤 파일 디스크립터가 어디로 리다이렉트되어 있는지를 놓치기 쉽다. 상황을 제대로 유지시키는 데 도움이 되도록 배시 셸은 lsof 명령을 제공한다.

 

[표] 기본 lsof 출력

 열  설명
 COMMAND  프로세스 안에 있는 명령 공정에서 명령의 이름의 첫 9 글자
 PID  프로세스 ID
 USER  프로세스를 소유한 사용자의 로그인 이름
 FD  파일 디스크립터 번호 및 접근 유형 [r-(읽기), w-(쓰기) ,u-(읽기/쓰기)]
 TYPE  파일의 유형 [CHR-(글자), BLK-(블록), DIR-(디렉토리), REG-(일반 파일)]
 DEVICE  장치의 번호 (주 번호 및 부 번호)
 SIZE  가능한 경우에는 파일의 크기
 NODE  로컬 파일의 노드 번호
 NAME  파일의 이름

 

 예제 (1)

$ /usr/sbin/lsof -a -p $$ -d 0,1,2    # -a : 다른 두 옵셤의 결과에 대해 부울 AND 연산을 수행
                                      # -p : 프로세스 ID(PID)를 지정
                                      # -d : 표시할 파일 디스크립터 번호를 지정
                                      # $$ : 현재 PID를 확인하기 위해 제공하는 시스템 환경 변수

bash shell script

출력 결과는 STDIN, STDOUT 및 STDERR에 할당된 파일 유형은 CHR(글자) 모드다. 모두 터미널을 가리키고 있으므로 출력 파일의 이름은 터미널의 장치 이름이다. 3가지 표준 파일 모두 읽기를 할 수 있도록 되어 있다

 

 예제 (2)

$ cat example.sh

#!/bin/bash

touch out_file1     # 테스트용 출력 파일을 생성
touch out_file2
touch input_file    # 테스트용 입력 파일 생성

exec 3> out_file1     # 출력 파일에 파일 디스크립터 3번을 할당
exec 6> out_file2     # 출력 파일에 파일 디스크립터 6번을 할당
exec 7< input_file    # 입력 파일에 파일 디스크립터 7번을 할당

/usr/bin/lsof -a -p $$ -d 0,1,2,3,6,7    # 현재 프로세스에서 열려있는 파일 디스크립터 정보를 출력

$ ./example.sh

이 스크립트는 3가지 사용자 정의 파일 디스크립터를 가지고 있으며 두 개는 출력에(3, 6), 하나는 입력에 사용한다(7). 파일 이름은 파일 디스크립터에서 사용되는 파일의 완전한 경로명이다. 출력 결과는 이들 파일이 각각 REG 유형임을 보여주고 있다. 이들 파일이 파일 시스템의 일반 파일임을 뜻한다

 

 리다이렉트 - 명령 출력 억제하기 linux bash shell script

때로는 스크립트의 출력을 모두 표시하고 싶지는 않을 수도 있다. 이문제를 해결하기 위해 STDERR를 널(null) 파일이라고 하는 특수한 파일로 리다이렉트할 수 있다. 셸이 널 파일로 보내는 모든 출력은 저장되지 않는다. 리눅스 시스템에서 널 파일의 표준 위치는 /dev/null이다.0000

$ ls -al > /dev/null
$ cat /dev/null
$

 

예제

$ cd ~
$ ls -al badfile .bashrc 2> /dev/null

「 

 

NULL 파일을 입력 리다이렉트에 입력 파일로 사용할 수도 있다. 파일을 지운 다음 다시 만들지 않고도 기존 파일의 데이터를 없애버리기 위해 이 방법을 사용한다.

 예제

$ vim example.sh
This is the 1st line.
This is the 2sd line.
THis is the 3rd line.
$ cat /dev/null > example.sh    # 기존 파일의 내용을 없애버린다.
$ cat example.sh

「 

 

 

 

 임시 파일 사용하기 linux bash shell script

개념 linux bash shell script

리눅스 시스템은 임시 파일을 위해 예약된 특별한 디렉토리 위치를 포함하고 있다. 리눅스는 무기한으로 유지할 필요가 없는 파일은 tmp 디렉토리에 두고 사용한다. 시스템의 모든 사용자 계정은 /tmp 디렉토리에 접근할 수 있다. 이를 위해 mktemp 명령을 제공하고 있다. 이 명령을 사용해 파일을 만들 때 기본 umask를 사용하지 않고 파일을 직접 생성하는 사용자에게만 r/w 권한을 부여하고 소유자로 설정한다. 그렇기 때문에 루트 사용자를 제외하고는 다른 사용자 계정으로는 접근이 불가하다.

 

임시 파일 사용하기 - 로컬 임시 파일 만들기 linux bash shell script

mktemp는 기본적으로 로컬 디렉토리에 파일을 만든다. 이 기능을 사용하여 파일을 만들려면 파일 이름 템플릿을 지정해야 한다. 이 파일 이름의 마지막에 여섯 개의 대문자 X가 붙어 있어야 한다.

 

 예제

$ vim example.sh
#!/bin/bash

TEMP_FILE=$(mktemp test.XXXXXX)    # 현재 디렉토리에 test. 바로 뒤 6개 글자가 무작위로 정해지는 임시 파일을 생성한다.

exec 3>$TEMP_FILE    # TEMP_FILE에 출력 파일 디스크립터 3번을 할당

echo "This script writes to temp file $TEMP_FILE"    # 출력 결과를 저장할 임시 파일명을 출력한다. (큰 따옴표를 사용하여 변수가 실제 값으로 치환될 수 있도록 한다)
echo "This is the 1st line" >&3    # 출력 결과를 3번 FD가 가리키는 파일에 쓴다.
echo "This is the 2nd line" >&3    # 출력 결과를 3번 FD가 가리키는 파일 끝에 덧붙인다.
echo "This is the 3rd line" >&3    # 출력 결과를 3번 FD가 가리키는 파일 끝에 덧붙인다.
exec 3>&-                          # 3번 출력 FD를 닫는다.

echo "Done creating temp file. THe contents are:"
cat $TEMP_FILE                    # 임시 파일에 저장된 데이터를 출력한다.
rm -rf $TEMP_FILE 2> /dev/null    # 임시 파일명을 삭제하고 진행 과정에 대한 출력 내용은 표시하지 않도록 한다.

「 

 

 임시 파일 사용하기 - /tmp에 임시 파일 만들기 linux bash shell script

-t 옵션은 mktemp 명령이 시스템의 임시 디렉토리에 임시 파일을 만들도록 강제한다.

※ 이 기능은 실행 후 임시 파일명을 포함한 전체 경로를 문자열 형태의 값을 반환한다.

 

 예제

$ vim example.sh
#!/bin/bash

TEMP_FILE=$(mktemp -t tmp.XXXXXX)    # /tmp 디렉토리에 tmp. 바로 뒤 6글자가 무작위로 정해지는 임시 파일을 생성한다.

echo "This is a test file." > $TEMP_FILE    # 기존 파일의 데이터를 내용을 덮어 쓴다.
echo "This is the second line of the test." >> $TEMP_FILE    # 파일의 끝에 데이터를 덧붙인다.

echo "The temp file is located at: $TEMP_FILE"    # 임시 파일의 경로를 출력한다. (큰 따옴표를 사용하여 변수가 실제 값으로 치환될 수 있도록 하였다)

「 

 

 임시 파일 사용하기 - 임시 디렉토리 만들기 linux bash shell script

-d 옵션은 mktemp 명령에게 파일 대신 임시 디렉토리를 생성하도록 지시한다.

※ 이 기능은 실행 후 임시 디렉토리의 전체 경로를 반환하지 않고, 명령 프롬프트에서 지정한 경로(상대경로 or 절대경로)를 포함한 디렉토리명을 문자열 형태로 반환한다.

 

 예제

$ vim example.sh

#!/bin/bash

TEMP_DIR=$(mktemp -d dir.XXXXXX)    # 임시 디렉토리명은 dir. 바로 뒤 6개 글자가 무작위로 정해지도록 했다.
cd $TEMP_DIR

TEMP_FILE1=$(mktemp temp.XXXXXX)    # 임시 파일명은 temp. 바로 뒤 6개 글자가 무작위로 정해지도록 했다.
TEMP_FILE2=$(mktemp temp.XXXXXX)
exec 7> $TEMP_FILE1                 # TEMP_FILE1에 파일 디스크립터 7번을 할당 (다시 할당하기 전까지 이 리다이렉션은 유지되어 지속적으로 접근이 가능하다)
exec 8> $TEMP_FILE2                 # TEMP_FILE2에 파일 디스크립터 8번을 할당 

echo "Sending data to directory $TEMP_DIR"
echo "This is a test line of data for $TEMP_FILE1" >&7    # 출력 결과를 7번 FD가 가리키는 파일에 쓴다. 
echo "This is a test line of data for $TEMP_FILE2" >&8    # 출력 결과를 8번 FD가 가리키는 파일에 쓴다.

$ chmod +x ./example.sh
$ ./example.sh
$ ls -al
$ cd dir.XXXXXX
$ ls -al

bash shell script

「 

 

 

 

메시지 로깅 linux bash shell script

 메시지 로깅 (1) linux bash shell script

●  tee 명령을 사용하여 출력을 두 번 리다이렉트할 수 있다.

●  tee 명령은 T자형 파이프 연결부와 비슷하다.

●  이 명령을 STDIN의 데이터를 동시에 두 개의 목적지로 보낸다.

 

 활용 예제

$ uname | tee testfile       # uname의 stdout으로 출력된 결과를 tee의 stdin으로 보낸다.
$ cat testfile               # testfile 파일의 내용을 출력해서 확인한다.

$ date | tee -a testfile     # -a 옵션을 붙이게 되면 기존 파일의 끝에 데이터를 추가한다.
$ cat testfile

bash shell script

「 

 

 메시지 로깅 (2) linux bash shell script

 활용 예제

.csv 파일로부터 개인 정보를 입력받아 MySQL DB에 저장할 수 있도록 INSERT 문을 만든다.

$ vim members.csv

louis,seoul,02-123-4567
neige,seoul,02-765-4321

$ vim example.sh

#!/bin/bash

OUT_FILE='members.sql'
IFS=','
while read name address phone
do

    cat >> ${OUT_FILE} << EOF    # EOF -> members.sql에 저장할 데이터의 시작을 알림
    INSERT INTO members (name,address,phone) VALUES ('$name','$address','phone');
EOF
# EOF <- members.sql에 저장할 데이터의 끝을 알림

done < ${1}    # 명령 프롬프트에서 입력받은 첫번째 매개변수를 입력으로 받는다.

$ ./example.sh members.csv
$ cat members.sql

bash shell script

「 

반응형
Comments