🧠
Hi, Daehuyn Lee
  • Fork-my-brain
  • Network
    • 7. "데이터가 전달되는 원리" OSI 7계층 모델과 TCP:IP 모델
    • [Netwhat] 연습문제 정리
    • 11. IP 라우팅(routing) 동작 과정
    • 3. IP address 란?
    • 2. 컴퓨터 구조를 통해 이해하는 파일(File)과 소켓(Socket)
    • 10 "더 편리한 인터넷을 위해" DHCP && DNS 프로토콜
    • 9. 데이터? 세그먼트? 패킷? 헷갈릴 땐 PDU를 알아보자
    • 8. TCP 와 UDP 차이를 자세히 알아보자
    • 5. 서브넷팅(subnetting)으로 네크워크를 효율적으로 관리하자
    • 4. 넷마스크(Netmask)와 서브넷마스크(Subnetmask)
    • 1. 비유로 이해하는 컴퓨터 구조
    • 6. 공인(Public) && 사설(Private) IP의 차이점
  • Django
    • [Django 1] 가상환경에 Django 설치하기
    • [Django 3] Hello World 웹사이트 만들기
    • [Django 9] static 으로 css 로드하기
    • [Django 10] 한 템플릿에서 복수의 css 파일 적용하기
    • [Django 11] URL app별로 관리하기
    • [Django 8] 템플릿 상속
    • [Django 4] MTV 패턴
    • [Django 6] 블로그 model 만들기
    • [Django 2] Django는 어떻게 작동할까
    • [Django 7] '새 글 작성' 기능 만들기
    • [Django 5] 템플릿 언어
  • Projects
    • 예발자닷컴
      • 4. 프론트엔드의 역할은 어디까지 - 더미데이터 만들기
      • 7. [React 리팩토링] CSS Inline Styling에 Props 사용하기
      • 6. [React 리팩토링] JSX에서 조건문 사용해 렌더링하기
      • 3. 예발자닷컴 프론트서버 업데이트 하기
      • 8. [React 리팩토링] 예발자 프로젝트에 Redux 적용하기
      • 5. [React 리팩토링] JSX로 HTML 렌더링하기
      • 1. 👨‍👨‍👦‍👦 Github로 협업 프로젝트 관리하기
      • 2. React Component를 활용한 웹페이지 디자인 연습
  • Git
    • [Git] Interactive Rebase 실습
    • 오픈소스 개발 참여에 필요한 Git 명령어 정리
    • 개발자가 오픈소스를 읽는 방법
    • 오픈소스 프로젝트 시작하기
    • SSH agent ; Passphrase 입력 없이 Push하기
    • SSH로 원격저장소 접속하기
    • [Github] 개인 저장소를 팀 저장소로 변경하기
    • GitHub Dependabot
    • Git add, commit, push 취소하기
    • 깃헙 잔디 관리 팁
    • 원격저장소 여러개 연결하기
    • Typora(마크다운 에디터) 사용법
  • C
    • C Piscine
      • 메모리 구조를 알아보자
      • Makefile 만들기
      • GCC로 정적 라이브러리 파일 만들기
      • 외부 라이브러리 GCC로 컴파일 하기
      • 정적(Static) 변수
      • 저수준 파일 입출력
      • Makefile 자주 사용하는 문법 정리
      • segmentation fault 해결하기
      • C의 구조체 개념
      • 연결 리스트(linked list)에서 이중 포인터 사용하기
      • 로컬에 Norminette 설치하기
    • GetNextLine
      • [GetNextLine] 과제소개-Reading a line on a fd is way too tedious
      • [GetNextLine] 삽질의 기록
      • [GetNextLine] 리팩토링-프로그램의 목적을 고려한 코드
    • ft_printf
      • 1. 과제소개
      • 2. 가변인자 (Variadic Arguments)
      • 3. 형식태그와 서식지정자 printf 함수의 옵션 알아보기
    • Libft
      • [Libft] Bonus
      • [Libft] Test Program
      • [Libft] 나만의 C 라이브러리 만들기
      • [Libft] Part 2
      • [Libft] Part 1
  • UNIX shell
    • [minishell] 4. 종료상태와 에러메세지 처리
    • [minishell] 1. 과제소개 및 선행지식
    • [minishell] 2. 프로그램 구조 및 개발 기록들
    • [minishell] 5. 파이프(Pipe) 처리
    • [minishell] 3. 시그널(Signal) 처리하기
    • [minishell] 6. 리다이렉션(Redirection) 처리
  • Web
    • Next.js
      • [Next.js] CSS모듈과 복수의 class 사용하기
    • Node.js
      • [Node.js] 웹페이지에 파일 띄우기
      • [Node.js] URL에서 쿼리스트링 추출하기
      • [Node.js] '새 글 작성' 페이지 만들기
    • React
      • [React] 2. 컴포넌트(Component) 생성 및 파일별로 분리하기
      • [React] 1. 파일 구조 이해하기
      • [React] 4. 컴포넌트의 State 란
      • [React] 3. 컴포넌트의 Props 란
    • Javascript
      • Click, Enter 두 개의 이벤트 동시에 등록하기
      • Click eventListener 등록하기
      • JavaScript & C 문법 비교
      • JavaScript 객체 지향의 특징
    • CSS
      • [CSS] box-model, display, position
  • Docker
    • ft_server
      • 2. 도커 설치부터 워드프레스 구축까지
      • 1. 선행지식-Docker? Debian Buster? Nginx? ...
      • 3. Dockerfile 만들기
  • Kubernetes
    • 🌌[쿠버네티스 아키텍처] 3. API 호출
    • 🌌[쿠버네티스 아키텍처] 1. 구성 및 설계
    • 🌌[쿠버네티스 아키텍처] 2. 오브젝트 (Objects)
  • Operating System
    • Philosophers
      • [Philosophers] 예시예제로 보는 뮤텍스와 세마포어의 차이
      • [Philosophers] 식사하는 철학자 문제 소개
  • CPP
    • [CPP-08] STL containers, iterators, algorithms
    • [CPP-06] CPP 형변환 연산자
    • [CPP-04 ex02] 인터페이스(Interface) 클래스
    • [CPP-04 ex00] 다형성(Polymorphism) 및 가상함수
    • [CPP-02] Canonical 클래스 복사 생성자와 대입 연산자 오버로딩
    • [CPP-07] Templates
    • [CPP-01] this 포인터와 문자열 스트림(stringstream)
    • [CPP-01] 클래스의 정적할당과 동적할당 new, delete
    • [CPP-01] 파일 입출력 및 문자열 치환하기
    • [CPP-01] 참조자(reference)와 포인터는 다르다
    • [CPP-02] 정수부동소수값 - 고정소수값 변환
    • [CPP-04 ex01] 추상 클래스의 필요성 순수 가상함수
    • [CPP-00] Megaphone! CPP 표준입출력
    • [CPP-03] (ClapTrap이 뭐지) 다중 상속과 가상 상속
    • [CPP-05] 예외 처리 (exception handling)
    • [CPP-00] 객체지향의 관점으로 클래스 이해하기
    • [CPP-01] 랜덤값 얻기
  • IBM Cloud
    • [IBM Cloud] 1. 클라우드 컴퓨팅 개요
    • [IBM Cloud] 5. 클라우드 컴퓨팅의 구성 요소
    • [IBM Cloud] 3. 클라우드 서비스 모델 및 배포 모델
    • [IBM Cloud] 2. 클라우드를 활용하는 새 기술들
    • [IBM Cloud] 4. 떠오르는 클라우드 트렌드
    • [IBM Cloud] 6. 클라우드 스토리지 유형 및 CDN
  • Assembly
    • [libasm] 어셈블리 프로그램 구조와 x64 레지스터 이해하기
    • [libasm] strlen 함수를 어셈블리어로 짠다면
    • [libasm] 어셈블리 명령어(opcode) 정리
