gnu bash reference 에 있는 자료를 토대로 bash 가 작동하는 원리를 정리한 내용이다.
bash 작동 원리
1. 파일 읽기
1
filename arguments
파일 실행
filename
을 읽어서 실행한다.- 그리고
exit
한다. - 이를
non-interactive shell
이라 한다. 사용자가 명령을 입력할 수 있는 경우를interactive
하다고 말한다.
1
2
When Bash finds such a file while searching the $PATH
for a command, it creates a new instance of itself to execute it.
bash
는 명령어를 실행할 때 무조건fork()
함수를 사용해서 자식 프로세스를 만든다.
파일 검색 순서
- 현재 디렉토리
$PATH
환경 변수에 저장된 경로
메모
filename
을 현재 디렉토리에서 찾아본다.- 만약, 실행 가능하다면
execve
함수로 실행시킨다. - 현재 디렉토리에 없으면
$PATH
환경변수에 저장된 경로를 찾아가서 파일이 존재하는지 확인하고, 실행 권한이 있으면execve
함수로 실행시킨다.
2. 토큰화
입력 받은 내용을 word 와 operator 로 구분하며, 이 과정에서 Quoting 규칙은 무시한다.
metacharacter 로 word 와 operator 가 구분되는데, 이를 token 또는 unit 이라 한다.
bash 에서는 토큰화 과정에서 Alias expansion 도 이루어진다.
💡 Alias expansion
따옴표로 묶이지 않은 명령어가 입력되면 먼저 alias 가 존재하는지 확인한다.
/
,$
, ```,=
와 메타 문자, 따옴표는 alias 의 이름으로 사용될 수 없다.alias 와 함께 arguments 를 넘길 수는 없다.
명령어가 실행되기 전에 alias 를 먼저 확장한다.
Alias expansion 이 토큰화에서 이루어지는데, Mandatory 에서는 명시된 사항이 아니라서 구현하지 않았다.
word
문자열을 의미한다.
쉘에서는 문자열을 unit 으로 판단하며, word 에는 metacharacter
가 포함되지 않는다.
metacharacter
word 를 구분하는 문자이다. 단, 따옴표로 묶이지 않은 문자열에 대해서만 구분한다.
종류는 다음과 같다.
- 공백, 탭, newline
|
,&
,;
,(
,)
,<
,>
💡 파이프나 redirection 은 공백을 사용하지 않아도 실행된다.
1
2
3
4
5
ls|cat # OK
ls | cat # OK
ls|cat>outfile # OK
ls | cat > outfile # OK
operator
operator
는 2가지 종류가 있다.
control operator
특정 기능을 수행하는 토큰이다.
- newline
||
,&&
,&
,;
,;;
,;&
,;;&
,|
,|&
,(
,)
||
(“or”) and&&
(“and”) separate two commands, resulting in the second command being executed if the first fail (i.e., returns with non-zero exit code) or succeeds (returns with zero exit code), respectively.;
, newline and&
(“background”) separate two commands. The first is useful if you want to put “unrelated” commands on the same line. The last also sends a command to the background, continuing execution without waiting for the command to finish.;;
separates two[case](http://mywiki.wooledge.org/BashGuide/TestsAndConditionals#Choices_.28case_and_select.29)
statements.(
and)
enclose a set of commands which are run in a subshell.|
(“pipe”) separates two commands, pointing standard output of the first command to standard input of the second command.|&
(“error pipe”) separates two commands, pointing standard output and standard error of the first command to standard input of the second command. Try(echo out; echo err >&2) 2>/dev/null |& cat
→ 참고자료 [stackoverflow]
redirection operator
- 명령어가 실행되기 전에 input 과 output 에 대한 redirect 가 먼저 이루어진다.
token
word 또는 operator에 해당한다.
token 을 unit 이라고 부르기도 한다.
token 은 metacharacter 에 의해 나누어진다.
예시
1
ls | grep joonhan > outfile
- word
ls
grep
joonhan
outfile
- metacharacter
- `` : 모든 공백
|
>
- operator
|
>
- token
ls
|
grep joonhan
>
outfile
Quoting
특정 문자나 특별한 기능에 대한 처리를 비활성화 하고, 매개 변수의 확장을 막는다.
- Escape character
\
문자 다음에 오는 문자를 리터럴로 처리한다.- Mandatory 에서 구현 사항은 아니다.
1
2
3
4
5
$ echo "$USER"
# joonhan
$ echo "\$USER"
# $USER
- Single Quotes
- 작은 따옴표 안에 있는 것들은 모두 문자열로 처리한다.
\
도 무시하고, 환경 변수도 모두 문자열로 처리한다.
1
2
3
4
5
$ echo '$USER'
# $USER
$ echo '$USER \$USER'
# $USER \$USER
- Double Quotes
- Single Quotes 와 달리 특수한 기능을 하는 문자들을 모두 작동하게 만든다.
- Mandatory 에서는 환경 변수를 확장하는
$
만 구현하면 된다.
1
2
3
4
5
6
7
8
echo He said,"Hello world"
# He said,Hello world
echo "'He said, Hello world'"
# 'He said, Hello world'
echo "$USER"
# joonhan
토큰화 예시 1
1
2
echo 'hello "world"' | cat > a
# hello "world"
Token 1
echo
: word
Token 2
'hello "world"'
: word
Token 3
|
: operator (pipe)
Token 4
cat
: word
Token 5
>
: operator (redirection)
Token 6
a
: word
토큰화 예시 2
1
echo aaa"kk"kk haha > ee
Token 1
echo
Token 2
aaa"kk"kk
Token 3
haha
Token 4
>
Token 5
ee
테스트 케이스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
echo "hello 'world' > outfile"
# echo
# "hello 'world' > outfile"
echo 'hello "world"' | cat > a
# echo
# 'hello "world"'
# |
# cat
# >
# a
echo aaa"kk"kk haha > ee
# echo
# aaa"kk"kk
# haha
# >
# ee
echo 'hello "world"' |>
# echo
# 'hello "world"'
# |
# >
ls | cat > outfile
# ls
# |
# cat
# >
# outfile
ba'sh'
# ba'sh'
'ba'"sh"
# 'ba'"sh"
b'as'h
# b'as'h
b'a'"s"h
# b'a'"s"h
"hello$USER".hi
# "hello$USER".hi
3. 파싱
shell command
- command 와 argument 는 space 로 구분된다.
1
2
3
# [command] [...arguments]
echo a b c
# a b c
- 명령어 실행하고 나면
waitpid
함수를 통해서 exit status 를 반환한다. - 시그널에 의해 멈추는 경우에는
128 + n
을 반환한다.n
은 시그널 번호이다.
arguments
파이프를 만나기 전에 command 뒤에 나오는 word 는 argument 이다.
1
2
3
4
5
ls << eof libft
# libft는 ls 의 argument 이다.
ls libft << eof
# 위와 동일하다.
redirection
redirection 바로 뒤에는 파일 이름이 온다.
1
2
3
4
5
6
7
8
9
10
11
ls > outfile
> outfile ls
ls >> outfile
>> outfile ls
< infile cat
cat < infile
heredoc
heredoc 바로 뒤는 LIMITER
이다.
1
<< eof ls libft/
테스트 케이스
1
2
3
4
5
6
7
echo 'hello "world"' | cat > a
# echo : COMMAND
# 'hello "world"' : ARGUMENT
# | : PIPE
# cat : COMMAND
# > : REDIR_RIGHT
# a : FILE_NAME
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
###############################
#### REDIRECTION INPUT (<) ####
#### OK ####
###############################
< Makefile cat
# < : REDIR_LEFT
# Makefile : FILE_NAME
# cat : COMMAND
# Makefile 대신 infile 을 cat 한 결과가 출력됨
< Makefile cat infile
# < : REDIR_LEFT
# Makefile : FILE_NAME
# cat : COMMAND
# infile : ARGUMENT
< Makefile ls -la
# < : REDIR_LEFT
# Makefile : FILE_NAME
# ls : COMMAND
# -al : ARGUMENT
cat < Makefile
# cat : COMMAND
# < : REDIR_LEFT
# Makefile : FILE_NAME
wc -l < Makefile
# wc : COMMAND
# -l : ARGUMENT
# < : REDIR_LEFT
# Makefile : FILE_NAME
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
##################################
#### REDIRECTION HEREDOC (<<) ####
#### OK ####
##################################
<< eof ls
# << : REDIR_HEREDOC
# eof : LIMITER
# ls : COMMAND
<< eof ls -al
# << : REDIR_HEREDOC
# eof : LIMITER
# ls : COMMAND
# -al : ARGUMENT
<< eof ls libft/
# << : REDIR_HEREDOC
# eof : LIMITER
# ls : COMMAND
# libft/ : ARGUMENT
<< eof ls -la libft/
# << : REDIR_HEREDOC
# eof : LIMITER
# ls : COMMAND
# -la : ARGUMENT
# libft/ : ARGUMENT
<< eof ls > outfile
# << : REDIR_HEREDOC
# eof : LIMITER
# ls : COMMAND
# > : REDIR_RIGHT
# outfile : FILE_NAME
<< eof ls | << foe cat
# << : REDIR_HEREDOC
# eof : LIMITER
# ls : COMMAND
# << : REDIR_HEREDOC
# foe : LIMITER
# cat : COMMAND
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
####################################
#### REDIRECTION OUTPUT (>, >>) ####
#### OK ####
####################################
ls > outfile
# ls : COMMAND
# > : REDIR_RIGHT
# outfile : FILE_NAME
> outfile cat infile
# > : REDIR_RIGHT
# outfile : FILE_NAME
# cat : COMMAND
# infile : ARGUMENT
ls -la >> outfile
# ls : COMMAND
# -la : ARGUMENT
# >> : REDIR_APPEND
# outfile : FILE_NAME
>> outfile cat infile
# >> : REDIR_RIGHT
# outfile : FILE_NAME
# cat : COMMAND
# infile : ARGUMENT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
##############################
#### REDIRECTION PIPE (|) ####
#### OK ####
##############################
ls | > outfile
# ls : COMMAND
# | : PIPE
# > : REDIR_RIGHT
# outfile : FILE_NAME
ls | >> outfile
# ls : COMMAND
# | : PIPE
# > : REDIR_APPEND
# outfile : FILE_NAME
문법 검사
따옴표가 제대로 닫혀있는지? (2. 토큰화에서 처리함)
1
echo "'hello"'
operator 뒤에 아무 것도 없는가?
1
cat infile >
파이프 바로 뒤에 파이프가 왔는가?
1
ls | | cat
redirection 뒤에 operator 가 왔는가?
1 2 3
cat > | ls cat > > ls
4. Shell expansion
Shell expansion
💡 Tilde Expansion
cd
명령어를 수행하기 전에는OLDPWD
환경변수가 없지만, 한번 수행하고 나면OLDPWD
가 생기면서 변수PWD
와 함께 지속적으로 갱신된다.서브젝트에서 요구하는 사항은 아님.
- 소괄호, 패턴 등과 같은 치환을 모두 수행한다.
- 환경 변수 치환 → word spliting → quote removal
- 그 다음 Quote Removal 을 하는데, expansion 의 결과를 제외한
\
,'
,"
문자들은 모두 지워진다."
큰 따옴표 안에 있는 환경변수는 치환된다.'
작은 따옴표 안에 있는 환경변수는 리터럴로 해석된다.
예시
1
2
3
4
5
6
7
8
9
10
export cute=hi
echo $cute
# hi
echo '$cute'
# $cute
echo "$cute"
# hi
환경변수 치환
환경변수 목록에 없으면 빈 문자열을 출력한다.
1
2
echo "hello$not_existed_variable world"
# hello world
테스트 케이스
- 환경변수를 확장하는 경우
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
##################################
# 1. 쌍따옴표 안에 있는 환경변수는 확장한다.
echo "hello $USER"
# echo "hello joonhan"
# hello joonhan
echo "'hello "$USER"'"
# echo "'hello "joonhan"'"
# 'hello joonhan'
echo "'hello '$USER''"
# echo "'hello 'joonhan''"
# 'hello 'joonhan''
echo ""$USER" '$USER'"
# echo ""joonhan" 'joonhan'"
# joonhan 'joonhan'
echo hello,"$USER".welcome
# echo hello,"joonhan".welcome
# hello,joonhan.welcome
echo hello,"$USER"
# echo hello,"joonhan"
# hello,joonhan
echo "hello $NAME$"
# echo "hello joonhan$"
# hello $
echo "hello $USER >> $USER"
# echo "hello joonhan >> joonhan"
# hello joonhan >> joonhan
echo "hello $USER "
# echo "hello joonhan "
# hello joonhan
echo "hello'$USER'world"
# echo "hello'$USER'world"
# hello'joonhan'world
echo "hello, $USER.hi".hi
# echo "hello, joonhan.hi".hi
# hello, joonhan.hi.hi
##################################
##################################
# 2. 따옴표에 감싸지지 않은 환경변수도 확장한다.
echo $USER
# echo joonhan
export a=ho
ec$a $USER
# echo joonhan
##################################
- 환경변수를 확장하지 않는 경우
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
##################################
# 1. 닫히지 않은 따옴표이기 때문에 오류가 발생한다.
"'$USER"'
##################################
##################################
# 2. 홑따옴표 안에 있는 환경 변수는 확장하지 않는다.
echo 'hello "$USER"'
# echo 'hello "$USER"'
# hello "$USER"
echo ""'$USER'""
# echo ""'$USER'""
# $USER
echo hello'world"$USER"'
# echo hello'world"$USER"'
# helloworld"$USER"
##################################
##################################
# 3. 환경변수가 없는 경우
echo "hello joon joonhan"
##################################
word spliting
따옴표에 묶인 상태에서 공백은 그대로 인식된다.
1
2
3
4
5
$ echo 'a "a"'
# a "a"
$ echo "a 'a'"
# a 'a'
하지만 환경변수의 확장이 일어나면 공백은 하나만 남기고 사라진다.
1
2
3
4
# 환경변수 설정
$ export a="a 'a' b"
$ echo $a
# a 'a' b
quote removal
따옴표로 묶인 문자열은 따옴표가 제거된다.
1
2
$ echo "a a"
# a a
다만, 따옴표 안에 포함된 따옴표는 제거되지 않는다.
1
2
$ echo "'hello'"
# 'hello'
그리고 확장(expansion)이 이루어진 문자열 내부에 포함된 따옴표도 제거되지 않는다.
💡 After the preceding expansions, all unquoted occurrences of the characters ‘\’, ‘’’, and ‘”’ that did not result from one of the above expansions are removed.
참고 : Bash Reference - Quote Removal
1
2
3
$ export a="a 'a'"
$ echo $a
# a 'a'
테스트 케이스
1
2
3
4
5
6
7
8
9
10
11
12
export a="a 'a'"
echo $a
# a 'a'
echo "hello $a"world'joon park''$a'
# hello a 'a'worldjoon parka 'a'
# 빈 문자열 출력
echo $not_existed_variable
#
echo
5. redirection
- 명령어가 실행되기 전에 input 과 output 이 설정되어야 한다.
- Redirection 은 왼쪽에서 오른쪽으로 순서대로 처리한다.
- Redirection 에 관련한 모든 작업이 끝나면, Redirection 연산자와 피연산자를 인자 목록에서 제거한다.
- 트리에서 제거하는 걸로 이해하면 될듯.
Heredoc
- Heredoc 실행 시, 임시 파일을 만들어서 저장한다. (참고자료 : How here document works in shell [stackoverflow])
6. Executing Commands
- 변수로 할당된 word 와 redirection 은 나중에 처리한다.
- 실행하기 전에 expansion 하고 redirection 을 한다.
7. Exit Status
- Exit Status 는 마지막에 실행된 명령어를 waitpid 해서 받은 값으로 반환한다. (전역 변수로 관리)
- 모든 자식 프로세스는 waitpid 로 회수하기.