fopen('data.txt', 'r')는 파일을 열고 핸들을 반환합니다. 핸들은 다른 파일 함수들이 첫 번째 인수로 받는 스트림 리소스입니다. fgets()(한 줄)나 fread()(바이트 수)로 읽고, fwrite()로 쓰고, fclose()로 닫습니다. 두 번째 인수는 모드입니다. 읽기는 'r', 쓰기는 'w', 이어 쓰기는 'a'입니다.
출력:
Got: apple
Got: banana
Got: cherry
fopen()은 파일을 열 수 없으면(r 모드에서 파일이 없음, 폴더가 없음, 권한 없음) false를 반환하고 PHP가 다음과 같은 경고를 출력합니다.
Warning: fopen(missing.txt): Failed to open stream: No such file or directory in /home/index.php on line 2
그러므로 핸들을 쓰기 전에 false인지 확인하세요. 작은 파일 전체를 한 번에 읽거나 쓰려면 file_get_contents()와 file_put_contents()가 더 짧습니다. fopen()은 조금씩 읽거나 열어 두는 파일을 위한 것입니다.
fopen 모드
| 모드 | 읽기 | 쓰기 | 파일이 있으면 | 없으면 | 시작 위치 |
|---|---|---|---|---|---|
r | 예 | 아니요 | 연다 | 실패 | 처음 |
r+ | 예 | 예 | 열고 내용 유지 | 실패 | 처음 |
w | 아니요 | 예 | 비운다 | 만든다 | 처음 |
w+ | 예 | 예 | 비운다 | 만든다 | 처음 |
a | 아니요 | 예 | 내용 유지 | 만든다 | 끝, 모든 쓰기가 덧붙음 |
a+ | 예 | 예 | 내용 유지 | 만든다 | 끝, 모든 쓰기가 덧붙음 |
x | 아니요 | 예 | 실패 | 만든다 | 처음 |
x+ | 예 | 예 | 실패 | 만든다 | 처음 |
c | 아니요 | 예 | 내용 유지 | 만든다 | 처음 |
c+ | 예 | 예 | 내용 유지 | 만든다 | 처음 |
두 행이 사람들을 놀라게 합니다. r+는 삽입하지 않고 기존 바이트를 그 자리에서 덮어쓰고, a+는 fseek()로 포인터를 옮긴 뒤에도 항상 끝에 씁니다(포인터 이동은 읽기에만 영향을 줍니다).
출력:
Xbc
Xbc
Xbc
end
Windows에서는 'rb'나 'wb'를 볼 수 있습니다. b 플래그는 바이너리를 뜻하며 Windows가 줄 끝을 변환하지 못하게 합니다. Linux와 macOS에서는 아무 일도 하지 않으므로 붙여도 해가 없고 이식성이 있습니다.
fgets()로 파일을 한 줄씩 읽기
fgets()는 \n을 포함한 다음 줄을 반환하고, 파일 끝에서는 false를 반환합니다. 한 번에 한 줄만 메모리에 있으므로 이 반복문은 어떤 크기의 로그 파일이든 처리하며, break로 일찍 멈출 수 있습니다.
출력:
2 errors in 5 lines
line 2: database timeout
line 4: disk full
단순한 while ($line = fgets($fh))가 아니라 !==로 false와 비교하세요. 줄바꿈 없이 "0"만 담긴 줄(파일의 마지막 줄이 그럴 수 있습니다)은 거짓으로 취급되어 반복문을 일찍 끝냅니다.
fwrite()로 쓰기와 이어 쓰기
fwrite()는 현재 위치에 문자열을 쓰고 쓴 바이트 수를 반환하며, 실패하면 false를 반환합니다. fputs()는 다른 이름의 같은 함수입니다. 파일에 있는 내용을 건드리지 않고 끝에 추가하려면 'a'로 여세요.
출력:
wrote 12 bytes
wrote 13 bytes
wrote 15 bytes
visit /home
visit /about
visit /contact
visit /home
PHP_EOL은 PHP가 실행되는 시스템의 줄 끝입니다(Linux와 macOS에서는 \n, Windows에서는 \r\n). 다른 프로그램이 읽을 파일이라면 하나를 명시적으로 고르세요. 보통 "\n"입니다.
fgetcsv()와 fputcsv()로 CSV 파일 읽고 쓰기
fputcsv()는 배열을 CSV 한 행으로 쓰고, 쉼표, 따옴표, 줄바꿈이 든 필드는 따옴표로 감쌉니다. fgetcsv()는 한 행을 배열로 다시 읽습니다. 구분자, 감싸는 문자, 빈 이스케이프 문자를 명시적으로 넘기세요. 그러면 표준 CSV가 되며, 이스케이프 인수를 생략하면 PHP 8.4가 폐지 예정 알림을 출력합니다.
출력:
name,city,note
Ada,London,"likes ""math"", a lot"
Linus,Helsinki,kernel
Ada lives in London: likes "math", a lot
Linus lives in Helsinki: kernel
explode(',', $line)로 줄을 나누면 필드에 쉼표가 들어가는 순간 깨집니다. fgetcsv()는 따옴표 처리를 대신 해 줍니다. 세미콜론으로 구분한 파일(유럽의 Excel 내보내기에서 흔함)이라면 구분자로 ';'를 넘기세요.
fseek(), ftell(), rewind()로 파일 안에서 이동하기
열린 파일에는 위치가 있습니다. 읽기와 쓰기는 거기서 시작하고 위치를 앞으로 옮깁니다. ftell()은 현재 위치를 알려 주고, fseek()는 바이트 오프셋으로 옮기며, rewind()는 처음으로 돌아갑니다. w+를 쓰면 같은 핸들로 파일을 쓰고 다시 읽을 수 있습니다.
출력:
11
world
hello world
flock()으로 파일 잠그기
카운터를 읽고 1을 더해 다시 쓰는 두 요청이 동시에 실행되면 둘 다 41을 읽고 둘 다 42를 쓸 수 있습니다. LOCK_EX와 함께 쓴 flock()은 첫 번째가 잠금을 풀 때까지 두 번째를 기다리게 합니다. 여기서는 c+ 모드가 맞습니다. 필요하면 파일을 만들고, w+와 달리 잠금을 잡기 전에 파일을 비우지 않습니다.
출력:
Visitor number 1
Visitor number 2
Visitor number 3
잠금은 권고 사항입니다. 역시 flock()을 호출하는 다른 코드만 막습니다. 한 줄을 덧붙이는 경우라면 file_put_contents($path, $line, FILE_APPEND | LOCK_EX)가 한 번의 호출로 같은 일을 합니다.
흔한 실수: while (!feof($fh))
예전 튜토리얼의 상당수는 feof()로 반복합니다. 이 함수는 읽기가 이미 끝에 닿은 뒤에야 true를 반환하므로, 반복문이 줄 수보다 한 번 더 돌고 마지막 fgets()가 false를 반환합니다.
출력:
string(2) "a
"
string(2) "b
"
string(2) "c
"
bool(false)
4 rounds for 3 lines
그 여분의 false는 보고서의 빈 행이나 배열의 빈 요소가 됩니다. 이 페이지의 다른 모든 예제처럼 읽기 자체로 반복하세요: while (($line = fgets($fh)) !== false).
자주 묻는 질문
PHP의 fopen 모드에는 무엇이 있나요?
r은 읽기, w는 파일을 비운 뒤 쓰기, a는 끝에 쓰기, x는 새 파일을 만들고 이미 있으면 실패, c는 비우지 않고 쓰기입니다. +를 붙이면(r+, w+, a+, x+, c+) 읽기와 쓰기가 모두 가능합니다. r과 r+를 뺀 모든 모드는 파일이 없으면 만듭니다.
fopen에서 w와 a의 차이는 무엇인가요?
w는 파일을 여는 즉시 비우고 처음부터 씁니다. a는 내용을 유지하며, fseek() 뒤에도 모든 fwrite()가 파일 끝으로 가므로 로그 파일용 모드입니다.
PHP에서 CSV 파일을 읽으려면 어떻게 하나요?
fopen($path, 'r')로 열고 반복문에서 fgetcsv()를 호출하세요: while (($row = fgetcsv($fh, null, ',', '"', '')) !== false) { ... }. 호출마다 한 행을 필드 배열로 반환하며, 따옴표와 필드 안의 쉼표도 처리해 줍니다.
PHP에서 fclose를 호출해야 하나요?
PHP는 스크립트가 끝날 때 열린 모든 파일을 닫으므로 짧은 스크립트는 없어도 동작합니다. 그래도 끝나는 즉시 fclose()를 호출하세요. 버퍼에 있는 쓰기를 내보내고, flock()으로 잡은 잠금을 풀고, 오래 실행되는 스크립트가 파일 핸들을 다 써 버리는 일을 막습니다.