Powered by GitBook
On this page
  • 1. 과제 소개
  • 1.1. Instructions
  • 1.2. Allowed Functions
  • 2. 선행지식
  • 1. shell 개요
  • 2. 허용함수
  • 3. 환경변수
  • 4. 연결리스트

Was this helpful?

  1. UNIX shell

[minishell] 1. 과제소개 및 선행지식

미니쉘 과제를 시작하기 전 알고있으면 좋을 Shell의 구성요소, Shell에서 프로세스를 시작하는 방법, 허용함수 동작방식을 정리했습니다.

1. 과제 소개

The objective of this project is for you to create a simple shell. Yes, your own little bash or zsh. You will learn a lot about processes and file descriptors.

With Minishell, you’ll be able to travel through time and come back to problems people faced when Windows didn’t exist.

1.1. Instructions

  • You must program a mini UNIX command interpreter.

  • 이 인터프리터는 명령 프롬프트(예를 들면 $>)를 띄워야하고, 사용자가 enter 키를 눌러 명령줄(command line)을 입력할 때 까지 기다려야 한다.

    • 프롬프트는 명령이 완전히 실행된 후에만 다시 표시된다.

  • PATH 변수 및 상대/절대 경로에 기반한 실행 파일(The executable)을 올바르게 찾아 실행한다.

    • 실행 파일을 찾을 수없는 경우 오류 메시지를 표시하고 프롬프트를 다시 표시해야 한다.

  • 다음과 같은 내장 기능을 구현한다.

    • echo (with option -n)

      • 새로 개행하지 않고 출력하게 한다.

    • cd (with only relative or absolute path)

    • pwd

    • export

    • unset

    • env (without any options and any arguments)

    • exit

  • ; 로 명령어를 분리할 수 있어야 한다.

  • 다음 기능을 일반 bash와 동일하게 동작해야하도록 구현한다.

    • multiline commands를 제외한 ' , "

      • ' 또는 " 가 홀수개로 들어오면 사용자 입력을 기다리게 되는데 이건 구현 안해도 된다는 뜻.

    • file descriptor aggregation를 제외한 리다이렉션(<, >, >>)

    • 파이프(|)

    • 환경변수($ followed by characters)

    • $?

    • ctrl-C, ctrl-D, ctrl-\

