1. 정의 / 결론

“Everything is a file”은 리눅스가 서로 다른 시스템 자원을 가능한 한 같은 방식으로 다루도록 만든 설계 원칙이다.
일반 파일만 뜻하는 말이 아니라, 장치, 터미널, 프로세스 정보, 파이프, 소켓 같은 대상도 읽기와 쓰기 중심의 공통 인터페이스로 다룬다는 뜻이다.

2. 핵심 요약

리눅스에서 file은 “디스크에 저장된 문서”만 뜻하지 않는다.
핵심은 대상의 정체가 아니라 같은 방식으로 접근할 수 있느냐에 있다.
그래서 파일, 장치, 표준입출력, 커널 정보가 하나의 흐름으로 연결되고, 그 결과 리다이렉션과 파이프 같은 기능이 자연스럽게 성립한다.

3. 왜 필요한가

운영체제가 다뤄야 하는 대상은 원래 서로 다르다. 디스크의 데이터는 저장 장치에 있고, 키보드는 입력 장치이며, 모니터는 출력 장치다. 네트워크는 통신 대상이고, 커널 내부 상태는 메모리와 자료구조로 유지된다. 이들을 전부 다른 규칙으로 다루게 만들면 프로그램은 대상마다 전용 API와 예외 처리를 별도로 알아야 한다. 그러면 시스템은 기능이 늘어날수록 복잡도가 함께 증가한다.

기존 한계는 바로 여기서 생긴다. 파일은 open해서 읽고, 장치는 다른 방식으로 접근하고, 프로세스 정보는 전용 명령으로만 보고, 출력은 또 별도의 채널로 보내야 한다면 도구를 조합하기 어렵다. 한 프로그램의 결과를 다른 프로그램으로 넘기는 일도 매번 특별한 규칙이 필요해진다. 운영체제의 기능이 풍부해져도 사용자와 프로그램 입장에서는 일관성이 깨진다.

리눅스는 이 문제를 추상화의 통일로 해결했다. 대상의 내부 구현은 달라도, 바깥에서는 가능한 한 file descriptor 기반의 동일한 모델로 다루게 만든 것이다. 그래서 어떤 대상은 실제 디스크 파일이고, 어떤 대상은 장치 파일이며, 어떤 대상은 커널이 실시간으로 만들어 주는 가상 파일이어도 사용자 관점에서는 “읽는다”, “쓴다”, “연결한다”라는 같은 동작으로 접근할 수 있다. 이 구조 덕분에 작은 명령어들이 서로 쉽게 연결되고, 시스템 사용법이 단순해진다.

실제 영향은 명확하다. 로그를 파일로 저장할 수도 있고 표준출력으로 흘릴 수도 있다. 프로그램의 에러만 따로 분리할 수도 있다. 장치나 커널 상태도 텍스트처럼 확인할 수 있다. 파이프를 통해 한 명령의 결과를 다른 명령의 입력으로 연결할 수도 있다. 이것은 편의 기능이 아니라, 운영체제 전체가 같은 규칙을 중심으로 설계되었기 때문에 가능한 결과다.

4. 예제

예제 1. 일반 파일에 쓰기

echo "hello" > hello.txt
cat hello.txt

결과는 hello.txthello가 저장되고, cat으로 읽으면 같은 문자열이 출력된다.

이 경우는 가장 익숙한 파일 사용 방식이다. 문자열을 디스크 파일에 쓰고, 다시 파일에서 읽는다. 여기까지는 특별할 것이 없어 보인다.
하지만 리눅스 철학은 여기서 끝나지 않는다. 같은 > 연산자가 일반 파일뿐 아니라 장치와 표준출력의 방향 전환에도 그대로 사용된다.

실무에서는 배치 작업 결과를 파일로 남기거나, 스크립트 실행 결과를 로그 파일에 저장할 때 이 구조를 그대로 사용한다. 핵심은 “저장 대상이 파일일 때만 쓰는 문법”이 아니라는 점이다.

예제 2. /dev/null에 쓰기

echo "hello" > /dev/null

결과는 화면에 아무것도 보이지 않고, 데이터도 저장되지 않는다.

/dev/null은 데이터를 버리는 특수 파일이다. 실제 문서 파일이 아니지만, 셸은 그것을 파일처럼 취급한다. 그래서 > 연산자로 출력 대상을 바꿀 수 있다.
여기서 드러나는 핵심은 장치가 일반 파일과 같은 방식으로 연결된다는 점이다. 저장이 아니라 폐기라는 동작인데도 인터페이스는 같다.

