보기 전에 혹시 떠올리셨나요? '아이디어'

 

제가 과거에 정리했던 내용이지만 전부 맞다고 생각이 들진 않아요.

 

본인의 아이디어를 떠올려보고 참고용으로 봐주시면 감사하겠어요.

 


1. prompt

prompt를 만들어주는 방법은 두 가지 방법이 있다.

첫 번째는 터미널 제어 속성을 수정해주는 방식이 있다.

두 번째는 readline을 이용한 방식이 있다.

 

첫 번째 방법은 시도해봤지만 너무 복잡하게 이어져있어서 두 번째 방법으로 구현했다.

 

일단 readline을 brew로 설치해야한다.

그리고 readline이 설치된 경로들을 알고있어야 한다.

컴파일시에 지정을 해줘야 하기 때문이다.

 

readline은 여러가지 변수들과 함수들을 사용할 수 있다.

하지만 함수들은 허용된 것들만 사용가능하고 변수는 따로 extern 해주어야 사용이 가능하다.

일단 허용함수들을 살펴보자.

rl_on_new_line : readline실행 중 다음줄로 이동되었다고 설정해주는 함수이다.

rl_replace_line : readline실행 중 들어온 입력을 첫번째 인자로 다시 설정해주는 함수이다.

rl_redisplay : readline실행 중 다시 readline을 실행할 때 사용하는 함수이다.

이들은 signal중 Ctrl + C를 제어할 때 사용된다.

 

변수들을 확인해보자.

rl_catch_signals : readline 실행중에 signal을 표시할지 안할지 설정하는 변수이다.(default = 1)

 

이제 readline을 알아보자.

readline은 엔터가 입력됬을때 입력된 값을 반환한다.

또한 add_history로 키보드 화살표 위, 아래키를 이용해서 이전 커멘드를 다시 표시할 수 있다.

 

이렇게 readline으로 prompt 끝!


2. tokenizing

tokenizing실행 전에 Error Check를 먼저 실행되어야 한다.

Error Check는 여러가지가 있다.

  1. ', "로 문자열을 열었지만 닫지 않은 경우
  2. '|', ';'를 연속적으로 사용했을 경우
  3. 기타 등등(전부 찾기는 힘들다.)

 

tokenizing은 readline으로 입력된 command를 '|', ';' 기준으로 자르는 과정이다.

  1. 과정중에 연속된 space는 하나의 space로 수정해주어야 한다.
  2. '$'로 시작하는 문자열은 환경변수를 참조해서 변경시켜주어야 한다.
  3. '로 둘러 쌓인 문자열은 수정이 가져와야 한다.(환경변수 참조X, 연속된 space변경X)
  4. "로 둘러 쌓인 문자열은 환경변수 참조하여 수정해준다.(환경변수 참조O, 연속된 space변경X)

위 과정으로 자른 문자열들을 queue에 넣어주어야하기 때문에 node에 넣어줄때 자른 token이 '|'라면 pipe인 것을 확인할 수 있는 변수를 만들어 설정해주는다.

또한, node에 넣을때는 space를 기준으로 넣어주는데 ', " 내부는 취급하지 않는다.

 

tokenizing 끝!


3. 로직

전체적인 로직은 아래와 같다.

  1. 모든것을 포함한 구조체 초기화
  2. 제작자들의 개성을 나타내 줄 logo 출력
  3. inf_loop 시행
    1. readline으로 cmd 입력
    2. cmd의 Error Check
    3. tokenizing
    4. execute
    5. tokenizing된 command_queue 메모리 해제
  4. 모든 구조체 메모리 해제

execute는 single command, multi command로 나눈다.

single command는 builtin function일 때만 자식 프로세스를 만들지 않고 실행된다.

왜냐하면 현재 프로세스의 데이터를 직접 수정, 생성해야하기 때문이다.

그 외는 fork를 이용해 자식 프로세스를 만들고 수행시킨다.


4. redirection

