Skip to main content

CLI / 터미널 기초

CLI(Command Line Interface)는 텍스트 명령어로 컴퓨터를 제어하는 인터페이스입니다. GUI에서 마우스 클릭으로 하는 작업을 CLI에서는 명령어 한 줄로 처리합니다. 차이는 자동화 가능성입니다. GUI 작업은 반복할 때마다 사람이 직접 클릭해야 하지만, CLI 명령어는 스크립트로 저장하여 무한히 반복할 수 있습니다. 100개의 모델을 학습시키고, 결과를 정리하고, 서버에 배포하는 과정 — 이 모든 것을 CLI로 자동화할 수 있습니다.

학습 목표

  • 셸, 터미널, 콘솔의 차이를 이해하고 자신의 셸 환경을 설정할 수 있다
  • 파이프와 리다이렉션을 조합하여 복잡한 데이터 처리 파이프라인을 구성할 수 있다
  • SSH를 사용하여 원격 GPU 서버에 접속하고, 파일을 전송하며, 터널링을 설정할 수 있다
  • 셸 스크립트와 cron으로 반복 작업을 자동화할 수 있다

왜 중요한가

AI/ML 개발에서 CLI 숙련도는 생산성을 결정짓는 핵심 역량입니다. 데이터 전처리 파이프라인을 설계하고, 원격 GPU 서버에서 학습을 실행하고, 결과 로그를 분석하고, 모델을 배포하는 전 과정이 CLI에서 이루어집니다. 특히 GPU 서버는 대부분 SSH로만 접근 가능합니다. GUI 없이 서버를 다뤄야 하는 상황에서, CLI 능력이 곧 작업 능력입니다. 또한 ML 실험은 본질적으로 반복적입니다 — 하이퍼파라미터를 바꿔가며 수십, 수백 번 학습을 돌리는 과정을 스크립트로 자동화하면 엄청난 시간을 절약할 수 있습니다.

셸과 터미널 개념

용어설명비유
터미널(Terminal)텍스트 입출력을 처리하는 프로그램 (창)전화기
셸(Shell)명령어를 해석하고 실행하는 프로그램통화 상대방 (운영체제)
콘솔(Console)물리적 터미널 또는 시스템 콘솔유선 전화 (직접 연결)
┌─────────────────────────────────────────┐
│ 터미널 에뮬레이터 (iTerm2, Wezterm 등)   │
│  ┌───────────────────────────────────┐  │
│  │ 셸 (bash, zsh, fish)              │  │
│  │  $ echo "Hello"                   │  │
│  │  Hello                            │  │
│  │  $ _                              │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

셸 비교

특징설정 파일추천 대상
bash리눅스 기본, 가장 보편적, POSIX 호환~/.bashrc, ~/.bash_profile서버 환경, 스크립트 작성
zshmacOS 기본, 풍부한 플러그인(oh-my-zsh)~/.zshrc개발 워크스테이션
fish자동완성 우수, 직관적 문법, POSIX 비호환~/.config/fish/config.fish입문자, 인터랙티브 사용
서버에서 스크립트를 작성할 때는 #!/bin/bash를 사용하세요. bash는 거의 모든 리눅스 서버에 기본 설치되어 있어 이식성이 가장 좋습니다. 로컬 개발 환경에서는 zsh의 풍부한 기능을 활용하세요.

파이프와 리다이렉션

기본 개념

기호이름설명예시
|파이프왼쪽 명령의 stdout을 오른쪽 명령의 stdin으로cat log | grep ERROR
>출력 리다이렉션 (덮어쓰기)stdout을 파일로 저장echo "hello" > file.txt
>>출력 리다이렉션 (추가)stdout을 파일 끝에 추가echo "world" >> file.txt
<입력 리다이렉션파일 내용을 stdin으로wc -l < data.csv
2>stderr 리다이렉션에러 출력을 파일로cmd 2> error.log
2>&1stderr → stdout 합치기에러와 출력 통합cmd > all.log 2>&1
&>stdout + stderr 리다이렉션모든 출력을 파일로 (bash)cmd &> all.log
/dev/null블랙홀출력 버리기cmd > /dev/null 2>&1
tee출력 분기stdout을 파일과 화면 모두에cmd | tee output.log
# 파이프와 리다이렉션 조합
# 학습 스크립트 실행: 화면에도 출력하면서 로그 파일에도 저장
python train.py 2>&1 | tee training.log

# 에러만 별도 파일로 저장, 일반 출력은 화면에
python train.py 2> errors.log