실무에서는 불필요한 표준출력을 제거할 때 자주 쓴다. 예를 들어 스케줄러에서 정상 출력은 버리고, 에러만 따로 보고 싶을 때 >/dev/null 조합이 사용된다.

예제 3. /proc에서 시스템 정보 읽기

cat /proc/cpuinfo

결과는 CPU 모델, 코어 정보, 클럭 정보 등이 텍스트 형태로 출력된다.

중요한 점은 /proc/cpuinfo가 일반 문서 파일이 아니라는 사실이다. 이것은 커널이 현재 시스템 상태를 파일처럼 노출한 가상 파일이다. 디스크에 고정된 데이터가 있는 것이 아니라, 읽는 순간 커널이 내용을 구성해 준다.
그런데 사용자 입장에서는 cat으로 읽을 수 있다. 파일을 읽는 명령으로 시스템 내부 상태를 확인하는 것이다.

실무에서는 메모리 상태를 cat /proc/meminfo로 확인하거나, 프로세스별 정보를 /proc/<pid> 아래에서 조회하는 방식으로 이어진다. 시스템 상태를 별도 전용 포맷이 아니라 파일 인터페이스로 제공하기 때문에 셸 도구와 연결하기 쉽다.

예제 4. 표준출력과 파이프 연결

ls -l | grep ".log"

결과는 ls -l의 출력 중 .log가 포함된 행만 필터링되어 보인다.

ls는 디스크 파일 하나를 만들지 않는다. 단지 표준출력으로 문자열을 흘려보낸다. 그런데 grep은 그것을 입력으로 받아 처리한다.
이 구조가 가능한 이유는 표준출력과 표준입력이 모두 파일처럼 다뤄지기 때문이다. 셸은 프로세스 간에 파이프를 만들고, 한쪽의 stdout을 다른 쪽의 stdin에 연결한다.

실무에서는 로그 분석, 프로세스 검색, 텍스트 필터링, 배치 파이프라인 구성에 바로 쓰인다. cat, grep, awk, sort, uniq가 서로 조합되는 이유도 결국 같은 구조 위에 있기 때문이다.

예제 5. 에러 출력도 별도 파일처럼 다루기

ls /not-exists 1>out.txt 2>err.txt

결과는 정상 출력은 out.txt로 가고, 에러 메시지는 err.txt로 저장된다.

여기서 1은 stdout이고 2는 stderr다. 둘 다 파일 디스크립터다. 즉, 화면에 보이는 출력도 내부적으로는 파일처럼 다루는 채널이다.
경로가 잘못되었기 때문에 정상 결과는 없고, 에러 메시지만 err.txt에 기록된다. 대상은 에러 스트림인데도 리다이렉션 문법은 파일과 동일하다.

실무에서는 배치 실패 원인을 따로 수집하거나, 정상 결과 파일과 오류 로그 파일을 분리할 때 이 구조가 필수다.

5. 실무 적용

1) 배치 스크립트 로그 분리

상황은 야간 배치나 크론 작업이다. 실행 결과는 남겨야 하지만, 정상 메시지와 오류 메시지를 한 파일에 섞어 두면 장애 원인 추적이 느려진다.
문제는 표준출력과 표준에러가 분리되지 않으면 “무엇이 실패 메시지인지” 판단하기 어려워진다는 점이다.
해결 방법은 stdout과 stderr를 각각 다른 파일로 리다이렉션하는 것이다.
효과는 장애 분석 시간이 줄어드는 것이다. 출력 채널을 파일처럼 다루기 때문에 이런 분리가 특별한 로깅 프레임워크 없이도 가능해진다.

2) 컨테이너 로그를 stdout으로 보내기

상황은 Docker나 Kubernetes 환경이다. 애플리케이션이 직접 로컬 파일에 로그를 쌓으면 수집과 회전 정책이 컨테이너 내부에 묶인다.
문제는 컨테이너는 일시적이고 교체 가능하다는 점이다. 파일 기반 로그는 수집 경로와 보존 위치가 불안정해진다.
해결 방법은 애플리케이션 로그를 stdout/stderr로 내보내고, 런타임이나 로그 수집기가 그 스트림을 모으게 하는 것이다.
효과는 로그 수집 경로가 표준화되는 것이다. stdout이 화면 출력이 아니라 파일처럼 연결 가능한 채널이라는 점이 이 구조의 전제다.

3) 시스템 정보 자동 수집

