더 발전된 쉘

신준우·2023년 6월 6일
0

시스템 프로그래밍

목록 보기
10/12

이전 내용 복습

  • 우리는 새로운 발전된 쉘인, smsh1을 만들었다
  • 그러나 개선점이 몇가지 있었다!
    • 여러가지 명령어를 한줄에 적을수 있어야 한다
      - 오리지널 쉘은 세미콜론(;)을 통해서 여러개의 명령어를 실행할 수 있다
      - 유저들이 한 줄에 여러개의 명령어를 실행 할 수 있도록 개선해보자
    • 백그라운드 처리
      - 오리지널 쉘은 사용자가 프로세스를 백그라운드에서 실행할 수 있다
      - 사용자가 명령어를 '&'로 끝내면, 프로세스를 백그라운드에서 실행할 수 있다
      - 백그라운드 실행이란? 다른 명령어를 사용하면서 해당 프로세스는 계속해서 실행되는 상태임을 의미
      - 다른 작업을 수행하면서 백그라운드에서 실행중인 프로세스를 동시에 관리 가능
    • exit 명령어
      - exit명령어를 통해 쉘을 종료시킬수 있는 기능을 추가해야한다!

이렇게, 여러가지 개선점이 있지만, 우선 if..then 즉 control flow를 적용시켜보자

if

if의 역할

  • 쉘은 if라는 제어구조를 제공한다
  • "We plan to back up our disk every Friday"라는 내용의 문자를 예시로 들어보자
if date | grep Fri
then
	echo time for backup. Insert tape and press enter
    read x
    tar cvf /dev/tape /home
fi

여기서 grep 프로그램이 "Fri"라는 단어를 찾았을때는 성공했음을 나타내기 위해 exit(0)을 호출한다

  • 0이라는 종료값은 성공을 나타냄!

스크립트에는 else블록을 추가할 수 있다. 이는 then블록과 유사하다

  • 문법을 확인해보자!

사실 예시만 봐서는 영 알아보기 힘들다. if가 어떻게 작동하는지 순차적으로 알아보자

  1. 쉘은 if라는 문자 뒤에 오는 명령을 실행한다
  2. 쉘은 명령의 종료 상태를 확인한다
  3. 종료 상태가 0이면 성공, 0이 아닌 값이면 실패를 의미한다
  4. 성공한 경우, then라인 이후의 명령을 실행한다
  5. 실패한 경우, else라인 이후의 명령을 실행한다
  6. "fi" 키워드는 if 블록의 끝을 표시한다

자! 이제 새로운 쉘을 다시 만들어보자!

smsh2

  • 새로운 레이어, 또는 프로세스를 가질것이다
  • if문장을 추가하고, smsh1의 로직을 좀 바꾼다

process는 뭘 하고, 어떻게 작동하는지 알아보자

  • 스크립트의 control flow를 관리하기 위해, if, then, fi와 같은 키워드를감지
  • 적절한 경우에만 fork와 exec함수를 호출하여 처리한다
  • 스크립트를 서로 다른 영역의 연속으로 본다
    - want_then 블록
    - then 블록
    - else 블록
    - neural 블록(if구조의 외부)

이렇게! 분리된다는 뜻

이제 본격적으로 코드를 살펴보자!

smsh2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include "smsh.h"

#define DFL_PROMPT	"> "

int main(){
	char *cmdline, *prompt, **arglist;
	int result, process(char**);
	void setup();
	
	prompt = DFL_PROMPT;
	setup();
	while((cmdline = next_cmd(prompt, stdin)) != NULL){
		if((arglist = splitline(cmdline)) != NULL){
			result = process(arglist);
			freelist(arglist);
		}
		free(cmdline);
	}
	return 0;
}

void setup(){		//setup함수가 정의됐다! 살펴보자
		signal(SIGINT, SIG_IGN);		//SIGINT는 인터럽트 신호, Ctrl+C를 입력하면 발생
                                        //SIG_IGN을 사용해서 시그널을 무시하도록 설정
        signal(SIGQUIT, SIG_IGN);		//SIGQUIT은 종료신호, Ctrl+\를 입력하면 발생
                                        //SIG_IGN을 사용해서 시그널을 무시하도록 설정
}

void fatal(char *s1, char *s2, int n){		//fatal함수 정의
		fprintf(stderr, "Error: %s, %s\n", s1, s2);
        // fprintf함수를 사용하여 에러 메세지를 출력, stderr은 에러 출력 스트림, s1값과 s2값을 출력
        exit(n);		//exit함수를 호출하여 프로그램 종료, n은 종료상태 코드, 프로그램의 종료상태를 나타냄
}

자! 이게 바로 바뀐 smsh2.c 코드이다!
main 함수 부분만 smsh1.c 에서 바뀌었다 한번 살펴보자

int main(){
	char *cmdline, *prompt, **arglist;		//세개의 포인터 변수 선언
	int result;		//int형 변수 선언
	process(char**)		//process라는 char**형 인자를 받는 함수 프로토타입 선언
    void setup();		//setup함수의 프로토타입 선언
	
	prompt = DFL_PROMPT;		//prompt라는 char형 포인터 변수에 매크로 정의된 DFL_PROMPT 대입
	setup();		//setup함수 호출
	while((cmdline = next_cmd(prompt, stdin)) != NULL){
    // next_cmd함수를 호출해서 prompt와 stdin에서 커맨드 라인을 읽어온다
    // 읽어온 결과를 cmdline 변수에 대입하고, cmdline이 NULL이 아닌동안에 반복
		if((arglist = splitline(cmdline)) != NULL){
        // splitline 함수를 호출, cmdline을 분할해서 arglist에 저장
        // arglist가 NULL이 아닌 경우 실행됨
			result = process(arglist);	process 함수의 결과값을 result에 대입한다
			freelist(arglist);		//arglist의 메모리 해제
		}
		free(cmdline);		//cmdline의 메모리 해제
	}
	return 0;		//반환값을 0으로 설정, 프로그램이 종료됨
}

