시스템 · 서버핵심 · 21분 읽기

셸의 실행 원리: 인용·환경·파이프·종료 상태·tar

입력한 문자열이 인수와 프로세스로 바뀌는 과정부터 환경 상속, 입출력 연결, 스크립트와 아카이브의 동작을 추적한다.

개념부터 찾아보기

정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.

이 노트에서 설명할 수 있게 되는 것

  1. 셸과 터미널·콘솔을 구분하고 인용이 인수에 미치는 영향을 설명한다.
  2. 표준 입출력과 파이프·종료 상태를 연결한다.
  3. 환경 상속과 스크립트 실행 방식, tar 생성·조회·추출의 차이를 적용한다.
먼저 알면 좋은 개념Linux 파일과 경로 읽기

01터미널·TTY·셸·프로세스의 관계

  • 터미널 에뮬레이터는 문자 입출력 화면과 키보드 입력을 제공하고, 셸은 그 안에서 명령을 해석하는 프로세스다.
  • 콘솔은 직접 관리에 쓰는 입출력 통로를 가리키며 물리·가상·직렬 콘솔이 있다.
  • SSH의 대화형 세션이나 터미널은 가상 터미널 장치인 PTS를 사용할 수 있다.
  • /bin/bash는 콘솔의 이름이 아니라 Bash 실행 파일 경로다.
  • 사용자가 명령을 입력하면 셸은 문법을 해석하고 확장·입출력 연결을 처리한 뒤 내장 기능 또는 외부 프로그램을 실행한다.
  • 외부 명령이 끝나면 셸은 종료 상태를 받는다.
  • cd는 현재 셸의 작업 디렉터리를 바꿔야 하므로 일반적인 사용에서는 셸 내장 명령으로 동작한다.
  • 자식 에서만 디렉터리를 바꾸면 부모 셸의 위치는 바뀌지 않는다.
  1. 셸이 입력을 단어·연산자 등으로 해석하여 단순 명령이나 파이프 등의 구조를 구분한다. 따옴표는 데이터의 경계와 이후 확장 방식을 정한다.
  2. 변수·명령 치환 등 해당 문맥에서 허용되는 확장을 처리한다. 인용하지 않은 확장 결과에는 ·경로명 확장이 추가로 적용될 수 있다.
  3. 과 파이프가 지정됐으면 파일 디스크립터의 연결을 준비한다. 을 열지 못하는 등 이 단계의 오류로 명령 실행이 실패할 수 있다.
  4. 현재 문맥에 따라 내장 명령이나 외부 프로그램을 실행한다. 외부 프로그램은 준비된 인수·환경과 연결된 표준 입출력을 전달받는다.
  5. 명령이 종료되면 셸은 상태를 받아 다음 조건식이나 후속 명령에 반영한다. 종료 상태와 표준 출력에 쓰인 데이터는 서로 다른 결과다.
bash
printf "%s\n" "$SHELL"
ps -p $$ -o pid=,ppid=,comm=,args=
tty
type -a cd
command -v bash

$SHELL은 로그인 셸 정보일 수 있다. 현재 셸 와 터미널 장치를 별도로 조회하는 예시. 출력 전체에 비밀값을 포함시키는 환경 조회는 생략했다.

02문자열을 인수로 나누는 인용과 확장

  • 셸이 해석하는 따옴표는 프로그램에 전달할 데이터의 경계를 정한다.
  • 는 내부 문자를 그대로 보존하고, 큰따옴표는 변수·명령 치환을 허용하면서 확장된 값이 공백으로 다시 나뉘거나 파일 이름 패턴으로 확장되는 것을 막는 데 사용한다.
  • 인용 없는 변수 확장은 와 경로명 확장의 영향을 받을 수 있으므로 파일 경로 같은 하나의 값을 전달할 때는 보통 큰따옴표로 감싼다.
  • 아래에서 첫 출력은 하나의 인수이고 두 번째는 공백으로 분리된 두 인수다.
    • 세 번째는 변수의 값이 아니라 문자 $name을 출력한다.
  • printf의 형식 문자열은 고정하고 데이터를 별도 인수로 주면 데이터 안의 % 문자를 형식 지시자로 해석하는 혼동도 피할 수 있다.
  • 프로그램이 실제로 받은 인수와 터미널에 보이는 명령 문자열은 같은 것이 아니다.
