WAS Engineer - Linux 7번째

이정빈·2022년 9월 26일
0

리눅스 복습

목록 보기
7/11
post-thumbnail

고도의 텍스트 처리

sed 명령어 : 스트림 에디터

  1. 비대화형 에디터

Stream Editor의 약자인 sed는 에디터이다. 하지만 비대화형 에디터이다. 따라서 대화형 에디터인 리눅스의 Vim과는 동작 방식이 다르다

대화형 에디터는 파일을 열어 메모리상에서 편집하고 적절한 시점에 저장하는 형태로 파일을 편집한다.

비대화형 에디터인 sed는 다음과 같이 동작한다.

  1. 셸에서 편집 내용을 인자로 지정하여 sed 명령어를 실행한다.
  2. sed가 편집을 수행한다.
  3. 편집이 완료된 내용을 표준 출력으로 출력한다.

sed는 비대화형으로 동작하는 필터 명령어이다. 예를 들어 여러 파일에서 Incheon이라는 문자열을 Seoul로 바꿀 때 유용하다. 또한, 정규 표현식도 사용할 수 있다.

  1. sed 명령어의 형식

sed 명령어의 서식은 다음과 같다.

  • sed로 텍스트 편집

sed [옵션] <스크립트> <대상 파일>

<스크립트>는 주소와 명령어를 조합한 문자열이다. 주소를 지정하지 않으면 입력으로 들어오는 모든 행에 대해 명령어가 실행된다. 그리고 명령어에는 인자나 플래그를 지정할 수 있다.

  1. 행 삭제

d는 행을 삭제하는 명령어이다. sed 명령어는 주소로 지정한 행에 대해서만 처리를 수행한다. 주소를 지정하는 방법이 몇 가지 있는데 먼저 행 번호를 지정하는 방법이 있다.

  • 첫 번째 행 삭제

$ sed 1d xxx.txt

  • 2행에서 5행까지 삭제

$ sed 2,5d xxx.txt

  • 3행에서 마지막 행까지 삭제

$ sed '3,$d' xxx.txt

주소를 지정하지 않으면 명령어가 모든 행에 적용된다.

  • 모든 행을 삭제

$ sed d xxx.txt

주소에는 행 번호뿐만 아니라 정규 표현식도 사용할 수 있다. 이 때 정규표현식은 /로 감싸야 한다.

  • B로 시작하는 행을 삭제

$ sed /^B/d xxx.txt

이 예에서는 /^B/가 주소이다. 이처럼 주소에 정규 표현식을 사용하여 다양한 패턴을 지정하는 것도 가능하다.

  1. 행 출력

p는 행을 출력하는 명령어이다.

  • 첫 번째 행을 출력하도록 지정했는데 모든 행이 출력됨

$ sed 1p xxx.txt

sed가 기본적으로 패턴 스페이스를 출력하기 때문. sed는 한 행을 읽으면 먼저 패턴 스페이스라는 장소에 복사하고 편집 명령어를 실행한 뒤 패턴 스페이스의 내용을 출력한다.

패턴 스페이스의 내용을 출력하지 않으려면 -n 옵션을 사용해야 한다. 그래서 -n 옵션과 p 명령어를 함께 사용하면 특정 행만 출력할 수 있다.

  • 첫 번째 행을 출력

$ sed -n 1p xxx.txt

-n 옵션은 치환해서도 쓸 수 있어 '치환이 발생한 행만 출력'하는데 사용하기도 한다.

  1. 행 치환

s는 행을 치환하는 명령어이다. sed는 주로 치환을 위해 사용되기 때문에 무척 자주 사용되는 명령어이다.

  • sed로 문자열 치환

s/치환 전 문자열/치환 후 문자열/옵션

마지막의 옵션은 생략이 가능하다.
발견한 모든 문자열을 치환하려면 g라는 옵션을 지정해야 한다.

치환 전 문자열에 정규 표현식을 지정할 수도 있다.

  • 정규 표현식을 사용하여 치환

$ sed 's/B.*r/Whisky/g' drink.txt

치환 전 문자열에 정규 표현식을 사용하는 경우가 많아서 s 명령어를 사용할 때는 언제나 위와 같이 작은 따옴표로 감싸는 것이 좋다.

치환 후 문자열을 비운 채 실행하면 해당 문자열 패턴을 지우는 것이 가능하다.

