# <readline/readline.h> 헤더 설치
	brew install readline	# readline 헤더 설치
    brew info readline		# 설치 확인 & 경로 확인
    # makefile로 컴파일 시 아래 플래그 추가
    -lreadline -Lreadline/lib -Ireadline/include
	# 나같은 경우는 경로가 /Users/42id/.brew/opt/readline/lib
    # 환경에 따라 다 다르게 나오는 것으로 보인다
	RL_LIB		=	-lreadline -L${HOME}/.brew/opt/readline/lib
	RL_INC		=	-I${HOME}/.brew/opt/readline/include
    
    $(NAME)		:	$(OBJS)
		$(CC) $(CFLAGS) -o $(NAME) $(OBJS) $(RL_LIB)
    
    %.o			:	%c
		$(CC) $(CFLAGS) -I$(INCLUDE) $(RL_INC) -c $< -o $@
⚠️ readline 설치 후
export LDFLAGS="-L/Users/42id/.brew/opt/readline/lib" export CPPFLAGS="-I/Users/42id/.brew/opt/readline/include"두 명령을 실행해 주어야 프로그램이 새롭게 설치한 readline.h에 제대로 접근함 ⚠️
-> 하지 않는 경우 rl_replace_line() 함수를 사용하지 못 함
-> Mac에 기본적으로 깔려있는 readline.h에는 존재하지 않는 함수이기 때문
	#include <readline/readline.h>
	char	*readline(const char *str);
free를 해주어야 한다.NULL을 반환한다EOF를 만나는 경우EOF 전에 처리할 문자가 있는 경우, EOF를 개행문자처럼 처리하여 문자열을 반환NULL을 반환입력 받은 문자열을 저장하고 그 메모리 주소를 반환한다
EOF를 만나는 경우 NULL return
프롬프트에 문장 입력받고 문자열 저장하는 용도
반드시 free 해야 함!
	char *s = readline("prompt: ");
    // 프롬프트에 prompt: 가 출력되고 그 뒤로 입력된 문장이 s에 저장
	#include <readline/readline.h>
	int	rl_on_new_line(void);
0 반환, 실패 시 -1 반환	#include <readline/readline.h>
	void	rl_replace_line(const char *text, int clear_undo);
rl_line_buffer에 입력받은 내용을 text로 대체한다.clear_undo는 내부적으로 유지하고 있는 undo_list를 초기화할 지 그 여부를 결정하는 값이다. 값이 0이면 초기화하지 않고, 다른 값인 경우 초기화한다.	#include <readline/readline.h>
	void	rl_redisplay(void);
rl_line_buffer의 값을 프롬프트와 함께 출력한다. 이 때, 프롬프트로 readline()에서 입력된 문자열이 사용된다.signal을 받을 때 사용된다.	#include <readline/history.h>
	int	add_history(const char *);
readline()을 통해 사용자가 입력했던 문자열을 저장하는 함수이다.0 반환, 실패 시 -1 반환#include "../include/minishell.h"
int	main(int ac, char **av)
{
	char	*cmd;
	(void)av;
	if (ac != 1)
		return (FAILURE);
	while (TRUE)
	{
		cmd = readline("minishell_$ ");
		if (!cmd)
			break;
		printf("%s\n", cmd);
		add_history(cmd); // 추가 시 상하 방향키로 명령어 히스토리 불러오기 가능
		free(cmd);
	}
	return (SUCCESS);
}
pid 반환, 자식 프로세스에겐 0 반환, 실패 시 0 반환int	main(void)
{
	pid_t	pid;
	pid = fork();
	if (pid > 0)
		printf("Parent ID : %d\n", getpid());
	else if (pid == 0)
		printf("Child ID : %d\n", getpid());
	return (SUCCESS);
}
Parent ID : 88613
Child ID : 88614
	#include <sys/wait.h>
    pid_t	wait(int *statloc);