bash
name="alpha beta"
printf "<%s>\n" "$name"
# <alpha beta>
printf "<%s>\n" $name
# <alpha>
# <beta>
printf "%s\n" '$name'
# $name

본문의 조건을 가정한 교육용 예시.

LEARN THROUGH PRACTICE

PATH 설정을 쓸 때와 source할 때의 변수 확장 비교하기

따옴표와 실행 시점이 설정 파일에 저장되는 문자열 및 현재 셸 환경을 어떻게 바꾸는지 추적한다.

실습 구성과 전제

  • 두 사진은 Miniconda 실행 경로를 셸 설정에 넣는 서로 다른 방법의 사례다. 운영용 /etc/bashrc를 그대로 수정하지 않고 새 실습 디렉터리의 파일로 재구성한다.
  • Bash의 큰따옴표와 를 비교한다. 실제 Miniconda 설치 경로는 환경마다 다르며 추가가 환경 활성화와 같은 작업은 아니다.
  1. 큰따옴표 안의 변수는 설정을 쓰는 순간 확장된다

    • 첫 사진은 echo의 큰따옴표 안에서 $를 사용하고 결과를 파일에 추가한다. 현재 셸이 변수 값을 먼저 넣으므로 저장되는 문자열에는 그 시점의 값이 포함된다.
    • 이 방식은 다른 계정·세션에서도 재평가할 식을 남기려는 의도와 다를 수 있다. 사진의 성공 여부와 별개로 파일에 실제 어떤 문자열이 쓰였는지 읽어야 한다.
    Linux 초기 실행 환경 설정 · RL 세팅 자동화원본 크기로 보기 ↗
    • echo 명령의 export PATH 표현이 큰따옴표로 감싸져 있다.
    • 출력은 >>로 /etc/bashrc에 추가되며 Miniconda의 bin 경로가 포함되어 있다.
    확인 기준

    파일에 $라는 글자가 남았는지 이미 긴 경로 목록으로 바뀌었는지 구분한다. 따옴표 종류가 단순한 꾸밈이 아니다.

  2. 작은따옴표로 식을 보존하고 실행 시점을 늦춘다

    • 두 번째 사진의 안에서는 셸이 $를 즉시 치환하지 않는다. sed가 추가한 설정 행을 나중에 셸이 읽을 때 변수 확장이 일어난다.
    • 재구성 예시는 새 파일에 식을 저장한 뒤 cat으로 확인한다. printf에 전달한 는 제거되지만 그 안의 $HOME·$ 문자열은 파일에 그대로 남는다.
    bash
    printf '%s\n' 'export PATH="$HOME/miniconda3/bin:$PATH"' > ./lab-env.sh
    cat ./lab-env.sh

    새 빈 실습 디렉터리에서만 사용할 파일 작성 예시다. 시스템 설정 파일을 변경하지 않는다.

    팀 · 실습·개념 참고 화면 4원본 크기로 보기 ↗
    • sed에 전달하는 추가 스크립트가 작은따옴표로 묶여 있고 $PATH가 포함되어 있다.
    • 그 뒤 source, PATH 조회, conda 관련 명령이 이어져 파일 작성과 환경 적용이 별도 단계임을 보여 준다.
    확인 기준

    설정 파일 내용에 변수를 남기는 것과 현재 셸에서 그 변수를 확장하는 것은 다른 시점의 작업이다.

  3. source의 영향 범위를 자식 셸로 제한해 확인한다

    • source는 현재 셸 안에서 파일의 문장을 실행한다. 반면 별도로 실행한 bash 의 환경 변경은 부모 셸로 거슬러 올라가지 않는다.
    • 아래 예시는 자식 Bash에서만 파일을 source한 뒤 경로와 명령 위치를 확인한다. 명령 발견이나 버전 출력은 환경이 활성화되었다는 증거와는 다르다.
    bash
    bash -c 'source ./lab-env.sh; printf "%s\n" "$PATH"; command -v conda; command -v python'
    command -v python

    첫 행의 환경 변경은 자식 Bash에만 적용된다. 다음 행은 원래 셸의 명령 탐색 결과다.

    확인 기준

    명령 위치가 기대한 설치 경로인지 확인한다. 에서 찾은 실행 파일과 의 활성 환경· sys.executable을 혼동하지 않는다.