그리고 패턴 스페이스를 출력하지 않는 -n 옵션과 치환이 발생한 경우에만 출력하는 p 옵션을 지정하면 다음과 같이 치환이 발생한 행만 출력할 수 있다. 그러면 큰 파일에서 치환할 때 어디서 치환이 발생했는지 쉽게 파악할 수 있다.

  • 치환한 행만 표시

$ sed -n 's/!//gp' drink.txt

sed에서의 확장 정규 표현식

sed에서 확장 정규 표현식을 사용하려면 -r 옵션을 지정해야 한다.

awk 명령어 : 패턴 검색 및 처리 언어

awk는 텍스트 검색, 추출, 가공과 같은 편집 작업을 위한 명령어이다.

awk도 sed처럼 셸에서 지정한 편집 작업을 실행하고 그 결과를 출력한다. 하지만 sed가 비대화형 에디터인 반면, awk는 좀 더 고도의 기능을 제공하여 텍스트 처리에 특화된 프로그래밍 언어이다.

현재는 텍스트 편집을 위해 루비나 파이썬 같은 스크립트 언어가 많이 사용되고 있지만, awk도 여전히 많이 사용되고 있다. awk의 사용법을 잘 익혀 두면 리눅스를 사용할 때 도움이 많이 된다.

  1. awk 명령어의 형식

awk도 sed와 마찬가지로 awk <스크립트> <대상 파일> 형식으로 실행한다. 대상 파일을 지정하지 않으면 표준 입력을 읽으며, 입력 텍스트를 한 행씩 읽어서 지정한 처리를 수행한다는 점도 sed와 같다. 하지만 awk를 사용하면 훨신 더 고도의 스크립트를 지정할 수 있다.

awk의 스크립트는 패턴과 액션으로 구성된다.

  • awk의 스크립트

패턴 { 액션 }
</서식>

패턴에 액션을 실행할지 여부를 결정하는 조건을 기술한다. awk는 대상 텍스트의 행마다 이 조건에 부합하는지 확인한다. 여기서 텍스트의 한 행을 awk에서는 레코드라 부른다.

액션에는 텍스트 추출, 치환, 삭제 등의 처리를 지정하며 패턴에 일치할 때만 액션이 실행된다.만약 패턴이 생략되면 모든 레코드에 대해 액션이 실행된다.

  1. print와 필드 변수

awk는 특정 필드를 추출할 때 많이 사용된다.
print는 이름 그대로 문자열을 출력하는 액션이다.

awk는 각 레코드를 필드로 자동으로 분리하여 각각 변수에 대입한다. 그리고 레코드 전체 대해서는 $0에 대입한다. print할 때 변수 여러 개를 쉼표로 구분하여 지정하면 각 값 사이에 공백이 표시되어 출력된다. 그리고 다음과 같이 쉼표 대신 공백으로 구분하여 지정하면 값 사이에 공백이 표시되지 않으니 주의해야 한다.

필드 변수와 함께 자주 사용하는 변수로 NF 변수가 있다. NF는 레코드의 필수 개수를 담고 있는 변수이다. 그래서 $NF를 print하면 레코드의 마지막 필드가 출력된다.

awk의 액션에서는 연산을 수행할 수 있어 NF에서 1을 뺀 값(NF-1)은 마지막에서 두 번째 필드를 의미한다.

  1. 패턴 지정

awk에서는 확장 정규 표현식을 사용하는 게 가능하다. 이 때 정규 표현식은 슬래쉬(/)로 감싸야 한다. 그리고 정규 표현식을 확인할 필드를 지정하기 위해 다음과 같이 ~(틸드)를 사용한다.

  1. 액션 생략

액션을 생략하면 단순히 레코드를 출력한다. 즉, {print $0}이 실행된다. 그리고 print에 아무런 인자를 지정하지 않으면 레코드 전체가 출력된다.

셸 스크립트

리눅스를 사용하다 보면 일련의 명령어를 반복적으로 실행해야 할 때가 상당히 많다. 그 때마다 길고 복잡한 커맨드 라인을 손으로 입력하는 것은 무척 번거로운 일이다. 이 때 실행할 명령어를 미리 파일에 입력해 놓고, 해당 파일을 셸이 실행하도록 할 수 있다.