그래서 그놈의 process함수는 어딨나요!
다음 코드에 있습니다!
next_cmd함수는 어딨나요?
마찬가지로 다음 코드에 있습니다!
메인함수는 거의 다른 함수를 사용해서 기능한다 어서 다른 함수들을 살펴보자!

process.c

#include <stdio.h>
#include "smsh.h"

int is_control_command(char*);		//함수의 프로토타입 선언

int do_control_command(char**);		//함수의 프로토타입 선언

int ok_to_execute();		//함수의 프로토타입 선언

int process(char**args){		//포인터 변수를 인자로 받는 process함수
	int rv = 0;		//int형 변수 rv 선언
	if (args[0] == NULL){		//매개변수로 받은 배열이 비어있는 경우,
		rv = 0;		//rv를 0으로 초기화
	}
	else if (is_control_command(args[0])){		//is_control_command가 args[0]의 값을 가질때
		rv = do_control_command(args); //do_control_command의 반환값을 rv에 저장한다
	}
	else if (ok_to_execute()){		//ok_to_execute()함수의 반환값이 입력되면
		rv = execute(args);		//execute함수의 반환값을 rv에 저장한다
	}
	return rv;		//rv값 반환!
}

여기서도 마찬가지로 다양한 함수를 사용해서 rv값을 반환하고 있다!
여기서도 다양한 함수의 프로토 타입만 정의되어있다. 다음 코드에 답이 있을것이다!
다음 코드를 한번 살펴보자

controlflow.c

#include <stdio.h>
#include <string.h>
#include "smsh.h"
enum states {NEUTRAL, WANT_THEN, THEN_BLOCK};	//enum은 열거형 변수
enum result {SUCCESS, FAIL};
static int if_state = NEUTRAL;
static int if_result = SUCCESS;
static int last_stat = 0;
int syn_err(char*);

int ok_to_execute(){
	int rv = 1; 	//rv 변수를 1로 초기화
	if(if_state == WANT_THEN){		//if_state가 WANT_THEN인 경우
		syn_err("then expected");		//then expected이라는 오류 메세지 출력
		rv = 0;		//rv는 0 으로 초기화
	}
	else if(if_state == THEN_BLOCK && if_result == SUCCESS){
    // if_state가 THEN_BLOCK이고, if_result가 SUCCESS와 값이 같다면
		rv = 1;		//rv값에 1 대입
	}
	else if(if_state == THEN_BLOCK && if_result == FAIL){
    // if_state가 THEN_BLOCK과 같고, if_result가 FAIL인 경우
		rv = 0;		//rv값을 0으로 설정
	}
	return rv;		//rv값 반환
}

int is_control_command(char* s){
	return(strcmp(s, "if")==0||strcmp(s, "then")==0||strcmp(s, "fi")==0);
    // strcmp 함수를 사용해서 문자열 s와 "if", "then", "fi"와 같은지 확인한다
    // 문자열이 일치하는 경우 1 반환
    // 일치하지 않는 경우 제어 명령어가 아니라고 판단, 0을 반환
}

int do_control_command(char** args){	
	char*	cmd = args[0];		//args 배열에서 명령어를 가져온다
	int rv = -1;		// rv를 -1로 초기화
	if(strcmp(cmd, "if") == 0){		//만약 if명령어인 경우
		if(if_state != NEUTRAL){		//if_state 가 NEUTRAL이 아닌경우
			rv = syn_err("if unexpected");		//if unexpected를 출력한다
		}
		else{		//나머지 경우에는
			last_stat = process(args+1);		//last_stat에서는 마지막 명령어의 실행 결과 저장
			if_result = (last_stat == 0 ? SUCCESS : FAIL);
            //if_reault값을 설정
			if_state = WANT_THEN;		//if_state를 WANT_THEN으로 설정
			rv = 0;		//0을 반환
		}
	}
	else if(strcmp(cmd,  "then") == 0){		//then 명령어일 경우
		if(if_state != WANT_THEN){		//if_state가 WANT_THEN이 아닐경우
			rv = syn_err("then unexpected");		//에러 메세지를 출력
		}
		else{		//아닌 경우?
			if_state = THEN_BLOCK;		//if_state에 THEN_BLOCK을 대입
			rv = 0;		//0을 반환
		}
	}
	else if(strcmp(cmd, "fi") == 0){		//fi명령어인 경우
		if(if_state != THEN_BLOCK){		//if_state가 THEN_BLOCK이 아닌경우
			rv = syn_err("fi unexpected");		//에러 메세지 출력
		}
		else{		//아닌경우?
			if_state = NEUTRAL;		//NEUTRAL을 설저
			rv = 0;
		}
	}
	else{		//그 외의 경우
		fatal("internal error processing:", cmd, 2);		//내부 오류를 알림
	}
	return rv;		//2를 반환한다
}

int syn_err(char* msg){		//이건 단순히 에러메세지 출력하는 함수!
	if_state = NEUTRAL;
	fprintf(stderr, "syntax error: %s\n", msg);
	return -1;
}

하 길고도 길었다 if, then, fi를 입력받았을때의 처리를 나타내는 코드이다!
이제 어서 결과가 어떻게 실행되는지 확인해보자

결과

정상적으로 if, then, fi가 실행됨은 볼 수 있다

자, 오늘은 이렇게 if, then, fi를 처리하는 코드를 작성해보았다! 다음에는 더 발전된 쉘을 만들어보자

profile
보안

0개의 댓글