Loki's Blog

쉘 내부 명령어와 외부 명령어

  • CS
  • 운영체제
2025-11-23

정의 및 특징


내부 명령어 (Built-in Commands)

쉘 프로그램 (/bin/bash/bin/zsh/bin/sh 등) 내부에 함수나 로직으로 이미 구현되어 있는 명령어다.

특징

  • 쉘 실행 파일의 코드 일부로 포함됨
  • 쉘이 시작될 때 메모리 (RAM) 에 함께 로드됨
  • 별도의 실행 파일로 존재하지 않음
  • 현재 쉘 프로세스 내부에서 직접 실행
  • 새로운 프로세스 생성 없음 (fork/exec 불필요)
  • PATH 환경 변수와 무관하게 항상 사용 가능

예시

cd          # 디렉터리 변경
export      # 환경 변수 설정
alias       # 별칭 설정
history     # 명령어 이력 조회
set         # 쉘 옵션 설정
source (.)  # 스크립트 실행
exit        # 쉘 종료
echo        # 텍스트 출력 (bash 내장)
pwd         # 현재 디렉터리 표시 (bash 내장)

외부 명령어 (External Commands)

별도의 실행 파일 (바이너리 또는 스크립트) 로 디스크에 저장되어 있는 명령어다.

특징

  • 독립적인 실행 파일로 존재 (/bin/usr/bin/usr/local/bin 등)
  • 실행 시 디스크에서 읽어와야 함
  • 새로운 프로세스를 생성하여 실행 (fork + exec)
  • PATH 환경 변수에서 파일 위치를 검색
  • 별도의 메모리 공간에서 실행

예시

ls          # 디렉터리 내용 표시 (/bin/ls)
cat         # 파일 내용 출력 (/bin/cat)
grep        # 텍스트 검색 (/bin/grep)
find        # 파일 검색 (/usr/bin/find)
gcc         # C 컴파일러 (/usr/bin/gcc)
python      # Python 인터프리터 (/usr/bin/python)

PATH 란?

PATH는 쉘이 외부 명령어(실행 파일)를 찾을 때 검색하는 디렉토리 목록을 담고 있는 환경 변수다.

# PATH 내용 확인
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

콜론(:)으로 여러 디렉터리 경로를 구분하고, 왼쪽부터 오른쪽 순서로 검색한다.

PATH=/첫번째/디렉터리:/두번째/디렉터리:/세번째/디렉터리

실행 방식의 차이


내부 명령어 실행 과정

코드 예시 (의사 코드)

// bash 내부 구조 (단순화)
int main() {
    while (1) {
        char *command = read_command();
 
        if (strcmp(command, "cd") == 0) {
            // 내부 명령어 - 직접 실행
            builtin_cd(args);  // 함수 호출
        } else if (strcmp(command, "export") == 0) {
            builtin_export(args);
        } else {
            // 외부 명령어 - fork/exec
            execute_external(command);
        }
    }
}
 
void builtin_cd(char *path) {
    if (chdir(path) != 0) {
        perror("cd failed");
    }
    // PWD 환경 변수 업데이트
    update_pwd();
}

외부 명령어 실행 과정

코드 예시 (의사 코드)

void execute_external(char *command, char **args) {
    // PATH에서 실행 파일 찾기
    char *path = find_in_path(command);  // "/bin/ls"
 
    pid_t pid = fork();  // 프로세스 복제
 
    if (pid == 0) {
        // 자식 프로세스
        execve(path, args, env);  // 프로그램 실행
        perror("exec failed");
        exit(1);
    } else {
        // 부모 프로세스 (쉘)
        int status;
        waitpid(pid, &status, 0);  // 자식 종료 대기
    }
}

왜 내부 명령어와 외부 명령어를 구분할까?


쉘 상태 변경 필요성

일부 명령어는 반드시 현재 쉘 프로세스의 상태를 변경해야 한다.

cd (디렉터리 변경)

내부 명령어로 구현해야 하는 이유

# cd가 만약 외부 명령어라면?
$ pwd
/home/user
 
$ cd /tmp  # 외부 프로세스에서 디렉터리 변경
$ pwd
/home/user  # 변경되지 않음!

왜 그럴까?

  • 외부 명령어는 자식 프로세스에서 실행
  • 자식 프로세스가 chdir() 를 호출해도 자식의 디렉터리만 변경
  • 부모 (쉘) 의 현재 디렉터리는 그대로
  • 자식이 종료되면 변경 사항도 사라짐

내부 명령어로 구현하면

$ pwd
/home/user
 
$ cd /tmp  # 쉘 프로세스 자체에서 디렉터리 변경
$ pwd
/tmp  # 변경됨!

export (환경 변수 설정)

내부 명령어로 구현해야 하는 이유

# export가 만약 외부 명령어라면?
$ echo $MY_VAR
(비어있음)
 