# 출력은 파일로, 에러는 버리기
python preprocess.py > result.txt 2>/dev/null

자주 쓰는 파이프라인 조합

파이프라인설명실무 예시
cmd | grep pattern출력에서 패턴 필터링ps aux | grep python
cmd | sort | uniq -c정렬 후 중복 카운트로그에서 에러 유형별 집계
cmd | head -n 20처음 20줄만 확인대용량 파일 미리보기
cmd | tail -f실시간 출력 추적학습 로그 모니터링
cmd | wc -l줄 수 카운트데이터셋 레코드 수 확인
cmd | awk '{print $N}'N번째 필드 추출특정 컬럼 데이터 추출
cmd | xargs -P N병렬 실행파일 일괄 처리
cmd | tee file | cmd2중간 결과 저장하면서 계속 처리파이프라인 디버깅
cmd1 && cmd2cmd1 성공 시에만 cmd2 실행빌드 → 테스트 → 배포 체인
cmd1 || cmd2cmd1 실패 시 cmd2 실행에러 시 대안 명령 실행
# GPU 메모리를 가장 많이 사용하는 프로세스 정렬
nvidia-smi --query-compute-apps=pid,used_gpu_memory --format=csv,noheader | sort -t',' -k2 -rn

# 학습 로그에서 에폭별 최저 loss 추출
grep "loss:" training.log | awk '{print $2, $4}' | sort -k2 -n | head -5

# CSV 파일의 특정 컬럼 값 분포 확인
cut -d',' -f3 data.csv | sort | uniq -c | sort -rn | head -10

# Python 파일 줄 수 통계
find . -name "*.py" | xargs wc -l | sort -n | tail -10

# 여러 서버의 GPU 상태 한번에 확인
for host in gpu01 gpu02 gpu03; do
  echo "=== $host ===" && ssh $host nvidia-smi --query-gpu=index,memory.used --format=csv
done

# 디렉터리별 파일 수 카운트
find /data/images -type d | while read dir; do
  echo "$(find "$dir" -maxdepth 1 -type f | wc -l) $dir"
done | sort -rn | head -10

SSH와 원격 작업

SSH 기본 사용법

# 기본 접속
ssh user@hostname

# 포트 지정
ssh -p 2222 user@hostname

# 키 파일 지정
ssh -i ~/.ssh/id_rsa user@hostname

SSH 설정 파일 (~/.ssh/config)

# ~/.ssh/config
Host gpu-server
    HostName 192.168.50.248
    User baeumai
    Port 22
    IdentityFile ~/.ssh/id_ed25519
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host gpu-t7910
    HostName 192.168.50.250
    User baeumai
    Port 22
    ForwardAgent yes

# 이후 간단히 접속
# ssh gpu-server
# ssh gpu-t7910

파일 전송

명령어특징사용
scp간단한 파일 복사scp file.py user@host:/path/
rsync증분 동기화, 재개 가능, 빠름rsync -avz --progress src/ user@host:/dest/
sftp인터랙티브 파일 전송sftp user@host
# 대용량 데이터셋 동기화 (변경분만 전송)
rsync -avz --progress /data/datasets/ user@gpu-server:/data/datasets/

# 모델 체크포인트 다운로드
rsync -avz user@gpu-server:/data/checkpoints/best_model.pt ./

# 학습 코드 동기화 (git으로 관리하지 않는 파일 포함)
rsync -avz --exclude='.git' --exclude='__pycache__' --exclude='.venv' \
  ./project/ user@gpu-server:~/project/

SSH 터널링 (포트 포워딩)

# 로컬 포워딩: 원격 서버의 서비스를 로컬에서 접근
# JupyterLab (원격 8888 → 로컬 8888)
ssh -L 8888:localhost:8888 user@gpu-server

# TensorBoard (원격 6006 → 로컬 6006)
ssh -L 6006:localhost:6006 user@gpu-server

# MLflow (원격 5000 → 로컬 5000)
ssh -L 5000:localhost:5000 user@gpu-server

# 백그라운드 터널 (접속 유지하면서 다른 작업)
ssh -fNL 8888:localhost:8888 user@gpu-server

tmux (터미널 멀티플렉서)

SSH 연결이 끊어져도 프로세스가 계속 실행됩니다. 원격 서버에서 학습을 실행할 때 필수입니다.
# 새 세션 생성
tmux new -s training

# 학습 실행 후 세션에서 분리 (학습은 계속 실행)
# Ctrl+B, D

