---
description: >-
  완성하기까지 총 7주. 예외처리를 하다보면 코드가 지저분해지고, 또 원래는 잘 동작했던 것들이 무용지물이 되는 경우가 많았습니다. 지쳐갈 때
  쯤 힘이 되어 주었던 건 많은 42 동료의 도움덕이었고, 그래서 저도 메모했던 내용들을 자세하게 정리해서 공유하고 싶었습니다.
---

# \[minishell] 2. 프로그램 구조 및 개발 기록들

## 1. 프로그램 구조 (Flow Chart)

> 위의 **플로우차트 및** [**코드**](https://github.com/TEAM-DaeGae/minishell) 참고 !

1. **초기화 :** 미니쉘에 필요한 각종 전역변수 및 리스트, 구조체 초기화를 해준다. 미니쉘에서는 SIGINT, SIGQUIT 시그널이 무시되어야 하므로 해당 시그널 처리를 해준다.
   *   그리고 우리 daelee/gaekim 팀에서는 팀 이름을 기반으로 미니쉘을 시작할 때 ASCII 그림을 띄워준다. 팀 마다 다들 개성있게 만드는 것 같다.

       ![](https://github.com/hidaehyunlee/Fork-my-brain/tree/4f7d19f4487fe8d450e22a9b6451bbed865ce125/images/37580034-107346925-9e9e0a80-6b08-11eb-920a-2382ce24ad95.png)
2. **Loop :** 그 후 메인 반복문에 진입한 뒤 사용자에게 명령어 input을 받는데, 이전 과제였던 GetNextLine 함수를 이용했다.
3. **Parse 및 Process 생성 :** input을 명령어 프로세스 별로 처리할 수 있도록 파싱하는 과정이 필요하다. 우리는 처음에 크게 파싱과 실행으로 역할분담을 했었다. 파싱에서는 input을 null, 세미콜론, 파이프 기준으로 분해해 연결리스트 노드에 담았다. 그리고 그 노드를 프로세스의 단위로 사용했다.
4. **Builtin 명령어 / Redirection 처리** : 각 프로세스는 구조체에 char \*\*comdline과 int flag를 가지고 있다. 플래그에 따라 미니쉘 과제에 따라 구현한 builtin 명령어면 exec\_builtin을, exec\_redir를 실행한다. 만약 쉘 내부 명령이면서 그 명령이 exit였다면 미니쉘을 종료한다.
5. **fork() :** 그 외의 모든 명령어(execve, pipe)는 fork 함수를 사용해 실행한다. 둘 다 자식프로세스를 만든다는 공통점이 있지만 분기를 만들어준 이유는 파이프 플래그가 있다면 dup2 함수로 프로세스 간의 표준입출력을 연결해줘야하기 때문이다.
6. **Loop :** 순차 수행에 따라서 모든 명령어 처리가 완료되었다면, 다시 프롬프트를 띄워서 명령어를 받아들이고 작업을 계속 수행한다. 기본적으로 부모 프로세스은 exit 를 만나기 전까진 지속적으로 수행되는 것이 보장된다.

## 2. Develop minishell

> 홀리그래프에는 약 2주가 소요될거라고 써있었지만... 미니쉘을 완성하기까지 총 7주가 걸렸다.
>
> 미니쉘은 과제를 어떻게 해석하냐에 따라, 그리고 개발하는 입장에서 어느 수준까지 bash와 비슷하게 구현하냐에 따라 처리해야할 예외의 경우의 수가 엄청 나게 많아질 수 있다. 예외처리들을 하다보면 코드가 점점 길어지고, 지저분해지고, 또 원래는 잘 동작했던 것들이 다시 또 무용지물이 되는 경우가 많았다.
>
> 지쳐갈 때 쯤 계속 힘이 되어 주었던 건 함께, 먼저 미니쉘을 학습했던 동료의 도움 덕이었고, 그래서 나도 최대한 자세하게 과제를 진행하며 메모했던 내용들을 정리해서 공유하고 싶었다.
>
> 그리고 "우리는 미니쉘이 빅쉘이 되는 것을 원치 않는다"는 어떤 42Paris 슬랙의 스레드를 봤다. 그게 42의 공식 입장(?)이라고 믿고 우리가 할 수있는 것들에만 집중했다. (변명스)

### 1. 파싱

사용자의 input을 담는 과정에 대한 설명은 생략했다. 역할을 분담해 개발했기 때문에 대략의 흐름만 나 스스로 이해하기 위해 정리했다.

```c
typedef struct    s_list
{
    void                    *content;
    struct s_list    *next;
}                                t_list;

typedef struct s_cmd
{
    char    **cmdline; // cmdline[0] = "echo", cmdline[1] = "hi", cmdline[2] = NULL
    int        flag; // flag 0: 세미콜론 또는 NULL, flag 1: 파이프
    char    quote; // 파싱과정에서 ' 또는 " 를 저장, 짝을 찾으면 0으로 초기화 됨.
}                 t_cmd;

typedef struct s_data
{
    t_list    *lstlast;
    t_cmd     *cmd; //위의 t_cmd 구조체를 t_data 안에 선언함으로써 t_data만으로 2개의 구조체 사용 가능
    char     *buff;
    ...
}                t_data;

void    exec_proc(t_list *head) // 인자는 연결리스트의 헤드포인터
{
    t_list    *cur_proc;
    t_cmd    *cmd;

    cur_proc = head->next;
    while (cur_proc != NULL)
    {
        cmd = cur_proc->content; // 각 노드의 content는 data->cmd의 주소를 갖는다.
        if (cmd->cmdline[0]) // 명령어가 있으면 실행
        {
            if (cmd->flag == 1)
                 exec_pipe(cur_proc, cmd);
      else if (cmd->flag == 0)
                exec_cmd(cmd->cmdline); // 인자는 cur_proc->content->cmdline
        }
        cur_proc = cur_proc->next; // 다음 노드로 이동
    }
    ft_lstclear(&head, free_cmdline); // 연결리스트 비워줌
}
```

* 노드는 프로세스 단위로 자른다.&#x20;
  * NULL, 세미콜론, 파이프 3가지를 제외하고는 글자를 buff에 계속해서 담음
* 각 노드는 `t_list` 자료형으로 `content`와 `next`를 갖고 있다
* 각 노드의 `content`는 `t_data->t_cmd`를 저장하고 있다.
* `t_cmd` 구조체는 `char **cmdline`과 `int flag`, `char quote`를 담고 있다.
* `cmdline`은 각 프로세스의 명령줄을 담고 있다.&#x20;
  * ex. cmdline={ "ls", "-al" , NULL }
* `cur_proc`은 현재 가리키고 있는 노드의 주소
* 반복문을 돌면서 cur\_proc = cur\_proc->next 로 다음 노드로 이동, 명령어를 실행한다.
  * 실행 단위 : `cur_proc->content->->cmdline[0]`

### 2. 쉘 내부 명령어 처리

#### 1. cd

**1.1. 평가 항목**

* Use the command cd to move the working directory and check if you are in the right directory with /bin/ls
* Repeat multiple times with working and not working cd
* try ‘.’ ‘..’ as arguments too

**1.2. 구현한 케이스**

* `cd`, `cd ~` 일때
  * -> HOME 경로 찾아서 path에 저장 후 이동
* `cd $변수` 일때
  * -> 변수 경로 찾아서 path에 저장 후 이동
* `cd ~ 234`  (cd \~ 후 인자가 들어왔을 때)
  * -> 에러메세지 없이 \~로 이동
* `cd $HSIDHSIHD`  (없는 환경변수일때)
  * -> 에러메세지 없이 \~로 이동

**1.3. 에러 메세지**

* `cd 234 987`  (cd 인자가 두 개 이상일때)
  * minishell: cd: 234: No such file or directory&#x20;
* `unset HOME` 한 후 `cd $HOME` 했을 때
  * "minishell: cd: HOME not set"
* 올바르지 않은 폴더 경로일때
  * "minishell: cd: 인자: No such file or directory"

**1,4. OLDPWD**

* 현재 경로를 getcwd로 저장해놨다가 cd 하면 $OLDPWD 와 $PWD를 새로 ft\_export 해준다.

**1.5 굳이 안해도 되지만 하면 좋은 것**

**1. cd -**

* OLDPWD로 이동

**2. cd \~ 와 \~ , 그러나 미니쉘에서 \~ 의 제대로 된 구현은 불가능 하다**

* cd 에서 `~` 를 제대로 구현하는게 불가능 하다는 걸 알게됐다.
* 나는 `~` 가 home 환경변수를 찾아서 거기로 이동시켜주면 되는거라고 생각했는데, home 환경변수를 지워버려도 이동이 된다.
* bash에 `~` 만 입력해도 내 home 경로가 뜨는 걸 보면, 그냥 bash 시스템 자체에서 `~` 를 home 주소로 자동으로 변경해주는 것 같다. 환경변수랑은 상관없는, 미니쉘 허용함수로는 구현할 수 없는 별도의 기능인 같다. 마치 `$?` 처럼!
*   bash에 대한 공식 문서를 보더라도 `..` , `.` , `_` 에 대한 얘기는 있는데 `~` 에 대한 얘기는 없는 걸 보면 정말 그런 것 같다.

    ![](https://github.com/hidaehyunlee/Fork-my-brain/tree/4f7d19f4487fe8d450e22a9b6451bbed865ce125/images/37580034-106360678-23e12c80-635d-11eb-86ee-25f9ed588af0.png)

#### 2. echo

**2.1. 평가 항목**

* Execute the echo command with or without arguments or options
* Repeat multiple times with different arguments

**2.2. 구현한 케이스**

* option 플래그가 0이면 `\n` 개행 출력.
*   싱글쿼트일 때 환경변수 치환하지않고 그대로 출력.

    ```bash
    /Users/daelee/Desktop/42cursus/minishell $ echo '$PWD'
    > $PWD
    /Users/daelee/Desktop/42cursus/minishell $ echo "$PWD"
    > /Users/daelee/Desktop/42cursus/minishell
    ```
* `echo $?`
  * g\_exit\_status 값 출력.
* `echo -nnnnnn hello`
  * -> `echo -n hello` 와 동일
* `echo hello ; ;` 혹은 `echo hello | |`&#x20;
  * -> `syntax error near unexpected token`;'\` (258)

#### 3. exit

**3.1. 평가 항목**

* Execute exit command with or without arguments
* Repeat multiple times with different arguments
* Don’t forget to relaunch the minishell

**3.2. 구현한 케이스**

1. exit 만 입력 받았다면 exit 출력 후 EXIT\_SUCCESS 넘기며 종료
2. exit 뒤에 인자가 있다면 그 인자를 부모에게 상태값으로 전달 (ex `exit 127`), exit 출력하지 않고 종료
3.  exit 뒤의 숫자 인자가 2개 이상이라면 "exit\n" "bash: exit: too many arguments" 두 줄 출력 후 **쉘 종료 X**

    ```bash
    /Users/daelee/Desktop/42cursus/minishell$ exit 123 456 
    exit
    minishell: exit: too many arguments
    /Users/daelee/Desktop/42cursus/minishell$
    ```
4.  exit 뒤의 인자가 숫자가 아니라면 "bash: exit: 첫 번째 인자: numeric argument required" 출력 후 **쉘 종료**

    ```bash
    /Users/daelee/Desktop/42cursus/minishell$ exit test hello
    minishell: exit: test: numeric argument required
    ✘ daelee@daelee  ~/Desktop/42cursus/minishell   error_daelee ±  (zsh)
    ```
5. 파이프 뒤에 있다면 자식프로세스의 exit이기 때문에 **쉘 종료 X**

#### 4. export

**4.1. 평가 항목**

* Export environment variables, create new ones and replace old ones
* Check them with env

**4.2. 구현한 케이스**

* 명령어가 export 하나면 `print_export(g_envp)`
* 명령어가 `export z` 와 같은 식으로 인자가 하나면 환경변수에 저장되지 않음.
* 명령어가 `export key=value` 일 때 1. 이미 존재하는 환경변수면 free 후 새로 dup 2. 새로운 환경변수면 원래 크기 + 2 짜리 `**new` 생성 1. 기존 환경변수 dup 하고 2. 새 환경변수, NULL 추가 3. 기존 환경변수 free 해주고 `**new`로 대체
* 파이프 뒤에 있다면 자식프로세스의 export이기 때문에 환경변수 생성 X
* key가 숫자만 있으면 안 됨.
  * value에는 숫자가 들어가도 됨.
* 작은, 큰 따옴표 제거하고 환경변수 목록에 추가되어야 함.

#### 5. unset

**5.1. 체크리스트**

* Export environment variables, create new ones and replace old ones
* Use unset to remove some of them
* Check the result with env

**5.2. 구현한 케이스**

* 인자로 들어온 환경변수가 유효한지 검사
  * 문자/숫자/\_로 시작해야함
* 이미 존재하는 환경변수인지 검사
* 파이프 뒤에 있다면 자식프로세스의 unset이기 때문에 환경변수 삭제 X

### 3. PATH 프로그램 명령어 실행

#### 3.1. 구현시 사용한 함수

* **execve(char 실행파일명, char \*옵션, char 환경변수)**
  * 외부 프로그램을 실행시키는 명령어, 실행파일 명은 경로(ls는 /bin/ls)까지 입력해야 하며 정상 실행 시 프로세스가 종료된다. 실패시 프로세스는 유지되며 execve 다음 명령어가 실행이 됨.
  * 자식 프로세스에서 execve를 사용할 것이다.
* **fork()**
  * 자식 프로세스를 생성하는 명령어이다. 반환값으로 부모 프로세스는 자식의 PID값을, 자식 프로세스는 0을 갖게 된다.
* **wait(int \*stauts)**
  * status는 자식 프로세스가 종료될 때의 상태 정보이다. 자식 프로세스가 정상적으로 종료되면 status의 하위 8bit에는 0이 저장되고 상위 8bit에는 exit(인자)의 '인자'가 저장된다. [출처](https://molrayo.tistory.com/entry/Process-wait%EC%97%90-%EA%B4%80%ED%95%9C-%EC%98%88%EC%A0%9C)&#x20;
  * wait() 를 쓰면 자식프로세스가 종료할때까지 해당영역에서 부모프로스쓰가 sleep() 모드로 기다리게 된다. 이는 자식프로세스와 부모프로세스의 동기화를 위한목적으로 부모프로세스가 자식프로세스보다 먼저 종료되어서 자식프로세스가 고아 프로세스(PPID 가 1)인 프로세스가 되는걸 방지하기 위한 목적이다. [출처 및 \&status 예제](https://www.joinc.co.kr/w/man/2/wait)&#x20;
* **stat(const char restrict path, struct stat restrict buf);**
  * 첫 번째 인자로 주어진 경로명에 대해, stat 함수는 그 이름의 파일에 대한 정보들을 담은 구조체(buf)를 돌려준다. 이 때 buf는 char \*가 아니라 stat이라는 구조체라는 점을 주의할 것!
  * 두 번째 인자는 우리가 제공해야 할 구조체의 포인터이다. 이 함수는 그 구조체를 채워주는 역할을 한다.&#x20;
  * **stat 함수를 사용해야하는 가장 큰 이유는 파일의 모든 정보를 알아야 하는 ls -l 명령 때문.** [출처](https://m.blog.naver.com/PostView.nhn?blogId=pjh791010\&logNo=60010481024\&proxyReferer=https:%2F%2Fwww.google.com%2F)
  * 성공 0, 실패 -1 리턴.

#### 3.2. fork 를 하고 exec을 하는 이유

fork()없이 exec()만 사용한다면 부모프로세스로 돌아올 방법이 없는데, fork()를 사용함으로서 병행으로 프로세스 실행 + exec 종료 후 다시 돌아올 프로세스 위치를 기억할 수 있게된다. 아래 그림이 이해에 도움이 된다.

undefined

### 4. 시그널

* 자세한 내용을 [\[minishell\] 3. 시그널(Signal) 처리하기](https://velog.io/@hidaehyunlee/minishell-3.-%EC%8B%9C%EA%B7%B8%EB%84%90Signal-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0)에 정리했습니다.

### 5. 에러메세지

* 자세한 내용을 [\[minishell\] 4. 종료상태와 에러메세지 처리 ](https://velog.io/@hidaehyunlee/minishell-4.-%EC%A2%85%EB%A3%8C%EC%83%81%ED%83%9C%EC%99%80-%EC%97%90%EB%9F%AC%EB%A9%94%EC%84%B8%EC%A7%80-%EC%B2%98%EB%A6%AC)에 정리했습니다.

### 6. 파이프(Pipe) 처리

* 자세한 내용을 [\[minishell\] 5. 파이프(Pipe) 처리](https://velog.io/@hidaehyunlee/minishell-5.-%ED%8C%8C%EC%9D%B4%ED%94%84Pipe-%EC%B2%98%EB%A6%AC)에 정리했습니다.

### 7. 리다이렉션(Redirection) 처리

* 자세한 내용을 [\[minishell\] 6. 리다이렉션(Redirection) 처리](https://velog.io/@hidaehyunlee/minishell-6.-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%EC%85%98Redirection-%EC%B2%98%EB%A6%AC)에 정리했습니다.
