Minishell

chez_bono·2022년 3월 26일
0

42cursus

목록 보기
8/17
post-thumbnail

🔗 더 깔끔한 노션 정리

서브젝트

필수

  • 닫히지 않은 따옴표나 \;처럼 정의되지 않은 특수문자는 처리하지 않음
  • 전역변수는 하나만 사용 가능
    사용하는 경우 왜 사용했는지 설명할 수 있어야 함
  • 새 명령어를 입력받을 때는 프롬프트를 띄울 것
  • History가 남아야 함 (키보드 화살표 위, 아래 키를 이용해서 이전 명령어 다시 실행하는 기능)
  • PATH 변수나 상대, 절대 경로에 따라 올바른 명령어를 검색하고 실행해야 함
  • ' 안의 내용은 해석하지 않음 (문자 그대로 받아들임)
  • " 안의 내용은 $를 제외하고 해석하지 않음 (환경변수만 해석하고 나머지는 문자 그대로)
  • 리다이렉션 구현
    • <는 인풋
    • >는 아웃풋
    • "<<"는 delimiter가 나올 때까지 현재 소스를 인풋으로 사용 (히스토리 업데이트 필요 없음)
    • ">>"는 append 모드로 아웃풋
  • 파이프(|): 각 명령어의 아웃풋이 파이프라인을 통해 다음 명령어의 인풋으로 연결되어야 함
  • 환경 변수($어쩌고)는 값으로 대체되어야 함
  • $?는 가장 최근에 실행된 파이프라인의 exit code로 대체되어야 함
  • ctrl-C ctrl-D ctrl-\는 배시에서처럼 작동해야 함
    • ctrl-C: 새로운 라인에서 새 prompt 출력
    • ctrl-D: 쉘 종료
    • ctrl-\: 아무것도 하지 않음

보너스

  • 우선순위 구현 (&&, || + 괄호)
  • 와일드카드(*)가 현재 작업 중인 디렉토리에서 작동해야 함

구현해야 할 빌트인

  • echo + -n
  • cd +  절대, 상대 경로
  • pwd
  • export
  • unset
  • env
  • exit

사용 가능 함수

printf malloc free

write open read close fork wait waitpid unlink execve dup dup2 pipe strerror exit
👉 pipex 참고

🤯 readline.h

전체적으로 잘 모르겠다... 일단 써봐야 할 듯

#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>

readline 설치

  1. brew install readline
  2. brew info readline으로 경로 확인해서 라이브러리와 헤더 인클루드

readline 라이브러리를 사용하려면 컴파일할 때 -lreadline 옵션을 줘야 함 (참고)

readline()

readline(3) - Linux manual page
minishell과 readline
터미널에 프롬프트를 띄우고 라인을 읽어들여서 리턴

char *readline(const char *prompt);
  • 인자로는 프롬프트에 띄울 문자열 전달 (예: $>)
    NULL이면 아무것도 하지 않음?
  • 리턴 값은 개행 문자(\n)가 제거된 상태이며, 따로 free() 해주어야 함
    빈 줄이 입력되면 빈 문자열 리턴
    EOF를 만났고 라인이 비어 있으면 NULL 리턴
    👉 gnl처럼 동작하는 듯
  • 문자열을 입력받기 전까지 다음 코드로 진행되지 않음

rl_on_new_line()

Tell the update functions that we have moved onto a new (empty) line, usually after outputting a newline.
업데이트 함수들에게 새 라인을 입력받겠다고 알려주는 함수?

int rl_on_new_line(void);
  • 리턴값은 뭘까...

rl_redisplay()를 실행할 때 필요하다고 함

rl_replace_line()

Replace the contents of rl_line_buffer with text. The point and mark are preserved, if possible. If clear_undo is non-zero, the undo list associated with the current line is cleared.
현재까지 입력된 프롬프트의 문자열을 대체하는 함수