셸에서 실행될 커맨드 라인을 입력해 놓은 파일을 셸 스크립트라 한다. 단순히 명령어를 나열하기도 하지만 조건 분기와 반복과 같은 복잡한 제어 구저도 사용할 수 있어 프로그래밍 언어에 가깝다. 리눅스를 사용하는 것은 곧 셸을 다루는 것이므로 셸 스크립트의 활용도는 무척 높다.

셸 선택

먼저 어떤 셸을 바탕으로 셸 스크립트를 작성할 것인지 정해야 한다.

  1. 어떤 셸의 셸 스크립트를 작성할 것인가

셸 스크립트는 셸에 의해 해석되기 때문에 어떤 셸을 사용하는지에 따라 문법도 달라진다. 요즘에는 sh나 bash용 셸 스크립트를 작성하는 것이 보통이므로 둘 중 하나를 선택해야 한다.

셸 스크립트 작성

셸 스크립트는 실행하고 싶은 명령어를 입력한 텍스트 파일이다. 따라서 Vim과 같은 텍스트 에디터로 입력하면 된다.

  • 홈 디렉터리의 파일 사용량을 출력

$ du -h ~ | tail -n 1
12M /home/ldk

여기서 사용된 du 명령어는 디렉터리 안의 파일 사용량을 출력하는 명령어이다. 디렉터를 ~로 지정하여 홈 디렉터리의 사용량을 출력한다. du 명령어는 모든 서브 디렉터리의 사용량도 출력하지만, 여기서는 홈 디렉터리 전체의 사용량만 출력하기 위해 tail을 사용해 마지막 한 행만 출력한다.

이제 다음과 같이 Vim을 실행하여 신규 파일을 만든다. 셸 스크립트의 이름은 homesize.sh라고 한다.

  • Vim으로 셸 스크립트 homesize.sh 작성

$ vim homesize.sh

확장자인 sh를 붙이지 않아도 동작하지만 파일 이름만 봐도 셸 스크립트인 것을 알 수 잇게 sh라는 확장자를 붙이는 것이 좋다.

Vim이 작동하면 다음 두 행을 입력한다.

#!/bin/bash
du -h ~ | tail -n 1

#!로 시작하는 첫번째 행을 셔뱅(shebang)이라고 한다.
그리고 두번째 행이 실제로 실행될 커맨드 라인이다. 이렇게 실행하고 싶은 명령어를 그대로 입력하면 셸 스크립트가 되는 것이다.

파일을 저장하고 Vim을 종료한다. 그리고 파일에 실행 권한을 부여해야 한다. chmod 명령어로 +x를 지정해 실행 권한을 부여한다.

  • 셸 스크립트로 실행 권한 부여

$ chmod +x homesize.sh

  • 셸 스크립트 실행

$ ./homesize.sh
12M /home/ldk

여기서 파일이름 앞에 ./를 붙여야한다. .은 현재 디렉터리를 의미하므로 '현재 디렉터리에 있는 homezie.sh를 실행'하도록 셸에게 알려야 한다.

셸 스크립트 실행 형식

#!으로 시작되는 셔뱅에 대해서 더 알아보자.

  1. 셔뱅

리눅스에서 파일을 실행할 때는 셸에서 파일 이름을 지정하면 된다. 셸 스크립트에서는 다음과 같았다.

./homesize.sh

이 때 셸의 실행 명령을 전달받은 리눅스 커널은 먼저 파일의 첫 부분을 확인한다. 그래서 #!가 있으면 그 뒤에 적힌 명령어를 실행한다.

즉, #!/bin/bash란 이 셸 스크립트는 /bin/bash를 사용한다고 명시적으로 선언한 것이다. 이것이 셔뱅의 역할이다.

/bin/bash scriptfile.sh와 같이 실행하면 scripst.sh에 적힌 명령어가 차례대로 실행된다. 따라서 셔뱅을 입력하면 셸 스크립트를 실행할 때 /bin/bash를 입력하지 않아도 되고, 또한 사용 중인 셸이 배시가 아니어도 자동으로 /bin/bash가 스크립트를 실행한다.

그리고 셸에서는 #으로 시작하는 행을 주석으로 간주한다. 따라서 셔뱅도 주석으로 간주한다.

  1. source 명령어 : 파일에서 명령어를 읽어서 실행

셸 스크립트를 실행하는 또 다른 방법으로 source 명령어를 사용하는 것이 있다.

  • source 명령어로 셸 스크립트 실행

$ source ./homesize-noshbang.sh
12M /home/ldk

