쉘 리다이렉션(2>&1)과 파일 디스크립터(FD) 이해하기

우리는 흔히 > file이나 2>&1 같은 구문을 사용합니다. 단순히 “저장한다”, “에러를 합친다”라고 외워서 쓰지만, 커널 내부에서 어떤 일이 일어나는지 이해하면 리눅스 시스템을 더 깊게 다룰 수 있습니다.

1. 파일 디스크립터(FD)란?

리눅스/유닉스 같은 POSIX 시스템에서 프로세스가 파일(또는 소켓, 파이프)에 접근할 때 사용하는 추상적인 식별 번호(Non-negative Integer) 입니다. 프로세스가 실행되면 커널은 기본적으로 3개의 FD를 할당합니다.

  • 0 (stdin) : 표준 입력 (키보드)
  • 1 (stdout) : 표준 출력 (모니터)
  • 2 (stderr) : 표준 에러 (모니터)

2. 2>&1의 동작 원리: 포인터 복사

pnpm build | pbcopy를 실행하면, 쉘은 기본적으로 FD 1(stdout) 만 파이프로 연결합니다. FD 2(stderr) 는 여전히 모니터를 가리키고 있어서 에러 로그가 파이프를 타지 못하는 것입니다.

2>&1“FD 2가 가리키는 주소를 FD 1이 가리키는 주소와 똑같이 복제하라” 는 명령입니다. (시스템 콜 dup2(1, 2) 호출)

순서가 생명이다

FD는 포인터 복사 개념이므로 순서가 매우 중요합니다.

  • ✅ 올바른 예: > log.txt 2>&1
  1. FD 1이 log.txt를 가리킴.
  2. FD 2가 FD 1을 따라 log.txt를 가리킴. (둘 다 파일로 저장 )
  • ❌ 잘못된 예: 2>&1 > log.txt
  1. FD 2가 FD 1(현재 모니터)을 따라 모니터를 가리킴.
  2. FD 1이 log.txt로 변경됨. (에러는 화면에, 정상 출력만 파일로 )

3. 리눅스에서 파일을 생성하는 6가지 방법 (FD 관점)

파일을 만드는 것도 결국 커널에게 FD를 발급받는(open) 과정입니다. 목적에 따라 다양한 방법이 있습니다.

3.1. 리다이렉션 (>)

가장 기본적인 방법입니다. > empty.txt만 쳐도 파일이 생성됩니다. 커널은 O_CREAT | O_TRUNC 플래그를 사용하여 파일을 생성하거나 기존 파일을 비웁니다.

# 빈 파일 생성
> empty.txt

# 명령어 출력을 파일로 저장
echo "Hello" > output.txt

3.2. touch

파일이 없으면 0바이트 파일을 생성하고, 있으면 타임스탬프만 갱신합니다. 안전한 생성을 원할 때 사용합니다.

# 파일 생성 (없으면 생성, 있으면 타임스탬프만 갱신)
touch newfile.txt

3.3. cat <<EOF (Here-doc)

설정 파일 등 여러 줄의 텍스트를 한 번에 파일로 쓸 때 유용합니다. 리다이렉션 순서는 중요하지 않으므로 두 가지 방식 모두 가능합니다.

# 방식 1: 리다이렉션을 앞에
cat > config.txt <<EOF
Hello
World
EOF

# 방식 2: Here-doc을 앞에
cat <<EOF > config.txt
Hello
World
EOF

3.4. install

CI/CD 스크립트에서 많이 사용합니다. 파일 복사(cp)와 동시에 권한(chmod)을 부여하고 디렉토리(mkdir)까지 만들어줍니다.

# 파일 복사 및 권한 설정을 한 번에
install -m 755 script.sh /bin/script.sh

3.5. fallocate

테스트용 대용량 파일이 필요할 때 dd보다 훨씬 빠릅니다. 실제 디스크 I/O 없이 메타데이터만 조작하여 공간을 할당합니다.

# 1GB 파일을 0.1초 만에 생성
fallocate -l 1G fast_dummy.img

3.6. mktemp

쉘 스크립트에서 임시 파일이 필요할 때 사용합니다. 랜덤한 이름으로 생성해주어 파일명 충돌(Race Condition)을 방지합니다.

# 임시 파일 생성
TEMP_FILE=$(mktemp)
echo "임시 데이터" > "$TEMP_FILE"
# 사용 후 삭제
rm "$TEMP_FILE"

4. 시스템 장애 예방: Open Files Limit

서버 운영 시 “Too many open files” 에러를 종종 봅니다. 이는 메모리가 부족한 게 아니라, 프로세스가 사용할 수 있는 FD 번호를 다 썼기 때문 입니다.

리눅스는 프로세스당 열 수 있는 파일 개수(소켓 포함)를 제한합니다. (기본값은 보통 1024로 매우 작음)

  • 확인: ulimit -n
  • 해결: Nginx나 DB 같은 고성능 서버는 반드시 이 제한(Soft/Hard Limit)을 /etc/security/limits.conf에서 늘려줘야 동시 접속자를 처리할 수 있습니다.

결론

“리눅스에서 모든 것은 파일이다”라는 철학을 이해하면, pbcopy를 쓰기 위해 에러 스트림을 조작하거나, 서버 튜닝을 위해 FD 리밋을 조절하는 작업이 모두 연결된 개념임을 알 수 있습니다.