미니쉘도 쉘이다..!
미니쉘 구현에서 놓치기 쉬운 부분들을 정리해보려 합니다.
처음 미니쉘을 시작하려면 굉장히 막막한데 이 글로 키워드를 얻어가시길 바랍니다.
다소 엣지한 케이스까지 포함한 글이니 스스로 판단하여 구현할 부분을 정하시면 됩니다.
이 글은 2022년 10월 서브젝트 기준 보너스까지 구현하는 것을 기준으로 합니다.
0. 참고할 문서
1.
GNU Bash Manual
영어로 되어있고 친절하게 설명된 문서는 아니지만 이 둘을 보는게 가장 빠른 길입니다.
bash는 posix의 명세에 따른 여러 shell 중 하나입니다.
1번의 bash 메뉴얼을 보고 무엇을 구현해야 하는지 파악하고 2번의 posix 명세에 따라 코드를 작성하면 됩니다.
posix의 shell grammar와 tokenizing 기준을 보고 따르면 좋습니다.
이 글에 모든 것을 적진 않았으니 위 문서를 꼭 확인하시길 바랍니다.
1. 파싱부
readline으로 쉘에 들어온 명령어를 한 줄씩 처리해야 한다.
제대로 과몰입 하기 위해서 컴파일러 강의를 듣고 LA LR 파서를 사용해 AST를 만들고 그에 따라 실행하는 방법이 있습니다.
하지만 단순히 재귀를 돌며 트리를 구성하고 후처리로 syntax error를 체크해도 미니쉘까진 구현 가능합니다.
1-1. scanner & Parser
스캐너로 토큰을 나누고 파서로 트리를 만듭니다.
저희 팀은 토큰은 word와 operator, 트리는 list, control operator(&&, ||), pipeline, pipe(|), simple command와 subshell로 나누었습니다.
이 기준은 bash와 정확히 일치하게 동작하는데는 무리가 있으나 미니쉘에는 충분합니다.
하지만 직접 명세를 읽고 구조체를 구성해보길 바랍니다.
scanner에서는 linked list, parser에서는 binary tree 자료구조를 사용했습니다.
2. 실행부
파서를 통해 나온 이진트리를 중위 순회하며 실행해 줍니다.
2-1. 확장
$?은 가장 최근에 실행된 파이프라인 명령어의 종료코드로 들어갑니다.
아주 정확하겐 확장은 아니지만, list에서도 정확하게 exit code가 들어가게 하기 위해 트리를 순회하면서 실행하기 직전에 확장을 수행합니다.
echo $$, echo $??, echo ?$, echo ?? 도 의도한 대로 잘 동작하는지 확인해 보세요~
2-1-1. 환경변수 확장
따옴표 주의 하세요~
ft_printf의 보너스를 구현하셨다면 추억을 되살리며 flag를 사용해 조건을 확인해보면 좋습니다.
•
cat $없는 환경변수 와 cat “$없는 환경변수”는 동작이 다릅니다..! 왜일까요????
•
물론 과몰입 주의
2-1-2. word(field) split
$IFS란 것이 있습니다.. 하지만 이걸 구현하는 건 과몰입인 것 같고 아래 케이스만 잘 되면 될 듯요
bash-3.2$
bash-3.2$ export a="a b c"
bash-3.2$ env | grep a
TERM_PROGRAM=Apple_Terminal
TMPDIR=/var/folders/zz/zyxvpxvq6csfxvn_n000cslc0036cv/T/
ZSH=/Users/jiwahn/.oh-my-zsh
USER=jiwahn
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.ohB1BIZD8v/Listeners
LSCOLORS=Gxfxcxdxbxegedabagacad
MAIL=jiwahn@student.42seoul.kr
PATH=/Users/jiwahn/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/munki
a=a b c
PWD=/Users/jiwahn
HOME=/Users/jiwahn
LOGNAME=jiwahn
bash-3.2$ cat "a"$a"c"
cat: aa: No such file or directory
cat: b: No such file or directory
cat: cc: No such file or directory
bash-3.2$
Shell
복사
2-1-3. wildcard(*) 확장
*/* 또는 */**은 서브젝트 기준 구현 범위가 아닙니다… current working directory only!
하지만 *, *.c, ft_*, M*k*e등등은 잘 돼야 되겠죠?
•
.* 은 어떻게 될까요?
•
*/ 은?
2-1-4. quote removal
quote를 파싱에서 때버리면 안되고 확장 때까지 잘 들고 가셔야 합니다..
물론 안그래도 되지만..
본인 마음이죠..
2-2. 리다이렉션 처리
•
heredoc에서의 시그널 처리도 꼭 고려해보세요
•
<< “$PATH”는 어떻게 동작할까요?
•
heredoc을 입력받을 때 $PATH는 확장이 될까요?
•
그럼 “$PATH”는? ‘$PATH’는?
•
근데 이건 사실 저도 안했습니다
2-3. 빌트인
•
PATH를 unset 하고 명령어를 실행해도 segmentation fault가 나진 않는지
•
export와 unset 여러개 인자도 동시에 가능하지
•
cd ~, cd - 등의 옵션도 가능한지
•
unset PWD, unset OLDPWD 이후 cd를 했을 때 환경변수가 부활하진 않는지
•
echo -n -asdf -n -n -n -nnnnnnnnnnnnnn이 bash와 동일하게 동작하는지
•
미니쉘의 exit은 인자를 안받는데 이 부분도 잘 처리하기
등을 살펴보시면 좋습니다
2-4. 서브쉘
•
(exit)
•
exit | exit
서브쉘은 사실 ()뿐 아니라 명령어의 실행 환경을 이릅니다.
그래서 서브쉘이 뭔가요 ⇒ 메뉴얼 읽기
저희는 subshell에서 끝까지 파싱하지 않고 다시 Main함수(같은 것)을 실행했습니다
끝까지 파싱할지 아닐 지는 구현하면서 선택하기!
3. 기타
•
-g -fsanitize=address
이걸 모르고 미니쉘까지 왔다..? 정말 수고하셨습니다
•
lsof -p (프로세스 번호)
열린 파일 디스크립터를 볼 수 있습니다.
•
while true; do leaks minishell; sleep 1; done;
lldb로 한 줄씩 넘겨가며 누수를 확인할 수 있습니다.
•
readline를 위한 makefile
$(shell brew —prefix readline), brew로 readlin를 설치한 경우에만!