상황은 운영 서버 점검 스크립트 작성이다. CPU, 메모리, 프로세스 상태를 한 번에 수집해야 한다.
문제는 시스템 정보가 전용 바이너리 인터페이스로만 제공되면 자동화 도구와 연결하기 어렵다는 점이다.
해결 방법은 /proc, /sys 같은 가상 파일 시스템을 읽고, 그 결과를 grep, awk, cut으로 후처리하는 것이다.
효과는 시스템 내부 상태가 텍스트 파일처럼 노출되기 때문에 자동화가 단순해지는 것이다. 별도 SDK보다 셸 도구 조합이 먼저 성립한다.

4) 작은 명령어 조합으로 데이터 처리

상황은 대용량 로그나 텍스트를 빠르게 분석해야 하는 경우다.
문제는 전용 프로그램을 새로 만들면 구현 비용이 생기고, 단순 필터링 작업에도 코드가 과해진다는 점이다.
해결 방법은 cat, grep, sort, uniq, wc를 파이프로 연결해 필요한 결과만 만든다.
효과는 프로그램 간 연결 비용이 낮아진다는 것이다. 출력과 입력이 파일 인터페이스 위에서 만나기 때문에 가능한 작업 방식이다.

6. 흔한 실수

실수 1. “모든 것이 진짜 디스크 파일이다”라고 이해하는 경우

잘못된 사용은 /proc/cpuinfo/dev/null도 전부 하드디스크에 저장된 일반 파일이라고 생각하는 것이다.
실제 결과는 개념을 잘못 잡게 된다. /proc은 가상 파일 시스템이고, /dev/null은 특수 장치 파일이다. 일반 텍스트 문서처럼 존재하는 것이 아니다.
원인은 “file”을 저장 매체 중심으로만 이해했기 때문이다. 리눅스에서 말하는 file은 접근 모델에 더 가깝다.
올바른 방법은 “모두가 일반 파일”이라고 이해하는 것이 아니라, “많은 대상이 파일 인터페이스처럼 보이게 설계되었다”라고 이해하는 것이다.

실수 2. 파이프가 파일 복사라고 생각하는 경우

잘못된 사용은 ls | grep txt를 실행하고 나서 중간 어딘가에 임시 파일이 만들어졌을 것이라고 추정하는 것이다.
실제 결과는 그렇지 않다. 셸은 프로세스 사이에 파이프를 만들고, 한쪽 출력 스트림을 다른 쪽 입력 스트림에 직접 연결한다.
원인은 표준입출력도 파일 디스크립터라는 구조를 놓치기 때문이다. 파이프는 파일 저장이 아니라 스트림 연결이다.
올바른 방법은 파이프를 “프로그램 간의 파일 디스크립터 연결”로 이해하는 것이다. 그래서 빠르고 조합 가능하다.

실수 3. stdout과 stderr를 같은 것이라고 보는 경우

잘못된 사용은 모든 출력이 화면에 보이니 전부 같은 채널이라고 생각하는 것이다.
실제 결과는 리다이렉션 시 예상과 다른 동작이 나온다. >만 쓰면 stdout만 이동하고, 에러는 여전히 화면에 남는다.
원인은 화면이라는 최종 표시 위치만 보고 내부 채널 구조를 보지 않기 때문이다. stdout과 stderr는 서로 다른 파일 디스크립터다.
올바른 방법은 정상 결과와 오류 결과가 구조적으로 분리되어 있다고 이해하는 것이다. 그래야 2>, 2>&1 같은 문법이 왜 필요한지 바로 연결된다.

실수 4. /dev/null을 “아무 일도 안 하는 마법 경로”로 외우는 경우

잘못된 사용은 이유 없이 명령 끝에 >/dev/null 2>&1을 붙이는 것이다.
실제 결과는 필요한 오류 메시지까지 사라져서 디버깅이 어려워진다. 특히 초기 설정 스크립트나 배포 스크립트에서 장애 원인이 숨는다.
원인은 /dev/null의 역할을 출력 폐기 장치라는 구조로 이해하지 않고, 그냥 조용히 만드는 주문처럼 사용하기 때문이다.
올바른 방법은 무엇을 버리고 무엇을 남길지 의도적으로 구분하는 것이다. stdout만 버릴지, stderr도 버릴지 판단한 뒤 사용해야 한다.

실수 5. “Everything is a file”을 절대 규칙으로 받아들이는 경우

잘못된 사용은 리눅스의 모든 자원이 완전히 같은 방식으로 동작한다고 이해하는 것이다.
실제 결과는 소켓, 메모리 매핑, 이벤트 인터페이스, ioctl 같은 영역에서 예외를 만나 혼란이 생긴다.
원인은 이 문장을 설계 철학이 아니라 문자 그대로의 완전한 법칙으로 보기 때문이다. 리눅스는 가능한 한 통일하려고 했지만, 모든 기능이 완전히 동일한 추상화만으로 끝나지는 않는다.
올바른 방법은 이 표현을 “강한 기본 원칙”으로 이해하는 것이다. 중심은 file descriptor 기반 모델이지만, 세부 구현에는 예외와 확장이 존재한다.

