mojo's Blog

tail 본문

42 seoul

tail

_mojo_ 2022. 2. 22. 15:31

tail

 

시스템 명령어 tail 와 동일한 기능을 하는 ft_tail 이라는 이름의 프로그램을 작성해보도록 한다.

처리해야 하는 옵션은 -c가 유일하며, ’+’ 나 ’-’ 기호는 처리하지 않아도 되며 errno 변수를 사용할 수 있다고 한다.

우선 tail 의 -c 옵션을 활용한 명령어에 대해서 몇가지 케이스를 분석한 후 구현에 들어가도록 한다.

 

 

① tail -c [숫자] 

예를 들어서 "tail -c 10" 을 입력하면 타이핑을 할 수 있는 상태가 된다.

무언가를 타이핑한 후에 [enter] 를 누르면 라인개행이 이뤄지면서 계속해서 타이핑을 할 수 있다.

그리고 [ctrl + D] 를 누르면 타이핑 상태가 종료되면서 가장 마지막에 [숫자] 길이 만큼 타이핑했던 내용이 뜬다.

 

 

다른 예로 "tail -c 0" 을 입력하게 된다면 의미없는 타이핑이 이뤄지며 [ctrl + D] 를 누르면 타이핑 상태가 종료되면서 아무런 결과없이 종료된다.

 

타이핑을 한다는 것은 fd = 0 즉, file descriptor 값을 0으로 설정하여 read(fd, &c, 1); 와 같이 단어를 타이핑하며 문자를 받아와야 한다.

그리고 [숫자] 만큼 동적 할당을 하여 char * 타입의 1차원 배열을 설정하여 읽어들인 단어를 계속 push 해야 하는 것을 생각할 수 있다.

 

② tail -c [숫자] 파일

이번에는 파일을 읽어들여서 가장 마지막의 [숫자] 길이 만큼의 내용을 보일 수 있다.

예를 들어서 "tail -c 50 Makefile" 를 입력하게 된다면 Makefile 의 가장 마지막의 50 길이 만큼의 내용이 보이게 된다.

 

 

파일을 읽으므로 open 함수를 이용하여 fd = open("파일명", O_RDONLY); 를 통해 fd 값을 얻을 수 있으며 해당 fd를 통해 ① 방법과 동일하게 read 하면 된다.

그렇다면 "파일명" 이 존재하지 않아서 open 하지 못할 경우 fd 값이 -1 이 된다면 이러한 예외 처리는 어떻게 해야 할까?

 

 

위와 같이 에러 문구를 띄우면서 종료되도록 해주면 된다.

 

 

③ tail -c [숫자] 파일1 파일2 파일3 ...

파일이 여러개 오는 경우도 생각해야 한다.

여러 파일에 대해 가장 마지막의 [숫자] 길이만큼 결과를 보여줘야 한다.

그리고 각 파일마다 현재 디렉토리 및 하위 디렉토리에 존재하는 파일, 존재하지 않은 파일에 대한 처리를 나눠서 해줘야 한다.

우선 기본적으로 파일이 전부 존재할 경우 결과는 다음과 같다.

 

 

파일이 여러개일 경우 파일명이 명시되면서 동시에 가장 마지막에 25 길이 만큼의 텍스트가 출력되는 것을 알 수 있다.

그리고 좀 더 디테일하게 본다면, 첫 파일명에 대한 결과를 보인 후에 다음 파일명에 대한 결과를 보일 때 라인개행('\n')이 이뤄지는 것을 알 수 있다. (이 부분을 고려하지 못해서 틀렸던 사람...)

 

 

위와 같이 빨간색 상자로 이뤄진 부분이 라인개행이 이뤄진 것으로 볼 수 있으며 첫 파일명에 대한 결과 이후에는 라인 개행 후, ==> 파일명 <== ... 와 같은 출력 결과를 보여야 한다.

그럼 파일이 존재하지 않은 더미 파일을 중간 중간에 넣어서 실행해보도록 하면 다음과 같다.

 

 

현재 디렉토리에 존재하지 않는 파일명은 x, y, z, 그리고 v 이며 존재하는 파일명은 a, b, 그리고 c 이다.

라인 개행은 먼저 첫 파일명인 "a" 에 대한 결과가 보여지고 나서 "b", "c" 에 대한 결과를 라인개행을 한 후에 보이는 것을 알 수 있으며 에러문구는 위에서 봤던 것 처럼 출력해주면 된다.

 


 

※ tail 구현 방법

 

① argc 값이 3일 경우와 아닐 경우에 대해 고려하도록 한다.

 