# 세션 목록 확인
tmux ls

# 세션 다시 연결
tmux attach -t training

# 세션 종료
tmux kill-session -t training
tmux 단축키동작
Ctrl+B, D세션 분리 (detach)
Ctrl+B, C새 창(window) 생성
Ctrl+B, N/P다음/이전 창 이동
Ctrl+B, %수평 분할 (pane)
Ctrl+B, "수직 분할 (pane)
Ctrl+B, 방향키pane 간 이동
Ctrl+B, [스크롤 모드 (q로 종료)
SSH 세션에서 직접 python train.py를 실행하면, SSH 연결이 끊어질 때 학습도 함께 종료됩니다 (SIGHUP 시그널). 반드시 tmux 또는 nohup을 사용하세요.

alias와 셸 함수

alias 설정

# ~/.bashrc 또는 ~/.zshrc에 추가

# 시스템 모니터링
alias gs='nvidia-smi'
alias gw='watch -n 1 nvidia-smi'
alias mem='free -h'
alias disk='df -hT | grep -v tmpfs'

# 자주 쓰는 작업
alias ll='ls -la'
alias ..='cd ..'
alias ...='cd ../..'
alias py='python'
alias activate='source .venv/bin/activate'

# 안전 장치
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Git 단축
alias gst='git status'
alias gco='git checkout'
alias gcm='git commit -m'
alias gp='git push'

셸 함수

# ~/.bashrc 또는 ~/.zshrc에 추가

# GPU 특정 번호로 학습 실행
train() {
    local gpu=${1:-0}
    local script=${2:-train.py}
    shift 2
    CUDA_VISIBLE_DEVICES=$gpu python $script "$@"
}
# 사용: train 0 train.py --lr 0.001

# 프로젝트 디렉터리로 이동 + 가상환경 활성화
proj() {
    cd ~/projects/$1 && source .venv/bin/activate
}
# 사용: proj my-experiment

# 포트 사용 중인 프로세스 확인
port() {
    lsof -i :$1
}
# 사용: port 8888

# SSH 터널 한번에 열기
tunnel() {
    local port=${1:-8888}
    local host=${2:-gpu-server}
    ssh -fNL $port:localhost:$port $host
    echo "터널 열림: localhost:$port$host:$port"
}
# 사용: tunnel 8888 gpu-server

셸 스크립트 기초

shebang과 기본 구조

#!/bin/bash
# 셸 스크립트의 첫 줄: 실행할 인터프리터 지정
# 실행 권한 부여: chmod +x script.sh
# 실행: ./script.sh 또는 bash script.sh

변수

#!/bin/bash

# 변수 선언 (= 앞뒤 공백 없음!)
NAME="experiment_01"
EPOCHS=100
LR=0.001
GPU_ID=0

# 변수 사용
echo "실험: $NAME"
echo "에폭: ${EPOCHS}, 학습률: ${LR}"

# 명령어 결과를 변수에 저장
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
GPU_COUNT=$(nvidia-smi -L | wc -l)

# 환경변수 내보내기 (자식 프로세스에 전달)
export CUDA_VISIBLE_DEVICES=$GPU_ID

조건문

#!/bin/bash

# if 문
if [ -f "checkpoint.pt" ]; then
    echo "체크포인트 발견 — 학습 재개"
    RESUME="--resume checkpoint.pt"
else
    echo "새 학습 시작"
    RESUME=""
fi

# GPU 사용 가능 여부 확인
if command -v nvidia-smi &> /dev/null; then
    GPU_AVAILABLE=true
    echo "GPU 사용 가능: $(nvidia-smi -L)"
else
    GPU_AVAILABLE=false
    echo "GPU 없음 — CPU 모드로 실행"
fi

# case 문: 인자에 따른 분기
case "$1" in
    train)
        python train.py "${@:2}"
        ;;
    eval)
        python evaluate.py "${@:2}"
        ;;
    export)
        python export_model.py "${@:2}"
        ;;
    *)
        echo "사용법: $0 {train|eval|export} [옵션...]"
        exit 1
        ;;
esac

반복문

#!/bin/bash

# for 문: 하이퍼파라미터 그리드 서치
for lr in 0.1 0.01 0.001 0.0001; do
    for batch_size in 16 32 64; do
        echo "학습: lr=$lr, batch_size=$batch_size"
        python train.py --lr $lr --batch-size $batch_size \
            --name "exp_lr${lr}_bs${batch_size}"
    done
done

