[42] Minishell (1)

kihongsi·2022년 2월 7일
0

42seoul

목록 보기
1/1

bash처럼 실행되는 명령프롬프트를 구현하는 과제이다.
The objective of this project is to create a simple shell, my own bash.

The existence of shells is linked to the very existence of IT. At the time, all coders agreed that communicating with a computer using aligned 1/0 switches was seriously irritating. It was only logical that they came up with the idea to communicate with a computer using interactive lines of commands in a language somewhat close to english.
쉘은 IT의 태초부터 함께했습니다. 그 당시, 모든 개발자들은 1과 0으로만 이루어진 스위치로 컴퓨터와 통신하는 것은 굉장히 성가시다는 점에 동의했어요. 그리고 영어와 비슷한 언어로 작성된 명령어의 줄을 이용해 컴퓨터와 소통한다는 아이디어를 떠올린 것도 당연한 수순이었죠.
With Minishell, you’ll be able to travel through time and come back to problems people faced when Windows didn’t exist.
Minishell과 함께라면, Windows가 존재하지 않았을 시절 사람들이 겪었던 문제를 마주해볼 수 있을 겁니다.


과제 설명

Your shell should:
당신의 쉘은:

  • Not interpret unclosed quotes or unspecified special characters like \ or ;.
    닫히지 않은 따옴표나 특정되지 않은 특수문자 (\나 ; 등...) 을 해석하지 않아야 합니다.

  • Not use more than one global variable, think about it and be ready to explain why you do it.
    전역변수는 한 개 이상을 사용할 수 없으며, 왜 전역변수를 사용했는지 깊게 생각해 보고 그 이유를 설명할 수 있어야 합니다.

  • Show a prompt when waiting for a new command.
    새로운 명령어를 입력할 수 있는 프롬프트를 보여줘야 합니다.

  • Have a working History.
    작업 히스토리를 갖고 있어야 합니다.

  • Search and launch the right executable (based on the PATH variable or by using relative or absolute path)
    (PATH 변수나 상대, 절대 경로를 활용하여) 올바른 실행 파일을 찾아 실행할 수 있어야 합니다.

  • It must implement the builtins:
    다음의 내장 기능을 실행할 수 있어야 합니다:

    • echo with option -n
      -n 옵션을 사용할 수 있는 echo

    • cd with only a relative or absolute path
      오직 상대 또는 절대경로만 사용하는 cd

    • pwd with no options
      옵션이 없는 pwd

    • export with no options
      옵션이 없는 export

    • unset with no options
      옵션이 없는 unset

    • env with no options or arguments
      옵션이나 인자값이 없는 env

    • exit with no options
      옵션이 없는 exit

  • ’ inhibit all interpretation of a sequence of characters.
    '는 일련의 문자열에 대한 해석을 금지합니다.

  • " inhibit all interpretation of a sequence of characters except for $.
    "는 $를 제외한 모든 문자열에 대한 해석을 금지합니다.

  • Redirections:
    리다이렉션:

    • < should redirect input.
      <는 입력을 리다이렉션 하여야 합니다

    • > should redirect output.
      >는 출력을 리다이렉션 하여야 합니다

    • << read input from the current source until a line containing only the delimiter is seen. it doesn’t need to update history!
      <<는 현재 소스에서 구분자를 포함한 줄을 만나기 전까지 입력값을 읽어들입니다. 기록을 업데이트할 필요는 없습니다!

    • >> should redirect output with append mode.
      >>는 출력을 추가 모드로 리다이렉션합니다.

  • Pipes | The output of each command in the pipeline is connected via a pipe to the input of the next command.
    파이프 | : 각 파이프라인마다 명령어의 출력값은 파이프로 연결되어 다음 명령어의 입력값으로 들어가야 합니다.

  • Environment variables ($ followed by characters) should expand to their values.
    환경 변수 ($ 다음에 문자열이 오는 형식) 은 그들의 값으로 확장되어야 합니다.

  • $? should expands to the exit status of the most recently executed foreground pipeline.
    $?는 가장 최근에 실행한 포그라운드 파이프라인의 종료 상태를 확장하여야 합니다

  • ctrl-C, ctrl-D and ctrl-\ should have the same result as in bash.
    ctrl-C, ctrl-D, ctrl-\ 는 bash와 동일하게 동작하여야 합니다.

  • When interactive:
    상호작용이 가능할 때:

    • ctrl-C print a new prompt on a newline.
      ctrl-C는 새로운 줄에 새로운 프롬프트를 출력합니다

    • ctrl-D exit the shell.
      ctrl-D는 쉘을 종료합니다.

    • ctrl-\ do nothing.
      ctrl-\은 아무런 동작도 하지 않습니다.




사용 가능 함수

readline

#include <readline/readline.h>

char *readline(const char *str)

문자열을 입력받아 저장하고 메모리 주소를 반환한다.
malloc에 의해 할당받기 때문에 free 해야 한다.

rl_on_new_line

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

int		 rl_on_new_line(void);

rl_replace_line

readline 실행 중 현재까지 들어온 입력을 첫 번째 인자 str으로 대체해주는 함수
(입력 버퍼 비워줌)

void		 rl_replace_line(char *str, int);

GNU 라이브러리에만 들어있는 함수라고 한다.

rl_redisplay

readline 실행 중 다시 readline을 실행할 때 사용하는 함수

void		 rl_redisplay(void);

add_history

#include <readline/history.h>