redirection은 총 4가지의 구성이 있다.

> {인자} : {인자}를 STDOUT_FILENO로 지정한다. [{인자}라는 파일을 생성(있다면 새로 만든 것처럼 생성)]

< {인자} : {인자}를 STDIN_FILENO로 지정한다.[{인자}라는 파일을 연다.]

>> {인자} : {인자}를 STDOUT_FILENO로 지정한다. [{인자}라는 파일을 생성(있다면 내부 데이터의 가장 끝을 가리켜야 한다.)]

<< {인자} : {인자}를 stop sign으로 취급하고 stop sign이 나올때까지의 문자열들을 STDIN_FILENO로 지정한다.[새로운 파일 생성해서 쓰고 그 파일을 STDIN_FILENO로 지정했다.][이 과정은 home document라고 칭한다.]

 

여기서 내가 가장 헤메게 된 이유는 하나의 문자열로 생각해서 많이 헤멨다.

이 말이 무슨 뜻이냐면 한꺼번에 처리를 해야한다는 생각이었는데 이는 맞는 말이지만 틀린말이기도 하다.

여러개의 redirection이 온다면 각각 지정하는것은 맞다.

하지만 이를 수행하는 것은 가장 마지막으로 지정한 것이다.

또한, 여러개의 redirection이 있을때 사이사이에 커멘드들이 들어갈 수 있었다.

그렇기에 따로 command를 지정할 수 있는 char **가 필요했다.

 

예시를 들어보면

$> < a > b1 > b2 cat > b3 -e >> b4

위의 풀이는 a를 STDIN_FILENO로 지정하고 b1, b2, b3, b4를 생성하는데 STDOUT_FILENO는 b4로 지정된 것이다.

또한, command는 'cat -e'으로 들어가게 되어 입력으로는 a가 들어가게 된다.

이를 검증하고 싶다면 'cat -e' 만 따로 bash의 입력으로 넣어주면 표준입력 입력할 수 있는 상황이 되는데 그 지점이 a가 되는 것이다.

그리고 만약 b4가 이미 존재하고 있다면 데이터의 가장 뒤에 a를 insert해주는 상황이 되는 것이다.

 

요약

command : cat -e

생성된 파일 : b1, b2, b3, b4

STDIN_FILENO : a라는 파일

STDOUT_FILENO : b4라는 파일

 


5. pipe

위에서 우리는 node당 pipe로 잘렸는지 확인하는 변수를 만들어주었다.

또한, 나는 node에 pipe[2]를 따로 지정해주었다.

pipe로 잘렸다면 사용이 되지만 pipe로 잘리지 않았다면 사용이 안된다.

 

pipe는 multi command 상황에서 사용된다.

3가지 경우의 수가 있다.

  1. 시작전 pipe를 열어주는 경우
  2. 자식의 pipe 관리해주는 경우
  3. 부모의 pipe 관리해주는 경우

 

시작전엔 pipe를 열어주는 것 뿐이다.

자식의 pipe관리는 이전 node의 pipe 상태를 보고 pipe[1]를 STDIN_FILENO로 넣어주고,

현재 node의 pipe상태를 보고 pipe[0]를 STDOUT_FILENO로 넣어주는 것을 관리해준다.

부모의 pipe관리는 현재와 이전 노드의 파이프 상태를 보고 pipe를 닫아주는 관리해준다.

 

즉, 이렇게 관리가 된다면 파이프로 연결되었던 커멘드들을 연결시켜줄 수 있는 상황이 되는 것이다.


6. builtin_function

 

cd

  1. cd or cd ~ -> $HOME참조
  2. cd {file_name} -> file_name이 없으면 "minishell: cd: {file_name}: No such file or directory\n"
  3. cd - -> $OLDPWD참조 -> 만약 없으면 "minishell: cd: OLDPWD not set\n"
  4. cd는 상대/절대 경로를 참조할 수 있다.
  5. cd는 인자로 2개 이상 들어온다면 첫번째 경로로 간다.
  6. cd로 움직였을경우 PWD, OLDPWD 모두 바뀌어야한다.
  7. cd로 처음 움직였을 때, OLDPWD가 없다면 생성해주어야 한다.
  8. 성공시 exit_status = 0, 실패시 exit_status = 1