실습에서 확인한 동작 원리

  • 따옴표는 변수 확장 여부를 정하고, 파일 작성과 source는 서로 다른 확장 시점을 만든다.
  • 환경은 부모에서 자식으로 전달되며 자식에서 바꾼 가 부모 셸을 자동 변경하지 않는다.

설정·출력 추가로 확인하기 1장

아래 화면을 펼쳐 위 실습의 설정값, 명령과 확인 기준을 비교해 보세요. 각 묶음은 별도 실습 기록입니다.

실습 화면 묶음 01 1장
팀 · 실습·개념 참고 화면 5원본 크기로 보기 ↗
  • 문서 문맥에 삽입된 원본 실습·개념 참고 화면이다. 셸의 실행 원리 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
동작·명령 참고 문서

03셸 변수·export·자식 프로세스 환경

  • 변수 대입은 현재 셸 안의 값을 만든다.
  • 로 표시한 변수는 이후 실행하는 자식 의 환경에 포함된다.
  • 자식이 환경 값을 바꾸어도 부모의 값이 자동으로 바뀌지 않으며, 이미 실행 중인 다른 에도 변경이 전달되지 않는다.
  • 명령 앞의 NAME=value는 그 명령의 실행 환경에 값을 제공하는 형태로 사용한다.
  • 는 명령 이름에 슬래시가 없을 때 실행 파일을 찾는 디렉터리 목록이며 앞에서부터 검색한다.
  • 앞쪽에 쓰기 가능한 디렉터리나 현재 디렉터리를 두면 의도하지 않은 같은 이름의 파일을 실행할 수 있다.
    • 별칭·함수·내장 명령도 해석에 관여하므로 type -a와 command -v로 무엇이 선택되는지 확인한다.
bash
STUDY_MODE=local
export STUDY_MODE
bash -c 'printf "%s\n" "$STUDY_MODE"'
# local
STUDY_MODE=child bash -c 'printf "%s\n" "$STUDY_MODE"'
# child
printf "%s\n" "$STUDY_MODE"
# local

본문의 조건을 가정한 교육용 예시.

04로그인·대화형·서비스 환경의 차이

  • Bash 로그인 셸은 /etc/profile과 사용자 시작 파일을 읽으며, 사용자 파일은 ~/.bash_profile, ~/.bash_login, ~/.profile 가운데 읽을 수 있는 첫 파일을 사용한다.
  • 대화형 비로그인 Bash는 일반적으로 ~/.bashrc를 읽는다.
  • 배포판의 시작 파일이 다른 파일을 다시 읽도록 구성될 수 있으므로 모든 파일이 무조건 읽힌다고 가정하지 않는다.
  • 시스템 서비스는 사용자의 대화형 셸에서 시작한 가 아니므로 그 사용자의 profile이나 를 자동으로 이어받지 않는다.
  • Unit의 Environment와 EnvironmentFile 등 서비스가 실제로 읽는 위치에 필요한 값을 둔다.
  • /etc/environment는 셸 스크립트가 아니므로 ·명령 치환·일반 셸 확장을 기대하지 않는다.
  • 환경 파일을 바꾼 뒤 새 가 어떤 값을 받는지가 적용 여부를 결정한다.

