1. 정의 / 결론

리눅스에서 <는 파일의 내용을 프로그램의 표준 입력(stdin) 으로 연결하는 리다이렉션 문법이다.
많은 사람이 <를 보면 단순히 “파일을 읽는 문법”이라고 받아들이지만, 그건 정확하지 않다. <의 본질은 파일을 직접 처리하는 데 있지 않다. 핵심은 프로그램이 원래 입력받는 기본 채널의 출처를 파일로 바꾼다는 데 있다.

프로그램은 보통 입력을 여러 방식으로 받을 수 있다. 사용자가 키보드로 직접 입력할 수도 있고, 파일명을 인자로 전달받아 스스로 파일을 열 수도 있고, 다른 프로그램의 출력이 파이프를 통해 들어올 수도 있다. 이 중 stdin은 “입력이 들어오는 표준 채널”이다. <는 바로 이 채널의 입력 원천을 바꾸는 문법이다. 즉, 사용자가 터미널에서 직접 타이핑하던 입력을 파일이 대신 공급하도록 만드는 구조다.

예를 들어 cat < input.txt를 보면 표면적으로는 cat이 파일을 읽는 것처럼 보인다. 하지만 실제 구조는 다르다. cat은 원래 stdin을 읽는 프로그램이고, 쉘이 실행 전에 stdin을 input.txt에 연결해 두었기 때문에 cat은 평소처럼 stdin만 읽었을 뿐이다. 프로그램 입장에서는 “파일을 직접 연다”가 아니라 “익숙한 입력 채널로부터 데이터를 읽는다”는 점이 중요하다.

이 구조를 이해하면 cat input.txtcat < input.txt가 결과는 비슷해 보여도 왜 내부 동작은 다른지 자연스럽게 이해된다. 전자는 프로그램이 파일명을 인자로 받아 직접 파일을 여는 방식이고, 후자는 쉘이 먼저 입력 채널을 파일로 바꾸고 프로그램을 실행하는 방식이다. 겉보기 결과만 보면 비슷할 수 있지만, 입력의 출처와 동작 주체는 완전히 다르다.

핵심을 한 문장으로 정리하면 이렇다.