void rl_replace_line(const char *text, int clear_undo);
  • rl_line_buffer에 있던 문자열이 인자로 전달된 text로 대체됨
  • clear_undo0이 아니면 현재 라인과 연관된 언두 리스트가 초기화..?

ctrl-C처럼 입력된 내용이 화면에 그대로 출력되지는 않지만 새로운 프롬프트를 띄워줘야할 때 rl_replace_line("", 1);처럼 사용하면 새 프롬프트를 띄울 자리를 깨끗하게 비워주게 된다고 함

rl_redisplay()

Change what's displayed on the screen to reflect the current contents of rl_line_buffer.
화면에 출력된 내용을 현재 rl_line_buffer에 들어 있는 내용으로 대체

void rl_redisplay(void);

rl_replace_line() 이후에 사용해서 새 프롬프트(readline()의 인자로 전달한 문자열)를 띄울 때 사용한다고 함

add_history()

Place string at the end of the history list. The associated data field (if any) is set to NULL.
히스토리 추가

void add_history (char *string)
  • string을 히스토리 리스트에 추가
  • 히스토리 리스트는 HIST_ENTRY **the_history_list;로 선언되며, HIST_ENTRY 구조체는 아래와 같음
typedef struct _hist_entry {
  char *line;
  char *data;
} HIST_ENTRY;
  • 히스토리 리스트는 아래와 같은 구조체로 관리됨
typedef struct _hist_state {
  HIST_ENTRY  **entries;         /* Pointer to the entries themselves. */
  int         offset;            /* The location pointer within this array. */
  int         length;            /* Number of elements within this array. */
  int         size;              /* Number of slots allocated to this array. */
  int         flags;
}               HISTORY_STATE;

프롬프트가 열린 상태에서 키보드 위, 아래 방향키를 이용해서 히스토리의 문자열들을 불러올 수 있음

sys/wait.h

wait3()

자식 프로세스의 종료를 기다리며, 종료된 프로세스의 상태와 자원 사용량을 알려줌

pid_t wait3(int *stat_loc, int options, struct rusage *rusage);
  • stat_loc: 자식 프로세스의 종료 상태를 나타내는 정보
  • options: 프로세스의 종료 상태 체크 시 전달할 옵션 (필요없으면 0)
  • rusage: 자식 프로세스의 리소스 사용량에 대한 정보 저장 (필요없으면 0)
  • 리턴값
    • 0보다 큰 값: 상태가 변경된 자식 프로세스의 id
    • 0: optionsWNOHANG이고 상태 변경이 없는 경우
    • -1: 오류

stactloc 매크로

매크로설명
WIFEXITED(status)자식 프로세스가 exit()이나 main()의 리턴으로 정상 종료된 경우 true
👉 true인 경우WEXITESTATUS(status)exit()의 인자에서 하위 8비트 값을 리턴
WIFSIGNALED(status)자식 프로세스가 시그널을 받아 비정상적으로 종료된 경우 true
👉 true인 경우WIFTERMSIG(status)시그널 번호를 리턴
WIFCOREDUMP(status)코어 파일이 생성된 경우 true
WIFSTOPPED(status)종료가 아니라 정지 시그널이 발생한 경우 true
👉 true인 경우WSTOPSIG(status)실행을 중단시킨 시그널 번호를 리턴
WIFCONTINUED(status)작업 제어 중지 이후 실행이 재개되었으면 true

options 매크로

매크로설명
WCONTINUED중지되었다가 실행을 재개한 이후 상태가 아직 보고되지 않은 자식도 리턴
WNOHANG자식 프로세스가 종료되어 있지 않으면 바로 리턴
(이 옵션을 주지 않으면 자식 프로세스가 종료될 때까지 블록 상태가 됨)
WUNTRACED중지되었으나 그 상태가 아직 보고되지 않은 자식도 리턴
(종료 뿐만 아니라 중지 상태도 찾기)

wait4()

특정 자식 프로세스의 종료를 기다리며, 종료된 프로세스의 상태와 자원 사용량을 알려줌