source 명령어는 지정한 파일 안의 커맨드 라인을 마치 셸에서 직접 입력한 것과 동일하게 실행한다.

source 명령어를 사용하면 셔뱅으로 지정한 셸이 아니라 언제나 현재 셸이 사용된다. 그리고 파일을 직접 실행하는 것이 아니기 때문에 파일에 실행 권한을 부여할 필요가 없다.

참고로 source 명령어와 동일하게 동작하는 .명령어가 있다. 배시에서는 . 명령어가 source명령어와 완전히 동일하게 동작한다.

  • . 명령어는 source 명령어와 동일함

$ . ./homesize-noshebang.sh
12M /home/ldk

역사가 오래된 sh에서는 source 대신에 . 명령어가 사용되었다. 그래서 일부 사용자는 여전히 .명령어를 선호하기도 한다. 하지만 .명령어를 사용하다 보면 실수하기 쉽기 때문에 source명령어를 사용하는 것이 좋다.

  1. 실행 방법에 의한 차이

    1. 파일 이름만으로 실행
    2. 셸의 인자로 지정하여 실행
    3. source 명령어를 사용하여 실행

1번의 경우 셔뱅을 기재해야 한다는 점만 빼고는 2번과 거의 동일하다
3번의 경우 다른 방법과 동작상의 차이점이 있다. source 명령어를 사용하면 현재 셸을 기준으로 파일에 기재된 커맨드 라인이 실행된다. 따라서 현재 설정된 셸 환경에 영향을 받는다. 예를 들어 현재 셸에 설정한 별명을 셸 스크립트 안에서도 사용할 수 있다.

셸 스크립트 배치

  • 셸 스크립트를 실행할 때 파일 이름 앞에 ./를 붙이지 않는 경우

$ homesize.sh
homesize.sh 명령을 찾을 수 없습니다.

명령어를 발견할 수 없다는 에러가 출력된다. 이는 검색 경로에서 homesize.sh라는 명령어를 찾을 수 없었다는 의미이다.

검색 경로에 등록된 디렉터리에 있지 않는 이상 셸 스크립트를 이름만으로 실행할 수 없다. 이 때는 상대 경로나 절대 경로로 파일의 위치를 지정해야 한다. ./는 상대 경로로 파일의 위치를 지정한 것이다. 이는 다음과 같이 절대 경로로 지정할 수도 있다.

$ /home/ldk/work/homesize.sh

  1. 셸 스크립트 배치용 디렉터리

~/bin이라는 디렉터리를 만들고 셸 스크립트를 해당 디렉터리로 옮긴다

  • 셸 스크립트를 ~/bin으로 이동

$ mkdir ~/bin
$ mv homesize.sh ~/bin

이어서 검색 경로에 ~/bin을 추가한다. Vim으로 ~/.profile을 열어 다음 내용을 추가한다.

PATH="$PATH:~/bin"

$PATH에서 검색 경로에 해당하는 디렉터리가 콜론을 구분자로 설정하고 있따. 해당 변수에 ~/bin을 추가하면 검색 경로에 추가된다.

이 설정을 적용하려면 로그아웃한 뒤 다시 로그인해야 한다. 혹은 source 명령어로 실행하여 바로 적용하여도 된다.

이제 어떤 디렉터리에서도 sh라고 입력하면 ~/bin에 배치된 셸 스크립트가 실행된다.

  1. source 명령어와 패스

source 명령어를 사용할 때 한 가지 주의해야 할점이 있다.

source 명령어를 실행할 때 PATH에 지정된 디렉터리에 있는 셸 스크립트라면 상대 경로나 절대 경로가 아닌 파일 이름만으로 지정할 수 있다.

하지만 이렇게 source를 사용할 때 이름만으로 셸 스크립트로 지정하다 보면 의도치 않은 스크립트를 실행할 가능성이 높아진다. 따라서 상대 경로나 절대 경로를 사용하여 명시적으로 셸 스크립트의 위치를 지정하는 것이 좋다.

아예 source 명령어에서 PATH에서 지정된 디렉터리에서 파일을 찾지 않도록 설정할 수도 있다. 다음과 같이 shopt 명령어로 sourcepath 옵션을 비활성화하면 된다.

  • source 명령어가 PATH에서 파일을 찾지 않도록 설정

$ shopt -u sourcepath <-검색 경로에서 파일을 찾지 않도록 설정
$ source homesize.sh <- 검색 경로에서 찾지 않으므로 에러 발생
-bash: homesieze.sh : 그런 파일이나 디렉터리가 없습니다