# while 문: GPU 메모리가 비워질 때까지 대기
while true; do
    FREE_MEM=$(nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits -i 0)
    if [ "$FREE_MEM" -gt 20000 ]; then
        echo "GPU 메모리 확보됨 (${FREE_MEM}MB) — 학습 시작"
        break
    fi
    echo "GPU 메모리 부족 (${FREE_MEM}MB) — 30초 후 재확인"
    sleep 30
done

python train.py

종료 코드와 에러 처리

#!/bin/bash
set -euo pipefail
# -e: 에러 시 즉시 종료
# -u: 미정의 변수 사용 시 에러
# -o pipefail: 파이프라인 중 하나라도 실패하면 에러

# 종료 코드 확인 ($? = 직전 명령의 종료 코드)
python train.py
if [ $? -eq 0 ]; then
    echo "학습 성공!"
    python evaluate.py
else
    echo "학습 실패! 종료 코드: $?"
    exit 1
fi

# trap: 스크립트 종료 시 정리 작업
cleanup() {
    echo "정리 작업 실행..."
    rm -rf /tmp/training_cache_$$
    echo "완료"
}
trap cleanup EXIT

cron과 자동화

crontab 문법

# ┌───────────── 분 (0-59)
# │ ┌───────────── 시 (0-23)
# │ │ ┌───────────── 일 (1-31)
# │ │ │ ┌───────────── 월 (1-12)
# │ │ │ │ ┌───────────── 요일 (0-7, 0과 7은 일요일)
# │ │ │ │ │
# * * * * * command

실무 예시

# crontab 편집
crontab -e

# === 로그 정리 ===
# 매주 일요일 새벽 3시: 30일 이상 된 로그 파일 삭제
0 3 * * 0 find /var/log -name "*.log" -mtime +30 -delete

# === 모니터링 ===
# 매 5분마다: 디스크 사용량 80% 이상이면 알림
*/5 * * * * df -h | awk '$5+0 > 80 {print}' | mail -s "디스크 경고" admin@example.com

# === 백업 ===
# 매일 새벽 2시: 모델 체크포인트 백업
0 2 * * * rsync -avz /data/checkpoints/ /backup/checkpoints/$(date +\%Y\%m\%d)/

# === GPU 모니터링 로그 ===
# 매 1분마다: GPU 상태 기록
* * * * * nvidia-smi --query-gpu=timestamp,gpu_name,utilization.gpu,memory.used --format=csv,noheader >> /var/log/gpu_usage.csv

# === 자동 학습 ===
# 매일 자정: 대기 중인 실험 실행
0 0 * * * /home/user/scripts/run_next_experiment.sh >> /var/log/experiments.log 2>&1

# 현재 crontab 확인
crontab -l
cron 작업에서는 환경변수가 로드되지 않습니다. 스크립트 내부에서 source ~/.bashrc 또는 절대 경로로 명령어를 지정하세요. 디버깅 시 * * * * * env > /tmp/cron_env.txt로 cron 환경을 확인할 수 있습니다.

Python/ML 개발자를 위한 CLI 팁

가상환경 관리

# 프로젝트 진입 시 자동 활성화 (direnv 사용)
# .envrc 파일에 작성:
# source .venv/bin/activate

# 또는 셸 함수로 관리
workon() {
    cd ~/projects/$1
    source .venv/bin/activate
    echo "프로젝트: $1 | Python: $(python --version) | venv: $VIRTUAL_ENV"
}

학습 스크립트 실행 패턴

# 기본: tmux + 로그 저장
tmux new -s train
python train.py 2>&1 | tee logs/train_$(date +%Y%m%d_%H%M%S).log
# Ctrl+B, D (분리)

# nohup: 간단한 백그라운드 실행
nohup python train.py > train.log 2>&1 &
echo $!  # PID 확인

# GPU 지정 + 환경변수 + 로그
CUDA_VISIBLE_DEVICES=0,1 \
OMP_NUM_THREADS=4 \
torchrun --nproc_per_node=2 train.py \
    --config configs/experiment.yaml \
    2>&1 | tee logs/distributed_$(date +%Y%m%d).log

실험 관리 스크립트

#!/bin/bash
# run_experiments.sh — 하이퍼파라미터 스윕 자동화
set -euo pipefail

EXPERIMENT_NAME="resnet50_sweep"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_DIR="logs/${EXPERIMENT_NAME}_${TIMESTAMP}"
mkdir -p "$LOG_DIR"