<는 파일을 직접 읽는 문법이 아니라, 프로그램이 입력받는 기본 채널(stdin)의 출처를 파일로 바꾸는 문법이다.`

이 개념을 정확히 이해하면 <|의 차이도 정리되고, 파일 인자를 주는 방식과 stdin으로 데이터를 흘려보내는 방식도 분명하게 구분된다. 더 나아가 here-doc, here-string, tee, file descriptor 0, 그리고 dup2() 기반 내부 구조까지 한 번에 연결된다. 결국 <는 단순 기호 하나가 아니라, 리눅스의 입출력 구조를 입력 방향까지 완성시키는 핵심 문법이다.

2. 핵심 요약

stdin은 프로그램이 입력을 받는 표준 채널이다.
일반적인 터미널 환경에서는 이 채널이 키보드와 연결되어 있기 때문에, 사용자가 입력한 값이 프로그램으로 들어간다. 하지만 이 연결은 고정된 것이 아니다. 쉘은 프로그램이 실행되기 전에 stdin의 출처를 바꿀 수 있고, <는 그때 사용하는 가장 대표적인 입력 리다이렉션 문법이다.

즉, <는 파일을 프로그램의 입력으로 넣는 방식이다.
이때 중요한 것은 프로그램이 파일명을 전달받는 것이 아니라, 파일 내용이 stdin으로 들어간다는 점이다. 따라서 <는 파일 인자 전달과 다르고, 파이프와도 다르다.

이 글에서 반드시 구분해야 할 축은 세 가지다.

첫째, 키보드 입력과 <의 차이다.
프로그램이 stdin을 읽는다고 할 때, 그 stdin은 기본적으로 터미널 입력일 수 있다. 하지만 < file.txt를 붙이면 그 입력은 더 이상 키보드가 아니라 파일이 된다. 즉, 입력 채널은 같지만 입력의 출처가 달라진다.

둘째, <와 파이프(|)의 차이다.
둘 다 프로그램의 stdin을 채운다는 점에서는 비슷해 보인다. 하지만 <는 파일을 stdin에 연결하는 것이고, |는 다른 프로그램의 stdout을 stdin에 연결하는 것이다. 전자는 정적인 입력 소스이고, 후자는 실행 중인 다른 프로세스의 출력 흐름이다.

셋째, 파일 인자 전달과 <의 차이다.
command file.txt는 프로그램이 파일명을 인자로 전달받아 스스로 파일을 연다. 반면 command < file.txt는 쉘이 먼저 stdin을 파일에 연결한 뒤 프로그램을 실행한다. 따라서 같은 데이터를 다뤄도 동작 주체가 다르고, 출력 형식이나 동작 방식도 달라질 수 있다.

간단히 축을 정리하면 이렇다.

# 키보드 입력
cat

# 파일을 stdin으로 연결
cat < input.txt

# 파일명을 인자로 전달
cat input.txt
# 파일이 stdin이 됨
sort < names.txt

# 다른 프로그램의 출력이 stdin이 됨
cat names.txt | sort
# 파일명을 인자로 전달
wc -l input.txt

# 파일 내용을 stdin으로 전달
wc -l < input.txt

이 차이를 이해하지 못하면 <를 그냥 “파일 읽는 기호” 정도로 오해하게 된다.
하지만 리눅스의 리다이렉션 구조는 그렇게 단순하지 않다. >가 출력 방향을 바꾸는 문법이라면, <는 입력의 출처를 바꾸는 문법이다. 이 둘을 함께 이해해야 비로소 입출력 전체 구조가 닫힌다.

3. 왜 필요한가

프로그램은 항상 파일명을 인자로 받아서만 데이터를 처리하지 않는다.
오히려 유닉스 계열 도구들의 중요한 특징은 입력 소스를 추상화해서 다룬다는 데 있다. 프로그램 입장에서는 데이터가 어디서 왔는지가 핵심이 아니라, 자신의 stdin으로 들어오느냐가 더 중요할 때가 많다. 이 구조 덕분에 같은 프로그램이 키보드 입력도 받고, 파일 입력도 받고, 다른 프로그램의 출력도 이어받을 수 있다.

예를 들어 cat, sort, wc, grep 같은 도구는 stdin 기반으로도 잘 동작한다. 이것이 중요한 이유는 입력 소스를 바꾸더라도 프로그램 자체는 거의 바뀌지 않기 때문이다. 프로그램은 그냥 stdin을 읽으면 되고, 입력이 키보드에서 오든 파일에서 오든 파이프에서 오든 쉘이 알아서 연결해 준다. 즉, 데이터 처리 로직과 입력 소스가 분리된다는 점이 리눅스 도구 설계의 핵심이다.

두 번째 이유는 자동화다.
사용자가 직접 키보드로 입력해야 하는 작업은 반복 실행이 어렵다. 한 번은 할 수 있어도, 동일한 입력으로 여러 번 테스트하거나, 배치 작업으로 돌리거나, 장애 상황을 재현하는 데는 불리하다. 하지만 입력을 파일로 저장하고 <로 넣을 수 있으면 상황이 달라진다. 같은 입력을 정확히 재현할 수 있고, 반복 실행도 쉬워지며, 사람이 매번 개입하지 않아도 된다.

./app < sample-input.txt

이 한 줄만으로도 테스트 입력 자동화, 알고리즘 문제 입력 재현, 초기 설정 자동 공급, SQL 배치 실행 같은 여러 실무 패턴이 가능해진다. 입력을 파일로 외부화하고, 프로그램은 stdin만 읽게 만들면, 입력의 재현성과 실행의 자동화가 동시에 좋아진다.

세 번째 이유는 구조 이해다.
많은 사람은 리다이렉션을 출력 쪽에서 먼저 배운다. >, >>, 2>, 2>&1 같은 문법이 눈에 잘 띄기 때문이다. 하지만 출력만 이해한 상태에서는 입출력 구조를 반만 이해한 셈이다. 리다이렉션은 결과를 어디로 보낼지만 바꾸는 기능이 아니라, 입력과 출력의 연결 지점을 전체적으로 재배치하는 메커니즘이다. 따라서 <를 이해해야 stdin, stdout, stderr, pipe, /dev/null, tee가 하나의 구조로 보이기 시작한다.

결국 <가 필요한 이유는 단순하다.
프로그램이 데이터를 받는 출처를 유연하게 바꾸기 위해서다. 그리고 이 유연성이 자동화, 재현성, 조합 가능성, 도구 재사용성을 만들어 낸다. 리눅스 명령줄이 강한 이유는 개별 명령이 똑똑해서가 아니라, 이런 연결 구조가 잘 설계되어 있기 때문이다.

4. 예제

예제 1. 키보드 입력 대신 파일을 stdin으로 넣기

가장 기본적인 예제는 cat이다.
cat은 파일명을 인자로 받을 수도 있지만, 인자가 없으면 stdin을 읽는다. 이 성질 덕분에 <를 설명하기 가장 좋다.

cat < input.txt

결과는 단순하다.
input.txt의 내용이 화면에 출력된다.

왜 이렇게 되는가.
cat은 인자가 없기 때문에 원래라면 키보드 입력을 기다린다. 그런데 쉘이 실행 전에 stdin을 input.txt와 연결해 두었으므로, cat은 더 이상 키보드를 기다리지 않고 파일 내용을 stdin에서 읽는다. 즉, cat은 파일을 직접 여는 것이 아니라, 그냥 stdin을 읽고 있을 뿐이다.

이 예제가 좋은 이유는 <의 본질을 가장 단순하게 보여 주기 때문이다.
<는 “파일을 화면에 출력한다”가 아니라 “파일이 프로그램의 입력이 된다”는 점을 한 번에 드러낸다.

예제 2. 같은 결과처럼 보여도 구조가 다른 경우

cat input.txtcat < input.txt는 출력만 보면 거의 동일하다.

cat input.txt
cat < input.txt

둘 다 input.txt의 내용을 출력한다.
하지만 내부 구조는 전혀 다르다.

첫 번째 명령에서는 catinput.txt라는 파일명을 인자로 전달받는다. 그 다음 cat이 직접 그 파일을 열고 읽는다.
반면 두 번째 명령에서는 cat은 아무 파일명도 전달받지 않는다. 쉘이 먼저 stdin을 input.txt에 연결하고, 그 상태로 cat을 실행한다. 그러면 cat은 파일이 아니라 stdin을 읽는다.

즉, 차이는 이렇게 정리할 수 있다.

# 프로그램이 파일명을 받음
cat input.txt
# 쉘이 stdin을 파일로 연결해 둠
cat < input.txt

이 차이가 중요한 이유는 <를 오해하지 않기 위해서다.
많은 초보 설명은 두 명령이 “거의 같다”고만 말하고 넘어가는데, 그 설명은 결과 중심에서는 맞지만 구조 중심에서는 틀리다. 리눅스에서 중요한 것은 단순 결과가 아니라 누가 무엇을 열고, 무엇이 무엇에 연결되었는가다. 이걸 놓치면 이후에 pipe, tee, dup2, FD 리다이렉션까지 이어지는 구조를 제대로 이해하기 어렵다.

예제 3. stdin 기반 프로그램에 입력 넣기

wc -l은 라인 수를 세는 명령이다. 이 도구는 파일명을 인자로 받을 수도 있고, stdin으로 입력을 받을 수도 있다.

wc -l input.txt
wc -l < input.txt

이 둘은 비슷해 보이지만 출력이 다를 수 있다.
첫 번째는 보통 라인 수와 함께 파일명이 함께 출력된다. 예를 들어 다음처럼 보일 수 있다.

42 input.txt

반면 두 번째는 stdin으로 들어온 순수 데이터만 처리했기 때문에 파일명을 알지 못한다. 따라서 보통 숫자만 출력된다.

42

왜 이렇게 되는가.
첫 번째는 wcinput.txt라는 파일을 직접 열었기 때문이다. 그래서 어떤 파일을 처리했는지를 알고 있다. 반면 두 번째는 그저 stdin으로 들어온 바이트 스트림만 읽었다. 데이터는 같더라도 출처 정보는 프로그램에 전달되지 않는다.

이 예제는 실무적으로도 의미가 있다.
파이프라인 안에서는 파일명보다 순수 데이터 스트림만 중요한 경우가 많다. 이때 <를 사용하면 출력 형식을 단순화하거나, 다른 stdin 기반 도구들과 일관되게 연결할 수 있다.

예제 4. 여러 줄 입력을 미리 준비해서 넣기

입력 리다이렉션은 대화형 입력을 자동화할 때 특히 강력하다.
예를 들어 표준 입력을 읽는 파이썬 스크립트가 있다고 하자.

python3 script.py < input.txt

이렇게 하면 사용자가 일일이 입력하지 않아도, input.txt의 내용이 그대로 프로그램 입력으로 들어간다.

같은 구조는 SQL 실행에도 자주 쓰인다.

mysql -u user -p dbname < init.sql

이 명령은 init.sql 파일에 들어 있는 SQL 문장을 mysql 클라이언트의 stdin으로 공급한다.
즉, 클라이언트가 파일을 인자로 해석하는 것이 아니라, SQL 텍스트가 입력 스트림으로 들어가는 구조다.

이런 패턴이 중요한 이유는 자동화와 재현성 때문이다.
한 번 손으로 입력해서 실행할 수 있는 작업은 많다. 하지만 실무에서는 같은 입력을 여러 환경에서 반복하고, CI/CD에서 돌리고, 장애 상황을 재현해야 한다. 이때 <는 “수동 입력”을 “재현 가능한 입력 파일”로 바꾸는 핵심 수단이 된다.

5. <와 pipe(|)의 차이

<|는 둘 다 프로그램의 stdin을 채운다는 점에서 자주 헷갈린다.
실제로 결과만 보면 거의 같아 보이는 경우도 많다.

sort < names.txt
cat names.txt | sort

이 두 명령은 둘 다 names.txt의 내용을 정렬한 결과를 출력할 수 있다.
하지만 구조는 다르다.

첫 번째 명령에서는 sort의 stdin이 names.txt 파일과 직접 연결된다.
즉, 입력의 출처는 파일이다.

두 번째 명령에서는 cat이 먼저 names.txt를 읽어서 stdout으로 내보내고, 그 stdout이 파이프를 통해 sort의 stdin으로 연결된다.
즉, 입력의 출처는 파일이 아니라 다른 프로세스의 출력이다.

차이를 구조적으로 그리면 이렇다.

sort < names.txt
-> names.txt ----> stdin(sort)
cat names.txt | sort
-> names.txt -> cat stdout ----> stdin(sort)

이 차이는 단순 성능 문제가 아니라 의미 문제다.
<는 정적인 입력 소스를 stdin에 연결하는 문법이고, |는 프로세스와 프로세스를 연결하는 문법이다. 전자는 “입력의 시작점”을 정의하고, 후자는 “처리 단계를 연결”한다.

그래서 일반적으로 파일에서 시작하는 단순 입력이라면 <가 더 직접적이다.

sort < raw.txt | uniq

이 명령은 파일을 stdin에 연결해 파이프라인의 시작점을 만들고, 이후에는 |를 사용해 처리 단계를 이어 붙인다. 이 구조가 중요한 이유는 <|가 경쟁 관계가 아니라 서로 다른 역할을 가진다는 점을 보여 주기 때문이다. <는 입력 소스를 정하고, |는 처리 흐름을 연결한다.

즉, <|를 같은 것으로 보면 안 된다.
둘 다 stdin을 바꾸지만, 무엇을 stdin에 연결하느냐가 다르다.
이 차이를 이해해야 파이프라인 설계와 리다이렉션 구조가 분명해진다.

6. 명령어 인자로 파일을 주는 것과 <의 차이

이 글에서 가장 중요한 비교 중 하나가 바로 이것이다.
겉보기 결과가 같기 때문에 가장 자주 오해되고, 가장 자주 잘못 설명되는 부분이기도 하다.

예를 들어 다음 두 명령을 보자.

grep ERROR log.txt
grep ERROR < log.txt

이 둘은 모두 log.txt 안에서 ERROR가 들어간 줄을 찾는 것처럼 보인다.
실제로 결과도 비슷할 수 있다. 하지만 구조는 다르다.

첫 번째는 greplog.txt라는 파일명을 인자로 전달받는다. 그래서 grep이 직접 파일을 열고 검색한다.
두 번째는 쉘이 stdin을 log.txt에 연결한 뒤 grep ERROR를 실행한다. 따라서 grep은 stdin으로 들어온 텍스트를 검색한다.

이 차이는 다음처럼 정리할 수 있다.

# 프로그램이 파일명을 전달받는다
command file.txt
# 프로그램은 stdin만 받는다
command < file.txt

중요한 점은 모든 프로그램이 stdin 기반으로 잘 동작하는 것은 아니라는 점이다.
어떤 도구는 파일명을 받아 직접 처리하는 것에 최적화되어 있고, 어떤 도구는 stdin을 잘 지원하지만 파일명을 줄 때와 출력 형식이 다를 수 있다. 또 어떤 프로그램은 아예 stdin을 읽지 않고 옵션이나 인자만 기대하기도 한다.

예를 들어 wc -l input.txtwc -l < input.txt는 출력 형식이 다를 수 있고, 어떤 CLI는 파일 인자를 주면 여러 파일을 동시에 처리할 수 있지만 stdin 방식에서는 그 기능을 쓸 수 없을 수도 있다. 반대로 어떤 필터형 도구는 stdin 기반으로 쓰는 것이 훨씬 자연스럽다.

즉, 파일 인자 전달과 <는 대체 관계가 아니라 의미가 다른 두 가지 입력 방식이다.
하나는 프로그램이 파일을 직접 다루는 방식이고, 다른 하나는 프로그램이 입력 스트림만 다루는 방식이다. 어느 쪽이 맞는지는 프로그램 설계와 사용 목적에 따라 달라진다.

7. 실무 적용

1) 배치 입력 재현

실무에서는 “같은 입력을 다시 넣어 보는 것”이 매우 중요하다.
문제가 발생했을 때 원인을 파악하려면 당시 입력을 재현해야 하고, 테스트를 자동화하려면 매번 사람이 입력하면 안 된다. 이때 <는 가장 단순하고 강력한 방법이 된다.

./app < sample-input.txt

이렇게 해 두면 사용자가 직접 입력하던 내용을 파일로 저장해 두고, 동일한 조건으로 반복 실행할 수 있다.
특히 알고리즘 문제 테스트, 인터랙티브 CLI 디버깅, 배치 작업 재현, 장애 상황 재실행 같은 경우에 매우 유용하다.

핵심은 입력을 코드 밖으로 빼내는 것이다.
입력이 파일이 되면 버전 관리도 가능하고, 테스트 데이터 세트 관리도 가능하며, 다른 환경으로 이동시키기도 쉽다. 프로그램은 stdin만 읽으면 되므로 구조가 단순해지고, 운영은 더 예측 가능해진다.

2) SQL / 스크립트 실행 자동화

CLI 도구 중 상당수는 stdin을 입력으로 받는다.
이 성질 덕분에 파일에 명령을 미리 적어 두고, <로 공급하는 패턴이 널리 쓰인다.

mysql -u user -p mydb < schema.sql
sqlite3 test.db < init.sql

이 방식의 장점은 명확하다.
운영자가 콘솔에 SQL을 하나하나 붙여 넣지 않아도 되고, 실행 이력을 파일로 남길 수 있으며, 동일 스크립트를 여러 환경에 반복 적용할 수 있다. 특히 초기 스키마 구성, 테스트 데이터 적재, 배치용 SQL 실행에서 흔하게 사용된다.

여기서 중요한 것은 데이터가 “파일로부터 입력된다”는 점이다.
도구가 꼭 파일 인자를 지원하지 않아도 상관없다. stdin을 읽기만 하면 <로 충분히 자동화할 수 있다.

3) 파이프라인 시작점 만들기

리눅스 파이프라인은 보통 다른 프로그램의 출력에서 시작하는 것처럼 보이지만, 실제로는 파일에서 시작하는 경우도 많다. 이때 <는 파이프라인의 시작점을 자연스럽게 만들어 준다.

sort < raw.txt | uniq

이 명령은 다음 구조다.

  • raw.txtsort의 stdin이 됨
  • sort의 stdout이 uniq의 stdin이 됨

즉, <|는 충분히 함께 쓸 수 있다.
오히려 실무에서는 이 조합이 자연스럽다. 파일을 입력으로 받아 첫 번째 필터에 넣고, 이후는 파이프로 이어서 여러 단계 처리한다. 이렇게 보면 <는 파이프라인의 반대 개념이 아니라, 파이프라인 앞단의 입력 소스 지정 도구다.

4) 인터랙티브 작업 자동화

일부 프로그램은 실행 후 여러 줄의 입력을 순서대로 요구한다.
사람이 직접 입력하면 한두 번은 가능하지만, 반복 작업에는 적합하지 않다. 이때 답변을 파일로 저장해 두고 <로 공급하면 초기 입력 과정을 자동화할 수 있다.

python3 wizard.py < answers.txt

이 패턴은 설치 마법사류 스크립트, 간단한 설정 도구, 학습용 입출력 프로그램, 내부 운영용 CLI 등에서 꽤 자주 쓸 수 있다. 물론 더 복잡한 상호작용이 필요하면 expect 같은 도구가 필요할 수 있지만, 단순 입력 흐름이라면 <만으로도 상당 부분 자동화가 가능하다.

즉, <는 단순 문법이 아니라 사람이 하던 입력 작업을 파일 기반 실행으로 바꾸는 수단이다.
이 차이는 자동화와 재현성에서 매우 크다.

8. 흔한 실수

실수 1. <를 “파일 읽기 문법”으로 이해하는 경우

가장 흔한 오해다.
command < file.txt를 보면 많은 사람이 “아, 이 명령이 file.txt를 읽는구나”라고 받아들인다. 하지만 이 해석은 절반만 맞고, 구조적으로는 틀리다.

실제로 <는 프로그램이 파일을 직접 열게 만드는 문법이 아니다.
쉘이 먼저 stdin을 file.txt와 연결하고, 그 상태로 프로그램을 실행하는 문법이다. 따라서 프로그램은 파일명을 전달받지 않는다. 그저 stdin을 읽을 뿐이다.

이 차이를 놓치면 이후에 파일 인자 전달과 stdin 리다이렉션을 구분하지 못하게 된다.
그러면 cat file.txtcat < file.txt, grep ERROR log.txtgrep ERROR < log.txt가 왜 구조적으로 다른지 설명할 수 없게 된다.

올바른 방향은 분명하다.

<는 파일을 직접 읽게 하는 문법이 아니라, 표준 입력의 출처를 파일로 바꾸는 문법이다.`

