infra
Platform

모듈 맵

[Linux] 서버 설정의 기초인 export, .bashrc, .profile 완벽 정리

0 / 37 완료

펼치기
0 / 37 완료0%

Linux · 09 / 37

[Linux] 서버 설정의 기초인 export, .bashrc, .profile 완벽 정리

$PATH, .bashrc, .bash_profile을 완전히 이해하고 개발 환경을 자유롭게 설정합니다

🚨INCIDENT ALERT
HIGH

새 서버에 접속했더니 python3 명령어는 실행되는데 pythoncommand not found가 납니다. .bashrc에 PATH를 추가했는데 재접속해도 반영이 안 됩니다. cron에 등록한 스크립트는 터미널에서 잘 되는데 자동 실행하면 항상 실패합니다.

이 모든 상황의 공통 원인은 환경변수와 dotfiles를 모르기 때문입니다. 이 모듈을 마치면 PATH 추적, 설정 즉시 적용, cron 환경변수 문제를 스스로 진단하고 해결할 수 있게 됩니다.

환경변수 & dotfiles

이번 챕터에서 배울 것
  • 1지역 변수와 export 환경변수의 차이를 이해하고 자식 프로세스 전달 범위를 설명할 수 있다
  • 2env, printenv, set으로 환경변수를 조회하고 unset으로 제거할 수 있다
  • 3.bashrc와 .bash_profile의 로드 순서와 적용 시점 차이를 설명할 수 있다
  • 4$PATH 구조를 이해하고 새 경로를 추가해 언어 런타임 버전을 전환할 수 있다
  • 5source로 설정을 즉시 적용하고, alias를 영구 등록하며, cron 스크립트에서 누락된 환경변수를 보완할 수 있다
실습 환경 준비
현재 환경변수 전체 목록 확인
env | sort
PATH 현재 값 확인
echo $PATH | tr ':' '\n'
.bashrc 편집 후 즉시 적용
source ~/.bashrc
/etc/environment 수정 시 주의

셸 스크립트 문법(export, 변수 치환)을 지원하지 않으므로 KEY=VALUE 형식만 사용합니다

💡개념

지역 변수 vs 전역 환경변수 — export의 역할

배포 스크립트를 짜다 보면 스크립트 안에서 설정한 변수가 자식 스크립트에서 안 보이는 상황을 만납니다. 또는 반대로, 터미널에서 export DB_HOST=localhost를 설정했는데 cron으로 실행하면 해당 변수가 없다는 에러가 납니다. 이 차이가 지역 변수와 환경변수의 구분에서 비롯됩니다. export를 붙이느냐 마느냐 한 단어 차이지만, 그 변수가 자식 프로세스에 전달되는지가 달라집니다. 스크립트 간 데이터 전달, 환경 설정 파일 구조 설계, cron 오류 디버깅 — 이 모든 상황에서 이 개념이 기준점이 됩니다.

쉘에서 변수를 만드는 방법은 두 가지입니다. export 여부에 따라 그 변수가 자식 프로세스에게 전달되는지가 결정됩니다.

환경변수 — export와 자식 프로세스 상속, dotfiles 로드 순서

지역 변수 (Local Variable)

현재 쉘 세션에만 존재하며, 자식 프로세스(새 쉘, 실행한 스크립트 등)에는 전달되지 않습니다.

로컬 터미널
# 지역 변수 선언
MY_NAME="alice"
echo $MY_NAME
# 출력: alice

# 자식 쉘에서는 보이지 않음
bash -c 'echo $MY_NAME'
# 출력: (아무것도 출력 안 됨)

전역 환경변수 (Environment Variable) — export 사용

export를 사용하면 현재 쉘과 그 쉘에서 실행되는 모든 자식 프로세스에게 변수가 전달됩니다.

로컬 터미널
# 환경변수로 내보내기
export MY_NAME="alice"

# 자식 쉘에서도 접근 가능
bash -c 'echo $MY_NAME'
# 출력: alice

# 선언과 동시에 export
export JAVA_HOME="/usr/lib/jvm/java-17-openjdk"

# 기존 지역 변수를 나중에 export
VERSION="3.11"
export VERSION

변수 확인 방법 비교env, printenv, set 명령어가 각각 어떤 변수를 보여주는지 비교합니다:

명령어보여주는 것
set지역 변수 + 환경변수 + 쉘 함수 모두
env현재 프로세스의 환경변수만
printenv환경변수만 (특정 변수 지정 가능)
printenv HOMEHOME 변수 하나만 출력
로컬 터미널
# 환경변수 전체 목록
env
# 출력 예시:
# HOME=/home/alice
# USER=alice
# SHELL=/bin/bash
# LANG=en_US.UTF-8
# PATH=/usr/local/bin:/usr/bin:/bin
# TERM=xterm-256color
# ...

# 특정 변수만 확인
printenv HOME
# 출력: /home/alice

printenv JAVA_HOME
# 출력: /usr/lib/jvm/java-17-openjdk

# set은 함수와 지역 변수까지 포함하므로 출력이 매우 많음
# 특정 변수를 찾을 때는 grep과 함께 사용
set | grep MY_NAME
# 출력: MY_NAME=alice

변수 삭제unset 명령으로 환경변수나 쉘 변수를 제거합니다:

로컬 터미널
unset MY_NAME
echo $MY_NAME
# 출력: (빈 줄)

