이미지 로딩 중...
AI Generated
2025. 11. 17. · 2 Views
Bash 파일 처리와 입출력 완벽 가이드
Linux/Bash 환경에서 파일을 읽고 쓰는 다양한 방법부터 리다이렉션, Here Document, 파일 디스크립터 활용까지 실무에서 바로 사용할 수 있는 파일 처리 기법을 초급자도 쉽게 이해할 수 있도록 상세히 설명합니다.
목차
- 파일 읽기 (read, cat, while read)
- 파일 쓰기 (>, >>)
- 리다이렉션 (<, >, >>)
- Here Document (<<EOF)
- 표준 입력/출력/에러 (stdin, stdout, stderr)
- 파일 디스크립터 활용
1. 파일 읽기 (read, cat, while read)
시작하며
여러분이 서버 로그 파일을 확인하거나, 설정 파일의 내용을 스크립트에서 처리해야 할 때 이런 상황을 겪어본 적 있나요? 파일 안에 있는 수백 개의 IP 주소를 하나씩 읽어서 처리해야 하는데, 어떻게 해야 할지 막막했던 경험 말이에요.
이런 문제는 실제 개발 현장에서 자주 발생합니다. 로그 분석, 배치 처리, 데이터 마이그레이션 등 거의 모든 자동화 작업에서 파일을 읽어야 하는 순간이 옵니다.
파일을 제대로 읽지 못하면 전체 스크립트가 작동하지 않거나, 데이터가 손실될 수 있습니다. 바로 이럴 때 필요한 것이 Bash의 파일 읽기 명령어들입니다.
cat으로 빠르게 내용을 확인하고, while read로 한 줄씩 처리하며, read 명령어로 사용자 입력까지 받을 수 있습니다.
개요
간단히 말해서, Bash에서 파일을 읽는다는 것은 파일 안의 텍스트 데이터를 스크립트가 이해하고 처리할 수 있는 형태로 가져오는 것입니다. 실무에서는 설정 파일을 읽어서 변수에 저장하거나, 로그 파일을 한 줄씩 분석하거나, CSV 파일의 데이터를 처리하는 등 다양한 상황에서 파일 읽기가 필요합니다.
예를 들어, 서버 목록이 담긴 파일을 읽어서 각 서버에 자동으로 배포하는 스크립트를 만들 때 매우 유용합니다. 전통적인 방법으로는 파일을 수동으로 열어서 내용을 복사-붙여넣기 했다면, 이제는 스크립트가 자동으로 파일을 읽어서 처리할 수 있습니다.
Bash는 세 가지 주요 방법을 제공합니다: (1) cat은 파일 전체를 한 번에 출력, (2) read는 한 줄 또는 특정 구분자까지 읽기, (3) while read 조합은 파일의 모든 줄을 반복 처리합니다. 이러한 특징들이 중요한 이유는 데이터의 크기와 처리 방식에 따라 적절한 방법을 선택해야 효율적이고 안전한 스크립트를 만들 수 있기 때문입니다.
코드 예제
# 방법 1: cat으로 파일 내용 전체 출력
cat config.txt
# 방법 2: while read로 한 줄씩 처리 (가장 많이 사용)
while IFS= read -r line; do
echo "처리 중: $line"
# 여기서 각 줄을 처리합니다
done < data.txt
# 방법 3: read로 변수에 저장
read -p "사용자 이름을 입력하세요: " username
echo "안녕하세요, $username님!"
# 방법 4: 파일의 첫 줄만 읽기
read -r first_line < config.txt
echo "첫 줄: $first_line"
설명
이것이 하는 일: 파일에 저장된 텍스트 데이터를 Bash 스크립트에서 읽어서 처리할 수 있게 만드는 것입니다. 마치 책을 읽듯이, 컴퓨터가 파일의 내용을 한 글자씩 또는 한 줄씩 읽어서 이해하는 과정이에요.
첫 번째 방법인 cat은 가장 간단합니다. cat config.txt를 실행하면 파일의 모든 내용이 화면에 출력됩니다.
이것은 짧은 파일을 빠르게 확인할 때 좋지만, 수천 줄짜리 로그 파일에는 적합하지 않습니다. cat의 결과를 파이프(|)로 다른 명령어에 전달할 수도 있어요.
두 번째 방법인 while read 조합은 실무에서 가장 많이 사용됩니다. while IFS= read -r line에서 IFS=는 공백이 포함된 줄도 정확히 읽기 위한 설정이고, -r은 백슬래시를 특수문자로 해석하지 않도록 합니다.
이렇게 하면 파일의 각 줄이 line 변수에 저장되고, do와 done 사이의 코드가 각 줄마다 실행됩니다. 마치 책의 페이지를 하나씩 넘기면서 읽는 것과 같습니다.
세 번째 방법인 read 명령어는 사용자로부터 입력을 받거나 파일의 특정 부분만 읽을 때 사용합니다. read -p는 프롬프트 메시지를 보여주고, 사용자가 입력한 값을 변수에 저장합니다.
read -r first_line < config.txt처럼 사용하면 파일의 첫 줄만 읽어서 변수에 저장할 수 있어요. 여러분이 이 코드를 사용하면 수동 작업 없이 파일 데이터를 자동으로 처리할 수 있습니다.
실무에서는 서버 목록 파일을 읽어서 각 서버에 명령어 실행하기, 로그 파일에서 에러 라인만 추출하기, CSV 파일을 파싱해서 데이터베이스에 입력하기 등의 작업이 가능해집니다. 또한 코드가 간결하고 이해하기 쉬워서 유지보수가 편리합니다.
실전 팁
💡 while read를 사용할 때는 반드시 IFS= read -r 형식으로 사용하세요. 이렇게 하지 않으면 앞뒤 공백이 제거되거나 백슬래시가 잘못 해석될 수 있습니다.
💡 큰 파일을 처리할 때는 cat보다 while read를 사용하세요. cat은 파일 전체를 메모리에 로드하지만, while read는 한 줄씩 처리해서 메모리 효율적입니다.
💡 파일이 존재하지 않을 때를 대비해서 [ -f "파일명" ]으로 먼저 확인하는 습관을 들이세요. 이렇게 하면 스크립트가 중간에 멈추는 것을 방지할 수 있습니다.
💡 read 명령어에 -t 옵션을 추가하면 타임아웃을 설정할 수 있습니다. 예: read -t 5 -p "5초 안에 입력하세요: " input
💡 파일의 마지막 줄이 개행 문자로 끝나지 않으면 while read가 읽지 못할 수 있으니, while IFS= read -r line || [ -n "$line" ] 형식을 사용하세요.
2. 파일 쓰기 (>, >>)
시작하며
여러분이 스크립트 실행 결과를 기록하거나, 처리한 데이터를 새 파일에 저장해야 할 때 어떻게 하시나요? 화면에 출력되는 내용을 일일이 복사해서 파일에 붙여넣기 하는 것은 비효율적이고 실수하기 쉽습니다.
이런 문제는 로그 기록, 백업 파일 생성, 보고서 자동 작성 등 거의 모든 자동화 스크립트에서 발생합니다. 실행 결과를 파일로 저장하지 않으면 나중에 추적할 수 없고, 문제가 생겼을 때 원인을 파악하기 어렵습니다.
또한 데이터를 영구적으로 보관할 방법이 없어집니다. 바로 이럴 때 필요한 것이 리다이렉션 연산자 >와 >>입니다.
는 파일을 새로 만들거나 덮어쓰고, >>는 파일 끝에 내용을 추가합니다. 이 두 가지만 알아도 대부분의 파일 쓰기 작업을 처리할 수 있습니다.
개요
간단히 말해서, 파일 쓰기는 명령어의 출력 결과나 특정 데이터를 파일에 저장하는 작업입니다. 화면에 표시되던 내용을 파일로 보내는 것이죠.
실무에서는 스크립트 실행 로그를 남기거나, 데이터 처리 결과를 파일로 저장하거나, 설정 파일을 자동으로 생성하는 등의 작업에서 필수적입니다. 예를 들어, 백업 스크립트를 실행할 때마다 백업 시간과 결과를 로그 파일에 기록하면, 나중에 백업 이력을 추적할 수 있습니다.
전통적인 방법으로는 명령어를 실행한 후 출력을 복사해서 텍스트 에디터에 붙여넣고 저장했다면, 이제는 > 또는 >> 연산자로 자동으로 파일에 저장할 수 있습니다. 두 연산자의 핵심 차이는 이렇습니다: (1) >는 파일을 새로 만들거나 기존 내용을 완전히 지우고 새로 씁니다 (덮어쓰기), (2) >>는 파일이 있으면 끝에 추가하고 없으면 새로 만듭니다 (추가하기).
이 차이를 이해하는 것이 중요한 이유는 >를 잘못 사용하면 중요한 데이터를 실수로 삭제할 수 있기 때문입니다.
코드 예제
# 방법 1: 파일 새로 만들기 또는 덮어쓰기 (>)
echo "새로운 로그 시작" > app.log
ls -la > file_list.txt
# 방법 2: 파일 끝에 추가하기 (>>)
echo "$(date): 백업 완료" >> backup.log
echo "에러 발생" >> error.log
# 방법 3: 여러 줄 추가
echo "서버1: 정상" >> status.txt
echo "서버2: 정상" >> status.txt
echo "서버3: 점검중" >> status.txt
# 방법 4: 명령어 결과를 파일로 저장
ps aux > running_processes.txt
df -h >> disk_usage.log
설명
이것이 하는 일: 명령어의 출력 결과나 텍스트를 화면 대신 파일로 보내서 저장하는 것입니다. 마치 프린터로 문서를 출력하는 것처럼, 출력 방향을 파일로 바꾸는 거예요.
첫 번째로, > 연산자는 리다이렉션의 가장 기본 형태입니다. echo "새로운 로그 시작" > app.log를 실행하면, echo의 출력이 화면에 표시되는 대신 app.log 파일에 저장됩니다.
만약 app.log가 이미 존재한다면, 기존 내용은 완전히 사라지고 새로운 내용으로 대체됩니다. 이것은 마치 공책의 모든 내용을 지우고 새로 쓰는 것과 같습니다.
그래서 중요한 파일에 >를 사용할 때는 주의해야 합니다. 두 번째로, >> 연산자는 기존 파일의 내용을 보존하면서 끝에 새로운 내용을 추가합니다.
echo "$(date): 백업 완료" >> backup.log를 실행하면, 현재 날짜와 시간, 그리고 "백업 완료" 메시지가 backup.log 파일의 맨 끝에 새 줄로 추가됩니다. 기존 내용은 그대로 유지됩니다.
이것은 일기장에 새로운 날짜의 일기를 추가하는 것과 같아요. 로그 파일처럼 계속 기록을 쌓아야 하는 경우에 >>를 사용합니다.
세 번째로, 명령어의 실행 결과도 파일로 저장할 수 있습니다. ls -la > file_list.txt는 현재 디렉토리의 모든 파일과 폴더 목록을 file_list.txt에 저장합니다.
ps aux > running_processes.txt는 현재 실행 중인 모든 프로세스 정보를 파일로 저장하죠. 이렇게 하면 시스템 상태를 스냅샷처럼 기록할 수 있습니다.
여러분이 이 코드를 사용하면 스크립트 실행 이력을 자동으로 기록하고, 데이터를 영구적으로 보관하며, 나중에 분석할 수 있는 로그를 생성할 수 있습니다. 실무에서는 cron 작업의 결과를 로그 파일에 기록하기, API 응답을 파일로 저장하기, 시스템 모니터링 결과를 시간별로 기록하기 등이 가능합니다.
또한 실수로 화면을 닫아도 결과가 파일에 저장되어 있어서 안전합니다.
실전 팁
💡 로그 파일에는 항상 >>를 사용하세요. >를 실수로 사용하면 이전 로그가 모두 사라집니다. 특히 cron 작업에서는 더욱 조심해야 합니다.
💡 중요한 파일을 덮어쓰지 않도록 set -o noclobber를 스크립트 시작 부분에 추가하세요. 이렇게 하면 >로 기존 파일을 덮어쓰려 할 때 에러가 발생합니다.
💡 로그 파일에 타임스탬프를 함께 기록하는 습관을 들이세요. echo "$(date '+%Y-%m-%d %H:%M:%S'): 작업 완료" >> app.log 형식으로 사용하면 언제 무슨 일이 있었는지 추적할 수 있습니다.
💡 파일 크기가 계속 커지는 것을 방지하려면 logrotate를 사용하거나, 날짜별로 다른 파일명을 사용하세요. 예: echo "로그" >> app_$(date +%Y%m%d).log
💡 여러 명령어의 결과를 한 파일에 모으려면 { } 블록을 사용하세요. { echo "시작"; ls; echo "끝"; } > output.txt
3. 리다이렉션 (<, >, >>)
시작하며
여러분이 프로그램에 데이터를 입력하거나 결과를 저장할 때, 매번 키보드로 타이핑하거나 화면을 보면서 복사-붙여넣기 하는 건 정말 비효율적입니다. 특히 같은 입력을 반복해야 하거나, 대량의 데이터를 처리해야 할 때는 더욱 그렇죠.
이런 문제는 테스트 자동화, 배치 처리, 데이터 파이프라인 구축 등 다양한 상황에서 발생합니다. 입력과 출력을 자동화하지 못하면 사람이 계속 개입해야 하고, 그만큼 실수할 가능성도 높아집니다.
또한 야간 배치 작업처럼 사람이 없을 때 실행되어야 하는 작업은 아예 불가능합니다. 바로 이럴 때 필요한 것이 리다이렉션입니다.
입력 리다이렉션 <는 파일에서 데이터를 읽어서 프로그램에 전달하고, 출력 리다이렉션 >와 >>는 프로그램의 결과를 파일로 저장합니다. 이 세 가지를 조합하면 완전 자동화된 데이터 처리 파이프라인을 만들 수 있습니다.
개요
간단히 말해서, 리다이렉션은 데이터의 흐름 방향을 바꾸는 것입니다. 기본적으로 프로그램은 키보드에서 입력을 받고 화면에 출력하는데, 이것을 파일에서 읽고 파일에 쓰도록 방향을 바꾸는 거죠.
실무에서는 설정 파일을 프로그램에 자동으로 입력하거나, SQL 쿼리 파일을 데이터베이스에 실행하거나, 테스트 데이터를 프로그램에 주입하는 등의 작업에서 필수적입니다. 예를 들어, mysql 명령어에 SQL 파일을 입력 리다이렉션으로 전달하면 데이터베이스 마이그레이션을 자동화할 수 있습니다.
전통적인 방법으로는 프로그램을 실행하고 키보드로 명령을 하나씩 입력했다면, 이제는 명령을 파일에 미리 작성해두고 < 연산자로 한 번에 전달할 수 있습니다. 리다이렉션의 세 가지 핵심 방향은: (1) < 입력을 파일에서 읽기, (2) > 출력을 파일에 쓰기(덮어쓰기), (3) >> 출력을 파일에 추가하기입니다.
이 세 가지를 하나의 명령어에서 동시에 사용할 수도 있습니다. 이러한 특징이 중요한 이유는 사람의 개입 없이 프로그램이 파일 기반으로 완전 자동화될 수 있기 때문입니다.
코드 예제
# 입력 리다이렉션: 파일에서 데이터 읽기
# input.txt의 내용을 sort 명령어에 전달
sort < input.txt
# 출력 리다이렉션: 결과를 파일로 저장
sort < input.txt > sorted.txt
# 입력과 출력 동시 사용: 파일 정렬 후 저장
sort < unsorted.txt > sorted.txt
# 실무 예제: SQL 파일 실행하고 결과 저장
mysql -u user -p database < schema.sql > result.log 2>&1
# wc 명령어에 파일 내용 전달 (라인, 단어, 바이트 수 계산)
wc -l < logfile.txt
# 에러 출력까지 함께 저장
./script.sh < input.txt > output.txt 2>> error.log
설명
이것이 하는 일: 프로그램이 데이터를 주고받는 방향을 키보드/화면에서 파일로 바꾸는 것입니다. 마치 물의 흐름을 파이프로 원하는 방향으로 보내는 것처럼, 데이터의 흐름을 조절할 수 있어요.
첫 번째로, 입력 리다이렉션 <는 파일의 내용을 프로그램에 전달합니다. sort < input.txt를 실행하면, sort 명령어가 키보드 입력을 기다리는 대신 input.txt 파일의 내용을 읽어서 정렬합니다.
이것은 파일의 내용을 복사해서 프로그램에 붙여넣는 것과 같지만, 자동으로 이루어집니다. 왜 이렇게 하는지는 같은 데이터로 반복 테스트할 때나, 대량의 데이터를 처리할 때 매우 유용하기 때문입니다.
두 번째로, 출력 리다이렉션 >와 >>는 앞에서 배운 것처럼 결과를 파일로 보냅니다. sort < input.txt > sorted.txt를 실행하면, input.txt의 내용을 정렬한 결과가 sorted.txt에 저장됩니다.
화면에는 아무것도 표시되지 않지만, 파일을 열어보면 정렬된 결과가 들어있습니다. 이렇게 하면 결과를 나중에 다시 사용하거나, 다른 프로그램에 전달할 수 있습니다.
세 번째로, 입력과 출력을 동시에 리다이렉션할 수 있습니다. mysql -u user -p database < schema.sql > result.log 2>&1은 복잡해 보이지만, 각 부분을 나누면 이해하기 쉽습니다: < schema.sql은 SQL 파일을 mysql에 입력하고, > result.log는 실행 결과를 로그 파일에 저장하며, 2>&1은 에러 메시지도 같은 파일에 저장합니다.
이렇게 하면 데이터베이스 마이그레이션을 완전 자동화하고 모든 결과를 기록할 수 있습니다. 네 번째로, wc -l < logfile.txt처럼 파일을 인자로 전달하는 대신 입력 리다이렉션을 사용하면, 파일명 없이 라인 수만 출력됩니다.
wc -l logfile.txt는 "100 logfile.txt"를 출력하지만, wc -l < logfile.txt는 "100"만 출력합니다. 이 차이는 결과를 변수에 저장하거나 다른 프로그램에 전달할 때 유용합니다.
여러분이 이 코드를 사용하면 반복 작업을 자동화하고, 대량의 데이터를 사람 개입 없이 처리하며, 모든 실행 이력을 파일로 보관할 수 있습니다. 실무에서는 데이터베이스 백업 및 복구, 로그 파일 분석 및 정리, API 테스트 자동화, CI/CD 파이프라인 구축 등에 활용됩니다.
또한 스크립트가 예측 가능하고 재현 가능해져서 디버깅이 훨씬 쉬워집니다.
실전 팁
💡 리다이렉션 순서는 중요하지 않습니다. command < input > output과 command > output < input은 같은 결과를 냅니다. 읽기 쉬운 순서로 작성하세요.
💡 2>&1은 에러 출력(stderr)을 표준 출력(stdout)과 같은 곳으로 보냅니다. 모든 출력을 한 파일에 모으려면 command > all.log 2>&1 형식을 사용하세요.
💡 기존 파일을 실수로 덮어쓰지 않으려면 >| 대신 > 사용 시 set -o noclobber를 설정하세요. 강제로 덮어쓰려면 >|를 사용하면 됩니다.
💡 파일이 존재하지 않을 때를 대비해서 [ -f input.txt ] && sort < input.txt > sorted.txt 형식으로 조건을 확인하세요.
💡 파이프(|)와 리다이렉션을 혼동하지 마세요. 파이프는 명령어끼리 연결하고(cat file | grep error), 리다이렉션은 파일과 연결합니다(grep error < file).
4. Here Document (<<EOF)
시작하며
여러분이 스크립트 안에서 여러 줄의 텍스트를 파일에 쓰거나, 프로그램에 여러 줄의 명령을 전달해야 할 때 어떻게 하시나요? echo를 여러 번 반복하는 것은 코드가 길어지고 관리하기 어렵습니다.
특히 설정 파일이나 SQL 쿼리처럼 여러 줄로 된 복잡한 텍스트를 다룰 때는 더욱 그렇죠. 이런 문제는 설정 파일 자동 생성, 이메일 템플릿 작성, 복잡한 데이터베이스 쿼리 실행 등에서 자주 발생합니다.
여러 줄을 하나씩 echo로 처리하면 코드가 지저분해지고, 들여쓰기나 특수문자 처리가 복잡해집니다. 또한 실제 내용이 어떻게 보일지 스크립트에서 직관적으로 파악하기 어렵습니다.
바로 이럴 때 필요한 것이 Here Document입니다. <<EOF로 시작해서 EOF로 끝나는 블록 안에 여러 줄의 텍스트를 그대로 작성하면, Bash가 이것을 하나의 입력으로 처리합니다.
마치 텍스트 에디터에서 작성하듯이 자연스럽게 코드를 작성할 수 있습니다.
개요
간단히 말해서, Here Document는 스크립트 안에 여러 줄의 텍스트 블록을 포함시키는 방법입니다. EOF(End Of File)라는 구분자로 텍스트 블록의 시작과 끝을 표시합니다.
실무에서는 Nginx나 Apache 설정 파일을 자동 생성하거나, 이메일 본문을 작성하거나, 복잡한 SQL 쿼리를 실행하거나, SSH로 원격 서버에 여러 명령을 전달하는 등의 작업에 매우 유용합니다. 예를 들어, Docker 컨테이너 안에서 여러 명령을 순차적으로 실행하는 스크립트를 작성할 때 Here Document를 사용하면 코드가 훨씬 깔끔해집니다.
전통적인 방법으로는 echo "첫 줄" >> file; echo "둘째 줄" >> file처럼 여러 번 반복했다면, 이제는 Here Document로 모든 내용을 한 번에 작성할 수 있습니다. Here Document의 핵심 특징은: (1) <<구분자로 시작해서 구분자로 끝남, (2) 블록 안의 내용은 그대로 유지됨(변수는 치환됨), (3) 들여쓰기와 줄바꿈이 보존됨, (4) 파일에 쓰거나 명령어에 입력으로 전달 가능합니다.
이것이 중요한 이유는 복잡한 텍스트를 직관적이고 유지보수하기 쉬운 방식으로 관리할 수 있기 때문입니다.
코드 예제
# 방법 1: 파일에 여러 줄 쓰기
cat > config.conf <<EOF
server {
listen 80;
server_name example.com;
root /var/www/html;
}
EOF
# 방법 2: 변수를 포함한 Here Document
NAME="홍길동"
cat > welcome.txt <<EOF
안녕하세요, ${NAME}님!
오늘은 $(date)입니다.
환영합니다.
EOF
# 방법 3: 변수 치환 방지 (따옴표 사용)
cat > script.sh <<'EOF'
#!/bin/bash
echo "이것은 $USER 변수입니다" # 실제 값으로 치환되지 않음
EOF
# 방법 4: SQL 쿼리 실행
mysql -u root -p database <<EOF
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100)
);
INSERT INTO users VALUES (1, 'Alice');
EOF
# 방법 5: SSH로 원격 명령 실행
ssh user@server <<EOF
cd /var/www
git pull origin main
sudo systemctl restart nginx
EOF
설명
이것이 하는 일: 스크립트 안에 여러 줄의 텍스트를 마치 별도의 파일처럼 포함시켜서, 파일에 쓰거나 프로그램에 입력으로 전달하는 것입니다. 마치 편지를 쓸 때 본문을 작성하는 것처럼, 내용을 자연스럽게 작성할 수 있어요.
첫 번째로, cat > config.conf <<EOF는 config.conf 파일을 생성하고, EOF가 나올 때까지의 모든 줄을 파일에 씁니다. 블록 안의 내용은 여러분이 작성한 그대로 파일에 저장됩니다.
들여쓰기, 공백, 줄바꿈이 모두 보존되기 때문에 Nginx 설정 파일처럼 형식이 중요한 파일을 만들 때 완벽합니다. 왜 이렇게 하는지는 echo를 여러 번 사용하는 것보다 훨씬 읽기 쉽고 실제 파일 내용을 직관적으로 파악할 수 있기 때문입니다.
두 번째로, Here Document 안에서 변수를 사용할 수 있습니다. ${NAME}이나 $(date)처럼 변수와 명령 치환이 실행되어 실제 값으로 바뀝니다.
이것은 템플릿 엔진처럼 동작해서, 사용자별 맞춤 파일이나 동적 설정 파일을 생성할 때 매우 유용합니다. 예를 들어, 배포 스크립트에서 서버 이름이나 날짜를 자동으로 포함시킬 수 있습니다.
세 번째로, 변수 치환을 방지하고 싶다면 <<'EOF'처럼 구분자를 따옴표로 감싸면 됩니다. 이렇게 하면 $USER 같은 변수가 실제 값으로 치환되지 않고 문자 그대로 유지됩니다.
이것은 쉘 스크립트 파일을 생성할 때 유용한데, 생성되는 스크립트 안의 변수는 실행 시점에 치환되어야 하기 때문입니다. 네 번째로, Here Document는 명령어에 입력을 전달하는 데도 사용됩니다.
mysql ... <<EOF는 EOF까지의 모든 SQL 문을 mysql 명령어에 입력으로 전달합니다.
이렇게 하면 복잡한 데이터베이스 초기화 스크립트를 깔끔하게 작성할 수 있습니다. ssh user@server <<EOF는 원격 서버에서 여러 명령을 순차적으로 실행할 수 있게 해줍니다.
여러분이 이 코드를 사용하면 설정 파일을 자동으로 생성하고, 복잡한 명령을 구조화된 방식으로 전달하며, 코드의 가독성을 크게 향상시킬 수 있습니다. 실무에서는 Docker 이미지 빌드 시 설정 파일 생성, CI/CD 파이프라인에서 동적 설정 주입, 서버 프로비저닝 자동화, 데이터베이스 마이그레이션 스크립트 작성 등에 널리 사용됩니다.
또한 코드 리뷰 시 실제 결과물을 쉽게 예상할 수 있어 협업에도 유리합니다.
실전 팁
💡 구분자는 EOF가 관례이지만 원하는 단어를 사용할 수 있습니다. END, DELIMITER, HEREDOC 등 의미 있는 이름을 선택하세요.
💡 들여쓰기를 제거하려면 <<-EOF처럼 - 기호를 추가하세요. 단, 탭 문자만 제거되고 스페이스는 유지됩니다.
💡 Here Document 안에서 작은따옴표나 큰따옴표를 이스케이프할 필요 없습니다. 그대로 작성하면 됩니다.
💡 긴 이메일 본문이나 HTML 템플릿을 생성할 때 Here Document를 사용하면 코드가 훨씬 깔끔해집니다.
💡 변수 치환이 필요한 부분과 그렇지 않은 부분이 섞여 있다면, 필요한 변수만 ${VAR}로 명시하고 나머지는 $로 이스케이프하세요.
5. 표준 입력/출력/에러 (stdin, stdout, stderr)
시작하며
여러분이 프로그램을 실행할 때 화면에 결과가 표시되거나 에러 메시지가 나타나는 것을 당연하게 여기시나요? 하지만 실제로는 프로그램이 세 가지 다른 통로를 통해 데이터를 주고받고 있습니다.
정상 출력, 에러 메시지, 입력이 각각 독립적인 경로를 사용하는데, 이것을 제대로 이해하지 못하면 로그 관리나 에러 처리에서 문제가 생깁니다. 이런 문제는 스크립트가 실패했을 때 원인을 찾거나, 정상 출력과 에러를 분리해서 기록하거나, 파이프라인에서 에러를 추적할 때 자주 발생합니다.
모든 출력이 섞여 있으면 나중에 로그를 분석할 때 정상 데이터와 에러를 구분하기 어렵고, 자동화된 에러 알림 시스템을 구축하기도 어렵습니다. 바로 이럴 때 필요한 것이 표준 스트림(stdin, stdout, stderr)의 개념입니다.
stdin은 입력(0번), stdout은 정상 출력(1번), stderr은 에러 출력(2번)을 담당하며, 각각을 독립적으로 리다이렉션할 수 있습니다. 이것을 알면 에러만 별도 파일로 기록하거나, 정상 출력만 다음 프로그램으로 전달하는 등의 고급 기술을 사용할 수 있습니다.
개요
간단히 말해서, 모든 프로그램은 세 가지 기본 통로를 통해 데이터를 주고받습니다: 표준 입력(stdin)으로 데이터를 받고, 표준 출력(stdout)으로 결과를 내보내며, 표준 에러(stderr)로 에러 메시지를 보냅니다. 실무에서는 스크립트의 에러만 별도 로그 파일에 기록하거나, 정상 출력은 파이프로 다음 명령어에 전달하면서 에러는 파일에 저장하거나, 모든 출력을 하나의 파일에 모으는 등의 작업이 필요합니다.
예를 들어, 백업 스크립트를 실행할 때 정상 동작은 backup.log에, 에러는 backup_error.log에 따로 기록하면 문제 발생 시 빠르게 원인을 찾을 수 있습니다. 전통적인 방법으로는 모든 출력이 화면에 섞여서 표시되었다면, 이제는 각 스트림을 독립적으로 제어해서 원하는 곳으로 보낼 수 있습니다.
세 가지 표준 스트림의 특징은: (1) stdin(파일 디스크립터 0)은 키보드 또는 파이프/파일에서 입력 받음, (2) stdout(파일 디스크립터 1)은 정상 결과를 화면 또는 파이프/파일로 출력, (3) stderr(파일 디스크립터 2)는 에러 메시지를 화면 또는 파일로 출력합니다. 이것이 중요한 이유는 정상 데이터와 에러를 분리해서 처리함으로써 로그 관리, 에러 처리, 데이터 파이프라인 구축이 훨씬 정교해지기 때문입니다.
코드 예제
# stdout만 파일로 (에러는 화면에 표시)
ls /존재하는폴더 > output.txt
# stderr만 파일로 (정상 출력은 화면에 표시)
ls /존재하지않는폴더 2> error.txt
# stdout과 stderr를 각각 다른 파일로
command > output.log 2> error.log
# stdout과 stderr를 같은 파일로
command > all.log 2>&1
# stderr를 stdout으로 보낸 후 파이프로 전달
command 2>&1 | grep "ERROR"
# 출력은 버리고 에러만 기록
command > /dev/null 2> error.log
# 모든 출력 버리기 (조용히 실행)
command > /dev/null 2>&1
# stdout과 stderr를 파일에 추가
command >> output.log 2>> error.log
설명
이것이 하는 일: 프로그램이 데이터를 주고받는 세 가지 독립적인 통로를 각각 제어하는 것입니다. 마치 집에 수도관, 하수관, 환기구가 따로 있듯이, 프로그램도 입력, 출력, 에러를 위한 별도의 통로를 가지고 있어요.
첫 번째로, 기본 리다이렉션 >는 사실 1>의 축약형으로, stdout(표준 출력)만 리다이렉션합니다. ls /home > output.txt를 실행하면 정상적인 파일 목록은 output.txt에 저장되지만, 에러가 발생하면 여전히 화면에 표시됩니다.
이것은 두 통로가 독립적이기 때문입니다. 그래서 파일에는 정상 데이터만 깔끔하게 저장되고, 에러는 실시간으로 확인할 수 있습니다.
두 번째로, 2>는 stderr(표준 에러)만 리다이렉션합니다. ls /nonexistent 2> error.txt를 실행하면, "폴더를 찾을 수 없습니다" 같은 에러 메시지가 error.txt에 저장됩니다.
정상 출력이 있다면 화면에 표시됩니다. 이렇게 하면 에러 로그를 별도로 관리할 수 있어서, 나중에 문제가 생겼을 때 error.txt만 확인하면 됩니다.
실무에서는 cron 작업의 에러만 추적하거나, 에러 발생 시 알림을 보내는 시스템을 만들 때 사용합니다. 세 번째로, 2>&1은 매우 중요한 패턴입니다.
이것은 "stderr(2번)를 stdout(1번)이 가는 곳으로 보내라"는 의미입니다. command > all.log 2>&1을 실행하면, 먼저 stdout이 all.log로 리다이렉션되고, 그 다음 stderr도 같은 곳(all.log)으로 리다이렉션됩니다.
순서가 중요한데, 2>&1을 먼저 쓰면 의도대로 동작하지 않습니다. 이렇게 하면 모든 출력이 하나의 파일에 시간순으로 기록되어 전체 실행 과정을 추적할 수 있습니다.
네 번째로, /dev/null은 "블랙홀"처럼 모든 데이터를 버리는 특수 파일입니다. command > /dev/null 2>&1을 실행하면 모든 출력이 사라지고 화면이 깨끗합니다.
이것은 출력이 필요 없는 백그라운드 작업이나, 성공 여부만 확인하고 싶을 때 사용합니다. command > /dev/null 2> error.log처럼 정상 출력은 버리고 에러만 기록할 수도 있습니다.
여러분이 이 코드를 사용하면 로그를 체계적으로 관리하고, 에러 처리를 정교하게 구현하며, 파이프라인에서 데이터 흐름을 완벽하게 제어할 수 있습니다. 실무에서는 에러 모니터링 시스템 구축, 로그 레벨별 파일 분리, CI/CD 파이프라인에서 에러 추적, 자동화 스크립트의 디버깅 강화 등에 필수적입니다.
또한 에러와 정상 데이터를 분리하면 로그 분석 도구와 통합하기도 훨씬 쉬워집니다.
실전 팁
💡 2>&1의 순서가 중요합니다. command > file 2>&1 (올바름)과 command 2>&1 > file (잘못됨)은 다른 결과를 만듭니다. 항상 출력 리다이렉션을 먼저, 에러 리다이렉션을 나중에 쓰세요.
💡 Bash 4.0 이상에서는 &> 단축 문법을 사용할 수 있습니다. command &> all.log는 command > all.log 2>&1과 같습니다.
💡 에러만 파이프로 전달하려면 command 2>&1 1>/dev/null | grep ERROR처럼 stdout은 버리고 stderr만 파이프로 보내세요.
💡 스크립트 전체의 모든 출력을 리다이렉션하려면 exec 1>output.log 2>error.log를 스크립트 시작 부분에 추가하세요.
💡 에러 발생 여부를 확인하려면 [ -s error.log ]로 에러 로그 파일이 비어있지 않은지 체크하세요.
6. 파일 디스크립터 활용
시작하며
여러분이 여러 파일을 동시에 읽거나 쓰면서, 각 파일을 명확히 구분해야 하는 복잡한 스크립트를 작성할 때 어떻게 하시나요? 일반적인 리다이렉션만으로는 두 개 이상의 파일을 동시에 다루기 어렵고, 파일을 열었다 닫았다를 반복하면 성능도 떨어집니다.
이런 문제는 로그를 여러 레벨로 분리하거나, 입력 파일 두 개를 비교하거나, 하나의 스크립트에서 여러 파일에 동시에 쓰는 복잡한 상황에서 발생합니다. 표준 입출력(0, 1, 2)만으로는 부족하고, 파일을 매번 열고 닫으면 코드가 복잡해지고 에러 처리도 어려워집니다.
바로 이럴 때 필요한 것이 파일 디스크립터입니다. 0, 1, 2번은 이미 stdin, stdout, stderr로 예약되어 있지만, 3번부터 9번까지는 여러분이 자유롭게 사용할 수 있습니다.
exec 명령어로 파일 디스크립터를 파일에 연결하면, 파일을 한 번 열어두고 계속 사용할 수 있어 효율적입니다.
개요
간단히 말해서, 파일 디스크립터는 열린 파일을 가리키는 번호입니다. 0, 1, 2번은 기본 입출력이고, 3번 이상은 여러분이 추가로 파일을 열 때 사용할 수 있는 슬롯입니다.
실무에서는 복잡한 로그 시스템을 구축하거나(INFO는 3번, ERROR는 4번으로), 두 파일을 동시에 읽으면서 비교하거나, 트랜잭션처럼 여러 파일에 원자적으로 쓰는 작업에 유용합니다. 예를 들어, 데이터베이스 백업 스크립트에서 성공 로그, 에러 로그, 통계 로그를 각각 다른 파일 디스크립터로 관리하면 코드가 훨씬 깔끔해집니다.
전통적인 방법으로는 매번 echo "로그" >> file.log처럼 파일을 열고 닫았다면, 이제는 exec 3> file.log로 한 번 열어두고 echo "로그" >&3처럼 계속 사용할 수 있습니다. 파일 디스크립터의 핵심 특징은: (1) 3-9번을 사용자 정의로 사용 가능, (2) exec로 파일과 연결, (3) >&숫자로 해당 파일에 쓰기, (4) <&숫자로 해당 파일에서 읽기, (5) exec 숫자>&-로 닫기입니다.
이것이 중요한 이유는 복잡한 파일 처리를 체계적으로 관리하고, 성능을 최적화하며, 코드의 가독성을 높일 수 있기 때문입니다.
코드 예제
# 파일 디스크립터 3번을 쓰기용으로 열기
exec 3> output.txt
echo "첫 번째 줄" >&3
echo "두 번째 줄" >&3
exec 3>&- # 파일 디스크립터 닫기
# 읽기용 파일 디스크립터
exec 4< input.txt
read -u 4 line1
read -u 4 line2
echo "읽은 내용: $line1, $line2"
exec 4<&- # 닫기
# 여러 로그 레벨 관리
exec 3> info.log # INFO 레벨
exec 4> error.log # ERROR 레벨
exec 5> debug.log # DEBUG 레벨
echo "정보 메시지" >&3
echo "에러 발생!" >&4
echo "디버그 정보" >&5
# 원본 stdout 백업 후 복원
exec 6>&1 # 원본 stdout을 6번에 백업
exec 1> temp.log # stdout을 파일로 리다이렉션
echo "이것은 파일로 갑니다"
exec 1>&6 # 원본 stdout 복원
exec 6>&- # 백업 디스크립터 닫기
echo "이것은 화면에 표시됩니다"
설명
이것이 하는 일: 프로그램이 동시에 여러 파일을 열어두고 각각을 번호로 구분해서 접근하는 것입니다. 마치 여러 개의 서랍을 번호로 관리하듯이, 각 파일에 번호를 부여해서 필요할 때마다 해당 번호로 접근할 수 있어요.
첫 번째로, exec 3> output.txt는 output.txt 파일을 쓰기 모드로 열고 3번 디스크립터에 연결합니다. 이제 echo "내용" >&3를 실행하면 화면이 아닌 output.txt에 내용이 써집니다.
&3는 "표준 출력을 3번 디스크립터로 리다이렉션하라"는 의미입니다. 파일을 한 번 열어두고 계속 사용하기 때문에, 매번 >>로 파일을 여는 것보다 효율적입니다.
특히 반복문 안에서 수천 번 쓰는 경우 성능 차이가 큽니다. 두 번째로, 읽기용 디스크립터도 만들 수 있습니다.
exec 4< input.txt는 파일을 읽기 모드로 열고, read -u 4 line은 4번 디스크립터에서 한 줄을 읽어서 line 변수에 저장합니다. -u 옵션은 "이 디스크립터에서 읽어라"는 의미입니다.
여러 파일을 동시에 읽으면서 비교하거나 병합하는 작업에서 매우 유용합니다. 예를 들어, 두 로그 파일을 동시에 읽으면서 타임스탬프를 비교하는 스크립트를 만들 수 있습니다.
세 번째로, 여러 로그 레벨을 관리하는 실전 패턴입니다. exec 3> info.log, exec 4> error.log, exec 5> debug.log로 세 개의 로그 파일을 열어두고, 필요에 따라 echo "메시지" >&3, echo "에러" >&4처럼 적절한 파일에 기록합니다.
이렇게 하면 로그 관리 함수를 만들 때 매우 깔끔합니다: log_info() { echo "$@" >&3; }, log_error() { echo "$@" >&4; } 형식으로 함수를 정의하면 됩니다. 네 번째로, 원본 stdout을 백업했다가 복원하는 고급 기법입니다.
exec 6>&1은 현재 stdout(1번)을 6번에 복사합니다. 그 다음 exec 1> temp.log로 stdout을 파일로 바꿉니다.
이제 모든 echo는 파일로 갑니다. 나중에 exec 1>&6으로 6번에 저장해둔 원본 stdout을 복원하면 다시 화면에 출력됩니다.
이것은 스크립트 중간에 일부 출력만 파일로 보내고 싶을 때 사용합니다. 마지막으로, exec 3>&-는 3번 디스크립터를 닫습니다.
파일을 다 사용한 후에는 반드시 닫아야 시스템 리소스가 정리됩니다. 특히 오래 실행되는 스크립트에서는 디스크립터를 닫지 않으면 파일 핸들이 누수될 수 있습니다.
여러분이 이 코드를 사용하면 복잡한 파일 처리를 체계적으로 관리하고, 로그 시스템을 레벨별로 분리하며, 여러 파일을 동시에 처리하는 고급 스크립트를 작성할 수 있습니다. 실무에서는 멀티 로그 시스템 구축, 파일 병합 및 비교 스크립트, 트랜잭션 기반 파일 처리, 성능이 중요한 대량 파일 쓰기 작업 등에 활용됩니다.
또한 코드가 더 모듈화되고 재사용 가능해져서 유지보수가 쉬워집니다.
실전 팁
💡 파일 디스크립터는 0-255까지 사용 가능하지만, 관례적으로 3-9번을 사용합니다. 10번 이상은 특수한 경우에만 사용하세요.
💡 파일 디스크립터를 사용할 때는 반드시 스크립트 종료 전이나 사용 후에 exec 숫자>&-로 닫는 습관을 들이세요. 그렇지 않으면 파일이 잠길 수 있습니다.
💡 exec 3>&1 1>&2 2>&3 3>&-처럼 stdout과 stderr을 서로 바꿀 수 있습니다. 디버깅할 때 유용한 트릭입니다.
💡 [ -e /proc/$$/fd/3 ]로 3번 디스크립터가 열려있는지 확인할 수 있습니다. 조건부로 디스크립터를 사용할 때 유용합니다.
💡 함수 안에서 exec로 디스크립터를 변경하면 전역적으로 영향을 미치므로 조심하세요. 필요하면 함수 내에서 local로 백업했다가 복원하세요.