실수 2. <와 pipe를 같은 것으로 보는 경우

다음 두 명령은 결과가 비슷할 수 있다.

sort < file.txt
cat file.txt | sort

이 때문에 둘을 완전히 동일한 것으로 이해하는 경우가 많다.
하지만 <는 파일을 stdin에 연결하는 것이고, |는 다른 프로세스의 stdout을 stdin에 연결하는 것이다.

이 차이를 무시하면 데이터 흐름과 프로세스 구조를 제대로 이해할 수 없다.
특히 파이프라인이 길어질수록 “입력의 시작점이 파일인지, 이전 프로세스 출력인지”는 중요한 차이가 된다.

즉, <|는 결과가 비슷할 수는 있어도 같은 개념은 아니다.
전자는 파일 기반 입력 연결이고, 후자는 프로세스 간 연결이다.

실수 3. 모든 프로그램이 stdin을 읽는다고 생각하는 경우

< file.txt를 붙였는데 기대대로 동작하지 않는 경우가 있다.
이때 “문법이 이상한가?”라고 생각하기 쉽지만, 실제 문제는 프로그램이 stdin을 지원하지 않는 데 있을 수 있다.

일부 프로그램은 파일 인자나 특정 옵션을 통해서만 입력을 받는다.
이 경우 <를 써도 아무 의미가 없거나, 전혀 다른 동작을 할 수 있다.

