42Seoul

Minishell - Built-in

millar 2023. 11. 9. 23:30

built - in

 

개요

 이번에는 Bash 셸 자체에 내장된 명령어인 built-in 명령어에 대해서 알아봅시다. 기본적으로 42 캠퍼스의 cluster에서 terminal을 키면 자동으로 Zsh이 실행되는데, bash 명령어를 통해 Bash를 실행시키고 built-in 명령어를 직접 입력하며 결과를 관찰하는 것이 좋습니다.

 

 이 글은 Bash의 명령어를 관찰하며 그를 기반으로 어떻게 7가지 built-in 명령어를 구현할지 탐구합니다. 과제에서 직접적으로, "의심이 있다면 Bash를 참고할 것."이라 방향을 제시했기 때문에, 기능을 추가하든 축소하든 일단 Bash 따라 만드는 것이 쉽습니다.

 

 각 명령어가 정상적인 실행을 하지 않은 경우, 에러 코드가 기록되는 것에 차이를 보이기 때문에, signal에 관한 부분을 빼놓을 수 없어 일부를 같이 담아 서술합니다.

 

 글 길이 상당히 길어질 것 같습니다. 제가 구분을 잘해놓을 테니, 필요한 부분만 얻어가시길 바랍니다.

 

이제 echo, cd, pwd, export, unset, env, exit 명령어를 순서대로 살펴봅시다.

명령어의 프로토타입은 Bash의 man page를 참고하여 설명드림을 미리 밝힙니다.


1. echo (with option -n) – write arguments to the standard output

echo [-n] [string ...] : 일련의 string을, 공백을 기준으로 출력합니다. -n 옵션은 마지막 출력의 줄 바꿈 여부를 결정합니다.

 

 echo는 옵션과 인자를 모두 가질 수 있습니다. 다만 옵션은 "-n"만 존재하므로 옵션으로 판별할 수 없는 경우 인자의 범주로 판단하게 됩니다. 다른 옵션이 없는데 굳이 -n 옵션으로 동작하라고 명시되어 있습니다. 다른 옵션이 있는 줄 알았어요.

 

 echo는 옵션이 존재하지 않으면 출력에 줄바꿈이 붙지만 -n 옵션은 이 줄 바꿈을 나타내지 않습니다.


인자를, 공백을 기준으로 출력하는 것은 어렵지 않습니다. echo에서 주목할 것은 옵션 판별입니다. 다음과 같이 헷갈리는 상황을 봅시다.

 

  1. echo -
  2. echo -a
  3. echo -na
  4. echo -n..........n ('.'은 'n'을 의미)
  5. echo -n -n -n ... -n ('.'은 '-n'을 의미)

 

 1, 2, 3번은 옵션으로 판단할 수 없는 경우입니다. 따라서 인자의 범주로 속하게 됩니다. 모습 그대로 출력이 되는 것이죠. 반면, 4, 5번은 옵션으로서의 자격을 갖춘 경우입니다. 저 둘은 출력되지 않고 옵션이 됩니다. 둘을 섞어 쓸 수도 있겠죠?

 

그럼 이러한 경우는 어떤지 생각해 봅시다.

 

1. echo -a abc -n

2. echo -n abc -n


위의 두 가지 경우는 echo의 프로토타입을 보면 쉽게 이해할 수 있습니다.

 

 1번은 -a부터 인자의 범주로 들어갔으니, 뒤에 옵션이 올 자리는 있을 수 없습니다. 따라서 3개의 인자가 공백을 기준으로 출력될 것입니다.

 

 2번은 처음으로 등장한 -n이 옵션의 역할을 하고 abc가 인자의 역할을 하므로 맨 뒤에 온 -n도 인자의 범주로 들어가게 됩니다.

 

 여기까지 이해하고 구현할 수 있다면 echo는 끝이라고 생각합니다. echo는 실패하는 경우가 없습니다. 잘못된 변수나 특수 문자를 잘못 사용했을 경우에 에러를 발생시킬 수 있다고 알고 있습니다만, 과제에서 허용한 특수 문자 이외에 해석할 이유가 없으므로 문제없을 것입니다.


2. cd (with only a relative or absolute path) - change the current working directory

cd [option] [directory] : 현재 디렉토리를 이동합니다. 다만 우리 쉘에서는 옵션을 구현하지 않습니다.

 

 cd는 허용함수, chdir 함수로 구현할 수 있습니다. 반드시 인자로 상대 경로 혹은 절대 경로가 온다고 못을 박았으므로 특수 변수(~, -) 등을 구현하지 않으셔도 됩니다. 평가자에 따라서는 인자가 없는 cd 구현도 할 필요가 없다고 들었던 바가 있습니다.

 

웬만한 관찰력으로는 cd가 어디까지 작동하는지 잘 모를 것입니다. 평가를 준비하면서 cd를 완벽하게 구현했다고 생각했는데, 이런 기능까지 있을 줄 몰랐습니다. 그럼, cd 구현 전 알아야 할 것은 어떤 것들이 있을까요?