argc 값이 3일 경우 "tail -c [숫자]" 즉, input 을 계속해서 읽어서 가장 마지막의 [숫자] 길이만큼 결과만 보이면 된다.

argc 값이 3이 아닐 경우 "tail -c [숫자] 파일" or "tail -c [숫자] 파일1 파일2 파일3 ..." 즉, 파일을 open 해서 read 하는 작업을 한 후에 가장 마지막의 [숫자] 길이만큼 결과를 보이도록 해야 한다.

이에 대한 case 분류를 다음과 같이 구현하였다.

 

int	main(int argc, char **argv)
{
	if (argc == 3)
		no_argument(argv[2]);
	else
		yes_argument(argc, argv);
	return (0);
}

 

 

② argc 값이 3일 경우를 고려해보도록 한다.

 

void	no_argument(char *size)
{
	go_tail(ft_atoi(size), 0);
}

 

이전에 구현했던 ft_atoi 함수를 통해 문자열로 받은 숫자를 int 형 숫자로 변환한 후 go_tail 함수로 이동한다.

이때 두번째 argument 인 0 은 file descriptor 값으로 볼 수 있으며 read() 함수에서 fd 를 정하기 위한 용도로 설정하였다.

go_tail 함수는 다음과 같다.

 

void	go_tail(int word_cnt, int fd)
{
	int		i;
	int		op;
	char	*ret;
	char	c;

	if (word_cnt == 0)
	{
		go_write(fd);
		return ;
	}
	...
}

 

우선 word_cnt 값이 0일 경우 즉, "tail -c 0" 에 대해서 의미없는 타이핑을 한 후에 [ctrl + D] 를 하면 아무런 결과가 출력되지 않고 종료되도록 해줘야 한다.

그래서 go_write() 함수를 따로 만들어서 의미없이 read 만 한 후에 종료되도록 구현하였다.

go_write() 함수는 다음과 같다.

 

void	go_write(int fd)
{
	char	c;

	while (read(fd, &c, 1))
		if (errno)
			return ;
}

 

word_cnt 값이 1 이상일 경우 가장 마지막에 word_cnt 길이 만큼의 결과를 보이도록 해야 한다.

그러기 위해서 word_cnt 길이 만큼 문자를 담기 위한 char 배열이 필요해 보인다.

따라서 char * 타입의 ret 변수를 선언하여 word_cnt 만큼 malloc 을 해줘서 문자 하나씩 읽으면서 ret 배열에 문자를 채워간다.

이 때 Circulation Queue 를 활용하여 문자를 채워가도록 한다.

예를 들어서 다음과 같이 word_cnt 값이 5라고 할 때 문자를 계속 읽는다고 가정해본다.

 

 

위와 같이 5 번째 문자를 읽고 push 한 다음에 6 번째 문자를 읽고 어떤 문자를 pop 하고 push 해야 하는지에 대한 기준을 정해야 한다.

우선, 인덱스 i 가 0, 1, 2, 3, 4 에 도달한 후에 5가 될 경우 ret 의 인덱스 범위를 넘어서게 된다.

이 경우에 i = (i + 1) % (word_cnt) 를 하여 0 으로 다시 돌아가도록 한다.

즉, 6 번째 문자를 읽은 후에 인덱스 0 으로 돌아가서 기존에 있던 문자를 pop 하고 새로운 문자를 넣어주도록 한다.

아래와 같이 문자를 pop 하고 push 를 하도록 하면 된다.

 

 

이에 대한 코드는 다음과 같다.

 

	...
	ret = (char *)malloc(word_cnt);
	op = 0;
	i = 0;
	while (read(fd, &c, 1))
	{
		if (errno)
			return ;
		if (i + 1 == word_cnt)
			op = 1;
		ret[i] = c;
		i = (i + 1) % word_cnt;
	}
	print_result(ret, i, op, word_cnt);
	free(ret);
}

 

op 는 단어를 5 개 이상 받게 될 경우 op = 1 로 설정하였다.

그리고 마지막 라인에 free(ret) 은 더이상 ret 배열을 쓸 일이 없기 때문에 free() 를 해준다.

즉, op = 0 일 경우 단순하게 "a", "ab", "abc", "abcd", "abcd" 와 같은 경우로 볼 수 있으며 op = 1 일 경우 "abcde", "abcdef", "abcdefg", ... 와 같은 경우로 볼 수 있다.

 

그렇다면 "abcdefgh" 를 읽은 후에 [ctrl + D] 를 입력하여 종료되었다고 가정해본다.

그렇다면 ret 배열에 남아있는 문자는 "defgh" 가 있어야 하며 "defgh" 가 출력되면서 종료되도록 해야 한다.

