셸이 명령을 해석하고 프로세스를 실행하는 방식

셸의 명령 해석, 프로세스 실행, 입출력 재지정, 파이프, 환경 변수와 스크립트 동작 방식을 정리한다.

2026-08-14 · 최초 발행 2026-01-12

커널 앞단에서 명령을 받는 셸

셸(Shell)은 사용자와 운영체제 커널 사이에서 명령을 해석하고 실행하는 명령줄 인터페이스(CLI, Command-Line Interface)다. 입력된 명령을 파싱한 뒤 실행 파일을 찾고, 필요한 프로세스를 만들며, 입출력 재지정과 파이프로 프로그램 사이의 데이터 흐름을 구성한다.

셸은 단순히 명령을 전달하는 창이 아니다. 환경 변수를 관리하고, 스크립트로 반복 작업을 자동화하며, 작업 제어(Job Control)를 통해 포그라운드와 백그라운드 프로세스를 다룬다. cd, echo, export처럼 별도 프로세스를 만들지 않고 셸 안에서 처리되는 명령도 있다.

‘Shell’은 커널이라는 핵심을 감싸는 외부 계층이라는 뜻에서 나온 이름이다. 사용자는 텍스트 기반 프롬프트에서 명령을 입력하고, 셸은 이를 읽고 해석해 실행 결과를 다시 출력한다. 이 대화형 흐름은 REPL(Read-Eval-Print Loop) 형태로 반복된다.

명령어 입력시스템하드웨어 제어결과반환출력사용자Shell(명령어 해석기)Kernel(운영체제 핵심)하드웨어

명령줄에서 처리하는 일

명령을 입력하면 셸은 명령어, 옵션, 인자를 구분한다. 공백과 탭을 기준으로 토큰을 나누고, 따옴표의 범위도 처리한다.

ls -la /home/user
# 명령어: ls
# 옵션: -la
# 인자: /home/user

와일드카드 확장(Globbing)도 셸이 맡는다. *는 0개 이상 문자, ?는 1개 문자, [abc]는 a·b·c 가운데 하나를 뜻한다.

ls *.txt        # 모든 .txt 파일
rm file?.log    # file1.log, file2.log 등

실행 대상은 크게 외부 명령어와 내장 명령어로 나뉜다. 외부 명령어는 PATH 환경 변수를 따라 실행 파일을 검색하고, fork()exec()로 프로세스를 만든 뒤 wait()로 종료를 기다린다. 반면 셸 자체의 상태를 바꿔야 하는 cd, echo, export 같은 명령은 셸 내부에서 직접 처리한다.

표준 입출력을 연결하는 방법

셸은 표준 입력·출력·오류 출력을 각각 파일 디스크립터로 다룬다. stdin은 0, stdout은 1, stderr는 2다. 재지정 연산자를 사용하면 이 흐름을 파일이나 다른 출력 대상으로 바꿀 수 있다.

command > file          # stdout을 file에 쓰기 (덮어쓰기)
command >> file         # stdout을 file에 추가
command < file          # stdin을 file에서 읽기
command 2> error.log    # stderr를 error.log에 쓰기
command &> all.log      # stdout + stderr를 all.log에 쓰기
command 2>&1            # stderr를 stdout으로 재지정
ls /nonexistent 2> error.log   # 에러 메시지를 error.log에 저장
cat < input.txt > output.txt   # input.txt 읽어 output.txt에 쓰기

파이프(|)는 앞선 명령의 출력을 다음 명령의 입력으로 넘긴다. 파일을 중간에 만들지 않고 여러 프로그램을 조합할 수 있다는 점이 명령줄 작업의 핵심이다.

ls -l | grep ".txt"         # ls 출력에서 .txt 파일만 필터링
ps aux | grep "apache" | wc -l  # apache 프로세스 수 세기
cat file.txt | sort | uniq  # 파일 정렬 후 중복 제거

이때 셸은 익명 파이프를 만들고 두 프로세스를 연결한다. 데이터 전달은 비동기적으로 이뤄진다.

환경 변수와 자동화의 기반

환경 변수는 셸과 프로그램이 공유하는 설정 값이며, 프로세스 간에 전달된다. 실행 파일을 찾는 경로, 홈 디렉터리, 사용자 이름, 현재 셸과 작업 디렉터리 같은 정보가 여기에 들어간다.