1.2. Allowed Functions

  • malloc, free

  • read, write, open, close,

  • opendir, readdir, closedir

  • getcwd, chdir

  • stat, lstat, fstat

  • fork, execve

  • wait, waitpid, wait3, wait4

  • signal, kill

  • exit

  • strerror, errno

  • dup, dup2, pipe

2. 선행지식

1. shell 개요

1.1. Shell

셸은 컴퓨터와 상호 작용할 수 있는 응용 프로그램이다. 셸에서 사용자는 프로그램을 실행할 수 있으며, 입력과 출력을 파일에서 가져오도록 리디렉션할 수도 있다. 셸은 또한 함수, 변수 등과 같은 프로그래밍 구조를 제공한다. 셸 스크립트라고 불리는 셸 프로그램은 편집, 기록, 파일 완성, 와일드카드, 환경 변수 확장 및 프로그래밍 구성과 같은 기능을 제공한다.

1.2. Shell의 구성요소

셸을 구현하는 작업은 다음 네 부분으로 나뉜다.

  • Lexer : 소스코드를 토큰 단위로 분석한다.

    • token : 쉘에서 입력을 처리하기 위해서는 적절한 단위로 명령문을 나눠야 한다. 이때, 명령문을 나누는 최소 단위를 토큰(token)이라고 한다. 토큰은 국어의 형태소와 비슷한 개념이다. 형태소가 문장을 이루는 의미를 가진 가장 작은 요소인 것과 같이 토큰은 의미를 가지는 글자끼리 모아둔 소스코드를 이루는 가장 작은 요소이다.

      token 예)

      10*2+3

      위 코드를 다음과 같이 쪼갤 수 있어야 한다.

      NUMBER 10
      STAR *
      NUMBER 2
      PLUS +
      NUMBER 3
  • Parser : la -al과 같은 명령을 읽은 뒤 Command Table(명령 테이블)이라는 데이터 구조에 삽입해 실행될 명령을 저장한다.

  • Executor : 명령 테이블의 모든 명령에 대해 새 프로세스를 생성한다. 필요한 경우 파이프(|)를 생성하여 한 프로세스의 출력을 다음 프로세스의 입력으로 전달한다. 또한 표준 입력, 표준 출력 및 표준 오류를 리디렉션(>, <, >>)한다.

  • Shell Subsystems

    • Environment Variables(환경변수) : ${VAR} 로 환경변수를 불러올 수 있다. 셸은 환경변수를 설정, 확인 및 출력할 수 있어야 한다.

    • Wildcards(와일드카드) : * 은 문자열 와일드 카드이다. 해당 디렉토리에서 내용이 일치하는 모든 파일을 불러온다.

    • Subshells : ( ), $( ), |, & 를 이용해 한 명령의 출력값을 새 명령의 입력값으로 활용할 수 있다. 이렇게 명령을 실행시킬 때 생성되는 shell을 subshell 이라고 한다.