따라서 <를 쓸 때는 항상 도구가 어떤 입력 모델을 기대하는지 확인해야 한다.
stdin 중심 도구인지, 파일 인자 중심 도구인지, 옵션 중심 도구인지를 구분해야 한다. 리눅스 명령어는 비슷해 보여도 입력 설계는 제각각이기 때문이다.

실수 4. cat < file이 항상 더 좋은 방식이라고 생각하는 경우

구조 설명용으로는 cat < file이 좋은 예제다.
왜냐하면 stdin 리다이렉션의 본질을 명확하게 보여 주기 때문이다. 하지만 실무에서는 항상 그 방식이 더 좋은 것은 아니다.

단순히 파일 내용을 보고 싶다면 cat file이 더 직접적이고 읽기 쉽다.
프로그램이 파일 인자를 자연스럽게 지원한다면 굳이 <를 사용할 필요가 없는 경우도 많다.

즉, <는 우월한 문법이 아니라 의미가 다른 문법이다.
입력 채널을 바꾸고 싶을 때 쓰는 것이지, 아무 상황에서나 파일명 인자보다 더 낫다고 볼 수는 없다.

실수 5. 입력 리다이렉션과 출력 리다이렉션을 방향만 반대로 생각하는 경우

<>를 단순히 좌우 반대 기호 정도로만 이해하면 문제가 생긴다.
둘은 비슷한 계열의 문법이지만, 다루는 대상이 다르다.

  • <는 stdin, 즉 0번 FD
  • >는 stdout, 즉 1번 FD
  • 2>는 stderr, 즉 2번 FD