LEARNING_RATES=(0.01 0.001 0.0001)
BATCH_SIZES=(32 64 128)

for lr in "${LEARNING_RATES[@]}"; do
    for bs in "${BATCH_SIZES[@]}"; do
        RUN_NAME="${EXPERIMENT_NAME}_lr${lr}_bs${bs}"
        LOG_FILE="${LOG_DIR}/${RUN_NAME}.log"

        echo "[$(date)] 시작: $RUN_NAME"

        CUDA_VISIBLE_DEVICES=0 python train.py \
            --name "$RUN_NAME" \
            --lr "$lr" \
            --batch-size "$bs" \
            --epochs 50 \
            > "$LOG_FILE" 2>&1

        if [ $? -eq 0 ]; then
            echo "[$(date)] 성공: $RUN_NAME"
        else
            echo "[$(date)] 실패: $RUN_NAME (로그: $LOG_FILE)"
        fi
    done
done

echo "모든 실험 완료. 로그: $LOG_DIR"
~/.ssh/configServerAliveInterval 60ServerAliveCountMax 3을 추가하세요. 60초마다 keepalive 패킷을 보내 연결을 유지합니다. 서버 측에서도 /etc/ssh/sshd_configClientAliveIntervalClientAliveCountMax를 확인하세요. 근본적으로는 tmux를 사용하면 SSH 연결이 끊어져도 작업이 계속 유지됩니다.
set -o pipefail을 사용하면 파이프라인 중 하나라도 실패 시 전체가 실패합니다. 디버깅 시에는 파이프를 끊고 단계별로 실행하거나, tee로 중간 결과를 파일에 저장하세요. 예: cmd1 | tee /tmp/step1.txt | cmd2 | tee /tmp/step2.txt | cmd3. $PIPESTATUS 배열로 각 단계의 종료 코드를 확인할 수도 있습니다.
tmux를 권장합니다. nohup은 단순히 SIGHUP을 무시할 뿐이지만, tmux는 세션을 완전히 분리하여 나중에 다시 연결할 수 있습니다. 학습 진행 상황을 확인하고, 로그를 스크롤하고, 여러 창을 관리할 수 있습니다. nohup은 일회성 간단한 작업에만 사용하세요.
-e는 에러 발생 시 즉시 종료하여 잘못된 상태로 계속 진행하는 것을 방지합니다. -u는 정의되지 않은 변수 사용을 에러로 처리하여 오타를 잡아냅니다. -o pipefail은 파이프라인의 중간 실패를 감지합니다. 세 옵션을 함께 사용하면 스크립트의 안정성이 크게 향상됩니다.
가장 흔한 원인: (1) 환경변수 미로드 — cron은 minimal 환경에서 실행되므로 PATH, CUDA_HOME 등이 없습니다. 스크립트 안에서 source ~/.bashrc를 추가하세요. (2) 권한 문제 — 실행 파일에 chmod +x가 되어있는지 확인하세요. (3) 절대 경로 미사용 — python 대신 /usr/bin/python3 또는 /home/user/.venv/bin/python을 사용하세요. cron 로그는 /var/log/cron 또는 journalctl -u cron으로 확인합니다.
SSH 터널링을 사용합니다. ssh -L 8888:localhost:8888 user@gpu-server로 접속한 후, 서버에서 jupyter lab --no-browser --port 8888을 실행하세요. 로컬 브라우저에서 http://localhost:8888로 접속하면 원격 Jupyter에 연결됩니다. ~/.ssh/configLocalForward 8888 localhost:8888을 추가하면 매번 -L 옵션을 쓰지 않아도 됩니다.

체크리스트

  • 셸, 터미널, 콘솔의 차이를 설명할 수 있다
  • 파이프(|)와 리다이렉션(>, >>, 2>&1)을 자유롭게 사용할 수 있다
  • SSH config를 설정하고, 원격 서버에 편리하게 접속할 수 있다
  • scp 또는 rsync로 파일을 전송할 수 있다
  • SSH 터널링으로 원격 서비스(Jupyter, MLflow)에 접속할 수 있다
  • tmux로 세션을 생성, 분리, 재연결할 수 있다
  • alias와 셸 함수를 .bashrc/.zshrc에 설정할 수 있다
  • 변수, 조건문, 반복문을 사용한 셸 스크립트를 작성할 수 있다
  • crontab으로 반복 작업을 스케줄링할 수 있다
  • nohup 또는 tmux를 사용하여 학습 스크립트를 안전하게 실행할 수 있다

다음 문서