add_history(const char *str)

str을 history list의 맨 끝에 위치시킨다.
최대 개수를 넘을 경우, 오래된 history는 삭제된다.

execve

int execve(const char *path, char const **argv, char const **envp)
  • path : 지정한 절대경로명의 파일을 실행함
  • argv : 인자(마지막은 null)
  • envp : 환경변수 문자열 배열리스트(마지막은 null)

wait, waitpid, wait3, wait4

자식 프로세를 생성했을 때, 부모와 자식 중 먼저 실행이 끝난 프로세스는 종료되고, 이 때 상태를 회수해주지 않으면 좀비 프로세스가 된다. 이를 방지하기 위해 wait계열 함수를 통해 부모와 자식 프로세스를 동기화시켜야 한다.

wait3

pid_t wait3(int *statloc, int options, struct rusage *rusage)

모든 자식 프로세스가 종료되는 것을 기다리며, 종료된 프로세스의 상태와 자원 사용량을 알려주는 정보

  • statloc : 자식 프로세스의 종료 상태
    WIFEXITED(status) 자식 프로세스가 정상적으로 종료된 경우 true
    WEXITESTATUS(status) exit()의 인자에서 하위 8비트 값을 리턴
    WIFSIGNALED(status) 자식 프로세스가 시그널을 받아 비정상적으로 종료된 경우 true
    WIFTERMSIG(status) 시그널 번호를 리턴
    WIFCOREDUMP(status) 코어 파일이 생성된 경우 true
    WIFSTOPPED(status) 현재 중지 상태이면 true
    WSTOPSIG(status) 실행을 중단시킨 시그널 번호를 리턴
    WIFCONTINUED(status) 작업 제어 중지 이후 실행이 재개되었으면 true
  • rusage : 자식 프로세스의 리소스 사용량에 관한 정보

wait4

pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage)

pid에 해당하는 프로세스만 기다림

signal

시그널을 처리하는 함수이다.

void	(*signal(int signum, void (*handler)(int)))(int);
  • signum : 시그널 type
    ex) SIGINT : ^C (터미널 인터럽트), SIGQUIT : ^\, SIGKILL : ^D(프로세스 종료)

  • handler : 특정 시그널이 발생했을 때 실행시킬 함수의 포인터를 넣어줌. 해당 함수는 int형 변수를 매개변수로 받음.

kill

프로세스에 시그널을 전공하여 중단한다.

int kill(pid_t pid, int sig)
  • sig : signal 번호
    양수 : 지정한 process id에만 시그널 전송
    0 : 함수를 호출하는 프로세스와 같은 그룹에 있는 모든 프로세스에 시그널 전송
    -1 : 함수를 호출하는 프로세스가 전송할 수 있는 권한을 가진 모든 프로세스에 시그널 전송
    음수 : 첫번째 pid의 절대값 프로세스 그룹에 속하는 모든 프로세스에 시그널 전송

getcwd

현재 작업중인 디렉토리의 절대 경로를 buf에 복사하고 포인터를 리턴한다.

#include <unistd.h>

char *getcwd(char *buf, size_t size)
  • size : 버퍼의 크기
    성공 시 현재 작업 디렉토리, 실패 시 null 포인터 리턴

chdir

작업 디렉토리를 변경한다.

#include <unistd.h>

int chdir(const char *path)

절대 혹은 상대경로 둘 다 사용가능

stat

파일의 상태를 입력받음.

int     stat(const char *, struct stat *);

struct stat 구조체 변수의 주소값을 넣어주면 파일의 정보를 담아줌.
몇가지 매크로를 사용하여 상태를 조회할 수 있음

  • ex) S_ISDIR(stat.st_mode);
    해당 파일이 디렉토리라면 non-zero value 리턴, 디렉토리가 아닐 경우 0 리턴

파일을 삭제하는 함수

int	 unlink(const char *);

정확히 말하면 파일의 하드링크의 이름과 하드링크가 참조하는 count를 감소시켜 디스크 상에서 사용할 수 없는 상태로 만든다고 하는데.. 실질적으로 삭제하는 것.
삭제할 파일명을 문자열로 넣어주면 된다.

strerror

char	*strerror(int __errnum);

<errno.h>의 errno를 매개변수로 넘겨주면 해당 errno에 해당하는 오류 내용을 반환해준다.

tcgetattr

터미널 세팅값을 얻어오는 함수

int     tcgetattr(int, struct termios *);

termios 구조체를 넘겨주면 현재 터미널 설정값을 담아줌.

표준입력으로 들어오는 Ctrl + d Handling
해당 글을 참고하여 코드를 작성하였다.

tcsetattr

세팅을 바꿔주고 tcsetattr함수를 사용해 설정값을 적용시킨다.

int     tcsetattr(int, int, const struct termios *);
  • 사용 예시
struct termios	term;

tcgetattr(STDIN_FILENO, &term); // 현재 정보 받아옴
term.c_lflag &= ~(ECHOCTL);	// 제어문자 반향 off
term.c_cc[VMIN] = 1;	// minimum number of bytes in input queue
term.c_cc[VTIME] = 0;	// how long to wait
tcsetattr(STDIN_FILENO, TCSANOW, &term); // 설정값 업뎃 🤗



이외에도 getenv, isatty, ioctl 등등 여러가지 함수를 사용할 수 있는데, 일단 내가 사용한 함수는 여기까지이다.

1개의 댓글