pid_t wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage);
  • pid: 대기할 자식 프로세스 지정
    • -1보다 작은 값: 해당 값의 절대값과 그룹 id가 일치하는 아무 자식 프로세스
    • -1: 아무 자식 프로세스
    • 0: 부모 프로세스와 같은 그룹에 있는 아무 자식 프로세스
    • 0보다 큰 값: 해당 값의 pid를 가진 자식 프로세스

signal.h

signal()

시그널 제어

void (*signal(int sig, void (*func)(int));)(int);
  • sig: 시그널 번호
  • (*func)(int): 시그널을 처리할 핸들러
    • 기본 핸들러로 돌려놓고 싶으면 SIG_DFL을 전달
    • 시그널을 무시하고 싶으면 SIG_IGN 전달
  • 성공 시 이전에 설정된 시그널 핸들러인 void *()(int), 실패 시 SIG_ERR 리턴
  • SIGKILLSIGSTOP은 제어할 수 없음
  • 실행 이후에 자식 프로세스를 포크하면 자식 프로세스에 설정이 상속됨
  • execve()로 실행된 프로세스에서는 시그널 설정이 리셋되지만, 무시된 시그널은 계속 무시됨
  • SIGCHLD에 대해 SIG_IGN을 지정하면 자식 프로세스가 종료될 때 좀비 프로세스가 생성되지 않음
    자식 프로세스의 종료 코드는 무시됨
    만약 부모 프로세스가 자식 프로세스를 wait()하고 있었다면, 모든 자식 프로세스가 종료될 때까지 실행이 연기되었다가 -1을 리턴

MacOS에 정의된 시그널

시그널

시그널은 프로세스가 멈추거나 다시 재개되거나 할 때 발생할 수 있음
시그널에는 두 종류가 존재
1. 프로세스 종료를 야기하는 것
2. 그렇지 않은 것
1번에는 회복 불가능한 오류가 발생하거나 인터럽트 문자(ctrl - C 등)를 입력하는 경우가 존재

kill()

프로세스에 시그널 전송

int kill(pid_t pid, int sig);
  • sigpid에 전송
    • sig0을 보내면 아무 시그널도 전송되지 않지만, 이 함수의 에러 체크 동작을 테스트할 수 있음 (pid의 유효성 검사)
    • pidwait()에서처럼 지정 가능
  • 성공 시 0, 실패 시 -1 리턴

sys/stat.h

struct stat

struct stat {
         dev_t    st_dev;    /* device inode resides on */
         ino_t    st_ino;    /* inode's number */
         mode_t   st_mode;   /* 파일 종류 및 접근 권한 */
         nlink_t  st_nlink;  /* number of hard links to the file */
         uid_t    st_uid;    /* user-id of owner */
         gid_t    st_gid;    /* group-id of owner */
         dev_t    st_rdev;   /* device type, for special file inode */
         struct timespec st_atimespec;  /* time of last access */
         struct timespec st_mtimespec;  /* time of last data modification */
         struct timespec st_ctimespec;  /* time of last file status change */
         off_t    st_size;   /* file size, in bytes */
         quad_t   st_blocks; /* blocks allocated for file */
         u_long   st_blksize;/* optimal file sys I/O ops blocksize */
         u_long   st_flags;  /* user defined flags for file */
         u_long   st_gen;    /* file generation number */
     };
  • st_mode는 아래와 같이 정의되어 있어서 비트 & 연산으로 여부 확인 가능
// 파일 유형 전체의 비트 or 연산 값
#define S_IFMT 0170000    /* type of file */
// 파일 유형
#define S_IFIFO  0010000  /* named pipe (fifo) */
#define S_IFCHR  0020000  /* character special */
#define S_IFDIR  0040000  /* directory */
#define S_IFBLK  0060000  /* block special */
#define S_IFREG  0100000  /* regular */
#define S_IFLNK  0120000  /* symbolic link */
#define S_IFSOCK 0140000  /* socket */
#define S_IFWHT  0160000  /* whiteout */
// 특수 권한 설정 비트
#define S_ISUID 0004000  /* set user id on execution */
#define S_ISGID 0002000  /* set group id on execution */
#define S_ISVTX 0001000  /* save swapped text even after use */
// 접근 권한 값
#define S_IRUSR 0000400  /* read permission, owner */
#define S_IWUSR 0000200  /* write permission, owner */
#define S_IXUSR 0000100  /* execute/search permission, owner */