statloc의 주소를 인자로 넣어 자식 프로세스의 상태를 받을 수 있다.pid 반환, 실패 시 -1 반환	#include <sys/wait.h>
    pid_t waitpid(pid_t pid, int *statloc, int options);
자식 프로세스를 기다릴 때 사용하는 함수, 즉, 자식 프로세스의 종료 상태를 회수할 때 사용한다.
그러나 wait()과 다르게 자식 프로세스가 종료될 때까지 차단되는 것을 원하지 않는 경우 옵션을 이용하여 차단을 방지할 수 있다.
함수 실행 성공 시 PID 반환, 실패 시 -1 반환
pid
1. pid == -1
: 임의의 자식 프로세스를 기다림
pid > 0pid와 동일한 자식 프로세스를 기다림pid < -1pid의 절댓값과 같은 자식 프로세스를 기다림pid == 0waitpid()를 호출한 프로세스의 프로세스 그룹 ID와 같은 프로세스 그룹 ID를 가진 프로세스를 기다림statlocoptionsWCONTINUEDWNOHANGWUNTRACED0wait()와 동일하게 작동0 입력 시int	main(void)
{
	pid_t	pid;
	int		status;
	int		ret;
	pid = fork();
	if (pid > 0)
	{
		printf("Parent ID : %d\n", getpid());
		ret = waitpid(pid, &status, 0);
		printf("Parent killed\n");
	}
	else if (pid == 0)
	{
		printf("Child ID : %d\n", getpid());
		usleep(100);
		printf("Child killed\n");
	}
	return (SUCCESS);
}
Parent ID : 29162
Child ID : 29163
Child killed
Parent killed
-> 자식 프로세스가 종료된 후 부모 프로세스가 종료
WNOHANG 입력 시int	main(void)
{
	pid_t	pid;
	int		status;
	int		ret;
	pid = fork();
	if (pid > 0)
	{
		printf("Parent ID : %d\n", getpid());
		ret = waitpid(pid, &status, WNOHANG);
		printf("Parent killed\n");
	}
	else if (pid == 0)
	{
		printf("Child ID : %d\n", getpid());
		usleep(100);
		printf("Child killed\n");
	}
	return (SUCCESS);
}
Parent ID : 29326
Parent killed
Child ID : 29327
Child killed
-> usleep(100)으로 인해 자식 프로세스가 종료되지 않아 부모 프로세스부터 종료됨
	#include <sys/wait.h>
    pid_t	waitpid(pid_t pid, int *staticloc, int options);
wait()와 같지만 옵션과 pid를 지정하여 사용자가 원하는 대로 제어 가능하다.waitpid(-1, status, options)와 동일하게 동작	#include <sys/wait.h>
	pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
rusage: 자식 프로세스의 리소스 사용량에 대한 정보가 담긴다.waitpid()와 동일하게 작동
wait3()와wait4()는waitpid()가 생긴 후로 구식함수가 되었다
	#include <signal.h>
    void	(*signal(int sig, void (*func)(int)))(int);
신호를 처리하는 함수
성공 시 이전 핸들러를 반환, 실패 시 SIG_ERR를 반환
sig
1. SIGABRT : 비정상적 종료
2. SIGFPE : 부동 소수점 오류
3. SIGILL : 잘못된 명령
4. SIGINT : 터미널 인터럽트 신호(ctrl + C)
5. SIGSEGV : 잘못된 메모리 참조
6. SIGTERM : 종료 신호
함수 포인터
1. SIG_DFL : 신호에 대한 기본 작업으로 처리
2. SIG_IGN : 신호 무시
3. 함수 이름 : 지정 함수 호출
	#include <signal.h>
    int	kill(pid_t pid, int sig);
