Skip to content
ioob.dev
Go back

Linux 기초 1편 — 셸과 파일시스템 구조

· 10분 읽기
Linux 시리즈 (1/8)
  1. Linux 기초 1편 — 셸과 파일시스템 구조
  2. Linux 기초 2편 — 파일 권한과 사용자/그룹
  3. Linux 기초 3편 — 프로세스와 시그널
  4. Linux 기초 4편 — 텍스트 처리와 파이프
  5. Linux 기초 5편 — 네트워크 도구
  6. Linux 기초 6편 — Systemd와 서비스 관리
  7. Linux 기초 7편 — 패키지 관리
  8. Linux 기초 8편 — Bash 스크립팅 기초
Table of contents

Table of contents

왜 셸부터 배워야 하는가

개발자가 EC2 인스턴스에 SSH로 처음 접속했을 때, 눈앞에 뜨는 건 까만 화면과 $ 한 글자뿐이다. 아무도 버튼을 눌러주지 않고, IDE의 자동완성도 없다. 이 상태에서 “아, 뭐부터 쳐야 하지” 하고 멈칫하는 순간이 실무의 첫 관문이다.

Linux를 배운다는 건 이 까만 화면과 친해지는 과정이다. 그 중심에 셸(Shell)이 있다. 셸은 사용자가 친 문자열을 해석해서 운영체제에게 일을 시키는 번역기고, 모든 Linux 작업의 진입점이다. 쿠버네티스 Pod 로그를 보든, Docker 컨테이너를 띄우든, CI/CD 파이프라인을 돌리든, 결국 어디선가 셸 명령이 실행된다.

이 편에서는 셸이 무엇이고, 디렉토리가 어떻게 생겼으며, $PATH 같은 환경변수가 왜 중요한지 — 매일 쓰는 기본 명령의 뒷면을 차례로 걷어본다.

셸이라는 번역기

Linux는 커널(Kernel)과 사용자 사이에 껍데기(Shell)를 한 층 둔다. 사용자가 키보드로 친 텍스트를 받아 해석하고, 커널이 이해할 수 있는 시스템 호출(system call)로 바꿔 전달한다.

flowchart LR
    USER["사용자<br/>(키보드)"] --> TERM["터미널<br/>에뮬레이터"]
    TERM --> SHELL["셸<br/>(bash / zsh / fish)"]
    SHELL --> KERN["리눅스 커널"]
    KERN --> HW["하드웨어<br/>(CPU / 디스크 / 네트워크)"]
    KERN --> SHELL
    SHELL --> TERM
    TERM --> USER

대표적인 셸이 bash(Bourne Again SHell)다. 1989년에 나온 후 거의 모든 배포판의 기본 셸 자리를 지켰고, 지금도 서버 환경에선 기본값이다. macOS는 2019년부터 기본 셸을 zsh로 바꿨다. 기능적으론 둘 다 비슷하다 — 프롬프트가 예쁘고 자동완성이 강력하냐, 아니면 가볍고 어디에나 있느냐의 차이 정도다.

어떤 셸을 쓰고 있는지 확인하는 법은 단순하다.

echo $SHELL
# /bin/zsh   (또는 /bin/bash)

echo $0
# -zsh

$SHELL은 로그인 시 기본으로 열리는 셸 경로를 담고, $0은 현재 실행 중인 셸 이름을 돌려준다. 두 값이 다를 수 있다 — 가령 zsh 사용자가 스크립트 안에서 bash script.sh로 서브셸을 띄우면 $SHELL은 여전히 zsh지만 $0은 bash가 된다.

이 시리즈는 bash 기준으로 쓰되, zsh에서도 99% 그대로 돌아가는 명령만 쓴다.

파일시스템은 하나의 거대한 나무다

Windows는 드라이브 문자가 여러 개 있다 — C:, D:, E:. Linux는 다르다. 모든 것이 /(루트) 하나에서 시작해서 아래로 뻗어나간다. USB를 꽂아도, 새 디스크를 추가해도, 어딘가의 디렉토리에 “마운트”되어 트리의 일부가 된다. 이것이 모든 것이 파일이다(Everything is a file)라는 Unix 철학의 첫 번째 얼굴이다.

표준적으로 리눅스가 따르는 디렉토리 계층은 FHS(Filesystem Hierarchy Standard)라는 규격으로 정해져 있다. 자세한 규격은 Linux Foundation의 FHS 문서에서 확인할 수 있다.

flowchart TB
    ROOT["/ (root)"]
    ROOT --> BIN["/bin<br/>기본 명령어"]
    ROOT --> SBIN["/sbin<br/>관리자용 명령어"]
    ROOT --> ETC["/etc<br/>시스템 설정"]
    ROOT --> HOME["/home<br/>사용자 홈"]
    ROOT --> VAR["/var<br/>가변 데이터 (로그, 캐시)"]
    ROOT --> TMP["/tmp<br/>임시 파일"]
    ROOT --> USR["/usr<br/>사용자 프로그램"]
    ROOT --> PROC["/proc<br/>커널/프로세스 정보"]
    ROOT --> SYS["/sys<br/>커널/디바이스 정보"]
    ROOT --> DEV["/dev<br/>장치 파일"]
    ROOT --> OPT["/opt<br/>3rd-party"]