중요한 개념: export는 현재 세션과 그 자식들에게만 적용됩니다. 다음 로그인 세션에서도 환경변수를 유지하려면 반드시 dotfile(.bashrc 또는 .bash_profile)에 기록해야 합니다.


환경변수 선언과 제거(export와 unset)

환경변수 안전하게 지우기: unset

선언된 로컬 변수나 환경변수를 메모리 상에서 완벽히 소거하여 후속 프로세스로의 전파를 차단할 때는 unset 명령을 사용합니다.

로컬 터미널
$ export MY_VAR="secret"
$ unset MY_VAR
$ echo $MY_VAR # 아무것도 출력되지 않음

PATH 환경변수 확장 시의 덮어쓰기 참사 예방

새 소프트웨어 바이너리 경로를 $PATH에 추가할 때, 기존 경로를 누락하는 중대한 실수를 저지르면 ls, cp 같은 시스템 기본 명령어가 먹통이 됩니다. 반드시 기존 $PATH를 뒤에 붙여 확장해야 합니다.

로컬 터미널
# 올바른 예시 (기존 PATH를 포함하여 뒤에 붙임)
$ export PATH="/opt/newapp/bin:$PATH"

# 절망적인 오답 예시 (기존 PATH 유실, 기본 명령어 마비됨!)
$ export PATH="/opt/newapp/bin"
💡개념

dotfiles 로드 순서 — 언제 어떤 파일이 실행되는가

dotfiles 로드 순서 — Login Shell vs Interactive Shell, 파일별 적용 범위

Linux 쉘은 시작 조건에 따라 서로 다른 설정 파일을 읽습니다. 이 순서를 모르면 "분명히 .bashrc에 썼는데 왜 적용이 안 되지?"라는 상황에 빠집니다.

쉘의 두 가지 시작 모드 — Login Shell과 Non-Login Shell은 로드하는 설정 파일이 다릅니다:

모드설명예시
Login Shell로그인 시 최초로 실행되는 쉘SSH 접속, 콘솔 로그인, bash --login
Interactive Non-Login Shell이미 로그인된 상태에서 새 터미널 열기터미널 에뮬레이터, bash 명령 실행
Non-Interactive Shell사용자 입력 없이 실행되는 쉘스크립트 실행, cron 작업

로드 순서 — Login Shell (SSH 접속 등) — SSH 접속 시 쉘이 읽는 파일 순서입니다:

1. /etc/profile          ← 시스템 전체 설정 (root만 수정)
2. /etc/profile.d/*.sh   ← 패키지가 추가하는 개별 설정 파일들
3. ~/.bash_profile       ← 사용자 로그인 설정 (없으면 아래 순서로 시도)
   또는 ~/.bash_login
   또는 ~/.profile

로드 순서 — Interactive Non-Login Shell (새 터미널 탭) — 터미널 앱에서 새 탭을 열 때 읽는 파일 순서입니다:

1. /etc/bash.bashrc      ← 시스템 전체 interactive 설정
2. ~/.bashrc             ← 사용자 interactive 설정

실무에서 쓰는 표준 패턴:

대부분의 Linux 배포판에서 ~/.bash_profile~/.bashrc를 직접 불러오도록 설정되어 있습니다. 이렇게 하면 로그인 쉘이든 인터랙티브 쉘이든 동일한 설정이 적용됩니다.

로컬 터미널
# ~/.bash_profile 일반적인 내용
# Login shell에서 .bashrc를 읽도록 연결
if [ -f ~/.bashrc ]; then
    source ~/.bashrc
fi

이 패턴 덕분에 개발자가 실제로 수정하는 파일은 거의 항상 ~/.bashrc 하나입니다.

시스템 전역 vs 사용자별 설정 — 어느 파일을 수정하느냐에 따라 적용 범위와 필요 권한이 달라집니다:

파일적용 범위수정 권한
/etc/environment시스템 전체 (모든 사용자, 모든 쉘)root
/etc/profile시스템 전체 login shellroot
/etc/profile.d/*.sh시스템 전체 (패키지별 분리)root
~/.bash_profile해당 사용자 login shell해당 사용자
~/.bashrc해당 사용자 interactive shell해당 사용자

/etc/environment 특이점 — 이 파일은 쉘 문법이 아닌 단순 KEY=VALUE 형식만 허용합니다:

로컬 터미널
# /etc/environment는 쉘 스크립트가 아닙니다
# export 키워드 없이 KEY=VALUE 형식만 사용
cat /etc/environment
# 출력:
# PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# LANG="en_US.UTF-8"

# 이 파일은 PAM(Pluggable Authentication Modules)이 읽으므로
# SSH, GUI 로그인, sudo 등 모든 경우에 적용됨

어떤 파일에 무엇을 써야 하는가:

  • ~/.bashrc: alias, 함수, PATH 추가, 프롬프트(PS1) 설정 — 대부분의 개인 설정
  • ~/.bash_profile: 로그인 시 한 번만 실행되어야 하는 것 (보통 .bashrc 호출만)
  • /etc/environment: 시스템 전체에 적용할 단순 KEY=VALUE 변수 (쉘 문법 사용 불가)

환경변수 선언과 제거(export와 unset)

환경변수 안전하게 지우기: unset

선언된 로컬 변수나 환경변수를 메모리 상에서 완벽히 소거하여 후속 프로세스로의 전파를 차단할 때는 unset 명령을 사용합니다.

로컬 터미널
$ export MY_VAR="secret"
$ unset MY_VAR
$ echo $MY_VAR # 아무것도 출력되지 않음

PATH 환경변수 확장 시의 덮어쓰기 참사 예방

새 소프트웨어 바이너리 경로를 $PATH에 추가할 때, 기존 경로를 누락하는 중대한 실수를 저지르면 ls, cp 같은 시스템 기본 명령어가 먹통이 됩니다. 반드시 기존 $PATH를 뒤에 붙여 확장해야 합니다.

로컬 터미널
# 올바른 예시 (기존 PATH를 포함하여 뒤에 붙임)
$ export PATH="/opt/newapp/bin:$PATH"

# 절망적인 오답 예시 (기존 PATH 유실, 기본 명령어 마비됨!)
$ export PATH="/opt/newapp/bin"

실습 — 기본기 다지기

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part2/exam_11 && cd /tmp/linux/part2/exam_11

# .env 파일 생성
cat > /tmp/linux/part2/exam_11/.env << 'EOF'
APP_ENV=development
DB_HOST=localhost
DB_PORT=5432
APP_PORT=8080
SECRET_KEY=dev-secret-change-in-prod
EOF

# 환경변수 로드 테스트 스크립트 생성
cat > /tmp/linux/part2/exam_11/app.sh << 'EOF'
#!/bin/bash
source .env
echo "Environment: $APP_ENV"
echo "DB: $DB_HOST:$DB_PORT"
echo "App Port: $APP_PORT"
EOF
chmod +x /tmp/linux/part2/exam_11/app.sh

이제 실습을 진행합니다.

현재 환경변수 전체 탐색하기

서버에 처음 접속했을 때 현재 환경이 어떻게 설정되어 있는지 파악하는 것이 첫 번째 습관입니다.

로컬 터미널
# 모든 환경변수 출력 (알파벳 정렬)
env | sort

출력 예시:

DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
HOME=/home/alice
LANG=en_US.UTF-8
LOGNAME=alice
MAIL=/var/mail/alice
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/alice/.local/bin:/home/alice/bin
PWD=/home/alice
SHELL=/bin/bash
TERM=xterm-256color
USER=alice
XDG_RUNTIME_DIR=/run/user/1000
로컬 터미널
# 특정 변수 하나만 확인
printenv PATH
# 출력: /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/alice/.local/bin

# 변수 존재 여부 확인 (스크립트에서 유용)
if [ -z "$JAVA_HOME" ]; then
    echo "JAVA_HOME이 설정되지 않았습니다"
fi
# 출력: JAVA_HOME이 설정되지 않았습니다

# 현재 로그인한 사용자 확인
echo "사용자: $USER, 홈 디렉토리: $HOME, 쉘: $SHELL"
# 출력: 사용자: alice, 홈 디렉토리: /home/alice, 쉘: /bin/bash

자주 사용하는 내장 환경변수 — 쉘이 기본으로 제공하는 변수들로 경로, 사용자, 홈 디렉터리 등을 담고 있습니다:

로컬 터미널
echo $HOME      # 홈 디렉토리: /home/alice
echo $USER      # 현재 사용자: alice
echo $SHELL     # 기본 쉘: /bin/bash
echo $PWD       # 현재 디렉토리: /home/alice/projects
echo $OLDPWD    # 이전 디렉토리 (cd - 명령의 동작 원리)
echo $HOSTNAME  # 호스트명: web-server-01
echo $LANG      # 언어 설정: en_US.UTF-8
echo $TERM      # 터미널 타입: xterm-256color
echo $$         # 현재 쉘의 PID: 12345
echo $?         # 직전 명령의 종료 코드: 0 (성공) 또는 1 이상 (실패)
🔍실행 후 확인할 것
  • env 명령 실행 시 현재 세션의 모든 환경 변수가 KEY=VALUE 형태로 출력된다
  • echo $USER, echo $HOME 결과가 현재 로그인한 사용자의 정보와 일치한다
  • echo $? 로 직전 명령의 종료 코드를 확인할 수 있다 — 성공은 0, 실패는 1 이상이다
  • 새 터미널 탭을 열었을 때 export로 설정한 변수는 유지되고, export 없이 선언한 변수는 사라진다
$PATH 이해하고 수정하기

$PATH는 쉘이 명령어를 찾는 디렉토리 목록입니다. 콜론(:)으로 구분되며, 왼쪽에서 오른쪽 순서로 검색합니다.

로컬 터미널
# PATH 현재 값 확인
echo $PATH
# 출력: /usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin

# 보기 좋게 한 줄씩 출력
echo $PATH | tr ':' '\n'
# 출력:
# /usr/local/bin
# /usr/bin
# /bin
# /usr/local/sbin
# /usr/sbin

# 특정 명령어가 어디서 실행되는지 확인
which python3
# 출력: /usr/bin/python3

which java
# 출력: /usr/lib/jvm/java-17-openjdk/bin/java

# which가 없는 명령어는 PATH에 없다는 뜻
which myapp
# 출력: which: no myapp in (/usr/local/bin:/usr/bin:/bin:...)

PATH에 디렉토리 추가 — PATH 앞이나 뒤에 디렉터리를 추가해 명령어 검색 경로를 확장합니다:

로컬 터미널
# 현재 세션에서만 유효한 PATH 추가 (맨 앞에 추가 — 우선순위 높음)
export PATH="/opt/myapp/bin:$PATH"

# 확인
echo $PATH
# 출력: /opt/myapp/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin

# 맨 뒤에 추가 (우선순위 낮음 — 기존 명령이 있으면 기존 것 사용)
export PATH="$PATH:/opt/legacy/bin"

PATH 추가를 영구적으로 ~/.bashrc에 기록 — 재부팅 후에도 PATH가 유지되도록 설정 파일에 추가합니다:

로컬 터미널
# ~/.bashrc 파일 끝에 추가
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc

# 변경사항 즉시 적용
source ~/.bashrc

# 확인
echo $PATH | tr ':' '\n' | grep local
# 출력: /home/alice/.local/bin

type 명령으로 명령어 종류 확인 — 명령어가 실행 파일인지, alias인지, 내장 명령인지 구분합니다:

로컬 터미널
# which보다 더 많은 정보를 제공
type ls
# 출력: ls is aliased to `ls --color=auto'

type cd
# 출력: cd is a shell builtin

type python3
# 출력: python3 is /usr/bin/python3

type ll
# 출력: ll is aliased to `ls -alF'
source 명령어 — 로그아웃 없이 설정 적용

.bashrc를 수정한 후 로그아웃하지 않고 즉시 적용하는 방법이 source 명령입니다. . (점)source의 동의어입니다.

로컬 터미널
# ~/.bashrc를 수정했다면
nano ~/.bashrc
# (편집 후 저장)

# 방법 1: source 명령
source ~/.bashrc

# 방법 2: . (점) 명령 — 완전히 동일한 동작
. ~/.bashrc

# 방법 3: bash를 다시 실행 (새 프로세스 생성 — source와 다름)
exec bash   # 현재 쉘을 새 bash로 교체

source vs 새 쉘 실행의 차이source는 현재 쉘에서, bash script.sh는 자식 쉘에서 실행해 변수 전달 방식이 다릅니다:

로컬 터미널
# source는 현재 쉘 컨텍스트에서 실행
# → 지역 변수, 현재 디렉토리 등이 유지됨

export TEST_VAR="hello"
source ~/.bashrc    # TEST_VAR가 유지됨 (현재 쉘에서 실행)
echo $TEST_VAR
# 출력: hello

# bash 스크립트로 실행하면 자식 프로세스에서 실행
# → 환경변수 변경이 부모 쉘에 영향을 주지 않음
bash some-script.sh   # 자식 프로세스 실행, 부모 쉘에 영향 없음

source 실전 활용 패턴.env 파일 로드, 설정 파일 즉시 반영 등 현장에서 자주 쓰는 source 패턴입니다:

로컬 터미널
# 환경 설정 파일을 분리해서 관리
# ~/.bashrc 내용:
cat ~/.bashrc
# ...
# source ~/.bash_aliases    # alias 모음
# source ~/.bash_functions  # 함수 모음
# source ~/.bash_private    # 비밀 키 등 (git에 올리지 않음)

# 각 파일이 존재할 때만 읽도록 방어적으로 작성
if [ -f ~/.bash_aliases ]; then
    source ~/.bash_aliases
fi

특정 파일이 실제로 로드되고 있는지 디버그 — 설정 파일이 로드되는지 확인하고 로드 순서 문제를 진단합니다:

로컬 터미널
# ~/.bashrc 최상단에 임시로 추가해서 확인
# echo "~/.bashrc loaded at $(date)" >> /tmp/shell-debug.log

# 어떤 순서로 파일이 로드되는지 추적
bash --login -x 2>&1 | grep "source\|^\+" | head -30
alias 정의하고 영구 적용하기

alias는 긴 명령을 짧게 줄이는 쉘 기능입니다. 생산성에 직접적인 영향을 주므로 자신만의 alias 세트를 만드는 것이 중요합니다.

로컬 터미널
# 현재 세션에서 alias 정의
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias grep='grep --color=auto'

# alias 목록 확인
alias
# 출력:
# alias grep='grep --color=auto'
# alias l='ls -CF'
# alias la='ls -A'
# alias ll='ls -alF'

# alias 사용
ll /etc
# 출력:
# total 1084
# drwxr-xr-x. 141 root root  8192 Mar 26 09:15 ./
# drwxr-xr-x.  18 root root   235 Mar 10 14:22 ../
# -rw-r--r--.   1 root root    16 Mar 10 14:22 adjtime
# ...

# alias 삭제
unalias ll

실무에서 자주 쓰는 alias 모음 — 반복되는 긴 명령어를 짧게 줄이는 현장 alias 모음입니다:

로컬 터미널
# 안전 장치
alias rm='rm -i'        # 삭제 전 확인 요청
alias cp='cp -i'        # 덮어쓰기 전 확인
alias mv='mv -i'        # 이동 전 확인

# 디렉토리 이동
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'

# 자주 가는 디렉토리
alias proj='cd ~/projects'
alias logs='cd /var/log'

# 시스템 정보
alias df='df -hT'                    # 사람이 읽기 좋은 형식
alias du='du -sh'                    # 디렉토리 용량 요약
alias ps='ps auxf'                   # 트리 형태로 프로세스 표시
alias ports='ss -tulnp'              # 열린 포트 확인
alias myip='curl -s ifconfig.me'     # 외부 IP 확인

# Git 단축어
alias gs='git status'
alias ga='git add'
alias gc='git commit'
alias gp='git push'
alias gl='git log --oneline --graph --decorate'

alias를 영구적으로 저장하는 패턴 — alias를 설정 파일에 추가해 재부팅 후에도 유지합니다:

로컬 터미널
# ~/.bash_aliases 파일 생성 (관리하기 쉽도록 별도 파일로 분리)
cat > ~/.bash_aliases << 'EOF'
# 안전 장치
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# 탐색
alias ll='ls -alF --color=auto'
alias la='ls -A'
alias ..='cd ..'
alias ...='cd ../..'

# 시스템
alias df='df -hT'
alias ports='ss -tulnp'
alias myip='curl -s ifconfig.me'

# Git
alias gs='git status'
alias gl='git log --oneline --graph --decorate -20'
EOF

# ~/.bashrc에 자동 로드 추가
echo '' >> ~/.bashrc
echo '# alias 파일 로드' >> ~/.bashrc
echo 'if [ -f ~/.bash_aliases ]; then' >> ~/.bashrc
echo '    source ~/.bash_aliases' >> ~/.bashrc
echo 'fi' >> ~/.bashrc

# 즉시 적용
source ~/.bashrc

# 확인
alias rm
# 출력: alias rm='rm -i'
개인 스크립트 디렉토리를 PATH에 추가하기

자주 쓰는 스크립트를 어느 디렉토리에서든 명령어처럼 실행하려면, 스크립트가 위치한 디렉토리를 PATH에 추가해야 합니다.

로컬 터미널
# 개인 스크립트 디렉토리 생성
mkdir -p ~/bin

# 스크립트 예시 작성
cat > ~/bin/server-info << 'EOF'
#!/bin/bash
echo "=== 서버 정보 ==="
echo "호스트명: $(hostname)"
echo "IP 주소: $(hostname -I | awk '{print $1}')"
echo "OS: $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)"
echo "커널: $(uname -r)"
echo "CPU 코어: $(nproc)"
echo "메모리: $(free -h | awk '/^Mem/{print $2}') 전체, $(free -h | awk '/^Mem/{print $7}') 사용 가능"
echo "디스크: $(df -h / | awk 'NR==2{print $2}') 전체, $(df -h / | awk 'NR==2{print $4}') 사용 가능"
echo "업타임: $(uptime -p)"
EOF

# 실행 권한 부여
chmod +x ~/bin/server-info

# ~/bin을 PATH에 추가 (이미 /etc/profile.d/가 추가한 경우도 있음)
# 현재 세션에서 테스트
export PATH="$HOME/bin:$PATH"

# 어느 디렉토리에서든 실행 가능한지 확인
cd /tmp
server-info

출력 예시:

=== 서버 정보 ===
호스트명: web-server-01
IP 주소: 192.168.1.100
OS: Red Hat Enterprise Linux 9.4 (Plow)
커널: 5.14.0-570.52.1.el9_6.x86_64
CPU 코어: 4
메모리: 7.6Gi 전체, 5.2Gi 사용 가능
디스크: 50G 전체, 35G 사용 가능
업타임: up 15 days, 3 hours, 42 minutes
로컬 터미널
# ~/.bashrc에 영구 등록
cat >> ~/.bashrc << 'EOF'

# 개인 스크립트 디렉토리
if [ -d "$HOME/bin" ]; then
    export PATH="$HOME/bin:$PATH"
fi

# ~/.local/bin도 있으면 추가 (pip install --user 등이 여기에 설치)
if [ -d "$HOME/.local/bin" ]; then
    export PATH="$HOME/.local/bin:$PATH"
fi
EOF

source ~/.bashrc

# PATH에 추가되었는지 확인
echo $PATH | tr ':' '\n' | grep -E "bin$"
# 출력:
# /home/alice/bin
# /home/alice/.local/bin

실무 — Java/Python/Node.js 버전 전환

💼
실무 맥락PATH 조작으로 언어 런타임 버전 전환하기
현업 패턴

실무에서는 프로젝트마다 다른 언어 버전이 필요한 경우가 많습니다. nvm, pyenv, sdkman 같은 도구들도 결국 내부적으로 PATH를 조작하는 방식으로 동작합니다. 그 원리를 직접 이해하면 어떤 도구든 트러블슈팅이 쉬워집니다.

원리: PATH 앞부분에 원하는 버전의 bin 디렉토리를 추가

PATH는 왼쪽에서 오른쪽으로 검색하므로, 맨 앞에 추가된 디렉토리가 항상 우선 실행됩니다.

로컬 터미널
# 현재 Java 버전 확인
java -version
# 출력: openjdk version "11.0.22" 2024-01-16
#        OpenJDK Runtime Environment (build 11.0.22+7)

# 시스템에 설치된 Java 버전들 확인
ls /usr/lib/jvm/
# 출력:
# java-11-openjdk-amd64
# java-17-openjdk-amd64
# java-21-openjdk-amd64

# Java 17로 전환 (현재 세션만)
export JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
export PATH="$JAVA_HOME/bin:$PATH"

java -version
# 출력: openjdk version "17.0.10" 2024-01-16
#        OpenJDK Runtime Environment (build 17.0.10+7)

# ~/.bashrc에 영구 적용
cat >> ~/.bashrc << 'EOF'

# Java 환경 설정
export JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
export PATH="$JAVA_HOME/bin:$PATH"
EOF

Python 버전 전환 (pyenv 원리 이해) — pyenv는 PATH 앞에 shim을 넣어 버전별 실행 파일을 전환합니다:

로컬 터미널
# pyenv를 설치하지 않고 수동으로 같은 원리 구현
# 여러 버전의 Python이 /opt/python/ 아래 설치되어 있다고 가정

ls /opt/python/
# 출력: 3.9.18  3.10.13  3.11.8  3.12.2

# Python 3.11 사용
export PATH="/opt/python/3.11.8/bin:$PATH"
python3 --version
# 출력: Python 3.11.8

# pyenv가 실제로 하는 일 확인 (pyenv 설치 시)
cat ~/.pyenv/shims/python3
# 실제로는 PATH 앞부분에 shims 디렉토리를 추가하고
# shim 스크립트가 올바른 버전으로 호출을 중계함

# pyenv 설치 시 ~/.bashrc에 들어가는 내용
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"    # pyenv가 PATH를 조작하는 함수 실행

Node.js 버전 전환 (nvm 원리) — nvm도 PATH를 조작해 활성화된 버전의 node를 먼저 찾도록 합니다:

로컬 터미널
# nvm 없이 수동으로 Node 버전 전환
ls /opt/node/
# 출력: v18.20.2  v20.12.0  v22.1.0

# Node 20 사용
export PATH="/opt/node/v20.12.0/bin:$PATH"
node --version
# 출력: v20.12.0

npm --version
# 출력: 10.5.0

# nvm 설치 시 ~/.bashrc에 들어가는 내용
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"   # nvm 로드
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"

프로젝트별 자동 버전 전환 — .nvmrc 또는 .python-version 활용 — 프로젝트 디렉터리에 들어오면 지정된 버전으로 자동 전환합니다:

로컬 터미널
# 프로젝트 디렉토리에 버전 파일 생성
echo "20" > ~/projects/my-app/.nvmrc
echo "3.11" > ~/projects/ml-project/.python-version

# ~/.bashrc에 디렉토리 이동 시 자동 전환 함수 추가
cat >> ~/.bashrc << 'EOF'

# 디렉토리 이동 시 .nvmrc 자동 감지
cd() {
    builtin cd "$@"
    if [ -f ".nvmrc" ] && command -v nvm &>/dev/null; then
        nvm use --silent
    fi
}
EOF

버전 전환 확인 스크립트 — 현재 활성화된 Python/Node 버전과 PATH를 한 번에 확인합니다:

로컬 터미널
cat ~/bin/check-versions << 'EOF'
#!/bin/bash
echo "=== 현재 활성 런타임 버전 ==="
command -v java &>/dev/null && echo "Java:   $(java -version 2>&1 | head -1)"
command -v python3 &>/dev/null && echo "Python: $(python3 --version)"
command -v node &>/dev/null && echo "Node:   $(node --version)"
command -v npm &>/dev/null && echo "npm:    $(npm --version)"
echo ""
echo "=== 런타임 위치 ==="
command -v java &>/dev/null && echo "java:   $(which java)"
command -v python3 &>/dev/null && echo "python3: $(which python3)"
command -v node &>/dev/null && echo "node:   $(which node)"
EOF
chmod +x ~/bin/check-versions

check-versions
# 출력:
# === 현재 활성 런타임 버전 ===
# Java:   openjdk version "17.0.10" 2024-01-16
# Python: Python 3.11.8
# Node:   v20.12.0
# npm:    10.5.0
#
# === 런타임 위치 ===
# java:    /usr/lib/jvm/java-17-openjdk-amd64/bin/java
# python3: /opt/python/3.11.8/bin/python3
# node:    /opt/node/v20.12.0/bin/node

트러블슈팅

상황: git status를 실행하면 command not found가 납니다. 분명히 예전엔 됐는데 갑자기 안 되거나, 새 서버에서 처음 접속했을 때 발생합니다.

원인: (1) 프로그램이 설치되지 않음, (2) 설치는 됐지만 실행 파일 경로가 PATH에 없음, (3) 오타 — 세 가지 중 하나입니다. PATH가 .bashrc 오타로 오염되거나, 다른 도구가 PATH를 덮어쓴 경우에도 발생합니다.

진단: 설치 여부와 PATH를 단계별로 확인합니다.

로컬 터미널
# 1단계: 실행 파일이 시스템 어디에 있는지 확인
which git          # PATH 검색
find /usr -name "git" -type f 2>/dev/null
# /usr/bin/git  ← 설치는 됐는데 PATH에 없는 것

# 2단계: 발견된 경로로 직접 실행 (PATH와 무관하게)
/usr/bin/git --version
# git version 2.39.3  ← 실행 됨 = PATH 문제 확정

# 3단계: 현재 PATH에서 무엇이 빠져 있는지 확인
echo $PATH | tr ':' '\n'
# /usr/local/bin
# /home/alice/bin
# ← /usr/bin이 없음

# PATH 오염 원인 추적
grep -n "PATH" ~/.bashrc ~/.bash_profile ~/.profile 2>/dev/null

해결:

로컬 터미널
# 임시 복구 (현재 세션)
export PATH="/usr/bin:$PATH"
git status  # 이제 됨

# PATH가 완전히 망가진 경우 기본값으로 리셋
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin"

# .bashrc에서 문제 줄 수정 후 적용
nano ~/.bashrc
source ~/.bashrc

상황: .bashrc를 수정하고 source ~/.bashrc를 실행했더니 ls, cat, which 같은 기본 명령어까지 전부 command not found가 납니다. 터미널이 거의 사용 불가 상태가 됐습니다.

원인: export PATH="/opt/myapp" 처럼 $PATH를 빠뜨리고 경로를 완전히 덮어쓴 경우 발생합니다. 기존 /usr/bin, /bin 경로가 모두 날아가면서 시스템 명령어들을 찾을 수 없게 됩니다.

진단: 현재 PATH가 무엇인지 확인합니다.

로컬 터미널
# 현재 PATH 확인
echo $PATH
# /opt/myapp   ← /usr/bin, /bin이 없음

# .bashrc에서 문제 줄 찾기 (절대 경로 사용 - PATH 없이)
/usr/bin/grep -n "PATH" /home/$USER/.bashrc
# 45: export PATH="/opt/myapp"   ← $PATH 누락

해결:

로컬 터미널
# PATH 즉시 복구 (export는 bash 내장 명령이므로 PATH 불필요)
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin"

# 이제 기본 명령어 사용 가능해짐
grep -n "PATH" ~/.bashrc
# 45: export PATH="/opt/myapp"

# 문제 줄 수정
nano ~/.bashrc
# export PATH="/opt/myapp:$PATH"  ← $PATH 추가

source ~/.bashrc  # 적용

# 예방: .bashrc 수정 전 백업 습관화
cp ~/.bashrc ~/.bashrc.backup.$(date +%Y%m%d)
# 새 PATH 설정은 서브쉘에서 먼저 테스트
(export PATH="/opt/newapp/bin:$PATH"; newapp --version)

상황: 터미널에서 직접 ~/bin/deploy.sh를 실행하면 잘 되는데, crontab에 등록하고 자동으로 돌리면 항상 실패합니다. 로그에는 command not found가 찍혀 있습니다.

원인: cron은 매우 제한된 환경변수(PATH=/usr/bin:/bin, SHELL=/bin/sh)만 가집니다. 터미널에서 설정한 JAVA_HOME, PYENV_ROOT, NVM_DIR 등은 cron 환경에서 존재하지 않습니다.

진단: cron 실제 환경변수를 파일로 덤프해 확인합니다.

로컬 터미널
# crontab에 1회성 디버그 작업 추가
crontab -e
# * * * * * env > /tmp/cron-env.txt 2>&1

# 1분 후 확인
cat /tmp/cron-env.txt
# HOME=/home/alice
# LOGNAME=alice
# PATH=/usr/bin:/bin       ← /opt/maven/bin이 없음
# SHELL=/bin/sh            ← bash가 아닌 sh
# ← JAVA_HOME, MAVEN_HOME 모두 없음

# cron과 동일한 환경에서 스크립트 테스트
env -i HOME=$HOME LOGNAME=$USER PATH=/usr/bin:/bin SHELL=/bin/sh bash ~/bin/deploy.sh

해결:

로컬 터미널
# 방법 1 (권장): 스크립트 내에서 환경변수 명시적 선언
cat > ~/bin/deploy.sh << 'EOF'
#!/bin/bash
export JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
export MAVEN_HOME="/opt/maven"
export PATH="$JAVA_HOME/bin:$MAVEN_HOME/bin:/usr/local/bin:/usr/bin:/bin"

cd /home/alice/projects/my-app || exit 1
mvn clean package -q >> /var/log/deploy.log 2>&1
EOF
chmod +x ~/bin/deploy.sh

# 방법 2: crontab 상단에 환경변수 선언
crontab -e
# SHELL=/bin/bash
# PATH=/opt/maven/bin:/usr/lib/jvm/java-17/bin:/usr/bin:/bin
# JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
# 0 2 * * * /home/alice/bin/deploy.sh >> /var/log/deploy.log 2>&1

# cron 로그 확인 (실행 자체가 안 될 때)
journalctl -u crond --since "1 hour ago"
grep CRON /var/log/syslog | tail -20

고급 활용 — dotfiles 체계적으로 관리하기

새 서버에 접속할 때마다 alias를 다시 설정하고, PATH를 다시 추가하고, 프롬프트를 다시 꾸미는 상황이 반복된다면, dotfiles를 체계적으로 관리할 때입니다. .bashrc를 Git 저장소로 관리하면 어느 서버에서든 git clone 한 번으로 동일한 환경을 복원할 수 있습니다. 아래는 실무에서 바로 쓸 수 있는 .bashrc 구조와 Git 기반 dotfiles 관리 패턴입니다.

로컬 터미널
# 실무에서 권장하는 ~/.bashrc 구조
cat ~/.bashrc
로컬 터미널
#!/bin/bash
# ~/.bashrc — Alice의 쉘 설정
# 마지막 수정: 2026-03-26

# 1. 인터랙티브 쉘이 아니면 즉시 종료 (중요!)
case $- in
    *i*) ;;
      *) return;;
esac

# 2. 히스토리 설정
HISTCONTROL=ignoreboth    # 중복 및 공백으로 시작하는 명령 저장 안 함
HISTSIZE=10000            # 메모리에 저장할 히스토리 수
HISTFILESIZE=20000        # 파일에 저장할 히스토리 수
shopt -s histappend       # 쉘 종료 시 히스토리 파일에 추가 (덮어쓰지 않음)

# 3. 쉘 동작 설정
shopt -s checkwinsize     # 터미널 크기 변경 시 자동 업데이트
shopt -s globstar         # **/ 패턴 지원