1.3. Shell 에서 프로세스를 생성하는 방법

프로세스를 시작시키는 것이 셸의 주요 기능임을 알았으니, 프로세스의 시작 방식과 진행 상황을 정확하게 알고 있어야한다. Unix에서 프로세스를 시작하는 방법은 두 가지 뿐이다.

  • Init

  • fork() : 대부분의 프로그램은 Init이 아니기 때문에 프로세스를 시작하는 실질적인 방법은 fork() syscall 뿐이다. 이 기능이 호출되면 운영 체제가 부로 프로세스로부터 자식 프로세스를 복제하여 두 프로세스를 병렬로 실행한다. 즉, 본질적으로 프로세스를 시작하는 유일한 방법은 복제 뿐이다.

    • 정리하자면,

  • 기존 프로세스는 두 개의 분리된 프로세스로 분기된다.

  • 그런 다음 자식 프로세스는 exec()을 사용하여 자신을 새 프로그램으로 바꾼다.

  • 부모 프로세스는 다른 작업을 계속 수행할 수 있으며 wait()를 사용하여 자식 프로세스를 계속 감시할 수도 있다.

2. 허용함수

2.1. fork()

fork()는 현재 실행중인 process를 복사해서 다른 process를 생성한다. 복사해서 생성하기 때문에, 가지고 있던 메모리등의 시스템 자원을 모두 원래의 process와 공유하게 된다.

fork()를 사용하여 생성한 프로세스는 부모 프로세스 Parent process, 새로 생긴 프로세스는 자식 프로세스 Child process 라고 부른다.

모든 프로세스는 (참고: 최상위 프로세스인 init는 pid 1을 가진다) 생성될 때 프로세스 아이디를 부여받는다. fork() 함수는 부모에게는 자식 프로세스의 pid를 반환하고, 자식에게는 0을 반환한다. 이를 이용하여 자식 프로세스에게 특정 명령을 시킬 수 있다.

주의: pid 는 변수명이지 실제 pid 를 의미하지 않는다. 따라서 getpid() 를 사용해야 이 함수를 부른 프로세스의 id 를 얻을 수 있다.

참고

2.2. wait, waitpid, wait3, wait4

fork 함수로 자식 프로세스를 생성하면 부모 프로세스와 자식 프로세스는 순서에 관계 없이 실행되고, 먼저 실행을 마친 프로세스는 종료한다. 이때 좀비 프로세스(zombie procss)같은 불안정 상태의 프로세스가 발생하는데 이를 방지하려면 프로세스 동기화 함수를 수행해서 부모 프로세스와 자식 프로세스를 동기화 시켜야한다.

프로세스 동기화 함수로 사용하는 것이 wait 계열 함수이다.

함수 원형

기능

pid_t wait(int *stat_loc);

임의의 자식 프로세스의 상태값 구하기

pid_t waitpid(pid_t pid, int *stat_loc, int options);

특정 프로세스의 상태값 구하기

동기화 후에는 자식 프로세스는 if(pid == 0)일 경우의 구문을 수행한 뒤 종료하며, 부모 프로세스의 경우엔 wait()를 통하여 자식 프로세스가 종료된 뒤 나머지 구문을 수행한 뒤 종료한다.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
  pid_t pid;

    pid = fork();
    if (pid == 0)
    {
        printf("자식 프로세스\n");
       // exit(0);  
    }
    if (pid > 0)
    {
        printf("Wait\n");
        //wait(NULL);
        printf("Exit\n");
    }
  return 0;
}

undefined

2.3. execve()

우리가 명령어로 생각하는 'ls', 'echo'등은 실은 $PATH 경로 안에 있는 실행파일이다. 즉 프로그램을 실행해 명령어를 사용한다는 뜻이다.

int execve(const char *path, char const **argv, char const **envp)

path에 지정한 절대경로명의 파일을 실행하며 argv, envp를 인자로 전달한다. argv와 envp는 포인터 배열이다. 이 배열의 마지막에는 NULL 문자열을 저장해야 한다. 첫번째 인자는 새 프로세스 파일의 경로. 두번째 인자는 프로그램 명. 더블 포인터인 이유는 int main(int argc, char **argv)에서 argv[0]이 프로그램 이름이었던 것과 같은 원리