PATH=/usr/bin:/bin:/usr/local/bin   # 실행 파일 검색 경로
HOME=/home/user                      # 홈 디렉토리
USER=john                            # 사용자 이름
SHELL=/bin/bash                      # 현재 셸
PWD=/home/user/projects              # 현재 작업 디렉토리

export로 환경 변수를 설정하고, echoenv로 확인하며, unset으로 제거할 수 있다.

export VAR="value"    # 환경 변수 설정
echo $VAR             # 환경 변수 출력
env                   # 모든 환경 변수 목록
unset VAR             # 환경 변수 제거

셸 스크립트는 여러 명령을 파일에 저장해 자동화와 배치 작업에 사용하는 방식이다. 다음 스크립트는 지정한 디렉터리를 압축해 백업 파일을 만든다.

#!/bin/bash
# backup.sh

SOURCE="/home/user/data"
DEST="/backup/data-$(date +%Y%m%d).tar.gz"

tar -czf $DEST $SOURCE
echo "Backup completed: $DEST"

실행 권한을 준 뒤 스크립트를 실행한다.

chmod +x backup.sh
./backup.sh

셸마다 다른 문법과 사용감

Bourne Shell(sh)은 1979년 Stephen Bourne이 Bell Labs에서 UNIX Version 7을 위해 개발한 주요 UNIX 셸이다. 간결한 문법을 갖고 POSIX 표준 기반으로 사용된다. 일반적인 경로는 /bin/sh다.

Bash(Bourne Again Shell)는 1989년 GNU 프로젝트의 Brian Fox가 개발했다. Bourne Shell과 호환되면서 명령어 자동 완성(Tab), 명령어 기록(history), 프롬프트 커스터마이징 같은 확장 기능을 제공한다. Linux 대부분 배포판과 macOS Catalina 이전에서 기본 셸로 사용됐으며 경로는 /bin/bash다.

C Shell(csh)은 1978년 UC Berkeley의 Bill Joy가 개발했다. C 언어와 유사한 문법, 작업 제어, 명령어 별칭 기능이 특징이다. tcsh는 csh를 개선한 TENEX C Shell 확장이다.

Korn Shell(ksh)은 1983년 Bell Labs의 David Korn이 개발했다. Bourne Shell과 C Shell의 기능을 결합하고 스크립팅 기능을 강화했다.

Z Shell(zsh)은 1990년 Paul Falstad가 개발했다. Bash 호환성과 확장 기능을 제공하고, 테마와 플러그인(Oh My Zsh), 향상된 자동 완성, 스펠링 교정 기능을 갖춘다. macOS Catalina 이후 기본 셸이며 일반적인 경로는 /bin/zsh다.

Fish(Friendly Interactive Shell)는 2005년 Axel Liljencrantz가 개발했다. 자동 제안(Autosuggestion), 구문 강조(Syntax Highlighting), 웹 기반 설정을 제공하는 사용자 친화적 셸이다. POSIX와 호환되지 않으며 독자적인 문법을 사용한다.

PowerShell은 Microsoft가 2006년 개발한 셸이다. Windows 7 이후 Windows 기본 셸로 쓰이며, 텍스트가 아니라 .NET 객체를 전달하는 객체 기반 파이프라인과 Cmdlet(Command-let)을 특징으로 한다. PowerShell Core를 통해 크로스 플랫폼 환경도 지원한다. Windows PowerShell은 powershell.exe, PowerShell 7+는 pwsh 경로를 사용한다.

프롬프트 뒤에서 반복되는 실행 흐름

셸은 프롬프트를 표시한 후 사용자 입력을 읽는다. 입력된 명령은 토큰으로 분리되고, 와일드카드 확장과 변수 치환을 거쳐 내장 명령인지 외부 명령인지 판별된다.

외부 명령이라면 셸은 fork()로 자식 프로세스를 만들고, 자식 프로세스에서 exec()로 프로그램을 로드해 실행한다. 부모 셸은 wait()로 자식 프로세스의 종료를 기다린다. 실행 결과는 stdout과 stderr로 출력되며, 이후 셸은 다음 명령을 받을 프롬프트를 다시 표시한다.

user@host:~$
YesNo 시작프롬프트 표시명령어 입력 읽기명령어 파싱내장 명령어? 내부 실행fork() 자식 프로세스 생성exec() 프로그램 실행wait() 종료 대기