env

  1. env는 입력되는 인자가 없어야한다. -> "env: {첫번째 인자}: No such file or directory"
  2. env는 node->is_ok가 1인 것만 출력한다. [node->is_ok는 '='의 존재 유무이다. 또한, $?는 -1로 고정]
  3. 성공시 exit_status = 0, 실패시 exit_status = 1

export

  1. export는 인자없이 들어왔을 때, Ascii code 오름차순 출력한다.[출력시 앞에 "declare -x "를 먼저 출력한다.]\
  2. export는 인자없이 들어왔을 때, node->is_ok가 -1이 아닌 것만 출력한다.[node->is_ok 가 0라면 key만 출력하고 '='는 출력X]
  3. export는 인자의 첫 문자가  알파벳 혹은 '_'이 아니면 "minishell: export: `인자': not a valid identifier"
  4. export는 인자의 첫 문자를 제외하고 숫자가 들어갈 수 있다.['_'이외의 특수문자가 들어가면 3번의 오류출력을 따른다.]
  5. export는 인자가 '_' 혼자 들어왔을 때 무시한다.[환경변수를 나타내지도 추가하지도 않음]
  6. export는 인자들은 반드시 나눠져있다.[node->command는 'char **' 이다.]
  7. export는 환경 변수를 추가할때 '='가 있는 인자는 node->is_ok=1 아니라면 node->is_ok=0이다.
  8. export는 중복되는 변수가 있다면 수정해준다.
  9. 성공시 exit_status = 0, 실패시 exit_status = 1

unset

  1. unset은 인자없이 들어왔을 때 행동없이 끝난다.
  2. unset은 인자가 여러개 들어올 수 있다.
  3. unset은 인자가 환경변수에 없다면 행동없이 끝난다.
  4. unset는 인자의 첫 문자가  알파벳 혹은 '_'이 아니면 "minishell: unset: `인자': not a valid identifier"
  5. unset는 인자의 첫 문자를 제외하고 숫자가 들어갈 수 있다.['_'이외의 특수문자가 들어가면 3번의 오류출력을 따른다.]
  6. 성공시 exit_status = 0, 실패시 exit_status = 1

echo

  1. echo는 인자없이 들어왔을 때 개행출력.
  2. echo는 인자가 여러개 들어올 수 있다.
  3. 내 생각엔 그냥 출력하면 될듯?[이미 tokenizing에서 "와 '구분을 전부 해놨음]
  4. exit_status = 0;

exit

  1. exit는 인자가 없으면 0
  2. exit는 인자는 256으로 나눈 나머지가 exit_status
  3. exit {인자} -> 인자가 숫자가 아니면 "minishell: exit: {인자}: numeric argument required" [exit_status = 255]
  4. exit는 여러개의 인자가 들어오면 "minishell: exit: too many arguments" [exit_status = 1]
  5. 3번이 4번보다 우선순위이다.
  6. exit는 single command일때 에러가 나도 exit를 출력하고 종료된다.[multi command일때는 에러만 출력한다.]
  7. 음수가 들어올 수 있다. 음수일 땐 256을 뺀 값이다.
  8. exit는 인자의 범위가 long long의 범위이다.[넘어가면 3번의 결과]

 

혼자 정리한 builtin_function은 여기까지지만 아직 찾을것이 많은 것 같다.

'42일기' 카테고리의 다른 글

CPP [OOP 상속성]  (0) 2022.02.15
MINIRT(1)  (0) 2022.01.12
python Dict, set 활용  (0) 2021.10.15
Philosophers  (2) 2021.07.09
Push_swap  (0) 2021.06.24

+ Recent posts