설정값이나 로그, 크롤링 결과를 파일로 떨궈 둘 일이 자주 있어서, 매번 StreamWriter를 열고 닫는 코드를 쓰는 대신 함수 하나로 만들어 두고 씁니다. 저장에 성공하면 True, 경로가 없거나 권한이 없어서 실패하면 False를 돌려주니 호출하는 쪽에서 If SaveText(...) Then으로 바로 확인할 수 있습니다.

    Function SaveText(fPATH As String, TEXT As String, Optional ED As System.Text.Encoding = Nothing) As Boolean
        On Error GoTo ErrPass
        Dim File As System.IO.StreamWriter
        If ED Is Nothing Then ED = System.Text.Encoding.Default
        File = My.Computer.FileSystem.OpenTextFileWriter(fPATH, False, ED) '// False = 새로쓰기, True = 이어쓰기
        File.WriteLine(TEXT)
        File.Close()
        SaveText = True
ErrPass:
    End Function

실패했을 때 False가 나오는 건 따로 대입하는 코드가 있어서가 아닙니다. VB.NET 함수는 반환값을 정하지 않고 끝나면 형식의 기본값을 돌려주는데, Boolean의 기본값이 False라서 오류로 ErrPass까지 건너뛰면 자연스럽게 False가 됩니다. 마지막 줄의 SaveText = True에 도달했을 때만 True가 되는 구조예요.

광고

호출은 어떻게 하나

프로그램 폴더 아래 log 폴더에 결과를 남기는 예입니다. 폴더가 없으면 저장이 실패하니 먼저 만들어 둡니다. CreateDirectory는 폴더가 이미 있으면 아무 일도 하지 않으니 매번 불러도 괜찮습니다.

Dim path As String = IO.Path.Combine(Application.StartupPath, "log", "result.txt")
IO.Directory.CreateDirectory(IO.Path.GetDirectoryName(path))
If Not SaveText(path, "첫 줄" & vbCrLf & "둘째 줄", System.Text.Encoding.UTF8) Then
    MessageBox.Show("저장 실패: " & path)
End If

경로를 "result.txt"처럼 파일 이름만 넘기면 현재 작업 폴더 기준으로 저장됩니다. 바로 가기에서 실행하거나 파일 열기 대화 상자를 한 번 거치면 작업 폴더가 바뀔 수 있어서, 엉뚱한 곳에 파일이 생기는 일이 생깁니다. 위처럼 실행 파일 위치나 사용자 문서 폴더를 기준으로 전체 경로를 만들어 넘기는 쪽이 안전합니다. Program Files 아래에 설치된 프로그램은 일반 사용자 권한으로 그 폴더에 쓸 수 없으니, 그런 경우에는 Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) 쪽에 저장하세요.

인코딩 인자

세 번째 인자를 생략하면 Encoding.Default로 저장합니다. .NET Framework에서 이 값은 윈도우의 시스템 기본 코드 페이지라서, 한글 윈도우라면 CP949(ANSI)로 저장됩니다. 메모장이나 예전 프로그램, VB6/VBA에서 그대로 읽어야 하는 파일이라면 이게 맞습니다. 반대로 웹으로 올리거나 다른 OS에서 읽을 파일이라면 System.Text.Encoding.UTF8을 넘겨 주세요. 참고로 .NET Core/.NET 5 이후에서는 Encoding.Default가 UTF-8이라 같은 코드라도 결과가 달라집니다.

UTF8을 넘기면 파일 앞에 BOM(EF BB BF)이 붙습니다. BOM을 싫어하는 프로그램(일부 PHP 스크립트, 리눅스 셸 등)에 넘길 파일이라면 New System.Text.UTF8Encoding(False)를 넘기면 BOM 없이 저장됩니다.

.NET 5 이후에서 일부러 CP949로 저장하려고 Encoding.GetEncoding(949)를 넘기면 그대로는 예외가 납니다. 이 환경에서는 코드 페이지 인코딩이 기본으로 등록돼 있지 않아서, 프로그램 시작 시 System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance)를 한 번 호출해 둬야 합니다.

동작에서 알아 둘 점

OpenTextFileWriter의 두 번째 인자 False는 덮어쓰기입니다. 같은 파일에 계속 덧붙여 쓰는 로그 용도로 쓰려면 이걸 True로 바꾸거나, 인자로 빼서 호출할 때 고르게 하면 됩니다.

WriteLine을 쓰기 때문에 내용 끝에 줄바꿈이 하나 더 붙습니다. 저장한 파일을 다시 읽어서 원래 문자열과 비교하면 끝의 줄바꿈 때문에 다르다고 나오는 경우가 있어요. 정확히 그대로 저장해야 한다면 Write로 바꾸면 됩니다.

On Error GoTo 방식이라 파일을 연 다음 쓰는 도중에 오류가 나면 Close를 건너뛰고 빠져나갑니다. 이러면 파일 핸들이 남아서 다음에 같은 파일을 열 때 "다른 프로세스에서 사용 중" 오류가 날 수 있습니다. 쓰기 단계에서 오류 날 일이 거의 없어 실무에서 크게 문제 되진 않았지만, 같은 함수를 Using으로 쓰면 이 부분이 해결됩니다.

Function SaveText(fPATH As String, TEXT As String, Optional ED As System.Text.Encoding = Nothing) As Boolean
    Try
        If ED Is Nothing Then ED = System.Text.Encoding.Default
        Using File As System.IO.StreamWriter = My.Computer.FileSystem.OpenTextFileWriter(fPATH, False, ED)
            File.WriteLine(TEXT)
        End Using
        Return True
    Catch
        Return False
    End Try
End Function

False만 돌려주면 왜 실패했는지 알 수가 없다는 게 이 함수의 단점입니다. 읽기 전용 속성이 걸린 파일인지, 엑셀이나 다른 프로그램이 파일을 붙잡고 있는지, 경로가 틀렸는지에 따라 대처가 다르니 디버깅 중에는 Catch ex As Exception으로 받아 ex.Message를 로그에 남겨 두면 원인 찾기가 빨라집니다.

한 번에 저장하고 끝나는 용도라면 System.IO.File.WriteAllText(fPATH, TEXT, ED) 한 줄로도 됩니다. 이 경우에는 끝에 줄바꿈이 붙지 않습니다. 저장할 폴더가 없으면 어느 방법이든 실패하니, 처음 저장하는 경로라면 IO.Directory.CreateDirectory로 폴더를 먼저 만들어 두세요.