참고

2.4. signal()

sig_t signal(int sig, sig_t func);

sig 는 시그널 번호, func 는 해당 시그널을 처리할 핸들러.

  • Signal이란 Software interrupt로, process에 무엇인가 발생했음을 알리는 간단한 메시지를 비동기적으로 보내는 것이다.

  • 시그널을 받았을 때

    시그널은 고유의 의미를 내포하고 있다. 이러한 시그널을 받은 실행객체인 프로세스는 그에 맞는 행동을 해야 한다. 시그널을 받은 프로세스는 다음중 한가지 행동을 취해야 한다.

    1. 그 시그널을 처리할 등록된 함수(handler)를 호출한다.

    2. 시그널을 무시한다.

    3. 시그널을 무시하지 않지만, 그렇다고 해서 특별히 함수를 호출하지도 않는다.

  • 시그널 종료는 다양하고 signal.h에 정의 되어있다.

키보드 입력으로 발생시킬 수 있는 시그널은 Ctrl+C 외에도 아래의 몇가지가 있다.

Ctrl+C

SIGINT

프로세스를 종료시킨다.

Ctrl+Z

SIGSTP

프로세스를 중단시킨다.

Ctrl+\

SIGQUIT

core dump를 남기고 프로세스를 종료시킨다.

ctrl+D

"end of file"을 의미한다. 터미널이 입력 상태이고, 라인의 맨 처음일 때에만 작동한다. (‘\0’를 STDIN에 입력하는 것)

일반적으로 프로세스의 경우 SIGINT(Ctrl+C) 시그널을 통하여 수행중인 프로세스(터미널)를 종료시킬 수 있지만 minishell의 경우엔 우리가 만든 minishell만 종료되고 터미널은 여전히 살아있도록 해야한다. 이런식으로 시그널을 받은 프로세스가 취할 행동을 바꿔주는게 handler 함수이다.

  • 하지만 fork()를 통하여 일반 명령을 수행하는 자식 프로세스의 경우엔 작업 도중에 수행을 중단시킬 수 있어야 하므로 자식 프로세스의 경우에 한해서만 SIGINT를 DEFAULT로 설정한다.

  • 자식 프로세스가 백그라운드로 수행중일 때는 쉘의 뒤편에서 암묵적으로 수행하는 프로세스이므로 SIGINT 시그널을 무시하도록 설정한다.

참고

3. 환경변수

환경변수(Environment variable)은 쉘에서 참조하는 변수이다. 쉘에서 참조하는 변수는 크게 쉘 변수와 환경변수로 나누어 지는데, 환경변수와 쉘 변수의 가장 큰 차이점은 child process을 생성할 때 환경변수는 상속이 되는 반면 쉘 변수는 그렇지 않다는 점이다.

한편 자식프로세스, 쉘 스크립트에서 생성한 환경변수는 부모프로세스에서 참조할 수 없다. Unix 시스템은 자식 프로세스가 부모 프로세스의 값을 바꿀 수 없기 때문이다. 반대로 부모 프로세스는 자신의 값을 바꾸고 자식 프로세스에게 전달할 수 있다. 아래 예제를 참고하면 이해에 도움이 된다.

 $ cat a.sh
   #/bin/bash
   export VAR="abcd"
   echo $VAR
 $ ./a.sh
 abcd
 $ echo $VAR

 $

3.1. 환경변수 관련 명령어

3.1.1. export

  • 전체 환경변수 목록 확인

    > export
  • 특정 환경변수 값 확인

    > echo $SHELL
  • 환경변수 값 설정

    > export 키=값
  • 쉘 변수를 환경 변수로 변경

    > NAME=value
    > export NAME

3.1.2. env

export 가 bash의 빌트인명령 이라면, env는 하나의 프로그램이다. 우리가 env를 호출하면 다음과 같은 일이 순차적으로 진행된다.

  1. env가 새 프로세스로 실행된다.

  2. 인자로 들어온 명령을 호출한다. env 프로세스는 명령의 프로세스로 대체된다.

Example:

env GREP_OPTIONS='-v' grep one test.txt

This command will launch two new processes: (i) env and (ii) grep (actually, the second process will replace the first one).