즉, 단순히 “방향 반대”가 아니라 서로 다른 채널을 다룬다는 점이 중요하다.
이걸 정확히 잡아야 2>&1, /dev/null, <, | 같은 문법들이 하나의 FD 구조 안에서 정리된다.

9. 관련 개념

<를 제대로 이해하려면 몇 가지 주변 개념과 함께 봐야 한다.

먼저 stdin / stdout / stderr가 있다.
이 셋은 각각 표준 입력, 표준 출력, 표준 에러이며, 리눅스 프로그램의 기본 입출력 채널이다. <는 이 중 stdin을 다루는 문법이다.

다음으로 pipe(|)가 있다.
pipe는 stdin의 데이터 출처를 파일이 아니라 다른 프로세스로 바꾸는 방식이다. 즉, <와 같은 영역의 문법이지만 연결 대상이 다르다.

here-doc, here-string도 같은 축에 있다.
이들은 파일 없이 여러 줄 텍스트나 짧은 문자열을 stdin으로 공급하는 방식이다. 결국 이것들도 “프로그램 입력을 어떻게 공급할 것인가”라는 동일한 문제를 다룬다.

tee는 stdout 분기 도구로 자주 나오지만, 입출력 구조 전체를 설명할 때 함께 연결하면 좋다. 입력이 어디서 오고, 출력이 어디로 가는지를 분해해서 봐야 tee의 위치도 자연스럽게 보인다.