# 4. PATH 설정
if [ -d "$HOME/bin" ]; then
    export PATH="$HOME/bin:$PATH"
fi
if [ -d "$HOME/.local/bin" ]; then
    export PATH="$HOME/.local/bin:$PATH"
fi

# 5. 개발 환경
export JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
export PATH="$JAVA_HOME/bin:$PATH"

export EDITOR="vim"
export VISUAL="vim"
export PAGER="less"

# 6. alias 로드
if [ -f ~/.bash_aliases ]; then
    source ~/.bash_aliases
fi

# 7. 프롬프트 설정 (PS1)
# \u = 사용자, \h = 호스트, \w = 현재 디렉토리
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '

# 8. 자동완성
if ! shopt -oq posix; then
    if [ -f /usr/share/bash-completion/bash_completion ]; then
        source /usr/share/bash-completion/bash_completion
    fi
fi

dotfiles를 Git으로 관리하는 방법 — 설정 파일을 Git 저장소로 관리해 서버마다 일관된 환경을 유지합니다:

로컬 터미널
# dotfiles 저장소 초기화
mkdir -p ~/dotfiles
cd ~/dotfiles
git init

# 심볼릭 링크 방식으로 관리
cp ~/.bashrc ~/dotfiles/bashrc
ln -sf ~/dotfiles/bashrc ~/.bashrc