05파일 디스크립터와 리다이렉션 순서

  • 표준 입력은 파일 디스크립터 0, 표준 출력은 1, 표준 오류는 2다.
    • 은 이 번호가 가리키는 대상을 바꾼다.
  • >는 기본적으로 을 새로 만들거나 기존 내용을 비우고, >>는 끝에 추가하며, <는 파일을 입력으로 연결한다.
  • 셸이 파일을 여는 단계에서 실패하면 대상 명령이 실행되기 전에 실패할 수 있다.
  • 2>&1은 오류를 현재 표준 출력과 같은 대상으로 연결한다.
    • 따라서 command >out.log 2>&1은 두 출력을 파일로 보내지만 command 2>&1 >out.log는 먼저 오류를 기존 출력 대상에 연결한 뒤 표준 출력만 파일로 바꾼다.
  • 명령 뒤에 쓴 기호를 프로그램의 자체 옵션으로 오해하지 않고 셸이 처리하는 연결로 읽는다.
  1. command >out.log 2>&1을 예로 든다. 처음에 표준 출력 1과 표준 오류 2가 터미널에 연결돼 있다고 가정한다.
  2. >out.log가 파일을 열고 표준 출력 1을 그 파일에 연결한다. 기존 파일의 내용을 비울 수 있으므로 읽기 전용 동작이 아니다.
  3. 2>&1이 이 시점의 표준 출력 연결을 표준 오류에도 복제한다. 1과 2 모두 같은 로 향하게 된다.
  4. 명령이 실행되면 표준 출력과 오류가 파일에 기록된다. 기호 순서를 2>&1 >out.log로 바꾸면 오류는 기존 터미널에 남는다.
표현연결 결과
command <input.txt파일 내용을 표준 입력으로 제공
command >out.log표준 출력을 파일로 연결
command 2>error.log표준 오류만 파일로 연결
command >out.log 2>&1두 출력을 같은 파일로 연결

06파이프와 종료 상태는 데이터와 별개다

  • 파이프는 앞 명령의 표준 출력을 뒤 명령의 표준 입력으로 연결한다.
  • 두 명령이 순차적으로 모든 출력을 파일에 저장한 뒤 실행되는 구조가 아니라 동시에 데이터를 주고받을 수 있는 연결이다.
    • 표준 오류는 따로 연결하지 않으면 파이프의 데이터가 아니다.
  • 뒤 프로그램이 입력을 더 읽지 않고 종료하면 앞 프로그램의 쓰기가 실패할 수도 있다.
  • 종료 상태 0은 일반적으로 성공을 나타내고 0이 아닌 값은 도구별로 의미가 다르다.
  • grep의 1은 일치 없음일 수 있어 실행 오류와 구분해야 한다.
  • 기본 파이프 종료 상태는 마지막 명령의 상태이고, Bash의 pipefail은 실패한 명령 중 가장 오른쪽의 상태를 반영한다.
  • 출력이 비어 있거나 마지막 명령이 성공한 것만으로 전체 단계가 성공했다고 판단하지 않는다.
bash
printf "%s\n" beta alpha alpha | sort | uniq -c
#      2 alpha
#      1 beta
false | true
printf "%s\n" "$?"
# 기본 설정에서는 0
( set -o pipefail; false | true; printf "%s\n" "$?" )
# 1

본문의 조건을 가정한 교육용 예시.

원리 심화

07파이프의 바이트 스트림·버퍼·EOF 조건

  • 일반 파이프는 읽기 끝과 쓰기 끝을 가진 단방향 바이트 통로다. 셸은 앞 명령의 stdout을 쓰기 끝에, 뒤 명령의 stdin을 읽기 끝에 연결한다.
  • 한 번 write한 단위가 한 번 read할 단위와 같다고 보장하지 않는다. 여러 쓰기가 합쳐지거나 나뉘어 읽힐 수 있어 줄·길이·구분자 같은 상위 규칙으로 레코드를 해석한다.
  • blocking 모드에서 빈 파이프의 read는 자료를 기다리고 가득 찬 파이프의 write는 소비를 기다린다. 소비자가 느리면 생산자도 계속 무제한으로 출력할 수 없다.
  • 버퍼의 남은 자료를 다 읽고 모든 쓰기 끝 참조가 닫혀야 읽기가 EOF를 볼 수 있다. 다른 자식이 불필요한 쓰기 FD를 잡고 있으면 생산자가 끝나도 소비자가 기다릴 수 있다.
  • 모든 읽기 끝이 닫힌 뒤 쓰면 SIGPIPE가 발생할 수 있고, 신호를 무시하는 경우 EPIPE 오류를 받는다. 뒤 명령이 필요한 양만 읽고 종료한 상황과 원래 생산자 장애를 구분한다.
예제로 추적