실수 6. 파일 권한과 장치 접근도 똑같이 단순할 것이라 생각하는 경우

잘못된 사용은 /dev/sda 같은 장치 파일도 텍스트 파일 읽듯이 아무 권한 없이 다룰 수 있다고 생각하는 것이다.
실제 결과는 권한 오류가 발생하거나, 잘못 접근하면 시스템 전체에 영향을 줄 수 있다.
원인은 겉으로 파일처럼 보여도 대상의 위험도와 커널 처리 방식은 다르다는 점을 무시하기 때문이다.
올바른 방법은 “인터페이스가 비슷하다”와 “위험도가 같다”를 구분하는 것이다. 장치 파일은 파일처럼 다루되, 실제 영향은 훨씬 크다.

7. 관련 개념

파일 디스크립터는 프로세스가 열어 둔 파일 또는 스트림을 가리키는 정수 핸들이다. 표준입력 0, 표준출력 1, 표준에러 2가 기본이다.

리다이렉션은 표준입출력의 연결 대상을 바꾸는 기능이다. 파일, 장치, 다른 스트림으로 방향을 전환할 수 있다.

파이프는 한 프로세스의 stdout을 다른 프로세스의 stdin에 연결하는 메커니즘이다. 파일 저장이 아니라 스트림 연결이다.

가상 파일 시스템/proc, /sys처럼 커널 정보나 장치 정보를 파일처럼 노출하는 구조다. 디스크의 일반 파일과는 성격이 다르다.

8. 더 깊이 보기

내부적으로 보면 “Everything is a file”의 중심에는 VFS(Virtual File System)file descriptor 모델이 있다. 리눅스 커널은 ext4 같은 실제 파일 시스템, procfs 같은 가상 파일 시스템, devtmpfs 같은 장치 관련 파일 시스템을 하나의 큰 파일 추상화 아래에서 다루려고 한다. 사용자는 open, read, write, close 같은 공통된 동작을 보지만, 실제 처리는 각 파일 시스템과 드라이버가 맡는다.

구조적 이유는 운영체제가 복잡한 자원을 한 번에 다루기 위해서다. 디스크 파일은 저장 장치 I/O이고, 터미널은 문자 장치이며, 파이프는 커널 버퍼를 통한 프로세스 간 통신이다. 내부 구현은 전부 다르다. 그런데 외부 인터페이스까지 다르게 두면 도구 생태계가 분절된다. 리눅스는 내부의 차이를 숨기고 외부 접근 모델을 최대한 통일했다. 그 결과 사용자는 적은 개념으로 많은 대상을 다룰 수 있게 되었다.

시스템 관점에서 이 철학의 효과는 조합 가능성이다. 유닉스 계열 시스템이 “작은 프로그램을 연결하는 문화”를 형성한 이유도 여기에 있다. 하나의 프로그램이 모든 일을 다 하지 않아도 된다. 한 프로그램은 읽고, 다른 프로그램은 걸러 내고, 또 다른 프로그램은 집계한다. 이 연결 비용이 낮은 이유는 입출력이 file descriptor 중심으로 통일되어 있기 때문이다. 즉, “Everything is a file”은 단순한 슬로건이 아니라, 도구 조합성을 만들어 낸 운영체제 설계 전략이다.

다만 이 문장을 과장 없이 이해해야 한다. 리눅스는 가능한 한 파일 모델을 중심에 두었지만, 현대 시스템에는 네트워크 이벤트, 메모리 매핑, 시그널, epoll, ioctl 같은 확장 메커니즘도 존재한다. 따라서 정확한 표현은 “리눅스는 많은 자원을 파일 인터페이스 중심으로 통일하려 했다”에 가깝다. 이 정도로 이해해야 실제 시스템 동작과 맞아떨어진다.

9. 정리

“Everything is a file”은 리눅스에서 모든 대상이 디스크 파일이라는 뜻이 아니다.
핵심은 파일, 장치, 표준입출력, 가상 시스템 정보를 같은 흐름으로 접근하게 만든 설계 원칙이라는 점이다.
그래서 리다이렉션, 파이프, 로그 분리, 시스템 정보 조회가 하나의 모델로 연결된다.
리눅스를 이해할 때 파일을 저장 단위로만 보지 말고, 시스템 자원을 다루는 공통 인터페이스로 봐야 전체 구조가 정리된다.