자 이제 원하는 대로 파싱이 끝났으니 남은 일은 트리를 순회하며 $으로 구분되어 들어온 환경변수의 치환과 따옴표를 풀어주는 것뿐.
작성한 코드를 모두 밀고 다시 짜는 불상사를 막기 위해 코드 작성 전 여러 입력을 bash에 넣어보며 각 엣지케이스별로 출력이 어떻게 되는지 확인해주었다.
당시 확인했던 사항은 대략 다음과 같다.
1) 따옴표 속의 따옴표
큰 따옴표 속에 작은 따옴표가 있는 경우 -> 맨 바깥의 큰 따옴표만 풀림. 내부의 환경변수는 치환
> echo "'$HOME'"
output : 'User/jaemjung'
작은 따옴표 속에 큰 따옴표가 있는 경우 -> 맨 바깥의 작은 따옴표만 풀림. 내부의 환경변수는 치환되지 않음
> echo '"$HOME"'
output : "$HOME"
2) 중첩된 여러개의 따옴표
여러 개의 따옴표가 중첩된 경우, 시작된 따옴표부터 가장 가까이 있는 따옴표까지 한 묶음으로 간주하고 풀어줌
> echo ""'$HOME'""
output : $HOME
// 왜냐? 첫번째 큰 따옴표들이 한 묶음, 중간의 작은따옴표로 묶인 '$HOME'이 한 묶음,
// 맨 뒤의 두 번째 큰따옴표들이 한 묶음으로 처리되기 때문
3) 환경변수 속의 환경변수
환경변수 속에 또 다른 환경변수가 들어있어도 모두 치환됨.
> export HELLO="HELLO, $USER"
> echo $HELLO
output : HELLO, jaemjung
4) 환경변수 속의 명령어와 리다이렉션
환경 변수 속에 명령어가 들어있어도 실행은 됨. 그런데 리다이렉션은 실행 안됨.
> export MY_LS="ls"
> $MY_LS
output : (실제 파일 목록 출력)
> export MY_LS="ls -al > result.txt"
> $MY_LS
output : ls: >: No such file or directory
ls: test.txt: No such file or directory
이 정도를 bash 찍어보니 어떻게 코드를 짜야 할 지 감이 오기 시작했다.
1) 문자열을 시작부터 읽으면서 나가다 $를 만나면,
2) 공백 혹은 특수문자가 나올 때 까지 문자열을 읽어 환경변수의 이름을 알아낸다.
3) 알아낸 이름으로 환경 변수 목록에서 문자열을 읽어와서 치환
4) 여기까지 마쳤을 때 문자열의 주소값(혹은 인덱스)은 원래 $가 있던 위치. (왜냐? 치환한 환경변수 안에 또 환경변수가 있으면 그걸 치환해줘야 하니까!)
5) 고런식으로 쭈욱 읽어가면서 문자열 치환!
그런데! 따옴표를 만나면 어떻게 해야하나?
1) 따옴표 종류 (크냐 작냐)에 따라 내부 환경변수 치환 여부가 바뀌므로
2) 따옴표 부분은 통째로 읽어서 큰따옴표면 내부의 환경변수를 모두 치환해 준 후 따옴표를 풀어준 문자열을 생성,
작은따옴표라면 원래 문자열을 유지한 상태에서 따옴표만 풀어준다.
3) 통째로 문자열을 합친다.
코드로 보면 요런 식으로 진행된다.
static int expand_env_in_str_and_unquote(char **str, t_list *our_env, int exit_status)
{
int i;
i = 0;
while ((*str)[i])
{
if ((*str)[i] == '$' && (*str)[i + 1] != '\0')
compare_and_join_env(str, our_env, i, exit_status);
else if ((*str)[i] == '\"' || (*str)[i] == '\'')
{
i = unquote_str(str, i, our_env, exit_status);
if (i < 0)
return (i);
continue ;
}
if ((*str)[i] != '$' && (*str)[i])
i++;
else if ((*str)[i] == '$' && (!ft_isalnum((*str)[i + 1])) && (*str)[i + 1] != '?')
i++;
}
return (0);
}
내부의 함수 compare_and_join_env와 unquote_str에서 파라미터로 넘어온 str을 계속 업데이트 하는 방식으로 구현하였다.
아참!!! 몇가지 더 주의해야할 사항
1) 환경변수로 사용할 수 있는 글자는 알파벳과 _(언더바) 뿐이다. 이 점 역시 고려하여 치환하는 함수를 짜야한다...
2) 환경변수가 없을 경우 빈문자열로 치환된다.
3) $ 뒤에 환경변수가 아니라 특수문자가 올 수 있는데, 본 과제에서 구현해야하는 것은 ?(직전 프로세스의 exit status)뿐이다.
4) parse error가 나는 경우가 있다. 대표적으로 리다이렉션의 대상 파일이 입력으로 들어오지 않은 경우(ex : ls -al > ). 이 경우에 대해서도 적절히 처리해줘야 한다.
5) 닫히지 않은 따옴표의 경우, 본 과제에서는 bash처럼 추가적인 입력을 받도록 구현해줄 필요가 없다. 따라서 적절한 처리를 해줄 필요가 있는데, 나는 그냥 parse error로 간주하고 에러리턴을 해주도록 했다.
나는 요정도를 고려해서 따옴표 풀기 및 환경변수 치환 코드를 완성했다.
본 프로젝트의 이름이 [minishell]이라는 것을 잊지 말자. 너무 bash와 똑같이 구현하려다보면 bigshell이 되어버린다... 적절히 타협해야하는 부분은 타협해야 내상을 깊게 입지 않고 프로젝트를 끝낼 수 있다...
내가 담당한 파싱 부분의 전체 코드는 요 아래서 볼 수 있다.
https://github.com/JaemooJung/42SEOUL/tree/master/circle_3/minishell/src/parse_user_input
GitHub - JaemooJung/42SEOUL: for 42SEOUL
for 42SEOUL. Contribute to JaemooJung/42SEOUL development by creating an account on GitHub.
github.com
'42 > 42cursus' 카테고리의 다른 글
[NetPractice] 2. 라우터와 라우팅테이블 (0) | 2022.04.10 |
---|---|
[NetPractice] 1. ip와 서브넷, 서브넷 마스크 (0) | 2022.04.02 |
[minishell] 2. 파싱하기(하) (0) | 2022.03.22 |
[minishell] 1. 파싱하기(상) (0) | 2022.03.21 |
[philosophers] 4. alt_usleep의 함정 (1) | 2022.02.19 |
댓글