셸 스크립트의 기본

  1. 셸 스크립트에 입력하기

셸 스크립트는 기본적으로 실행하고 싶은 명령어를 파일에 입력한 것이다. 여러 명령어를 입력하면 차례로 실행된다. 예를 들어 다음 셸 스크립트(rootls.sh)를 보면 echo, cd, ls 명령어가 차례로 입력되어 있다.

  • rootls.sh

#!/bin/bash
echo "root directory"
cd /
ls -l

이 셸 스크립트를 실행하면 echo, cd, ls 명령어가 실행되어 다음처럼 루트 디렉터리의 파일 목록이 출력된다.

명령어를 ;으로 연결하면 다음과 같이 한 행으로 입력할 수 있다.

#!/bin/bash
echo "root directory";cd /;ls -l

셸 스크립트에서 빈행은 무시된다.

한 커맨드 라인을 여러 행에 표기

커맨드라인이 길어질 경우에는 행 끝에 \를 입력하면 여러 행에 걸쳐 입력할 수있다. 그러면 사람이 보기에는 여러 행으로 나눠져 있지만, 셸 스크립트는 한 행으로 인식한다.

다음은 rootdir.sh에서 echo 명령어를 여러 행으로 나눠서 입력한 것이다.

  • rootdir.sh

#!/bin/bash
echo \ <- 개행
"root directory"

옵션이나 인자로 인해 하나의 커맨드 라인이 지나치게 길어질 때 여러 행에 나눠 입력하면 가독성을 좀 더 높일 수 있다.

주석

셸 스크립트 안에 주석을 남길 수도 있다. 주석은 주로 코드의 동작에 대한 설명을 기록할 때 사용하며 실행에 포함되지는 않는다. 주석을 잘 남기면 코드를 쉽게 이해할 수 있을 뿐만 아니라 훗날 자기 자신이 코드를 다시 봤을 때 도움이 된다.

셸 스크립트에서는 #으로 주석을 남긴다. #을 행의 첫 부분에 써놓으면 행 전체가 주석이 되며, 중간에 쓰면 이후 부분이 주석이 된다.

변수

셸 스크립트에서도 일반 프로그래밍 언어처럼 변수를 사용하여 값을 저장할 수 있다. 이를 셸 변수라 한다.

변숫값을 참조하려면 변수명 앞에 $를 붙여야 한다. 그러면 셸 스크립트가 변수를 값으로 변환하여 스크립트를 실행한다. 다음 예는 변수 appdir의 값을 echo 명령어로 출력한 것이다.

  • var.sh

#!/bin/bash
appdir=/home/ldk/myapp
echo $appdir

var.sh를 실행하면 다음과 같이 셸 변수 appdir의 값이 출력된다.

  • 변수 appdir의 값을 출력

$ ./var.sh
/home/ldk/myapp

  1. 변수 사용 시 주의점

셸 스크립트에서 변수를 사용할 시에 주의해야 할 점이 있다.

  • 대입할 때는 $를 붙이지 않는다.

변수에 값을 대입할 때는 $를 붙이지 않아야한다.

  • = 양옆에 공백이 없어야 한다

많은 프로그램이 언어에서는 변수에 값을 대입하 ㄹ때 = 양옆에 공백을 넣을 수 있따. 하지만 셸 스크립트에서는 공백이 허용되지 않는다.

  • 변수명에 사용할 수 있는 문자

변수명에 사용할 수 있는 문자는 알파벳과 숫자, 언더스코어(_)뿐이다. 그리고 숫자는 첫 글자는 사용할 수 없다. 즉 첫 글자에는 알파벳이나 언더스코어만 사용할 수 있다.

알파벳은 대소문자 모두 사용할 수 있다. 보통 환경 변수에는 대문자를 사용하고 그 외의 일반 변수에는 소문자를 사용한다.

  • 변수를 명확히 구분한다

변수값에 문자열을 연결하고 싶을 때는 문자열까지 변수 이름에 포함되어 해석되지 않도록 주의해야 한다.

쿼팅

배시에서는 공백을 기준으로 명령어 인자를 구별한다

쿼팅을 통해 공백이나 메타 문자를 의도한 대로 사용할 수 있다. 작은 따옴표나 큰 따옴표로 감싸주는게 그 방법이다.

  1. 쿼팅 안에서 변수 확장하기