08세 명령의 종료 상태를 pipefail로 비교하기

  • 아래 Bash 파이프에서 앞 두 서브셸은 각각 3과 7로 종료하고 마지막 true는 0으로 종료한다. 데이터를 생산하지 않는 상태 추적 예시다.
  • pipefail을 끈 첫 실행의 전체 상태 $?는 마지막 명령의 0이다. 앞에서 두 명령이 실패 상태를 돌려도 전체 상태만 보면 성공처럼 보인다.
  • PIPESTATUS에는 가장 최근 foreground 파이프 각 명령의 상태 3·7·0이 들어 있다. 다른 명령을 실행하기 전에 읽어야 그 파이프의 정보를 보존한다.
  • pipefail을 켠 둘째 실행은 0이 아닌 상태 중 가장 오른쪽인 7을 전체 상태로 사용한다. 실패 코드 중 최댓값을 고르는 규칙은 아니다.
  • pipefail은 상태 합성 규칙만 바꾼다. 이미 수행한 앞 단계의 작업을 되돌리거나 출력 데이터의 완전성을 검사하지는 않는다.
bash
set +o pipefail
(exit 3) | (exit 7) | true
printf 'overall=%s statuses=%s\n' "$?" "${PIPESTATUS[*]}"
# 예상: overall=0 statuses=3 7 0

set -o pipefail
(exit 3) | (exit 7) | true
printf 'overall=%s statuses=%s\n' "$?" "${PIPESTATUS[*]}"
# 예상: overall=7 statuses=3 7 0

실행하지 않은 Bash 전용 교육 예시. 명령 치환으로 실행한 결과가 아니며 printf 인수 확장은 해당 명령 실행 전에 이루어진다.

검증·예외

09인수 경계·서브셸·FD 누수를 나눠 진단하기

  • Bash 배열 args=("alpha beta" "*.log")를 "${args[@]}"로 확장하면 두 인수의 경계를 유지한다. 문자열 하나로 합친 뒤 다시 인용 없이 확장하면 ·경로명 확장으로 의미가 달라질 수 있다.
  • Bash의 일반적인 파이프 구성에서는 각 명령이 서브셸 환경에서 실행되어 while read 안의 변수 변경이 바깥에 남지 않을 수 있다. lastpipe 같은 예외 설정을 포함해 실행 문맥을 확인한다.
  • 실패 명령 다음에 echo나 logger를 실행하고 $?를 읽으면 그 후속 명령의 상태를 읽게 된다. 원래 상태가 필요한 분기에서 즉시 보존한다.
  • 생산자의 종료 시각만 보지 않고 파이프 쓰기 끝을 상속한 다른 자식이 남았는지 확인한다. EOF를 보내는 별도 문자가 있다고 생각하면 FD 수명 문제를 놓친다.
  • 작은 자료는 통과하지만 큰 자료에서 멈추면 버퍼 용량과 읽기·쓰기 순서를 조사한다. 파이프 용량을 고정 상수로 가정하거나 소비 없이 무제한 누적하는 방식은 이식성과 안정성을 해친다.

10조건·반복·함수로 상태를 명시하기

  • if는 뒤에 실행한 명령의 종료 상태에 따라 분기한다.
  • 함수도 명령처럼 실행되고 마지막 명령 또는 return의 값을 상태로 돌려준다.
  • 출력 값과 종료 상태는 서로 다른 통로이므로 결과 문자열을 성공 여부 대신 사용하지 않는다.
  • 파일 경로를 줄 단위로 읽을 때는 공백과 역슬래시를 보존하도록 IFS= read -r 형태를 사용할 수 있다.
  • set -e는 모든 실패를 자동으로 안전하게 처리하는 장치가 아니다.
  • 조건식·논리 연산·파이프·함수의 문맥에 따라 동작이 달라질 수 있다.
  • 중요한 실패는 if/else에서 상태를 즉시 저장하고 성공·실패 경로를 명시한다.
  • set -u는 미정의 변수 사용을 검사하고 pipefail은 파이프 상태를 바꾸지만 입력 검증이나 ·원복을 대신하지 않는다.
bash
for item in alpha beta; do
  printf "item=%s\n" "$item"
done
if command -v tar >/dev/null 2>&1; then
  printf "%s\n" "tar available"