이때 ret 배열이 문자가 담긴 상태와 인덱스의 정보를 알 수 있다.

 

 

인덱스 i 와 ret 배열, word_cnt, 그리고 단어의 갯수를 초과해서 읽었는지 아닌지를 판단하기 위한 op 를 통해 결과를 출력할 수 있다. 

print_result() 함수를 통해 결과를 출력하도록 구현하였고 코드를 보도록 한다.

 

void	print_result(char *s, int idx, int op, int cnt)
{
	int	start;

	if (op)
	{
		start = idx;
		write(1, &s[start++], 1);
		start = start % cnt;
		while (start != idx)
		{
			write(1, &s[start++], 1);
			start = start % cnt;
		}
	}
	else
	{
		start = 0;
		while (start < idx)
			write(1, &s[start++], 1);
	}
}

 

우선, 단순하게 op = 0 인 경우를 고려해보도록 한다.

"a", "ab", "abc", "abcd" 와 같은 경우로, start = 0 으로 설정하여 매개변수로 받은 idx 전 까지 출력하면 된다.

op = 1 인 경우 idx 가 결국 처음으로 출력해야 할 문자임을 짐작할 수 있다.

 

 

"abcdefgh" 를 입력하여 idx = 3 일 때, "defgh" 를 출력해야 하기 때문에 start 변수를 idx 로 설정하여 출력해가도록 한다.

 

 

 

 

위 그림과 같이 start = idx 을 시작으로 하여 다시 idx 에 도착할 때까지 출력을 진행하고 start 가 idx 에 도착하게 된다면 출력을 멈추면 된다.

이렇게 해서 read 하고 write 하는 과정을 봤다.

 

 

③ argc 값이 3이 아닐 경우를 고려해보도록 한다.

 

void	yes_argument(int argc, char **argv)
{
	int	i;
	int	fd;
	int	newLine;

	i = 2;
	newLine = 0;
	while (++i < argc)
	{
		errno = 0;
		fd = open(argv[i], O_RDONLY);
		if (fd == -1)
		{
			error_message(argv[i], argv[0]);
			continue ;
		}
		if (argc > 4)
		{
			if (newLine++)
				print_message("\n");
			special_message(argv[i]);
		}
		go_tail(ft_atoi(argv[2]), fd);
		close(fd);
	}
}

 

우선 파일을 열 때, 정상적으로 파일을 열지 못해 fd 값이 -1이 될 경우 해당 파일에 대한 에러 메시지를 출력해야 한다.

파일을 정상적으로 열 경우, argc 값이 4일 경우와 4를 초과한 경우에 대해서 고려해야 한다.

우선 4일 경우 단 하나의 파일에 대한 처리이므로 "==> 파일명 <==" 에 대한 출력 없이 바로 결과를 보였다.

그리고 여러 파일이 들어왔을 경우 "==> 파일명 <==" 에 대한 출력이 반드시 있었으며 두번째 파일에 대한 결과부터 라인개행이 있었다는 것을 위에서 살펴봤다.

go_tail 함수는 위에서 설명하였으므로 생략한다.

 

error_message(), special_message() 함수 구현은 다음과 같다.

 

void	special_message(char *s)
{
	print_message("==> ");
	print_message(s);
	print_message(" <==\n");
}

void	error_message(char *file, char *exec_name)
{
	print_message(basename(exec_name));
	print_message(": ");
	print_message(file);
	print_message(": ");
	print_message(strerror(errno));
	print_message("\n");
}

 

special_message() 는 파일명을 받은 매개변수 s 에 대해서 "==> 파일명 <==" 를 처리하기 위한 함수이다.

그리고 error_message() 는 파일명과 실행파일의 이름을 받아와서 에러 문구를 띄워야 한다.

이 문제에서 실행파일명을 ft_tail 라고 했으므로 에러문구는 다음과 같이 출력되야만 한다.

 

ft_tail: 파일명: No such file or directory

 

basename() 함수를 통해서 exec_name = "./ft_tail" 에서 ./ 를 제거하여 실행파일명 만을 가져오도록 할 수 있으며 errno 변수를 통해 file 을 정상적으로 open 하지 못할 경우 자동으로 errno 변수에 2 값이 들어가게 된다.

따라서 errno 값을 이용하여 strerror() 함수에 errno 값을 넣어줌으로써 "No such file or directory" 문자열을 받아와 출력하도록 한다.

 

'42 seoul' 카테고리의 다른 글

hexdump  (2) 2022.02.25
Split  (0) 2022.02.22
Comments