쿼팅할 때는 작은 따옴표 혹은 큰 따옴표를 사용할 수 있다. 비슷해 보이지만 두 방식에는 커다란 차이가 있다. 작은 따옴표 안의 $는 특별한 의미를 가지지 않는 일반 문자로 취급된다. 그런데 큰 따옴표 안에서는 $로 시작하는 변수가 값으로 치환된다.

큰 따옴표를 사용할 때 $ 앞에 \를 붙이면 문자 그대로 출력할 수 있다.

명령어 치환

셸 스크립트를 작성하다 보면 명령어의 출력 결과를 이용하고 싶을 때가 생긴다. 이 때 명령어 치환을 사용하면 명령어를 실행하고 출력된 결과를 취득할 수 있다.

명령어 치환은 $()같은 형식으로 괄호 안에 실행하려는 명령어를 작성하면 된다. 그러면 작성한 명령어를 실행하고 출력되는 표준 출력으로 치환된다.

위치 파라미터

대부분 리눅스 명령어는 인자를 받는다. 셸 스크립트에서는 위치 파라미터라는 셸 변수를 사용해 전달받은 인자를 다룰 수 있다.

위치 파라미터는 $1, $2, $3... 처럼 숫자를 이름으로 하는 변수로 셸 스크립트를 실행할 때 지정한 인자가 각각 할당된다.

  1. 인자 개수

셸 스크립트를 실행할 때 지정한 인자 개수는 특수 파라미터인 $#으로 참조할 수 있다. $#은 스크립트에 전달된 인자 개수를 확인하는 용도로 많이 사용된다.

  1. 인자 전체 조회하기

위치 파라미터를 사용하면 자동으로 분할된 인자를 참조할 수 있다. 인자를 분할하지 않은 채 전체를 참고하고 싶다면 #@ 혹은 $*를 사용해야 한다.

  • 커맨드 라인 인자와 관련된 셸 변수
변수내용
$0셸 스크립트 파일 이름
$1, $2,...커맨드 라인 인자의 값(위치 파라미터)
$#위치 파라미터의 개수
$@모든 위치 파라미터. 큰 따옴표로 감싸면 각각의 위치 파라미터가 큰 따옴표로 감싸짐
$*모든 위치 파라미터. 큰 따옴표로 감싸면 전체가 하나의 문자열로 감싸짐

제어 구조

배시에서도 일반 프로그래밍 언어처럼 값에 의한 조건 분기, 반복 처리를 기술할 수 있다. 이를 제어 구조라고 하며 프로그래밍의 기본이 되는 중요한 개념이다.

셸 스크립트에서는 제어 구조를 사용하기 위해 복합 명령어(compound command)를 사용한다.

  1. if 문

if는 주어진 조건을 평가하여 참인지 거짓인지에 따라 처리를 분기하는 제어 구조이다. 다음 셸 스크립트 if-bin.sh를 통해 동작을 샬퍼보겠다. if-ban.sh 파일은 커맨드 라인 인자가 bin이면 OK를 출력한다.

  • if-bin.sh
#!/bin/bash

if [ "$1" = "bin" ]; then
	echo "OK"
else
	echo "NG"
fi

[ "$1" = "bin" ]이 조건식에 해당하여 첫 번째 인자가 bin인지를 판정한다. 맞으면 OK를 출력하고 아니면 NG를 출력한다.

문법상의 주의점

if 문을 사용할 때 주의점이 있다. 기본적으로 조건식에 이어 세미콜론(;)을 붙여야 한다. 붙이지 않으면 에러가 발생한다.

대신 then을 다음 행에 기재하면 세미콜론을 생략할 수 있다.

그리고 [ ]의 전후에는 반드시 공백이 있어야 한다.

if 뒤에 오는 것은 조건식이 아니라 명령어

많은 프로그래밍 언어에서 if 뒤에 오는 것은 조건식이다. 앞선 예시에서도 마치 조건식이 사용된 것처럼 보인다.

하지만 셸 스크립트에서 if문 뒤에 오는 것은 조건식이 아니라 명령어이다. 먼저 if문의 사용법을 정리하면 다음과 같다.

  • if 문의 구조
if <명령어 1>; then
	<명령어 1>의 결과가 참일 때 실행될 처리
elif <명령어 2>; then
	<명령어 2>의 결과가 참일 때 실행될 처리
