42Seoul/Minishell

[Minishell] 터미널 제어

  • -
728x90

https://hyeonski.tistory.com/5?category=471028 

 

[C/UNIX] 개행 없이 한 글자씩 입력받는 getch() 만들기 (터미널 제어)

프롬프트 또는 콘솔 환경을 만들기 위해서 키보드 입력을 한 자씩 받아야할 때가 있다. unistd.h의 read함수를 STDIN에서 1바이트만큼 받으면 되지 않을까? 해서 다음과 같은 코드를 실행해보았다. #in

hyeonski.tistory.com

https://man7.org/linux/man-pages/man3/termios.3.html

 

termios(3) - Linux manual page

termios(3) — Linux manual page TERMIOS(3) Linux Programmer's Manual TERMIOS(3) NAME         top termios, tcgetattr, tcsetattr, tcsendbreak, tcdrain, tcflush, tcflow, cfmakeraw, cfgetospeed, cfgetispeed, cfsetispeed, cfsetospeed, cfsetspeed - get and

man7.org

미니쉘(콘솔/프롬프트 환경)을 만들 때 터미널 제어를 위해 키보드 입력을 한 글자씩 처리를 해야한다.  

한 글자씩 처리를 위해 그냥 read()함수를 이용하여 1바이트 씩 처리하고 버퍼를 다시 할당하여 처리하고 있었지만 히스토리, 커서제어를 하려면 터미널 제어를 해야한다고 한다.  

 

기존의 방식을 보면 아래 코드와 같다.  

char	*realloc_input(char *ptr, size_t size)
{
	char	*ret;

	ret = (char *)malloc(size);
	if (!ret)
		return (0);
	ft_memmove(ret, ptr, size);
	free(ptr);
	return (ret);
}

int		get_input(char **input)
{
	int		r_nbr;
	int		idx;
	int		cnt;
	char	buf;

	idx = 0;
	cnt = 1;
	*input = malloc(1);
	if (*input == NULL)
		return (READ_ERR);
	while (1)
	{
		r_nbr = read(0, &buf, 1);
		if (r_nbr == 0 || buf == '\n')
		{
			*(*input + idx) = 0;
			return (READ_SUC);
		}
		*(*input + idx) = buf;
		idx++;
		*input = realloc_input(*input, cnt + 1);
		cnt++;
	}
	return (READ_ERR);
}

int		main(int argc, char **argv, char **envv)
{
	char	*input;

	...
    
	while (1)
	{
		write(1, "minish $> ", 10);
		if (get_input(&input) == READ_ERR)
			printf("Error");

		...

		free(input);
	}
	return (0);
}

read()를 1바이트씩 읽어 개행이나 읽은 값이 없으면 새로운 입력값을 읽는다.

하지만 새로운 입력값이 들어오기를 항상 기다리고 있다. 즉, 개행(\n)이 오기 전까지는 입력을 기다리고 있는 것이다.  

 

<termios.h>

터미널 제어 함수를 통해 실시간으로 읽어보자.

void	terminal_init(struct termios *term)
{
	tcgetattr(STDIN_FILENO, term);
	term->c_lflag &= ~ICANON;		// non-canonical input 설정
	term->c_lflag &= ~ECHO;			// 입력 시 터미널에서 보이지 않기
	term->c_cc[VMIN] = 1;			// 최소 입력 버퍼 크기
	term->c_cc[VTIME] = 0;			// 버퍼 비우는 시간(timeout)
	tcsetattr(STDIN_FILENO, TCSANOW, term);
}

터미널 설정을 받아와 수정할 termious 구조체를 선언한다. tcgetattr()를 통해 STDIN 설정을 받아오고, lfalg와 c_cc를 수정하여 tcsetattr()를 통하여 반영시킨다.

http://www.terms.co.kr/canonical.htm

 

[텀즈] canonical and non-canonical

canonical이란, "규정대로"하는 프로그래밍을 의미하며, non-canonical이란 "규정에 따르지 않고"하는 프로그래밍을 의미한다. 초기의 교회에서, "canon" 즉 교회의 법규는 공식적으로 선정된 성구(聖句)

www.terms.co.kr

canonical and noncanonical

anonical이란, "규정대로"하는 프로그래밍을 의미하며, non-canonical이란 "규정에 따르지 않고"하는 프로그래밍을 의미한다. 초기의 교회에서, "canon" 즉 교회의 법규는 공식적으로 선정된 성구(聖句)였다. New Hacker's Dictionary의 저자인 에릭 레이몬드에 따르면, 이 단어는 그리스와 라틴 어원에서 "갈대"를 의미했으며, 일정한 길이의 갈대는 표준 척도로 사용되었다고 한다. 음악이나 문학과 같은 일부 지식 영역에서, "canon"은 모든 사람들이 공부하는 주요 저작품을 의미한다.

 

이 noncanonical input에서 VMIN과  VTIME을 가지고 입력 프로세스 기준을 설정하는데, VMIN은 최소 입력 버퍼의 크기이다.(크기가 3이라면 3만큼의 입력을 받고나서 가장 먼저 입력된 것을 처리한다.) VTIME은 입력 버퍼를 비워주는 timeout 시간이다. 설정된 시간만큼 입력이 없으면 버퍼에 있던 값들을 처리해준다.

 

위 함수를 minishell loop가 돌기 전에 초기화 해주고 실행한다면 아무 값도 보이지 않을 것이다. ~ECHO를 통하여 입력 시 터미널에서 보이지 않게 처리했기 때문이다. 후에 커서 제어를 위해 이 입력값들을 버퍼에 저장시켜두고 커서를 옮기고 히스토리를 출력할 과정을 위함이다.

일반적인 아스키 코드값을 제외한 값들(방향키, 특수키 등)은 보이지 않게하고 그렇지 않은 값들은 출력처리를 해주면 일반적인 쉘에서 처럼 처리할 수 있다.

 

termcap라이브러리를 이용한 커서 제어

입력받은 값(asdf 방향키 좌우상하)을 출력해보면 위와 같은 값이 나온다.(맥 os 기준)

따라서 방향키나 특수키를 사용하기 위해서는 버퍼의 크기가 char형으로는 부족하므로 int형으로 받아준다.

그리고 버퍼는 항상 0으로 초기화 하여 사용하여야되는데, 그 이유는 일반 ascii 범위의 문자들은 1바이트이기 때문에 다른 부분에 다른 값이 남아있으므로 키값이 깨지게 된다.

 

termcap 라이브러리 세팅

커서 제어를 위해서 termcap 라이브러리를 사용해야 한다.

작성중...

728x90
300x250
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.