stat()

파일 상태 얻기 (파일 크기, 권한, 생성일시, 최종 변경일 등)

int stat(const char *restrict path, struct stat *restrict buf);
  • path: 상태를 얻을 파일의 경로
    • 심볼릭 링크를 넘기면 원본 파일의 정보를 가져옴
  • buf: 상태 정보를 저장할 구조체 포인터
  • 성공 시 0, 실패 시 -1 리턴

lstat()

파일 상태 얻기

int lstat(const char *restrict path, struct stat *restrict buf);
  • stat()과 다르게 심볼릭 링크는 그 링크 자체의 정보를 얻어옴

fstat()

파일 상태 얻기

int fstat(int fildes, struct stat *buf);
  • fildes: 파일 디스크립터

dirent.h

opendir()

디렉토리 열기

DIR *opendir(const char *filename);
  • filename: 열 디렉토리 이름
  • 성공 시 디렉토리 정보를 담은 구조체 포인터 리턴
    실패 시 NULL 리턴

readdir()

디렉토리 내의 파일과 디렉토리 정보 얻기

struct dirent *readdir(DIR *dirp);
  • dirp: opendir()에서 리턴받은 포인터
  • 성공 시 디렉토리 내 파일 또는 디렉토리 정보를 담은 구조체 포인터 리턴
    실패 시(더이상 읽어들일 정보가 없으면) NULL 리턴
  • 디렉토리 내 파일 또는 디렉토리 정보를 순서대로 읽어들임
    읽어들이는 순서는 정의되지 않음
  • struct dirent는 아래와 같이 정의되어 있음
struct dirent
{
	long		    d_ino;		
	unsigned short	d_reclen;	
	unsigned short	d_namlen;	/* Length of name in d_name. */
	char		    d_name[260]; /* [FILENAME_MAX] */ /* File name. */
};

closedir()

디렉토리 닫기

int closedir(DIR *dirp);
  • dirp: opendir()에서 리턴받은 포인터
  • 성공 시 0, 실패 시 -1 리턴

sys/errono.h

errno

마지막으로 발생한 오류 번호

extern int errno

🔗 오류 목록

unistd.h

getcwd()

현재 작업 중인 디렉토리의 절대 경로 얻기

char *getcwd(char *buf, size_t size);
  • buf: 현재 작업 디렉토리를 저장할 공간이 할당된 포인터
    • NULL인 경우 필요한 공간이 할당되어 리턴되며, size는 무시됨
      나중에 free()해야 함
  • size: buf에 할당된 메모리의 크기 (바이트 단위)
    • 문자열의 크기보다 작으면 오류
  • 성공 시 경로 문자열을 가리키는 포인터 리턴, 실패 시 NULL 리턴

chdir()

작업 디렉토리 변경

int chdir(const char *path);
  • path: 변경할 디렉토리의 상대 또는 절대 경로
  • 성공 시 0, 실패 시 -1 리턴
  • 프로세스는 해당 디렉토리에 대한 실행(탐색, execute) 권한이 있어야 함

tty

teletypewriter
컴퓨터와 상호작용을 위한 디바이스로, OS에서 제공하는 가상 char *
ttyname(int fd);콘솔

isatty()

파일 디스크립터가 터미널인지 확인

int isatty(int fd);
  • fd: 파일 디스크립터
  • 맞으면 1, 아니면 0 리턴

ttyname()

터미널 이름 얻기

char *ttyname(int fd);
  • fd: 파일 디스크립터
  • 성공 시 터미널 이름, 실패 시 NULL 리턴

ttyslot()

현재 사용자 터미널의 슬롯 찾기