fork()는 현재 프로세스를 복제한다. 부모 프로세스에서 fork()의 반환값은 자식 PID이고, 자식 프로세스에서는 0이다. exec()는 현재 프로세스의 메모리 공간을 새 프로그램으로 교체하며 execvp(), execve() 등이 있다.

pid_t pid = fork();
if (pid == 0) {
    // 자식 프로세스
    execvp("/bin/ls", args);
} else {
    // 부모 프로세스 (셸)
    wait(NULL);
}

스크립트에 쓰는 문법

스크립트 첫 줄의 Shebang은 실행할 인터프리터를 지정한다.

#!/bin/bash
# 스크립트를 실행할 인터프리터 지정

한 줄 주석은 #으로 작성한다.

# 한 줄 주석

변수는 공백 없이 선언하고 $변수명 또는 ${변수명}으로 참조한다.

NAME="John"
AGE=30

echo "Name: $NAME, Age: $AGE"
echo "Name: ${NAME}, Age: ${AGE}"  # 중괄호로 명확히

명령어 치환으로 명령 실행 결과를 변수에 담을 수 있다.

CURRENT_DATE=$(date +%Y-%m-%d)
FILES=$(ls *.txt)

조건 분기는 if, 반복 처리는 forwhile, 인자별 분기는 case로 작성한다.

if [ $AGE -gt 18 ]; then
    echo "Adult"
elif [ $AGE -eq 18 ]; then
    echo "Just turned adult"
else
    echo "Minor"
fi
for file in *.txt; do
    echo "Processing: $file"
done

for i in {1..5}; do
    echo "Number: $i"
done
COUNT=0
while [ $COUNT -lt 5 ]; do
    echo "Count: $COUNT"
    COUNT=$((COUNT + 1))
done
case $1 in
    start)
        echo "Starting..."
        ;;
    stop)
        echo "Stopping..."
        ;;
    *)
        echo "Unknown command"
        ;;
esac

함수는 이름 뒤에 ()를 붙여 정의하며, 인자는 $1처럼 참조한다. 함수의 반환값은 종료 상태 코드로 받을 수 있다.

greet() {
    echo "Hello, $1!"
}

greet "Alice"
greet "Bob"
add() {
    return $(($1 + $2))
}

add 3 5
result=$?
echo "Result: $result"

셸 내부에서 바로 실행되는 명령

내장 명령어는 셸이 직접 처리하므로 fork()exec()를 거치지 않는다. 현재 작업 디렉터리나 셸 환경처럼 셸 자신의 상태를 바꿔야 하는 작업에 필요하다.

cd는 작업 디렉터리를 변경한다.

cd /home/user
cd ..
cd ~

echo는 문자열이나 환경 변수 값을 출력한다.

echo "Hello, World!"
echo $PATH

export는 변수를 환경 변수로 내보낸다.

export VAR="value"

source 또는 .은 현재 셸에서 설정 파일이나 스크립트를 읽어 실행한다.

source ~/.bashrc
. ./config.sh

alias는 자주 쓰는 명령에 별칭을 붙인다.

alias ll='ls -la'
alias ..='cd ..'

type은 명령이 셸 내장 명령인지, 실행 파일인지 확인할 때 사용한다.

type cd        # cd is a shell builtin
type ls        # ls is /bin/ls

history는 명령어 기록을 다루며, ! 표기법으로 기록된 명령을 다시 실행할 수 있다.

history         # 명령어 기록 표시
!10             # 10번째 명령어 재실행
!!              # 마지막 명령어 재실행

시작 파일로 대화형 환경을 구성하기

Bash는 /etc/profile에서 시스템 전체 설정을 읽는다. 로그인 셸 설정에는 ~/.bash_profile, 대화형 셸 설정에는 ~/.bashrc, 로그아웃 시 실행할 작업에는 ~/.bash_logout을 사용한다.

Zsh에서는 ~/.zshrc에 Zsh 설정을 두고, 로그인 셸 설정은 ~/.zprofile에 둔다.

Bash 프롬프트는 ~/.bashrc에서 PS1 변수로 설정할 수 있다.

# ~/.bashrc
PS1='\u@\h:\w\$ '
# \u: 사용자 이름
# \h: 호스트 이름
# \w: 현재 디렉토리
# \$: $ (일반 사용자) 또는 # (root)

별칭도 같은 시작 파일에 넣어 대화형 셸 환경에 적용한다.

# ~/.bashrc
alias ll='ls -la'
alias gs='git status'
alias ..='cd ..'
운영체제Bash프로세스셸 스크립트