elif <명령어 3>; then
	<명령어 3>의 결과가 참일 때 실행될 처리
else
	위 모든 명령어의 결과가 거짓일 때 실행될 처리
fi

if 문은 fi를 만날 때까지 차례로 명령어의 결과를 확인하며 분기한다.

elif는 else if를 의미하며 추가로 분기를 기술할 때 사용하며, else는 모든 명령어가 거짓일 때 분기된다. elif나 else는 생략할 수 있다.

살펴본 예에서 if 뒤에 오는 [ "$1" = "bin" ]이 마친 조건식처럼 보이지만, 사실은 [가 배시의 내장 명령어에 해당한다.

명령어의 종료 상태

ls나 grep 같은 모든 명령어는 종료 상태라고 부르는 정숫값을 반환한다. 이 값은 $?라는 셸 변수로 확인할 수 있다.

  • 명령어의 종료 상태를 확인

$ ls <- ls 명령어 실행
if.args.sh
$ echo $? <- 직전에 실행한 명령어의 종료 상태를 출력
0 <- 종료 상태는 0

일반적으로 명령어가 정상 종료하면 0, 에러가 발생하면 0 이외의 값을 반환한다.

if 와 종료 상태

[ 명령어는 인자로 전달된 조건식을 판정하여 참이면 0을, 그 외에는 0이 아닌 종료 상태를 반환한다.

grep 명령어는 지정한 패턴이 검색된 경우에만 종료 상태 0을 반환한다.

  1. test 명령어와 연산자

if 문의 동작 원리를 알았다면, [ 명령어의 조건식에서 사용할 수 있는 연산자를 알아보자. 연산자에는 두문자열의 일치 여부를 확인하는 = 외에도 다양한 연산자가 있다.

리눅스의 test 명령어는 [ 명령어와 유사한 기능을 제공한다. 유일한 차이점은 test 명령어를 사용할 때는 마지막 인자로 ] 을 지정하지 않아도 된다는 점이다. 즉, 다음 두 표현은 동일한 의미이다.

if [ "$1" = "bin" ]; then
if test "$1" = "bin"; then

가독성의 이유로 [가 더 많이 사용된다. 여기서는 부르기 쉽도록 [ 명령어와 test 명령어를 합쳐 test 명령어라 지칭하겠다.

문자열 비교

  • 문자열에 관한 평가 연산자
연산자내용
str1 = str2str1과 str2가 같음
str1 != str2str1과 str2가 같지 않음
-n str1str1이 빈 문자열이 아님
-z str1str1이 빈 문자열임

여기서 str1, str2는 임의의 문자열을 뜻한다.

정수 비교

다음 표에서 int1, int2는 임의의 정수를 의미한다.

  • 정수에 관한 비교 연산자
연산자의미
int1 -eq int2int1과 int2가 같음
int1 -ne int2int1과 int2가 같지 않음
int1 -lt int2int1이 int2보다 작음
int1 -le int2int1이 int2 이하임
int1 -gt int2int1이 int2보다 큼
int1 -ge int2int1이 int2 이상임

정수를 비교하는 연산자는 정수만 다룬다는 점에 주의해야 한다.

파일 속성

파일의 속성을 평가하는 연산자는 종류가 많기 때문에 자주 사용되는 것만 소개하겠다. 더 자세한 내용은 배시의 매뉴얼에서 CONDITIONAL EXPRESSION을 참고하기를 바란다.

  • 파일 속성에 관한 비교 연산자
연산자의미
-e filefile이 존재함
-d filefile이 존재하고 디렉터리임
-h filefile이 존재하고 심볼릭 링크임
-L filefile이 존재하며 심볼릭 링크임(h와 동일)
-f filefile이 존재하며, 일반 파일임
-r filefile이 존재하며, 읽기 권한이 부여되어 있음
-w filefile이 존재하며, 쓰기 권한이 부여되어 있음
-x filefile이 존재하며, 실행 권한이 부여되어 있음
file -nt file2file1의 변경 시각이 file2보다 최근임
file1 -ot file2file1의 변경 시각이 file2보다 오래됨

연산자 결합

여러 조건식을 지정할 때는 연산자를 결합할 수 있다.

  • 결합 연산자
연산자의미
조건식1 -a 조건식2조건식 1과 조건식 2가 모두 참이면 참(AND)
조건식 1 -o 조건식 2조건식 1과 조건식 2 중 적어도 하나가 참이면 참(OR)
! 조건식조건식의 진위값을 반대로 함(NOT)
( )조건식을 그룹화

셸 변수 datadir이 디렉터리이고(-d), 그리고(-a) 읽기 권한이 부여된 경우(-r)'와 같은 조건식은 다음과 같이 기술 할 수 있다.

!는 부정을 의미하여 다음과 같이 ! -d라고 기술하면 디렉터리가 아닌 조건을 지정할 수 있다.

&&와 ||

&&와 ||를 사용하면 여러 명령어를 순차적으로 평가할 수 있다.

명령어 && 명령어 2와 같이 입력하면 명령어1이 정상 종료하여 0이 반환된 경우에만 명령어2가 실행된다.

||는 명령어1 || 명령어2 같이 사용한다. 이때는 반대로 명령어 1의 종료 상태가 0이 아니면 명령어2가 실행된다. 즉, 명령어1이 정상 종료하지 않아야 명령어2가 실행된다.

if에서 && 사용하기

if 문에서 &&를 사용하면 여러 명령어의 종료 상태가 전부 0이라는 AND 조건을 기술할 수 있다. 다음 스크립트는 test 명령어 두 개를 &&로 연결하여 셸 변수 int1이 3보다 크고 6보다 작으면 메시지를 출력한다.

  • if-and.sh
#!/bin/bash

int1=$1
if [ "$int1" -gt 3 ] && [ "$int1" -lt 6 ]; then
	echo "int1 > 3 and int3 <6"
fi
  • 실행 예
$ ./if-and.sh 4
int1 > 3 and in3 < 6 <--인자로 전달된 값이 3보다 크고 6보다 작은 경우
$ ./if-and.sh 2
$                    <-- 그 외의 값이면 아무것도 출력되지 않음

셸 스크립트의 종료 상태

일반 명령어처럼 셸 스크립트도 종료 상태를 반환할 수 있다.

명시적으로 종료 상태를 지정하고 싶다면 exit 명령어를 사용해야 한다.

  1. for문

for는 공백이나 탭으로 구분된 단어 리스트에 대해 반복 처리를 수행하는 구문이다.

  • for 문 구조

for 변수 in 리스트
do
반복 처리
done

변수에는 리스트의 각 요소의 값이 대입되며 반복 처리된다.

커맨드 라인 인자와 for

for 문의 리스트에 $@를 지정하면 모든 커맨드 라인 인자에 대해 동일한 처리를 수행할 수 있다.

  1. case 문

case는 지정한 문자열의 패턴에 따라 분기할 수 있는 제어 구조이다.
case의 사용법은 다음과 같다. 패턴과 세미콜론 사이에 실행하고자 하는 처리를 작성한다. 이 때 패턴은 언제나 )로 끝나야 한다. 그리고 case 문의 마지막은 case를 거꾸로 한 esac으로 끝난다. 패턴 개수에는 제한이 없다.

  • case 문의 구조
case <문자열> in
	<패턴 1>)
    	처리 1
        ;;
    <패턴 2>)
    	처리 2
        ;;
        
...
esac

case문은 패턴을차례로 확인하다 일치하는 패턴이 있으면 처리를 실행한다. 어느 패턴에도 일치하지 않으면 아무것도 실행하지 않고 case문을 벗어난다.

  1. while 문

while문은 지정한 조건이 참일 동안에만 반복하여 처리를 실행하는 제어 구문이다.

  • while 문의 구조
while <명령어>
do
	반복 처리
done

while 뒤에 명령어가 온다. if 문과 마찬가지로 명령어의 종료 상태가 0이면 do와 done 사이에 입력된 처리를 시행한다. while 뒤에는 보통 if 문과 마찬가지로 test 명령어에 의한 조건식이 온다.

산술 연산자

sh 기반의 셸 스크립트에서는 간단한 산술 연산에 대해서도 expr이라는 외부 명령어를 사용해야 한다. 그래서 셸 변수 i에 2를 더하려면 다음과 같이 기술해야 한다.

i='expr $i + 2'

위와 같이 expr을 사용하면 매번 외부 명령어를 호출하기 때문에 처리 속도가 느려지고 가독성도 떨어진다.

배시에서는 $(())를 사용하여 산술 연산을 쓸 수 있다. 이 때 셸 변수에 추가로 $를 붙이지 않아도 된다.

profile
WAS Engineer, Cloud Engineer(지망)

0개의 댓글