int ttyslot(void);
  • 성공 시 슬롯 번호(fd)
    실패 시 0 또는 -1 (시스템 차이가 있다고 함)

tty 관련 명령어 사용 예시

int tty_fd = ttyslot();
if (isatty(tty_fd))
	printf("valid fd\n");
char *tty_name = ttyname(fd);
printf("tty_name: %s\n", tty_name);

sys/ioctl.h

ioctl()

ioctl() 디바이스 제어
[Kernel] Linux kernel (3) - ioctl function and make test program
in-out control
디바이스(터미널 등) 컨트롤

int ioctl(int fildes, unsigned long request, ...);
  • fileds: 열려 있는 파일 디스크립터
  • request: 디바이스에 전달할 명령 (sys/ioctl.h에 정의된 매크로를 수행할 수도 있음)
  • ...: request 수행 시 필요한 인자가 있다면 뒤에 원하는대로 전달
  • 실패 시 -1 리턴
  • read()write()로만 해결되지 않는 제어를 하거나 디바이스의 상태를 얻기 위해 사용
  • tty 디바이스를 제어할 때 사용한다고 함

stdlib.h

getenv()

환경변수 값 구하기

char *getenv(const char *name);
  • name: 환경변수명
  • 성공 시 환경변수 값 리턴
    실패 시(존재하지 않는 경우) NULL 리턴

termios.h

termios 구조체
https://blog.naver.com/choi125496/130034222760

struct termios

TERMinal In Out interfaces Structures

struct termios
{
    tcflag_t c_iflag;    /* input flags */
    tcflag_t c_oflag;    /* output flags */
    tcflag_t c_cflag;    /* control flags */
    tcflag_t c_lflag;    /* local flags */
    cc_t     c_cc[NCCS]; /* control chars */
    speed_t  c_ispeed;   /* input speed */
    speed_t  c_ospeed;   /* output speed */
};

tcsetattr()

터미널 속성 얻기

int tcgetattr(int fildes, struct termios *termios_p);
  • fildes: 열려 있는 파일 디스크립터
  • termios_p: 터미널의 정보를 저장할 구조체 포인터
  • 성공 시 0, 실패 시 -1 리턴
  • 해당 파일 디스크립터가 열린 터미널의 정보를 가져옴

tcgetattr()

터미널 속성 설정

int tcsetattr(int fildes, int optional_actions, const struct termios *termios_p);
  • fildes: 열려 있는 파일 디스크립터
  • optional_actions: 아래의 매크로들로 설정할 수 있음
    • TCSANOW: 즉시 변경
    • TCSADRAIN: 모든 아웃풋이 파일 디스크립터에 쓰이고 난 뒤 변경 (터미널 속성 변경이 아웃풋에 영향을 주는 경우 사용)
    • TCSAFLUSH: 모든 아웃풋이 파일 디스크립터에 쓰이고 난 뒤 변경 (받았지만 아직 쓰지 않은 인풋은 무시)
    • TCSASOFT: 이 값을 다른 속성에 or 연산하면 c_cflag, c_ispee, c_ospeed 필드가 무시됨
  • termios_p: 터미널의 정보를 세팅할 구조체 포인터
  • 성공 시 0, 실패 시 -1 리턴
  • 아웃풋 스피드로 0을 설정하면 터미널 연결을 종료함
  • 인풋 스피드로 0을 설정하면 아웃풋 스피드와 같은 값으로 설정됨

curses.h + term.h

https://www.gnu.org/software/termutils/manual/termcap-1.3/html_chapter/termcap_2.html
http://www.xevious7.com/linux/lpg_8_2_1.html
함수와 커서제어
[UNIX] termcap 라이브러리를 이용한 커서 제어
커서 제어를 위한 termcap(terminal capability) 라이브러리

termcap 라이브러리를 사용하려면 컴파일할 때 -Incurses 옵션을 줘야 함

terminfo database

터미널 정보를 가진 데이터베이스
터미널의 기능(capabilities)에 대한 정보들을 담고 있음