다만 minishell 과제에서는 env 를 다른 추가 옵션이나 인자 없이 구현하기 때문에, 현재 지정되어 있는 환경변수 목록을 출력하기만 하면 된다.

3.1.3. unset

  • 변수 제거

    > str="hello world"
    > echo $str
    > hello world
    > unset str
    > echo $str
    
    >

3.2. char **envp in main

int main(int argc, char **argv, char **envp)
  • argc - 명령행 인자 개수

  • argv - 명령행 인자 벡터

  • envp - 환경변수 목록

    • 환경 변수 목록의 각 항목은 키와 값으로 구성하고 =로 구분한다.

    • 맨 마지막 항목 뒤는 NULL

3.3. 대표적인 환경변수 목록

$BASH        사용하는 bash 쉘 경로
$COLUMNS     터미널 컬럼 수
$DISPLAY     X 디스플레이 이름
$EDITOR      기본 편집기
$HISTFILE    history 파일 경로
$HISTSIZE    history에 저장되는 개수
$HOME        사용자 홈 디렉토리
$HOSTNAME    호스트 이름
$LANG        기본 언어
$LINES       터미널 라인 수
$LOGNAMES    로그인 이름
$MAIL        메일을 보관하는 경로
$MANPATH     man 페이지 경로
$OSTYPE      운영체제 타입
$PATH        실행 파일 경로
$PS1         명령 프롬프트변수
$PWD         현재 작업 디렉토리
$SHELL       로긴 쉘
$TERM        터미널 타입
$UID         사용자 UID
$USER        사용자 이름
$VISUAL      Visual 편집기

4. 연결리스트

우리 팀의 미니쉘에서는 사용자에게 입력받은 command line를 저장하기위해 연결리스트를 사용했다.

  • 연결리스트는 자기 참조 구조체에 속한다.

    • 자기 참조 구조체 : 자신의 구조체를 가리키는 포인터를 멤버로 가진다.

  • 연결리스트 : 구조체 변수를 포인터로 연결한 것. 첫 번째 변수의 위치만 알면 나머지 변수는 포인터를 따라가 모두 사용 가능. 단방향과 양방향의 연결리스트로 나뉨.

42seoul의 첫 과제인 Libft의 t_list 연결리스트를 예로 들어서 설명하면 아래 코드와 같다.

typedef struct    s_list
{
    void                    *content;
    struct s_list    *next;
}                                t_list;

t_list *head = ft_lstnew(NULL);
t_list *cur_proc = head->next;

//content에 값(**cmdline)을 담는 과정은 생략

while(cur_proc != NULL)
{
  printf("%s", cur_proc->content->cmdline[0]); 
  cur_proc = cur_proc->next;
}
  • cur_proc이 노드2을 가리키도록 초기화한다.

  • 헤드포인터가 첫번째 노드의 시작주소를 계속 갖고 있도록 보존하기 위해 cur_proc을 따로 쓴 것이고, 노드1인 head의 content는 NULL로 남겨둔다.

  • cur_proc이 담는 주소값을 다음 노드로 변경함으로서 cur_proc의 현재 노드를 갱신한다.

그 외 Shell의 내장 함수, 시그널, 파이프, 리다이렉션 등의 minishell을 구현하면서 알아야 할 중요한 개념들은 구현을 시작하면서 다음 글들에서 따로 정리했다.

Previous[minishell] 4. 종료상태와 에러메세지 처리Next[minishell] 2. 프로그램 구조 및 개발 기록들

Last updated 3 years ago

Was this helpful?

parent process 에서 설정한 변수나 함수는 export 해야지만 child process 에서 사용할 수 있다. 하지만 subshell 에서는 export 하지 않아도 사용할 수 있는 것이 특징이다.

시그널에 대한 더 자세한 내용은 에 따로 정리했습니다.

각 명령어에 대한 자세한 내용은 에서 더 자세하게 설명해놓았습니다.

출처 :

출처
반효경 운영체제 8. Process Management 1
반효경 운영체제 9. Process Management 2
https://hahahia.tistory.com/138
https://www.it-note.kr/157
다음 글
https://www.joinc.co.kr/w/Site/system_programing/Book_LSP/ch06_Signal
만약 source 로 실행한다면 ?
다음 글
What's the difference between set, export and env and when should I use each?