각 디렉토리가 어떤 성격의 파일을 담는지 짚어보자.

한 줄로 요약하면 이렇다. /etc에 설정, /var에 로그와 데이터, /bin에 명령어, /home에 개인 파일. 이 네 개만 머리에 담아도 일상 운영의 80%는 커버된다.

pwd, cd, ls — 걸음걸이 세 종류

셸에 익숙해지는 건 결국 “지금 어디에 있고, 어디로 가고, 여긴 뭐가 있나”를 빠르게 파악하는 일이다. 거기에 쓰이는 명령이 셋이다.

# 지금 어디에 있는지 (Print Working Directory)
pwd
# /home/alice

# 어디로 이동 (Change Directory)
cd /var/log
cd ~           # 홈 디렉토리로
cd -           # 바로 직전에 있던 디렉토리로

# 여긴 뭐가 있나 (LiSt)
ls
ls -l          # 상세 정보 (long)
ls -la         # 숨김 파일까지 (all)
ls -lh         # 사람이 읽기 쉬운 크기 (human)

ls -la의 출력은 매일 본다. 왼쪽부터 하나씩 해부해두면 이후 장이 편해진다.

-rw-r--r--  1 alice  staff  1024  Apr 20 10:30  .bashrc
drwxr-xr-x  3 alice  staff    96  Apr 20 09:15  projects

절대 경로와 상대 경로

경로는 두 가지 방식으로 표현한다. 절대 경로/에서 시작한다. /etc/nginx/nginx.conf가 그 예다. 어디서 명령을 실행하든 똑같은 파일을 가리킨다.

상대 경로는 현재 위치(pwd)를 기준으로 한다.

cd ~/projects/blog
cat ./package.json       # /home/alice/projects/blog/package.json
cat ../notes.md          # /home/alice/projects/notes.md
cat ~/.bashrc            # /home/alice/.bashrc
cat /etc/hostname        # 절대 경로

스크립트를 쓸 때는 절대 경로가 안전하다. 상대 경로는 스크립트가 어느 디렉토리에서 실행됐는지에 따라 깨지기 쉽다.

cat, less, head, tail — 파일 들여다보기

파일 내용을 보는 도구는 상황에 따라 고른다.

# 파일 통째로 출력
cat /etc/hostname

# 여러 파일을 한 번에 이어붙여서 출력 (cat의 원래 의미: conCATenate)
cat file1.txt file2.txt

# 페이지 단위로 읽기 — q로 종료, /검색어로 찾기
less /var/log/syslog

# 앞부분만 — 기본 10줄
head /etc/passwd
head -n 3 /etc/passwd

# 뒷부분만 — 로그 확인의 기본
tail /var/log/syslog
tail -n 100 /var/log/syslog
tail -f /var/log/syslog     # 실시간 추적 (follow)

실무에서 가장 자주 쓰는 건 tail -f다. 앱 로그가 흐르는 걸 지켜볼 때, Kubernetes에서 kubectl logs -f가 하는 일과 본질적으로 같다.

cat 하나로 로그 전체를 토해낼 수도 있지만, 수백만 줄짜리 로그에 그렇게 하면 터미널이 몇 분간 얼어붙는다. 대용량 파일엔 lesstail로 접근하는 습관을 들인다.

$PATH — 명령어를 어떻게 찾는가

셸에 ls를 쳤을 때, 왜 /bin/ls가 실행될까? 셸이 “ls라는 프로그램이 어디 있을지” 어떻게 안단 말인가. 그 비밀이 $PATH에 있다.

$PATH는 셸이 명령어를 찾을 디렉토리 목록을 :로 이어붙인 환경변수다.

echo $PATH
# /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

셸이 ls를 입력받으면 위에서부터 차례로 /usr/local/bin/ls, /usr/bin/ls, /bin/ls를 확인하면서 처음으로 찾은 실행 파일을 실행한다. 그래서 같은 이름의 프로그램이 두 군데 있으면 $PATH 앞쪽에 있는 게 이긴다.

어떤 경로의 실행 파일이 실제로 실행되는지 확인하려면 whichtype을 쓴다.

which python3
# /usr/bin/python3

type ls
# ls is aliased to `ls --color=auto'

type cd
# cd is a shell builtin

cd는 쉘 내장 명령(builtin)이라 파일이 없다. 반면 ls/bin/ls라는 실제 파일이지만, 배포판에 따라 --color=auto 옵션이 별칭(alias)으로 걸려 있는 경우가 많다.