else
  printf "%s\n" "tar unavailable" >&2
fi

본문의 조건을 가정한 교육용 예시.

11스크립트 실행·source·작업 제어

  • 직접 실행하는 스크립트의 shebang은 인터프리터를 지정한다.
  • #!/bin/bash는 고정 경로를 사용하고 #!/usr/bin/env bash는 실행 환경의 에서 Bash를 찾는다.
  • bash script.sh로 실행하면 지정한 Bash가 파일을 읽고, source 또는 점(.)으로 읽으면 현재 셸 안에서 처리하므로 변수와 디렉터리 변경이 현재 세션에 남을 수 있다.
  • 괄호로 묶은 명령은 서브셸 환경에서 실행되어 내부 상태 변경을 분리할 수 있다.
  • 뒤에 &를 붙이는 것은 비동기 실행이며 작업이 서비스로 등록되었다는 뜻은 아니다.
  • jobs·fg·bg는 셸의 작업 제어 기능이고, 장기 서비스의 재시작·로그·부팅 연동은 같은 서비스 관리의 범위다.
  • 네트워크 변경으로 SSH가 끊길 때 복구할 콘솔 경로도 별도로 필요하다.

12tar는 묶음과 메타데이터를 만드는 도구다

  • tar는 파일·디렉터리·경로·일부 메타데이터를 하나의 로 묶는다.
  • 은 별도 기능이며 -z는 gzip, -j는 bzip2, -J는 xz를 사용한다.
  • 는 생성, -t는 목록, -x는 추출이며 -f 뒤에는 경로가 온다.
  • 는 뒤에 처리하는 상대 경로의 기준 디렉터리를 바꾼다.
    • 아래 생성 예시는 준비한 실습 디렉터리와 새 출력 경로를 가정한다.
  • 을 원본 디렉터리 안에 두면 자기 자신을 묶는 문제가 생길 수 있다.
  • 목록 조회는 실제 추출과 구분하고 절대 경로·상위 이동·심볼릭 링크·민감 파일을 살펴본다.
  • 은 파일 일치 확인에 쓰며 신뢰할 수 있는 출처의 값과 비교해야 의미가 있다.
bash
tar -c -z -f /tmp/study-new.tar.gz -C /srv/study payload
tar -t -v -f /tmp/study-new.tar.gz

/srv/study/payload가 준비되고 출력 경로가 새 파일인 실습 예시. 첫 줄은 를 생성하며 두 번째 줄은 내용 목록만 조회한다.

13추출과 배포·백업의 경계

  • 검토한 는 새 검증 디렉터리에 일반 사용자 권한으로 추출해 내용과 권한을 먼저 확인할 수 있다.
  • 의 --no-same-owner와 --no-same-permissions는 기록된 소유자·권한을 그대로 강제 복구하지 않기 위한 선택이며 적용되는 와 정책도 결과에 영향을 준다.
  • 빈 디렉터리가 준비되지 않았으면 기존 서비스 경로를 대신 사용하는 식으로 진행하지 않는다.
  • 추출은 배포 완료가 아니다.
    • 의존성, 설정, 서비스 계정, 건강 상태와 원복 경로를 따로 다룬다.
  • 실행 중인 DB 파일을 tar로 복사하는 것만으로 일관된 이 보장되지 않는다.
  • save/load 도 일반 애플리케이션 파일 묶음과 형식·복원 절차가 다르다.
  • 무엇의 어떤 상태를 보관하는지 이해한 뒤 알맞은 방법을 선택한다.
bash
tar -x -f /tmp/study-new.tar.gz -C /tmp/study-verify \
  --no-same-owner --no-same-permissions

원본·목록을 검토했고 /tmp/study-verify가 새 빈 검증 디렉터리로 준비된 추출 예시.

학습 확인

이해했는지 확인하기

  • 입력과 전제 조건에서 정상 상태까지의 처리 순서를 설명한다.
  • 조회 결과가 입증하는 범위와 아직 확인하지 않은 상태를 구분한다.
  • 예제 설정의 대상·권한·환경을 실제 실습 조건에 맞게 정한다.

함께 확인할 공식 자료