$ export MY_VAR=hello  # 외부 프로세스에서 환경 변수 설정
$ echo $MY_VAR
(여전히 비어있음)

왜 그럴까?

  • 환경 변수는 각 프로세스마다 독립적
  • 자식 프로세스가 환경 변수를 설정해도 부모에게 영향 없음
  • 자식이 종료되면 환경 변수도 사라짐

내부 명령어로 구현하면

$ echo $MY_VAR
(비어있음)
 
$ export MY_VAR=hello  # 쉘 자체의 환경 변수 설정
$ echo $MY_VAR
hello  # 설정됨!

속도와 효율성

외부 명령어 실행 비용

1. fork() 시스템: 프로세스 복제
2. PATH 검색: 디스크에서 파일 찾기
3. 파일 읽기: 디스크 I/O
4. 메모리 로드: 프로그램을 RAM에 적재
5. exec() 시스템: 프로그램 실행
6. exit()  wait(): 종료 처리

차이가 나는 이유

  • 내부 명령어: 메모리에서 직접 실행
  • 외부 명령어: fork, 디스크 읽기, exec 등의 오버헤드

RAM 에 상주시키는 이유


디스크 vs RAM 속도 차이

접근 속도 비교

RAM 접근 시간:    ~100 나노초 (0.0001 밀리초)
SSD 접근 시간:    ~0.1 밀리초
HDD 접근 시간:    ~10 밀리초
 
RAM이 SSD보다 1,000 빠름
RAM이 HDD보다 100,000 빠름

매번 디스크에서 읽는 경우의 문제

외부 명령어 실행 과정 (디스크 읽기 포함)

# 사용자가 ls를 100번 실행한다면?
$ ls  # 1번: 디스크에서 /bin/ls 읽기 (10ms)
$ ls  # 2번: 디스크에서 /bin/ls 읽기 (10ms)
$ ls  # 3번: 디스크에서 /bin/ls 읽기 (10ms)
...
$ ls  # 100번: 디스크에서 /bin/ls 읽기 (10ms)
 
# 총 1000ms (1초) 소요

문제점

  1. 매번 디스크 I/O 발생 → 느림
  2. 디스크 헤드 이동 시간 소요 (HDD 의 경우)
  3. 자주 사용하는 명령어일수록 비효율적

내부 명령어로 RAM 에 상주시키는 경우

쉘 시작 시 한 번만 로드

부팅/ 시작:
1. /bin/bash 실행 파일을 디스크에서 읽기
2. bash 코드를 RAM에 로드
3. 내부 명령어 함수들도 함께 RAM에 로드
 
이후 명령어 실행:
1. RAM에서 직접 함수 호출 (~0.0001ms)
2. 디스크 접근 불필요

장점

# 사용자가 cd를 100번 실행한다면?
$ cd /home  # 1번: RAM에서 직접 실행 (0.0001ms)
$ cd /tmp   # 2번: RAM에서 직접 실행 (0.0001ms)
$ cd /var   # 3번: RAM에서 직접 실행 (0.0001ms)
...
 
# 총 0.01ms (거의 즉시)

RAM 사용의 트레이드오프

장점

  1. 속도: 디스크 접근 없이 즉시 실행
  2. 효율성: 프로세스 생성 오버헤드 없음
  3. 응답성: 사용자 경험 향상

단점

  1. 메모리 사용: 쉘이 항상 RAM 공간 차지
  2. 유연성 제한: 쉘을 재시작하지 않고는 업데이트 불가

균형점

자주 사용하는 명령어내부 명령어 (RAM 상주)
- cd, export, alias, echo, pwd
-  메모리 사용: 매우 작음 (수백 KB)
 
가끔 사용하는 명령어외부 명령어 (디스크 저장)
- find, gcc, ffmpeg
- 필요할 때만 로드RAM 절약

페이지 캐시를 통한 최적화

실제로 외부 명령어도 자주 사용하면 빨라진다.

OS 의 페이지 캐시

 실행:
$ ls  # 디스크에서 읽기 (10ms)
      # OS가 /bin/ls를 페이지 캐시에 저장
 
이후 실행:
$ ls  # 페이지 캐시에서 읽기 (0.1ms)
$ ls  # 페이지 캐시에서 읽기 (0.1ms)

메커니즘

  1. 외부 명령어를 처음 실행할 때 디스크에서 읽음
  2. OS 가 자동으로 RAM 의 페이지 캐시에 저장
  3. 이후 실행 시 캐시에서 읽어옴 (디스크 접근 불필요)
  4. 메모리가 부족하면 자동으로 캐시 제거

장점

  • 자주 사용하는 외부 명령어도 빠르게 실행
  • 명시적 관리 불필요
  • 메모리 효율적 (자동 관리)

관련 글