1. 경로의 최대 길이? 

2. 인자가 2개 이상 들어온 경우

 3. 환경 변수 HOME이 제거되었을 경우

 4.  환경 변수 OLDPWD와 PWD의 수정

 

 우선 첫 번째, 경로의 최대 길이가 존재합니다. 다만, 시스템 아키텍처마다 다릅니다. 환경마다 다를 수 있다는 것이죠. 이는 <limit.h>의 PATH_MAX 변수에 관해서 공부해 보세요.

두 번째, cd는 인자가 여러 개 들어와도 반드시 첫 번째 인자만 봅니다. 두 번째 인자가 올바르게 들어왔던 아니던, 첫 번째 인자의 옳고 그름만을 따지고 명령어가 수행됩니다.

 

 세 번째, 환경 변수 HOME이 제거되었을 경우에는 일반적으로 잘못된 경로를 입력했을 때와 다른 에러 출력문이 발생합니다. 하지만 에러 코드는 동일합니다.

 

 네 번째가 중요한 부분입니다. cd 명령어와 환경 변수를 같이, 면밀히 보지 않았다면 놓치기 쉬운 부분입니다. 눈치채기만 어렵지 구현은 쉽습니다. cd를 사용할 때마다 이전 경로 OLDPWD와 현재 경로 PWD를 수정해 주셔야 합니다.

 

마지막으로 잘못된 경로로 인해 디렉토리 이동에 실패했을 경우, 에러 코드($?) 1을 지정해야 합니다.

 

PWD와 OLDPWD를 지우고 cd 명령어를 수행하면 다시 변수가 등록될까?

 

다행히도 그렇지 않았습니다.


3. pwd (with no options) - return working directory name

pwd [-L | -P] : 현재 디렉토리의 경로를 표시합니다. 우리 쉘에서는 옵션을 구현하지 않습니다.

 

 pwd는 프로토타입에서 볼 수 있듯이 옵션만 받습니다. Bash의 pwd에서는 잘못된 옵션일 경우 에러가 발생하고, 인자는 전부 무시하는 것을 볼 수 있습니다.

 

 따라서, 우리 쉘의 pwd는 실패할 경우를 고려하지 않아도 됩니다. 그럼 매우 쉬워 보이네요. 뭔가 더 있을까요? 제가 구현 전 생각해 본 한 가지가 있습니다.

 

환경 변수 PWD를 제거하면 pwd 명령어가 고장 날 수도 있지 않을까?

 

다행히도 그렇지 않았습니다.


4. export (with no options) - set environment variable

export [option] [equation string] : 시스템의 환경 변수를 표시하거나 설정, 삭제할 수 있습니다. 우리 쉘에서는 옵션을 구현하지 않습니다.

 

 export는 개인적으로 가장 까다로운 명령어였습니다. 환경 변수와 직접적으로 관련있는 명령어인 데다가 알아야 할 예외적인 경우가 많았거든요. 또한, env와 비교되는 명령어입니다. 가장 간단한 차이는 표시 형식도 있겠지만, export는 사전 순으로 정렬된다는 점이 있습니다.

 

 export는 인자로 방정식 문자열을 받습니다. 방정식은 "키 =  값" 형식을 따릅니다. 키는 반드시 다음과 같은 을 따릅니다.

  • 키는 첫 문자가 '_'(언더 바), 알파벳으로 시작해야 합니다.
  • 이후 키는 알파벳과 숫자로 구성될 수 있습니다.
  • "="문자와 값은 입력하지 않아도, 환경 변수는 키만 등록될 수 있습니다.
  • 허용되지 않은 문자가 키를 이룰 경우 에러 코드 1입니다.

그럼 헷갈리는 경우를 살펴봅시다.

1. "export a" vs "export a="

2. 여러개의 인자가 들어오는 경우에서 키룰 에러 발생