프로세스에 시그널을 전송하는 함수
성공 시 0, 실패 시 -1 반환
pid
pid > 0 : 지정한 프로세스 ID에만 시그널 전송pid == 0 : 함수를 호출하는 프로세스와 같은 그룹에 있는 모든 프로세스에 시그널 전송pid == -1 : 함수를 호출하는 프로세스가 전송할 수 있는 권한을 가진 모든 프로세스에 시그널 전송pid < -1 : pid의 절댓값 프로세스 그룹에 속하는 모든 프로세스에 시그널 전송int	main(void)
{
	pid_t	pid;
	int		status;
	int		ret;
	pid = fork();
	if (pid > 0)
	{
		printf("Parnet ID : %d\n", getpid());
		ret = waitpid(pid, &status, 0);
		printf("Parent killed\n");
	}
	else if (pid == 0)
	{
		printf("Child ID : %d\n", getpid());
		usleep(100);
		kill(pid, SIGKILL); // 부모가 먼저 죽는다
		printf("Child killed\n");
	}
	return (SUCCESS);
}
Parnet ID : 88149
Child ID : 88150
[1] 88149 killed ./minishell
	#include <unistd.h>
    char	*getcwd(char *buf, size_t size);
현재 작업 경로를 가져오는 함수
성공 시 경로, 실패 시 NULL 리턴. 실패 시 errno에 상세 오류 내용이 저장된다.
buf: 경로 저장 변수
-> buf가 NULL인 경우 함수 내에서 malloc 후 리턴하므로 메모리 해제가 필수적이다.
size: 버퍼에 할당된 바이트 수
int	main(void)
{
	char	*pwd;
	pwd = NULL;
	printf("%s\n", getcwd(pwd, 0));
	free(pwd);
	return (SUCCESS);
}
/goinfre
	#include <unistd.h>
    int	chdir(const char *)
0, 실패 시 -1 반환int	main(void)
{
	char	path[100];
	printf("%d\n", chdir(readline("Path to change: ")));
	printf("%s\n", getcwd(path, 100));
	return (SUCCESS);
}
성공:
Path to change: /goinfre/
0
/goinfre
실패:
Path to change: ~
-1
/goinfre
	#include <sys/stat.h>
    int	stat(const char *path, struct stat *buf);
    int	lstat(const char *path, struct stat *buf);
    int	fstat(int fd, struct stat *buf);
파일 정보를 읽어오는 함수
심볼릭 링크 파일인 경우
1. stat : 구조체에 원본 정보 채우기
2. lstat : 심볼릭 링크 파일 정보 채우기
fstat의 경우 현재 열린 파일의 정보를 읽어 온다.
-> open()으로 연 파일 등
성공 시 0, 실패 시 -1 반환
path : 파일의 절대경로
buf : stat 구조체
	#include <unistd.h>
    int	unlink(const char *path);
하드 링크를 생성하지 않은 파일은 disk space를 해제하여 바로 삭제한다.
하드 링크가 있는 파일은 이름을 삭제하고 하드 링크가 참조하는 카운트를 1만큼 감소한다. 하드 링크의 카운트가 0이 되면 실제 파일의 내용이 저장되어 있는 disk space를 해제하여 완전히 삭제한다.
open()으로 파일을 연 경우 unlink()를 사용하면 정보는 삭제되더라도 disk space는 해제되지 않는다.
성공 시 0, 실패 시 -1 반환
path : 상대 경로
int	main(void)
{
	int	ret;
	if ((ret = unlink(readline("File to delete: "))))
		printf("Deleted\n");
	else
		printf("Fail to delete\n");
}
File to delete: ~/goinfre/a
Deleted
외 execve(file, null, null)이 않되나요..
	#include <unistd.h>
    int	execve(const char *filename, char *const argv[], char *const envp[]);