tgetent()

커서를 제어할 엔트리 설정

int tgetent(char *bp, const char *name);
  • bp: 설정된 엔트리를 저장할 버퍼
    • Unix 버전 termcap을 쓰면 필수라는데, 아니기 때문에 필요없으니 NULL로 설정하면 된다고 함
    • NULL 전달 시 알아서 필요한 공간을 할당하는데, 나중에 tgetent()를 다시 호출하면 메모리를 해제함
  • name: 엔트리를 설정할 장치 이름
  • 리턴 값
    • 성공 시 1
    • 엔트리가 없으면 0
    • terminfo 데이터베이스가 없으면 -1

getenv()TERM을 조회하면 현재 장치 이름을 받을 수 있다고 함

아래의 함수들을 사용할 때 터미널 정보를 넘겨줄 필요는 없음. 가장 최근에 tgetent()를 실행한 정보가 자동으로 사용됨

Capability values can be numeric, boolean (capability is either present or absent) or strings. Any particular capability always has the same value type; for example, co always has a numeric value, while am (automatic wrap at margin) is always a flag, and cm (cursor motion command) always has a string value. The documentation of each capability says which type of value it has.
👉 사용할 기능(capability)에 따라 아래의 tget 함수를 골라 쓰면 되는 듯

tgetflag(), tgetnum(), tgetstr()에 전달된 문자열의 첫 두 문자만 유효하게 해석된다고 함

tgetflag()

기능에 대한 불리언 값 받기

int tgetflag(char *id);
  • id: 기능 코드
  • 해당 기능이 터미널에 있으면 1 리턴
    아니면 0 리턴

tgetnum()

기능에 대한 숫자 값 받기

int tgetnum(char *id);
  • id: 기능 코드
  • 해당 기능이 터미널에 있으면 음수가 아닌 수 리턴
    아니면 -1 리턴

tgetstr()

기능에 대한 문자열 값 받기

char *tgetstr(char *id, char **area);
  • id: 기능 코드
  • area: 얻은 문자열의 복사본을 저장할 버퍼 (Unix 버전인 경우에는 유일한 방법이라 필수)
    • NULL을 전달하면 필요한 만큼 공간을 할당해서 리턴 (나중에 free()해야 함)
  • 해당 기능이 터미널에 있으면 얻은 문자열 리턴
    아니면 NULL 리턴
  • 여기서 얻은 문자열을 tputs()로 실행

tgoto()

커서 움직임을 제어

char *tgoto(const char *cap, int col, int row);
  • cap: 기능 코드
    • 보통 cm(cursor motion)에 사용
    • BC(backspace if not bs)나 UP(cursor up)에도 쓴다고 함
  • col: 열
  • row: 행
  • 설정한 행, 열에 해당하는 부분의 주소를 리턴
    오류 시 NULL 리턴
  • 행, 열 순서가 아니라 열, 행 순서인 것에 주의!
  • 터미널의 가장 왼쪽 위가 (0, 0)
  • 내부 스태틱 버퍼에 저장된 터미널의 문자열들이 있는데, tgoto()를 호출할 때마다 재활용
    여기 저장된 문자열을 가지고 리턴할 주소를 찾음

tputs()

기능 실행

int tputs(const char *str, int affcnt, int (*putc)(int));
  • str: tgetstr()tgoto()의 리턴값
  • affcnt: 영향을 받을 라인 수 (현재 라인만 해당하면 1)
  • putc: 한 번에 하나의 문자를 받아서 출력하는 putchar() 같은 함수
  • 리턴값 (확실하지 않음..)
    • 성공 시 1
    • strNULL이면 0
    • terminfo 데이터베이스가 없으면 -1
  • putc()의 리턴값은 무시됨

참고

minishell
[minishell] 2. 프로그램 구조 및 개발 기록들
Minishell 허용함수 및 Redirection
Minishell-study
Minishell(3)

profile
목표는 행복한 베짱이

0개의 댓글