3. 환경 변수 선언만 한 뒤, 커맨드 라인에서 값을 변경하기.

 

 첫 번째 경우 두 명령어의 차이가 있습니다. "등호가 있고 없고 차이이지만 둘다 값이 없는 것은 동일하지 않느냐?" 네 맞는 것 같습니다. 저도 차이를 발견하지 못했는데 답은 env 명령어에 있었습니다. export에서는 전자, 후자 모두 표시되나, env에서 전자는 표시되지 않지만, 후자는 env에서 표시됩니다.

 

 두 번째, export는 여러개의 환경 변수를 동시에 선언할 수 있습니다. 순서대로 처리하지 않습니다. 다음을 봅시다.

 

  1. export a=1 2=2 c=3 4=4
  2. export a=1 b=$a

 먼저 1번, 명령어를 실행하면, "2=2"와 "4=4"에서 키룰 에러가 발생하고 인자 순서대로 에러 출력문이 나타납니다. 그래서 인자를 순서대로 처리한다고 착각할 수가 있습니다. 또한, 키룰 에러가 한 번이라도 발생하면 에러 코드 1을 남깁니다.

 

 정말 순서대로 처리했다면 2번에서 a, b는 동일한 값을 가져야 합니다. 그런데 그렇지 않습니다. 따라서 동시에 인자를 처리한다고 추측할 수 있습니다. -수정합니다-

 순서대로 처리하지 않은 것은 맞습니다만, 동시에 인자를 처리한다는 표현이 모호했습니다. 정확한 표현은 '인자가 처리되는 시점이 export 명령어가 적용되는 시점'이라고 정정합니다. 따라서 2번의 경우에 b가 a의 값으로 적용되는 것은 export명령어가 완전히 수행된 후 라는 것입니다.

 

 세 번째, export에서 선언한 환경 변수를 커맨드 라인에서 바로 변경할 수 있습니다. export 명령어는 선언만 할 때 필요할 뿐, 선언 이후에 값은 커맨드 라인에서 수정 가능하다는 것입니다.

 

 이것이 가능한 이유는 '='기호가 특수 문자의 역할을 하기 때문인데, 과제에서 허용한 문자가 아니므로, 취향에 따라 선택하시길 바랍니다.


5. unset (with no options) - delete environment variable

unset [option] [environment name] : 환경 변수를 제거합니다. 우리 쉘에서는 옵션을 구현하지 않습니다.

 

 설명한 pwd처럼 옵션이 틀릴 경우에 에러가 발생하고 인자에 대해서도 에러가 발생할 수 있습니다. 인자에서 에러가 발생하는 경우는 환경 변수의 이름이 키 룰에 반하는 경우뿐입니다. 에러 코드 1을 남깁니다.

 

 하나 알아둘 것은 export와 마찬가지로 unset도 환경 변수를 동시에 제거한다는 것입니다. 다음 예시를 봅시다.

 a가 먼저 제거된다면 $a를 해석할 수 없을 것입니다. 하지만 결과는 HOME과 a가 모두 제거되는 것을 확인할 수 있습니다. 따라서 동시에 인자를 처리합니다.


6. env (with no options or arguments) - return environment variable list

env [-0iv] [-u name] [name=value ...]

env [-iv] [-P altpath] [-S string] [-u name] [name=value ...]

         utility [argument ...]

 

옵션과 인자를 구현하라고 했으면 어쩔뻔 봤습니까?

 

환경 변수 나열만 합시다! 저는 과제를 자주자주 안 봐서 인자 구현을 해버렸습니다. export에서 설명한 것처럼 등호 없이 선언된 환경 변수 선언만 제외하고 출력하도록 합시다.


7. exit (with no options) - exit current shell

exit [n] : 현재 쉘을 종료합니다. 인자로 특정 종료 상태를 나타낼 수 있습니다. 인자의 범위는 [0 ~ 255]이며 unsigned char형 범위인 것을 알 수 있습니다.

 

exit도 인자를 받기 때문에, 그것도 숫자로 받기 때문에 생각하고 처리해야 할 상황이 많습니다. 다음과 같은 경우를 생각해 볼 수 있습니다.


1. unsigned char를 넘어가는 범위

2. 숫자가 아닌 경우

3. 2개 이상의 인자

 

 첫 번째부터 알아봅시다. exit에 0 ~ 255 범위 밖의 수를 인자로 넣어도 결국, 종료코드가 0 ~ 255의 수로 나타나는 것을 볼 수 있습니다. 오버플로우가 발생한다는 것이죠. 그럼, 어디까지 오버플로우가 발생할까요? 이는 2번에서 이어집니다.

 

 두 번째, 인자가 숫자가 아닌 경우 "numeric argument required"가 발생합니다. 또한 exit의 종료코드가 long의 자료형을 넘어가도 동일한 에러가 발생합니다. 

종료코드는 255로 동일합니다.

 

 세 번째가 경우가 좀 갈립니다. 인자가 2개 이상일 때 일반적으로 "too many argument"가 발생합니다만, 두 가지 경우로 구분됩니다. 첫 번째 인자가 어떤 범주에 해당하는지로 갈린다는 의미입니다.

  1. 가능 범위의 숫자로 인자가 시작했을 경우 - too many arguments : error 1
  2. 아닌 경우 - numeric argument required : error 255

또한 "too many arguments"는 현재 쉘을 종료하지 않습니다.

 

여기까지 입니다. 수고하셨습니다.


마치며

작성한 글은 Bash 3.2에 기반합니다. 클러스터의 Bash와 동일합니다. 제가 서술한 내용 말고도 built-in이 더 많은 기능을 할 수도 있습니다. 하지만 확인한 사항에 관해서는 직접 실행하고 관찰한 경험이므로 틀림이 없을 것입니다.