현재 실행되는 프로그램의 기능을 없애고, filename 프로그램을 처음부터 실행
성공 시 0, 실패 시 -1 반환
함수 호출 후에 일어나는 프로세스의 변화
1. signal 설정이 default로 변경됨
2. opendir로 열린 directory stream이 close 됨
-> 일반 file discriptor는 close되지 않음
3. atexit(3), on_exit(3)으로 등록된 exit handler가 해제됨
4. 모든 스레드가 사라짐
...
filename
- 교체할 실행 파일 or 명령어
- 실행가능한 binary거나 shell이어야 함
- 절대 경로나 상대 경로로 정확한 위치를 지정해야 함
argv
- main(int ac, char **av);의 **av와 유사
envp
- key=value 형식의 환경변수 문자열 배열리스트로 마지막은 NULL이어야 함
만약 이미 설정된 환경변수를 사용하고자 하면 environ 전역변수 사용
extern char	**environ;
int	main(int ac, char **av)
{
	char	**new_av;
	char	cmd[] = "ls";
	int		idx;
	new_av = (char **)malloc(sizeof(char *) * (ac + 1));
	new_av[0] = cmd;
	for (idx = 1; idx < ac; idx++)
		new_av[idx] = av[idx];
	new_av[ac] = NULL;
	if (execve("/bin/ls", new_av, environ) == -1) // where ls로 위치 찾을 수 있음
	{
		fprintf(stderr, "error: %s\n", strerror(errno));
		return 1;
	}
	printf("ls 명령 실행으로 출력되지 않음\n");
	return 0;
}
Makefile include src README.md minishell
	#include <unistd.h>
    int	dup(int fd);
    int	dup2(int fd, int fd2);
dup()은 파일 복제 시 사용하지 않는 fd 중 가장 작은 번호가 자동으로 지정되고, dup2()는 사용자가 원하는 fd로 지정 가능하다. 만약 사용 중인 경우 해당 fd의 파일을 다은 후 지정한다.fd를 얻어야 하므로 open하여 사용한다int	main(void)
{
	int fd1, fd2, fd3;
	fd1 = open("test", O_RDONLY);
	fd2 = dup(fd1);
	fd3 = dup2(fd2, 5);
	printf("fd1: %d\nfd2: %d\nfd3: %d\n", fd1, fd2, fd3);
	close(fd1);
	close(fd2);
	close(fd3);
}
fd1: 3
fd2: 4
fd3: 5
	#include <unistd.h>
    int	pipe(int fd[2])
파이프를 생성하여 만들어진 디스크립터를 할당한다.
디스크립터를 얻으면 부모와 자식 프로세스가 사용할 수 있다.
fork()에 의해 복사가 되지 않는다
성공 시 0, 실패 시 -1 반환
fd[2]
- fd[0]: 파이프로부터 읽는 디스크립터
- fd[1]: 파이프에 쓰는 디스크립터
fd[0]을 닫고, 표준 출력이 fd[1]이 가리키는 스트림을 가리키도록 변경한다.fd[1]을 닫고, 프로세스의 표준 입력을 fd[0]이 가리키는 스트림으로 리다이렉션한다.➜ 이렇게 자식 프로세스가 쓰는 모든 데이터를 부모 프로세스의 표준 입력을 통해 읽을 수 있다
int	main(int ac, char **av)
{
	int 	fd[2];
	pid_t	pid;
	if (pipe(fd) == -1)
	{
		fprintf(stderr, "pipe error: %s\n", strerror(errno));
		return (1);
	}
	pid = fork();
	if (pid == -1)
	{
		fprintf(stderr, "fork error: %s\n", strerror(errno));
		return (1);
	}
	if (pid == 0) // Child process
	{
		dup2(fd[1], 1);	// 표준 출력을 파이프의 쓰는 쪽으로 설정
		close(fd[0]);	// 자식은 파이프에서 읽지 않으므로 읽는 쪽을 닫는다
		int ste = execlp("ls", "ls", NULL);
		// execlp: PATH에 등록된 모든 디렉토리에 있는 프로그램을 실행하므로 프로그램 이름만 입력해도 실행이 됨
		// ps 중복: av[0]처럼 프로그램 전체 이름을 의미하기 때문
		// 즉 두 번째 인자인 ps부터 실행한다. (ps aux를 실행한 것)
		if (ste == -1)
		{
			fprintf(stderr, "run error: %s\n", strerror(errno));
			return (1);
		}
	}
	// 부모만 여기에 도달
	dup2(fd[0], 0);	// 표준 입력을 파이프의 읽는 쪽으로 리다이렉션
	close(fd[1]);	// 부모는 파이프에 쓰지 않으므로 쓰는 쪽을 닫는다
	char	line[255];
	while (fgets(line, sizeof(line), stdin) != 0)
		printf("%s", line);
	return (0);
}
Makefile
README.md
a.out
include
src
test
test.c
pipe()는 파이프와 2개의 디스크립터를 생성하나.	#include <dirent.h>
    DIR				*opendir(const char *);
    struct dirent	*readdir(DIR *);
    int				closedir(DIR *);
	#define __DARWIN_STRUCT_DIRENTRY { \
    	__uint64_t  d_ino;      /* file number of entry */ \
		__uint64_t  d_seekoff;  /* seek offset (optional, used by servers) */ \
		__uint16_t  d_reclen;   /* length of this record */ \
		__uint16_t  d_namlen;   /* length of string in d_name */ \
		__uint8_t   d_type;     /* file type, see below */ \
		char      d_name[__DARWIN_MAXPATHLEN]; /* entry name (up to MAXPATHLEN bytes) */ \
	}