마지막으로 file descriptor 0, 1, 2가 있다.
<는 결국 0번 FD, 즉 stdin의 연결 대상을 바꾸는 문법이다. 이 관점을 잡으면 리다이렉션은 단순 기호가 아니라 파일 디스크립터 재연결 구조라는 점이 선명해진다.

10. 더 깊이 보기

표면적으로 보면 < file.txt는 그저 문법 하나처럼 보인다.
하지만 내부적으로는 쉘이 프로그램을 실행하기 전에 stdin(FD 0)을 파일에 연결하는 동작에 가깝다.

즉, 개념적으로는 다음과 비슷하다.

int fd = open("input.txt", O_RDONLY);
dup2(fd, 0);
close(fd);
execvp(...);

이 코드가 의미하는 바는 명확하다.

  1. input.txt를 읽기 전용으로 연다
  2. 그 파일 디스크립터를 0번 FD(stdin)에 복제한다
  3. 원래 fd를 닫는다
  4. 그 상태로 프로그램을 실행한다

이렇게 되면 실행된 프로그램은 파일명을 전달받지 않는다.
그저 평소처럼 stdin을 읽을 뿐인데, 그 stdin이 파일과 연결되어 있을 뿐이다.

이 지점이 중요한 이유는 <의 본질이 쉘 레벨의 “입력 채널 재지정”이라는 점을 보여 주기 때문이다.
프로그램이 파일을 특별히 인식하지 않아도 되고, stdin만 읽을 수 있으면 동일한 방식으로 동작한다. 이것이 유닉스 도구의 조합 가능성을 만드는 핵심 구조다.