cp ~/.bash_aliases ~/dotfiles/bash_aliases
ln -sf ~/dotfiles/bash_aliases ~/.bash_aliases

# Git에 추가
cd ~/dotfiles
git add bashrc bash_aliases
git commit -m "초기 dotfiles 설정"

# GitHub에 올려두면 새 서버에서 바로 복원 가능
git remote add origin git@github.com:alice/dotfiles.git
git push -u origin main

새 서버에 dotfiles 설치 스크립트 — 새 서버에서 한 번의 실행으로 개인 환경 설정을 모두 적용합니다:

로컬 터미널
cat > ~/dotfiles/install.sh << 'EOF'
#!/bin/bash
# 새 서버에 dotfiles 설치

DOTFILES_DIR="$HOME/dotfiles"

echo "dotfiles 설치 시작..."

# 기존 파일 백업
for file in bashrc bash_aliases bash_profile; do
    if [ -f "$HOME/.$file" ] && [ ! -L "$HOME/.$file" ]; then
        mv "$HOME/.$file" "$HOME/.$file.backup.$(date +%Y%m%d)"
        echo "백업: ~/.$file → ~/.$file.backup.$(date +%Y%m%d)"
    fi
done

# 심볼릭 링크 생성
ln -sf "$DOTFILES_DIR/bashrc" "$HOME/.bashrc"
ln -sf "$DOTFILES_DIR/bash_aliases" "$HOME/.bash_aliases"
ln -sf "$DOTFILES_DIR/bash_profile" "$HOME/.bash_profile"