opendir()DIR 포인터, 실패 시 NULL 반환readdir()NULL 반환closedir()0, 실패 시 -1 반환int	main(void)
{
	DIR				*path = NULL;
	struct dirent	*file;
	if (!(path = opendir(readline("Dir path: "))))
		printf("No such path\n");
	while ((file = readdir(path)))
		printf("name: %s\n", file->d_name);
	closedir(path);
}
Dir path: .
name: .
name: ..
name: test
name: Makefile
name: include
name: README.md
name: a.out
name: test.c
name: src
	#include <unistd.h>
    int	isatty(int fd);
ttyname()을 이용하여 이와 연관된 파일 이름을 얻을 수 있다.1, 그렇지 않은 경우 0 반환int	main(void)
{
	int fd;
	printf("%d\n", isatty(0));	// 표준입력은 터미널에 연결되어 있으므로 1을 출력한다.
	fd = open("test100", O_RDWR);
	printf("%d\n", isatty(fd));	// 파일은 터미널에 연결되어 있지 않으므로 0을 출력한다.
	close(fd);
	fd = open("/dev/ttyS0", O_RDONLY);
	if (fd < -1)
	{
		printf("open error\n");
		exit(0);
	}
	printf("%d\n", isatty(fd));
	close(fd);
	return (0);
}
1
0
0
	#include <unistd.h>
    char	*ttyname(int fd);
NULL 반환int	main(void)
{
	printf("%s\n", ttyname(0));
	printf("%s\n", ttyname(5));
	return (0);
}
/dev/ttys000
(null)
	#include <unistd.h>
    int	ttyslot(void);
	#include <sys/ioctl.h>
    int	ioctl(int fd, unsigned long request, ...);
	#include <stdlib.h>
    char	*getenv(const char *name);
int main(void)
{
	printf("%s\n", getenv(readline("env: ")));
}
env: HOME
/Users/42id
	#include <termios.h>
    int	tcsetattr(int fd, int action, const struct termios * termios_p)
    int	tcgetattr(int fd, struct termios *termios_p)
터미널 파일 fd에 대한 터미널 설정하고 가져온다.
tcsetattr()
: termios 구조체 초기화
: termios_p의 내용대로 구조체 멤버를 설정한다
tcgetattr()
: action 필드의 내용대로 설정한다
- TCSANOW: 값을 즉시 변경
- TCSADRAIN: 현재 출력이 완료되었을 때 값 변경
- TCSAFLUSH: 현재 출력이 완료되었을 때 값을 변경하나, 현재 읽을 수 있으며 read 호출에서 아직 반환되지 않는 입력값을 버린다
: 프로그램이 시작하기 전의 값으로 터미널 설정값을 복구하는 것이 중요함
	#include <curses.h>
    #include <term.h>
    int		tgetent(char *bp, const char *name);
    int		tgetflag(char *id);
    int		tgetnum(char *id);
    char	*tgetstr(char *id, char **area);