또 이 관점으로 보면 >2>도 자연스럽게 연결된다.
>는 1번 FD를 바꾸고, 2>는 2번 FD를 바꾸며, <는 0번 FD를 바꾼다. 즉, 리다이렉션은 기호 암기가 아니라 FD 연결 재배치다.

다만 여기서 너무 멀리 가서 프로세스 생성 전체 구조까지 퍼질 필요는 없다.
이 글의 핵심은 쉘이 실행 전에 stdin을 바꿔 놓는다는 점이다. 그 정도만 잡아도 <를 단순 문법이 아니라 구조로 이해하는 데는 충분하다.

11. 정리

리눅스에서 <는 파일의 내용을 stdin으로 연결하는 입력 리다이렉션 문법이다.
중요한 것은 파일을 직접 읽는 것이 아니라, 프로그램이 입력받는 기본 채널의 출처를 파일로 바꾼다는 점이다.

이 관점을 잡으면 여러 차이가 한 번에 정리된다.
command file.txt는 프로그램이 파일명을 전달받는 방식이고, command < file.txt는 프로그램이 stdin으로 데이터를 받는 방식이다.
< file.txt는 파일을 stdin에 연결하는 것이고, cmd1 | cmd2는 다른 프로그램의 stdout을 stdin에 연결하는 것이다.

실무적으로도 <는 매우 유용하다.
입력 자동화, 테스트 재현, SQL 실행, 배치 입력 공급, 파이프라인 시작점 정의 같은 패턴이 모두 이 문법 위에 서 있다. 특히 사람이 직접 입력하던 작업을 파일 기반 실행으로 바꾸는 데 강력하다.

실전에서 기억해야 할 핵심은 분명하다.

# 파일명을 인자로 전달
command file.txt
# 파일 내용을 stdin으로 전달
command < file.txt

그리고 가장 중요한 문장은 이것이다.

<는 파일을 직접 읽는 문법이 아니다. 프로그램이 입력받는 기본 채널(stdin)의 출처를 파일로 바꾸는 문법이다.`

이 기준으로 보면 리눅스의 입출력 구조는 훨씬 단순해진다.
프로그램은 stdin을 읽고, stdout과 stderr로 내보내며, 쉘은 그 연결 지점을 <, >, 2>, | 같은 문법으로 바꾼다. 출력 리다이렉션을 이미 이해했다면, <를 통해 이제 입출력 전체 구조가 비로소 완성된다.