자신이 만든 스크립트를 어디서든 실행하고 싶으면 $PATH에 그 디렉토리를 추가한다.

# 임시로 추가 (현재 셸에서만)
export PATH="$HOME/bin:$PATH"

# 영구 추가 — ~/.bashrc 또는 ~/.zshrc 에 한 줄 추가
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

$HOME/bin맨 앞에 붙이는 게 관례다. 같은 이름의 도구가 시스템에 있어도 내 것이 우선이기 때문이다.

환경변수 — 셸의 기억장치

환경변수는 셸과 자식 프로세스가 공유하는 키-값 저장소다. 대소문자를 구분하고, 관례상 모두 대문자로 쓴다.

# 지금 셸의 환경변수 전부 보기
env

# 개별 확인
echo $HOME
echo $USER
echo $LANG

# 새 변수 설정 — 현재 셸 안에서만 (자식에게 전파 안 됨)
GREETING="hello"
echo $GREETING

# export 해야 자식 프로세스가 물려받는다
export GREETING="hello"
bash -c 'echo $GREETING'
# hello

여기서 중요한 구분 하나. 변수와 환경변수는 다르다. 단순히 X=1을 쓰면 셸 변수일 뿐, 자식 프로세스에선 안 보인다. export X=1을 해야 환경변수가 된다. 스크립트를 돌렸는데 값이 비어 있다면 이 차이를 제일 먼저 의심한다.

자주 마주치는 환경변수는 아래 정도다.

변수의미
HOME현재 사용자 홈 디렉토리 (/home/alice)
USER로그인 사용자명
PATH실행 파일 탐색 경로
PWD현재 작업 디렉토리
LANG로케일 (en_US.UTF-8, ko_KR.UTF-8)
EDITORgit commit 같은 도구가 띄울 기본 에디터
SHELL로그인 셸 경로

프로그램을 실행할 때 앞에 변수를 쓰면 그 한 번의 실행에만 환경변수를 준다.

# 이번 한 번만 LANG을 바꿔서 실행
LANG=C date
# Mon Apr 20 00:30:00 UTC 2026

# 다시 실행하면 원래 LANG으로 돌아가 있다
date
# 2026년 4월 20일 월요일 00시 30분 00초 UTC

Docker 컨테이너를 띄울 때 -e KEY=VALUE 옵션이 하는 일도 결국 이것이다. 앱이 환경변수에서 설정을 읽도록 짜면 배포 환경마다 코드 수정 없이 동작을 바꿀 수 있다. 12 Factor App의 3번째 원칙이 바로 이 이야기다.

셸 설정 파일 — 매 접속마다 실행된다

터미널을 열 때마다 $PATH를 손으로 설정할 수는 없다. 그래서 셸은 시작할 때 자동으로 실행하는 설정 파일 몇 개를 가진다.

bash 기준:

zsh 기준:

설정 파일을 수정했다면 현재 셸에 반영하려 source 명령으로 다시 읽는다.

source ~/.bashrc    # or: . ~/.bashrc

source는 해당 파일을 현재 셸에서 실행한다. bash ~/.bashrc로 실행하면 자식 셸에서 돌아가므로 현재 셸의 환경이 바뀌지 않는다. 두 명령의 차이를 헷갈리면 설정이 안 먹는 걸로 착각하기 쉽다.

/proc에서 커널을 들여다보기

마지막으로 /proc을 한 번 열어보자. 이 디렉토리가 왜 재미있는지 알면 Linux가 “모든 것이 파일이다”라는 선언을 얼마나 진심으로 했는지 체감할 수 있다.

# CPU 정보
cat /proc/cpuinfo | head -20

# 메모리 상태
cat /proc/meminfo

# 현재 실행 중인 셸의 정보
echo $$                       # 현재 셸의 PID
cat /proc/$$/status | head
#   Name:   bash
#   State:  S (sleeping)
#   Pid:    12345
#   PPid:   12344

/proc의 파일들은 디스크에 실제로 존재하지 않는다. 열어달라고 요청하는 순간 커널이 텍스트로 조립해서 돌려준다. htop, ps, free 같은 모니터링 도구의 거의 모든 정보가 여기서 나온다. 3편에서 프로세스를 다룰 때 다시 찾게 된다.

이 편에서 다룬 것

핵심만 다시 훑어보면 이렇다.

다음 편에서는 파일마다 붙어 있는 rwx 권한 기호의 의미, chmod·chown·sudo·umask의 동작 방식, 그리고 SUID/SGID 같은 특수 권한 비트를 들여다본다.

2편: 파일 권한과 사용자/그룹


Related Posts

Share this post on:

Comments

Loading comments...


Previous Post
ArgoCD 8편 — 실전 패턴: App of Apps, CI 연동, 트러블슈팅
Next Post
Linux 기초 2편 — 파일 권한과 사용자/그룹