echo "설치 완료. 'source ~/.bashrc' 실행하세요."
EOF
chmod +x ~/dotfiles/install.sh

빠른 참고표

환경변수 관련 명령어 요약 — 자주 쓰는 환경변수 관련 명령어와 용도를 한눈에 정리했습니다:

명령어용도예시
export VAR=value환경변수 설정 및 내보내기export DEBUG=true
unset VAR변수 삭제unset DEBUG
printenv VAR특정 변수 값 출력printenv HOME
env모든 환경변수 출력env | sort
set변수 + 함수 모두 출력set | grep PATH
source FILE파일을 현재 쉘에서 실행source ~/.bashrc
. FILEsource의 단축형. ~/.bashrc
which CMD명령어 위치 확인which python3
type CMD명령어 종류 확인type ls
alias NAME='CMD'alias 정의alias ll='ls -alF'
unalias NAMEalias 삭제unalias ll
alias모든 alias 목록alias

dotfiles 로드 순서 요약 — 쉘 시작 모드별 설정 파일 로드 순서를 시각화합니다:

SSH 로그인 (Login Shell)
├── /etc/environment           ← PAM이 읽음, 모든 로그인에 적용
├── /etc/profile               ← 시스템 전체 login shell 설정
├── /etc/profile.d/*.sh        ← 패키지별 시스템 설정
└── ~/.bash_profile            ← 사용자 login shell 설정
    └── (보통 ~/.bashrc 호출)

새 터미널 탭 (Interactive Non-Login Shell)
├── /etc/bash.bashrc           ← 시스템 전체 interactive 설정
└── ~/.bashrc                  ← 사용자 interactive 설정

스크립트 / cron (Non-Interactive, Non-Login)
└── (아무 설정 파일도 자동으로 읽지 않음!)
    → 필요한 환경변수는 스크립트 내에서 직접 설정

다음 모듈에서는 프로세스 관리(Process Management)를 다룹니다 — ps, top, kill, nice, pgrep 등으로 실행 중인 프로세스를 조회하고 제어하는 방법을 배웁니다.

지식 확인

퀴즈 — 5문제

Q1

지역 변수와 환경변수의 차이를 올바르게 설명한 것은?

Q2

`.bashrc`와 `.bash_profile`의 차이로 올바른 것은?

Q3

현재 셸에서 설정한 환경변수를 제거하려면 어떤 명령어를 사용해야 합니까?

Q4

`$PATH`에 새 디렉토리를 추가할 때 기존 경로를 보존하는 올바른 방법은?

Q5

`/etc/environment` 파일의 특징으로 올바른 것은?

0 / 5 답변

🧪 실습으로 확인하기

새 서버 인수인계 — 처음 30분

초급

낯선 Linux 서버를 인수받았을 때 OS, 서비스, 로그를 빠르게 파악하는 루틴을 직접 수행한다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

linux중급 · 50
[Linux] kill/pkill 시그널의 종류와 프로세스 안전 종료 규칙
Linux 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점