tgetent(): 엔트리 이름을 가져옴. 성공 시 1, 해당 엔트리가 없는 경우 0, terminfo 데이터베이스를 찾을 수 없는 경우 -1 반환tgetflag(): id의 boolean 정보를 가져옴. 실패 시 0 반환tgetnum(): id의 숫자 정보를 가져옴. 실패 시 -1 반환tgetstr(): id의 문자열 정보를 가져옴. 실패 시 NULL 반환tputs()를 이용해 반환된 문자열을 출력한다.area에도 저장된다.	#include <curses.h>
	#include <term.h>
	char *tgoto(const char *cap, int col, int row);
	int tputs(const char *str, int affcnt, int (*putc)(int));
tgoto(): 매개변수를 주어진 기능으로 인스턴스화. 출력은 	tputs()로 전달된다.tputs(): str에 패딩 정보를 적용하고 출력한다.str은 terminfo 문자열 변수이거나 tparm(), tgetstr() 또는 tgoto()의 반환값이어야 한다.affcnt는 적용되는 줄 수로, 적용되지 않는 경우 1로 설정한다.➜ 정보가 많이 없다..🙂🙃🙂🙃
-n 옵션(개행 삭제)-nnnnn 이어도 작동 되며 -n -n 이런 식으로 중복되어도 작동 됨echo -nnnn hi : hi%echo -n -na hi : -na hi%echo -nnnn1 -n hi: -nnnn1 -n hiecho -nnnn -n hi: hi%경로 이동 명령어
절대경로 혹은 상대경로만 사용하도록 구현
현재 경로 출력
리다이렉션 앞 뒤로 공백 없어도 작동 함 .. 주의할 것
공백 기준으로 split 한 후에 redirection 있는지 한 번 더 확인해야 할 듯
	command > file
    # command의 출력 결과를 file로 저장
  	
    > file
    # 터미널에 입력한 내용을 file에 저장
    # zsh는 터미널에 입력 받을 수 있는데 bash는 작동하지 않음, 대신 file 내용이 공백으로 업데이트됨
⚠️
ls > file을 할 때 file이 존재하지 않는 경우 file부터 만들고ls의 결과를 file에 저장(ls명령 실행 결과에 file도 포함이 됨) ⚠️
	command >> file
    # command의 출력 결과를 file 맨 뒤에 이어쓰기
    
    >> file
    # 아무 동작하지 않음(공백을 맨 뒤에 붙이는 것이기 때문)
	< file
    # 아무 동작 하지 않음
	<< keyword
    # keyword가 나올 때까지 입력만 받고 keyword 나오면 입력받기 종료
닫히지 않은 따옴표는 다룰 필요 없음
	echo "${HOME}"
    echo "'${HOME}'"
/Users/42id
'/Users/42id'
	echo '${HOME}'
    echo '"${HOME}"'
${HOME}
"${HOME}"
^C 없앨 때 tcsetattr 함수 사용 ?!
	 SIGINT
새로운 줄에 새로운 프롬프트 생성
초기 설정은 minishell 종료
minishell 종료
초기 설정은 새로운 줄
bash는 logout
	SIGQUIT
아무런 동작도 하지 않아야함
아무 설정도 하지 않으면 minishell quit
([1] PID quit ./minishell)
bash는 아무 동작 x
ref
unlink: https://www.it-note.kr/177
pipe: https://nomad-programmer.tistory.com/110
tty: https://powergi.tistory.com/entry/terminal-%ED%86%B5%EC%8B%A0
isatty: https://www.joinc.co.kr/w/man/3/isatty
termios: https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=y851004&logNo=20063499242