shell script는 실행되는 "환경"에 많은 영향을 받는다. script를 실행하는 shell의 선언된 환경 변수, 해당 command를 실행하기 위한 path의 설정 값, 그리고 user 의 권한, 심지어 os버전에 따른 shell 버전 등 에 영향을 받는다. shell script에 대한 좀 더 포괄적인 개념을 체크해보자.
우리가 실행하는 "bash"도 shell의 종류이며 근본적으로 하나의 SW 중 하나이다. 그리고 일반적으로 /bin
는 cli 관련 파일이 저장되며 역시 우리가 사용하는 bash
역시 /bin/bash
에 존재한다.
.sh
file을 (정확하게 확장자는 영향을 받지 않는다) bash targetfile.sh
로 실행하면 바로 bash로 실행을 하며, 최상단에 #!/bin/bash
를 작성안해도 된다.
그리고 그 bash
cli를 실행하면 실행되는 것은 사실 /bin/bash
파일이며, full command는 /bin/bash targetfile.sh
이 되는것 과 같고, bash
file은 Binary 파일이다. 그리고 /bin/bash
로 실행되는 이유는 $PATH
설정 값 때문이다. path에 대해서는 밑에서 다시 살펴보자.
바이너리 파일은 Non-Text file이라고도 불리며, "이진 형태로 인코딩 된 파일" 이다. 바로 컴퓨터가 실행가능 한 상태의 파일이라고 이해하면 편하다. 즉 소스코드 형태가 아닌 실행 파일을 나타낸다. 대표적으로 exe, dll, zip, rar, mp3, mpg, jpg, png
등이 존재한다.
text file은 "읽기 가능한 형식" 으로 작성하며 binary file은 "데이터 형태 그대로" 저장한다. 요즘은 통신도 "json과 같은 읽기 가능한 형식"으로 데이터를 주고 받는게 흔하지만, 예전에는 통신의 경우에도 binary file을 주고 받는 경우도 많았으며 이 경우 자리 수, 위치, 크기 등이 매우 까다로웠다. 하지만 text file에 비해 용량 압축, 그에 따른 속도 향상, 그에 따른 비용 감축 등 의 유리함도 분명하게 존재했다.
binary file이 정확하게 어떤 역할을 하는지는 "직접 분석" 을 해봐야 알 수 있긴 하다. 흔히 그 행위를 reverse engineering
라고 한다. 보통 보안 하시는 분들은 바이너리 파일 개념과 리버스 엔지니어링에 아주 친숙할 수 있다.
mysql
) 만 쳤는데 어떻게 바로 그 바이너리 파일을 execute 할 수 있을까? 예를 들어서 python
도 말이다. python
설치 후 python
만 입력해도 바로 대화형 인터프리터로 진입하는 것 같이 말이다. 이건 PATH
변수 덕분이다. echo $PATH
를 쳐보자!:
값을 기점으로 여러 절대 경로가 저장되어 있다.
linux에서는 특정 명령어(command)를 입력하면 이 명령어를 실행할 수 있는 친구 를 찾는다. 그 명령어를 실행할 수 있는 친구의 경로를 미리 저장해둔 "환경 변수 값"이 바로 PATH 이다.
export PATH=새패스:$PATH
와 같은 command를 생각없이 친 경험이 분명히 있을 것이다. 그리고 docker
환경에서는 이 PATH config가 굉장히 유용하며 중요하다.
PATH
값은 보안적으로 굉장히 critical한 값이다. 예를 들어 누군가 우리의 su
command를 위장해 binary file을 만들어 두고 설치한 뒤, PATH에 그것이 설치된 경로를 추가해 두었다고 치자.
아무 생각없이 su
를 통해 root
권한 실행을 하려고 할 때 공격자가 설치한 프로그램이 execute 되면서 입력한 비밀번호가 전송된다면? 우리는 아무것도 모른채로 root password를 탈취당할 수 있다. 그러니 우리의 PATH
값은 굉장히 중요하다.
위에서 언급한 export PATH=새패스:$PATH
command를 통해 추가할 수 있다. 이 command를 bash 설정 파일 가장 하단에 세팅해서 해당 bash 환경에 들어갈때마다 PATH
값이 먹히게 할 수 있다.
하지만 default system의 전체 PATH 값은, linux에서, /etc/profile
에 저장이 된다. 그리고 각 사용자(os user, linux user)는 HOME 디렉토리의 .profile
파일에 PATH 값을 가지고 있다.
이런 path 설정에 대한 더 자세한 설명은 이 글에 정리가 잘 되어있다.
환경변수란 프로세스가 컴퓨터에서 동작하는 방식에 영향을 미치는 동적인 값들의 모임이다. 그리고 OS에서 자식프로세스들을 생성할때 참조하는 '변수들' 이라고 생각하면 좋다. 그리고 셸 변수(shell variable)와 확실하게 다른 개념이다.
환경변수는 시스템의 실행 파일이 놓여 있는 디렉터리의 지정 등 OS 상세서 동작하는 응용소프트웨어가 참조하기 위한 설정이 기록된다. 응용소프트웨어는 시스템콜(system call)이나 OS의 표준 API 등을 통하여 간단히 값을 얻을수록 되어있다. 쉽게 이야기해서 각자 깊숙이 있는 응용프로그램을 쉽게 꺼내쓰기 위해서 미리 변수로 등록해 놓는 것을 말한다.
export 설정할변수명=값
라는 command로 지금 shell에 전역적으로 해당되는 환경 변수를 세팅할 수 있다. 그리고 export
만 쳐서 어떤 환경변수 값이 세팅되어 있는지 볼 수 있다.
Option | Description |
---|---|
-a | 생성, 변경되는 변수를 export 함 |
-e | 오류가 발생하면 스크립트 종료 |
-x | 수행하는 명령어를 출력 후 실행 |
-c | 다음의 명령을 실행. ex) bash -c "echo 'A'". bash -c date |
-o | 옵션 설정 |
-e
옵션의 예시로, 다음의 명령어를 실행하면 Hello 까지만 출력되고 프로그램이 종료된다.#!/bin/bash
set -e
echo "Hello"
aaa
echo "World"
-x
와 +x
를 통한 예시를 살펴보자#!/bin/bash
set -x
echo "AA"
ls -alh ./
# 위의 명령을 실행 할 때는 디버깅 옵션이 출력되지만 아래 명령을 실행할 때는 출력되지 않습니다.
set +x
echo "BB"
ls -alh ./
set -o vi
나 set -o emacs
를 이용해서 "명령어 입력 모드를 설정" 할 수 있다. 홈키나 엔드키를 입력했을때 문자가 대문자, 소문자로 변경된다면 set -o emacs로 입력 모드를 변경하면 된다.현재 환경 변수를 표시하거나 환경 변수를 변경한 후에 프로그램을 실행하는 "유틸리티" 이다.
env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]
형태를 가진다.
옵션 없이 사용하면 현재 환경 변수를 보여준다. -i
option을 사용하면, "환경 변수를 모두 지운후에 프로그램을 실행" 할 수 있다.
가벼운 사용법은 이 글로 대체한다.
POSIX-compliant shell (sh, dash, bash, ksh, etc.; also zsh) 에서 export
를 사용해 환경변수를 세팅하는게 기본이다. 하지만 Pre-POSIX Bourne shells 시대에는 export
를 지원하지 않는다. setenv
를 통해 환경변수 값을 세팅했다. 그리고 env
는 shell command라기 보다는 itself - program 이다.
"."
으로 실행하고 "source"
실행한다. 그 둘의 차이는 무엇일까? 우선 둘 다 shell script를 실행하는 "built-in command" 이다.핵심적인 차이는 new shell execute
vs current shell execute
이다.
"."
으로 실행하면 새로운 셸에 실행되는 것과 같고 실행되면서 적용되는 다양한 변수 또는 결과들은 새로운 셸에 실행되고 적용된 후에 script가 끝나면 모두 사라진다. 그에 반해 "source"
로 실행한 셸 스크립트는 지금 shell user그대로, execute 하며 그 결과값은 오롯이 지금 shell에 다 적용이 된다. 다음 셸과 실행 결과를 체크해보자.
# temp
echo $HELLO
export RESULT=결과
"."
으로 실행을 바로하면permission denied: ./temp
을 마주하게 될 것이다. 이유는? 새로운 셸에서 실행되면서 execute하는 user가 지금 내 current shell과는 달라진다. ls -al
로 위 셸로 만든 temp
file의 파일 권한을 보자.-rw-r--r-- 1 nuung staff 33 1 29 23:20 temp
group은 staff이며 소유 user는 내 user (nuung) 로 되어 있고 execute 권한이 root에만 있다. 그래서 chmod
로 권한을 control 해줘야 실행이 된다. 하지만 source
로는 바로 실행이 되는 것을 알 수 있다.
그리고 실행 결과값을 대조하면 아래와 같다.
"."
로 execute 했을 땐 shell script 내부에서 export 한 값이 안먹혀 있지만 source 로 하면 먹혀있다. 이게 가장 큰 차이점이다.아쉽게 사용법만 익히다 보면 위와 같이 linux, os의 가장 기본 개념과 세팅에 대해 지나치는 경우가 굉장히 많다. 규모가 큰 기업일 수록 개개인의 업무와 특히 business logic 만 다루게 되고 OS의 기본은 놓칠 수 밖에(?) 없어진다.
docker를 다루고 OS 값에 따라 control하고 CI/CD를 구성하다 보면 이런 사소한 것들에서 해메고, 고통받는 경우도 많다. shell에 익숙해지면 OS kernel에 한 단계 더 다가갈 수 있고, OS ENV, PATH, BASH 그리고 USER 에 대한 개념은 사실 다 이어져 있고 결국 내가 application을 구성할 때 더 큰 도움이 되는 것 같다.
Command run, PATH, Profile, OS ENV, shell script RUN
AWK(오크: Aho Weinberger Kernighan)는 유닉스에서 처음 개발된 일반 스크립트 언어이다. 약자는 기능을 디자인한 사람들의 이니셜을 조합하여 만든 이름이다. (A:Alfred V. Aho, W:Peter J. Weinberger, K:Brian W. Kernighan) 그래서 이름과 기능은 관계가 없다!
shell 자체가 가진 기능이 아쉬워서 보완하기 위해 등장했고, "파일로부터 레코드(record)를 선택하고, 선택된 레코드에 포함된 값을 조작하거나 데이터화하는 것을 목적으로 사용" 하는 하나의 S/W 이다.
위 사진과 같이 awk command가 있으며, "awk programming language" 프로그래밍 언어로 작성된 프로그램을 실행하는 것과 같다. 즉, awk에 대한 활용법을 배우는 것은 사실 awk 프로그래밍 언어 자체를 배우는 것과 같다.
유닉스 버전 7에 처음 등장해서 지금까지 사용되는 매우 오래된 도구로, 오늘날에는 거의 모든 유닉스 계열 운영체제에 포함되어 있는 표준 도구로 자리잡았으며 다른 운영체제에서도 사용할 수 있다. 하나의 유틸 언어체계로써, 활용 가능한 기본 기능이 방대해서 하나의 글로는 모든 것을 담을 수 없다. 이 글에서는 "기본 활용"에 대해 살펴보자. official docs를 확인하면서 진행하자!!
awk [옵션] '/패턴/ { 동작 }'
으로, 크게 "명령 파일" 과 "주 입력 파일" 두 가지 데이터를 받아 실행된다. awk가 입력 데이터를 어떤 규칙에 따라 처리할지를 지시한다. "패턴"은 "정규표현식" 을 의미한다.
awk [-F fs] [-v var=value] [-f progfile | 'prog'] [file ...]
로 이뤄져 있다. 아래는 흔한 웹서버 로그이다. 해당 로그를 가지고 awk 실습을 해보자!
10.0.3.221 [05/Feb/2023:01:42:45] GET /admin/ HTTP/1.1 200
10.0.3.221 [05/Feb/2023:01:42:46] GET /ht HTTP/1.1 301
10.0.0.166 [05/Feb/2023:01:42:48] GET /metrics HTTP/1.1 200
10.0.3.221 [05/Feb/2023:01:42:54] GET /admin/users/user/ 200
10.0.3.221 [05/Feb/2023:01:42:59] GET /admin/users/user/?o=8 200
10.0.0.166 [05/Feb/2023:01:43:00] GET /ht HTTP/1.1 301
awk '{ print }' ./test.txt
로 pattern을 생략해 test.txt
내용을 모두 출력할 수 있다.
awk '{print $1}' ./test.txt
로 $1
- 특정 열 값만 출력할 수 있다.
> awk '{print $1}' ./test.txt
10.0.3.221
10.0.3.221
10.0.0.166
10.0.3.221
10.0.3.221
10.0.0.166
사진에서 처럼 $1 ... $N
값은 해당 출력값, text file 등에서 열 순서를 의미한다. 그리고 $0
은 한 행을 의미한다.
그럼 "열"을 구분하는 값은 공백이어야만 할까? 아니다. -F
option을 통해서 열 구분인자를 세팅할 수 있다. awk -F":" '/301/ {print $1}' test.txt
의 결과값은 아래와 같다.
# 301을 포함한 행 출력, 근데 행 구분자는 ":" 이며, 첫 열만 출력
> awk -F":" '/301/ {print $1}' test.txt
10.0.3.221 [05/Feb/2023
10.0.0.166 [05/Feb/2023
패턴은 기본적으로 정규식을 활용한다. 하지만 행의 문자 길이 체크, 특정 열끼리 곱셈의 결과값의 조건 등과 같은 다양한 조건을 걸 수 있다.
awk 'length($0) > 55 { print $3, $4, $5}' ./test.txt
를 통해 행의 길이가 55 초과이며, 해당하는 행은 3, 4, 5열을 출력하는 command이다.
> awk 'length($0) > 55 { print $3, $4, $5}' ./test.txt
GET /admin/ HTTP/1.1
GET /metrics HTTP/1.1
GET /admin/users/user/ 200
GET /admin/users/user/?o=8 200
> awk '/admin|ht/ { print $1, $3, $4}' ./test.txt
10.0.3.221 GET /admin/
10.0.3.221 GET /ht
10.0.3.221 GET /admin/users/user/
10.0.3.221 GET /admin/users/user/?o=8
10.0.0.166 GET /ht
awk 패턴(pattern) 중에는 "BEGIN" 과 "END" 라고 하는 특별한 패턴이 존재한다.
awk가 BEGIN 패턴을 식별하면 입력 데이터로부터 첫 번째 레코드를 처리하기 전에 "BEGIN"에 지정된 액션을 실행한다. 그리고 "END" 패턴은 모든 레코드를 처리한 다음 "END"에 지정된 액션을 실행하는 원리이다.
> awk 'BEGIN { print "MORE THAN len 55"} length($0) > 55 { print $3, $4, $5} END {print "Finished"}' test.txt
MORE THAN len 55
GET /admin/ HTTP/1.1
GET /metrics HTTP/1.1
GET /admin/users/user/ 200
GET /admin/users/user/?o=8 200
Finished
{ print "MORE THAN len 55"}
action을 먼저 수행하고, length($0) > 55 { print $3, $4, $5}
위에서 언급한 패턴 처리해서 열 출력을 한 뒤 끝나면 {print "Finished"}
action을 수행한다.
그 외에 if
등을 활용해 사용할 수 도 있다. 그리고 "개행 문법"을 사용할 수 있다. 아마 금융권에서 "배치 전문" 등을 가장 편리하고 쉽게 처리하는 방법 중 하나로, awk
를 개행문법을 통해 아래와 같은 command를 먹이는 경우를 많이 봤으리라!
awk '{
if ($3 >=35 && $4 >= 35 && $5 >= 35)
print $0,"=>","Pass";
else
print $0,"=>","Fail";
}' filename
action
에 print 할 때 괄호와 쌍따옴표를 활용해 아래와 같은 예시로, 열 출력시에 우리가 원하는 text값을 붙여서 출력할 수 있다. > awk '/admin|ht/ { print ("ip: " $1, "method: " $3, "uri: " $4)}' ./test.txt
ip: 10.0.3.221 method: GET uri: /admin/
ip: 10.0.3.221 method: GET uri: /ht
ip: 10.0.3.221 method: GET uri: /admin/users/user/
ip: 10.0.3.221 method: GET uri: /admin/users/user/?o=8
ip: 10.0.0.166 method: GET uri: /ht
사실 awk에 대해서는 다루지 못한 내용이 너무나 많다. awk가 그만큼 활용도가 넓고, 잘쓰면 raw data를 추가 coding없이 아주 쉽고 빠르게 처리하기에 awk만큼 좋은게 없다. 게다가 대부분의 유닉스 계열 OS에는 표준 도구로 자리잡고 있으니, 운영 서버에서도 바로바로 awk를 돌리기도 좋다.
또 하나 살펴보면 좋은게 awk 또한 역시 하나의 language 로써, .awk
file을 만들어서 사용할 수 있다.
사실 확장자는 크게 중요하지 않고, awk
file을 명시하고, 문법대로 코딩 한 뒤에 execute 할 때 awk -f
만 사용하면 된다.
#!/bin/awk -f
BEGIN { print "File\tOwner" }
{ print $8, "\t", $3}
END { print " - DONE -" }
# 실행방식 : awk [-f옵션] [awk스크립트파일] [참조할파일]
$ awk -f script.awk filename
pipe
로 다양한 출력 커멘드와 엮어쓰기 진짜 좋다. 특히 ps
그리고 ls
에서 말이다![user@ip conf.d]$ ls -l | awk '/conf/ { print $9 }'
celery-flower-dev.conf
healthd_logformat.conf