CSV는 「그냥 텍스트」가 아닙니다 ── C# 업무 앱의 CSV 실무(문자 코드·Excel 호환·인젝션 대책)

· 업데이트: · · CSharp, .NET, CSV, Excel, 문자 코드, 파일 연동, Windows 개발, 기술 상담

수정 이력(7건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
목차·표의 장 링크가 제목에서 만들어지는 앵커와 맞도록 고쳤습니다.
기사 앞머리에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건) 대응으로 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하십시오.
참고 링크 설명문에서 대괄호와 소괄호가 이어져, Excel 메뉴 이름이 링크로 해석되어 링크가 깨져 있던 것을 수정했습니다.
판단표에서 각 장으로 이동할 수 있게 하고, CSV 라이브러리를 검토할 때의 구체적 이름(CsvHelper)을 명시했습니다. 더불어 Excel이 BOM 없는 UTF-8을 어떻게 다루는지 정리, 이스케이프 처리의 입출력 대응표, BOM을 바이트 열로 확인하는 예를 추가했습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.21635406)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「CSV는 「그냥 텍스트」가 아닙니다 ── C# 업무 앱의 CSV 실무(문자 코드·Excel 호환·인젝션 대책)」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/csv-file-handling-practical-guide/

DOI(등록된 아카이브)
10.5281/zenodo.21635406
DOI(마지막 등록 버전)
10.5281/zenodo.21635407

CSV를 이용한 데이터 연동은, 업무 앱 세계에서는 지금도 현역의 주역입니다. 그리고 「텍스트를 쉼표로 잇기만 하면 된다」고 보이기 때문에, 자체 파서가 필드 안의 쉼표에서 깨지고, Excel로 열면 문자가 깨지고, 전화번호 앞자리 0이 사라지는 사고가 반복되는 영역이기도 합니다.

이 글에서는 C#으로 CSV를 읽고 쓰는 업무 앱을 전제로, CSV 사양(RFC 4180)과 현실의 방언, 문자 코드 결정 방법, Excel과의 궁합 문제, CSV 인젝션 대책, 운영상의 함정까지를 실무에서 판단이 갈리는 순서로 정리합니다.

1. 먼저 결론(판단표)

CSV 설계 판단은 「누가(무엇이) 그 파일을 여는가」에서 거꾸로 잡습니다. 이 장은 두 부분으로 구성되어 있습니다. 판단표(1.1)는 「이 용도라면 이것」을 찾기 위한 표이고, 원칙(1.2)은 용도와 관계없이 통하는 5가지 규칙입니다. 판단표의 「상세」 열에서 해당 장으로 이동할 수 있게 해 두었으므로, 필요한 장만 읽는 사용법도 가능합니다.

1.1 용도에서 문자 코드를 정한다(판단표)

용도 문자 코드의 제1 후보 설계상의 요점 상세
사람이 Excel에서 더블클릭해 연다 UTF-8(BOM 포함) BOM이 없으면 Excel이 문자 코드를 오인해 문자가 깨지는 환경이 있다. 앞자리 0·날짜화 문제는 문자 코드로 막을 수 없다 4.1 / 제5장
신규 시스템 간 연동 UTF-8(BOM 없음) 문자 코드·따옴표·개행 코드·헤더 유무를 사양서에 명시한다. BOM 유무도 명시한다 제2장 / 4.1
레거시 시스템과의 연동 상대 사양에 따른다(Shift_JIS가 많다) .NET(Core)에서는 Shift_JIS에 CodePagesEncodingProvider 등록이 필요하다. 기종 의존 문자 취급을 먼저 합의한다 4.2
대량 데이터의 배치 연동 사양에 따른다 모든 행을 메모리에 올리지 않고 한 줄씩 스트리밍으로 처리한다. 배타 제어와 쓰기 중인 파일 취급도 설계한다 제7장
사용자 입력을 포함한 데이터를 출력한다 용도에 따른다 = + 로 시작하는 값을 무해화하지 않으면, 연 사람의 환경에서 수식이 실행될 수 있다 제6장
사람이 Excel로 보고·인쇄하는 것이 주목적 ── 애초에 CSV가 아니라 xlsx 출력을 검토한다(FAQ 참조) 제5장

1.2 전체에 통하는 원칙

용도와 관계없이 공통되는 결론을 먼저 적습니다.

  • String.Split(',')으로 CSV를 파싱하는 방식은, 동작하는 것처럼 보여도 사양상 깨집니다. 필드 안의 쉼표·개행·따옴표를 처리할 수 없기 때문입니다. .NET 표준 범위라면 TextFieldParser를 사용하십시오(제3장).
  • 문자 코드 사고는 「Excel이 어떻게 해석하는가」에서 일어납니다. 사람이 Excel로 여는 CSV는 BOM 포함 UTF-8로 하는 것이 현재의 무난한 선택입니다. 또한 .NET에서 BOM을 붙일지는 Encoding 지정 방법에 따라 달라진다는 함정이 있습니다(제4장).
  • .NET(Core)에서는 Shift_JIS를 추가 패키지 없이는 사용할 수 없습니다. System.Text.Encoding.CodePages 패키지의 CodePagesEncodingProvider를 등록해야 비로소 Encoding.GetEncoding("shift_jis")가 성공합니다1. .NET Framework에서 마이그레이션한 CSV 처리가 프로덕션에서 처음 예외를 내는 전형적인 지점입니다(4.2).
  • 앞자리 0 소실·날짜화는 CSV 쪽에서는 막을 수 없습니다. 큰따옴표로 감싸도 막을 수 없다는 것이 중요한 사실입니다(제5장).
  • 사용자 입력을 CSV로 출력하는 앱에는 CSV 인젝션 대책이 필요합니다. = 나 + 로 시작하는 필드는 Excel에서 수식으로 해석됩니다(제6장).

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 24건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. CSV의 「사양」 ── RFC 4180과 현실의 방언

CSV에는 사실상의 표준으로 RFC 4180이 있습니다2. 실무에서 잡아 둘 규칙은 다음 네 가지뿐입니다.

  1. 레코드 구분은 개행(CRLF), 필드 구분은 쉼표.
  2. 필드에 쉼표·개행·큰따옴표가 들어가면, 그 필드 전체를 큰따옴표로 감싼다.
  3. 필드 안의 큰따옴표는 "" 처럼 두 개를 겹쳐 이스케이프한다.
  4. 감쌀 필요가 없는 필드는 감싸도 되고 감싸지 않아도 된다.

즉, 다음 한 줄은 3필드 레코드입니다.

1001,"株式会社サンプル 営業部","住所は""東京都○○区1-2-3""です,と入力された備考"

그리고 RFC 4180은 「사실상의 표준」일 뿐, 현실의 CSV에는 방언이 수없이 많습니다. 개행이 LF만인 경우, 따옴표 처리를 하지 않는 경우, 구분자가 탭이나 세미콜론인 경우, 헤더 행이 있기도 없기도 하는 경우──그렇기 때문에 연동 사양서에 「RFC 4180 준수」라는 한 줄과, 문자 코드·BOM·개행 코드·헤더 유무를 명시하는 것이, 이후 문제를 막는 비용 대비 효과가 가장 높은 작업이 됩니다.

3. Split(‘,’)로 자체 구현하면 안 된다 ── 읽기의 실무

앞 장의 규칙을 전제로 하면, String.Split(',') 가 깨지는 이유는 분명합니다. 필드 안의 쉼표에서 과잉 분할되고, 따옴표 안의 개행에서 행이 중간에 끊깁니다. 「지금 오는 파일에는 쉼표가 들어간 데이터가 없어서 동작한다」는 것뿐이며, 데이터에 따라 언제든 깨집니다.

.NET 표준 범위에서 올바르게 읽으려면 Microsoft.VisualBasic.FileIO.TextFieldParser 를 사용할 수 있습니다. 네임스페이스는 Visual Basic이지만, C#에서도 그대로 사용할 수 있습니다3.

using Microsoft.VisualBasic.FileIO; // Microsoft.VisualBasic.Core(Windows 데스크톱 대상 SDK에 포함)

using var parser = new TextFieldParser(path, System.Text.Encoding.UTF8)
{
    TextFieldType = FieldType.Delimited,
    HasFieldsEnclosedInQuotes = true, // 따옴표로 감싼 필드를 올바르게 처리한다
    TrimWhiteSpace = false, // 기본값은 true(앞뒤 공백이 조용히 제거된다). 공백이 의미를 갖는 데이터에서는 무효화한다
};
parser.SetDelimiters(",");

while (!parser.EndOfData)
{
    try
    {
        string[]? fields = parser.ReadFields();
        if (fields is null) break;
        // fields[0], fields[1], ... 를 처리한다
    }
    catch (MalformedLineException ex)
    {
        // 깨진 행: ErrorLine / ErrorLineNumber 로 내용과 행 번호를 기록할 수 있다
        logger.LogWarning("CSV의 {Line}행이 잘못된 형식입니다: {Content}",
            ex.LineNumber, parser.ErrorLine);
    }
}

TextFieldParser의 실무상 이점은, 따옴표 처리(HasFieldsEnclosedInQuotes)에 더해 깨진 행을 MalformedLineException으로 행 번호와 함께 검출할 수 있다는 점입니다3. 업무 CSV 가져오기에서는 「한 행이 깨져 있으면 파일 전체를 거부할지, 그 행만 건너뛰고 보고할지」가 사양의 갈림길이 되므로, 이 예외 처리 설계를 그대로 사양에 반영할 수 있습니다.

열과 객체의 매핑·형식 변환·수백만 행 규모의 성능까지 필요하다면, 실적이 있는 CSV 라이브러리 도입을 검토하십시오. .NET에서 가장 널리 쓰이는 것은 CsvHelper(NuGet 패키지 이름도 CsvHelper)이며, RFC 4180 준수를 명시하고, MS-PL과 Apache 2.0 듀얼 라이선스로 상용 이용도 가능합니다4. 클래스로의 매핑, ClassMap으로 열 이름을 맞추는 기능, IEnumerable<T> 지연 읽기까지 갖추고 있으므로, 「TextFieldParser로 string[]를 받아 직접 형식 변환하고 있는」 단계를 넘으면 마이그레이션 대상의 제1 후보가 됩니다.

다만, 어느 라이브러리를 고르느냐보다 고를 때 무엇을 확인할지가 더 중요합니다. 확인할 관점은 「RFC 4180의 따옴표 처리」「문자 코드 지정」「깨진 행의 취급」「스트리밍 읽기 가능 여부」 네 가지이며, 이 네 가지를 충족하면 실무에서 크게 빗나가지 않습니다. 반대로, 사내 규정으로 외부 패키지 추가에 제약이 있는 현장에서는, TextFieldParser 로 충분한지를 먼저 판단하십시오.

쓰기 쪽은 사양이 단순하므로 자체 구현도 현실적이지만, 이스케이프 처리는 반드시 하나의 함수에 모으십시오.

// 쓰기 쪽 이스케이프는 이 한 함수에 모은다
static string ToCsvField(string? value)
{
    value ??= string.Empty;
    bool needsQuoting = value.Contains(',') || value.Contains('"')
        || value.Contains('\r') || value.Contains('\n');
    return needsQuoting ? $"\"{value.Replace("\"", "\"\"")}\"" : value;
}

이 함수가 무엇을 하는지는, 입력과 출력을 나란히 두면 한눈에 보입니다.

입력(C# 문자열의 내용) ToCsvField 의 반환값 이유
東京都 東京都 특별한 문자가 없으므로 그대로
株式会社サンプル, 営業部 "株式会社サンプル, 営業部" 쉼표를 포함하므로 전체를 감싼다
住所は"東京"です "住所は""東京""です" 따옴표를 "" 로 치환한 다음 전체를 감싼다
1行目(改行)2行目 "1行目(改行)2行目" 개행을 포함하므로 감싼다. 감싸면 1필드로 올바르게 읽힌다
null (빈 문자열) ??= 로 빈 문자열에 맞추고 있다

3행의 「치환한 다음 감싼다」 순서가 핵심입니다. 반대로 하면, 자신이 붙인 감싸기 따옴표까지 "" 가 되어 깨집니다. 「금액 컬럼만 손으로 string.Join 하고 있는 곳」이 한 곳이라도 남아 있으면, 그곳이 장래의 사고 현장이 됩니다.

4. 문자 코드 ── Excel 동작과 .NET의 함정

4.1 Excel로 연다면 BOM 포함 UTF-8

CSV 문자 깨짐의 대부분은 Excel이 파일의 문자 코드를 오인하는 것으로 일어납니다. BOM(파일 선두에 붙는 수 바이트의 표시)이 없는 UTF-8 CSV를 더블클릭으로 열면, 환경에 따라 ANSI(일본어 환경에서는 Shift_JIS)로 해석되어 일본어가 전부 깨집니다. BOM 포함 UTF-8이면 UTF-8로 인식되므로, 사람이 Excel에서 더블클릭해 여는 CSV는 BOM 포함 UTF-8로 하는 것이 현재의 무난한 선택입니다.

「환경에 따라」라고 쓴 것은, BOM 없는 UTF-8을 어디까지 자동 판별하는지가 Excel 버전·빌드·OS 지역 설정에 좌우되어, 어느 버전이면 반드시 판별된다고 단언할 수 없기 때문입니다. 판단 근거로 삼을 수 있는 것은, Microsoft의 현행 지원 기사가 다음 형태로 안내하고 있다는 점입니다5.

파일 상태 Microsoft의 안내
BOM 포함 UTF-8 보통대로 열 수 있다
BOM 없는 UTF-8 Power Query를 거쳐 연다([데이터] 탭 > [데이터 가져오기] > [파일에서] > [텍스트 또는 CSV에서]), 또는 가져오기 마법사에서 문자 코드를 지정한다

즉, BOM 없는 UTF-8의 더블클릭이 항상 성공한다고는 공식으로 안내되어 있지 않습니다. 배포 대상 Excel의 버전이나 지역 설정을 스스로 확인할 수 없는 업무 앱에서는, 「BOM을 붙여 더블클릭으로 열리는 상태로 두기」가 지원 문의를 줄입니다. 반대로, 가져오기 대상이 시스템이라면 BOM은 불필요한 3바이트로서 해석 오류의 원인이 되므로 BOM 없음으로 합니다.

그 BOM의 정체는 파일 선두 3바이트 EF BB BF 입니다. 바이너리 에디터나 PowerShell의 Format-Hex 로 선두를 보면, 어느 쪽으로 출력되었는지 바로 판별할 수 있습니다.

BOM 포함 UTF-8:  EF BB BF 44 61 74 65 2C ...   ← 선두 3바이트가 EF BB BF, 그 뒤에 "Date," 가 이어진다
BOM 없는 UTF-8:  44 61 74 65 2C ...            ← 바로 "Date," 부터 시작한다

「BOM 포함이어야 할 파일 선두에 EF BB BF 가 없다」「가져오기 쪽에서 첫 열 이름만 일치하지 않는다(보이지 않는 3바이트가 열 이름 머리에 붙어 있다)」는, 어느 쪽이든 이 3바이트를 보면 5초면 판정할 수 있습니다.

여기에 .NET 쪽 함정이 겹칩니다. BOM이 붙는지는 Encoding 을 넘기는 방식에 따라 달라집니다.

// BOM 없는 UTF-8: 인코딩 지정을 생략한 경우의 기본값
File.WriteAllText(path, csv);

// BOM 포함 UTF-8: Encoding.UTF8(정적 속성)은 BOM을 출력하는 설정이다
File.WriteAllText(path, csv, Encoding.UTF8);

// 의도를 명시하려면 생성자로 지정하는 편이 오해가 없다
File.WriteAllText(path, csv, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));

Encoding.UTF8 을 넘긴 때와 생략한 때 결과 파일이 달라진다는 것은, 모르면 눈치채기 어려운 사양입니다6. 「시스템 간 연동은 BOM 없음」「Excel용은 BOM 포함」을 사양으로 정했다면, 코드상에서도 new UTF8Encoding(...) 로 의도를 명시하는 것을 권합니다.

4.2 .NET(Core)에서 Shift_JIS를 쓰려면 등록이 필요하다

레거시 시스템과의 연동에서는 지금도 Shift_JIS(코드 페이지 932) CSV가 현역입니다. 여기서 중요한 것은, .NET(Core)/.NET 5 이후는 기본값으로 Unicode 계열과 ASCII 등 극히 일부 인코딩만 지원한다는 점입니다. Encoding.GetEncoding("shift_jis") 는 System.Text.Encoding.CodePages 패키지를 추가하고 CodePagesEncodingProvider 를 등록하기 전까지 예외가 됩니다1.

using System.Text;

// 앱 기동 시 한 번만 등록한다(Program.cs 등)
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

// 이로써 Shift_JIS(코드 페이지 932)를 취득할 수 있게 된다
Encoding sjis = Encoding.GetEncoding("shift_jis");

.NET Framework 시절의 CSV 처리를 .NET으로 마이그레이션한 앱이, 프로덕션에서 처음 Shift_JIS 파일을 받고 예외를 낸다──라는 전형적인 사고 지점입니다. 마이그레이션 시 점검 관점은 「.NET Framework→.NET 마이그레이션 전 체크리스트」에 정리해 두었습니다.

또한 Shift_JIS를 쓰는 이상, 기종 의존 문자(①이나 ㈱ 등)·물결 대시 문제·Unicode에만 있는 문자의 결락은 피할 수 없습니다. 이 부근의 문자 코드 기초는 「Windows의 문자 코드와 문자 깨짐의 구조」와 「Windows의 문자 코드와 개행 코드의 실무」에서 자세히 설명합니다.

5. Excel로 열면 깨지는 데이터 ── 앞자리 0·날짜화·지수 표기

문자 코드를 올바르게 해도, Excel 열람에는 또 하나의 문제군이 있습니다. Excel은 CSV를 열 때 값을 「해석」한다는 점입니다.

원래 데이터 Excel에서의 표시 무엇이 일어났는가
09012345678(전화번호) 9012345678 숫자로 해석되어 앞자리 0이 사라진다. 11자리가 10자리가 되어 전화번호로 쓸 수 없게 된다
007(상품 코드) 7 상동
1-1(가지 번호) 1月1日 날짜로 해석된다
1234567890123456(16자리 관리 번호) 1.23457E+15 지수 표기가 되어 하위 자리가 사라진다

그리고 중요한 것은, 이들은 필드를 큰따옴표로 감싸도 막을 수 없다는 점입니다. 따옴표는 CSV 구문상의 구분만 나타낼 뿐, Excel은 내용을 다시 해석합니다.

실무상의 대응은 다음 세 가지입니다.

  1. Excel 쪽의 올바른 절차로 열게 한다. 더블클릭이 아니라 Excel의 「데이터 가져오기(텍스트/CSV에서)」에서 열의 형식을 「문자열」로 지정해 읽으면 깨지지 않습니다. 운영 절차서에 적을 가치가 있습니다.
  2. 열람이 주목적이라면 xlsx로 출력한다. 셀 서식을 출력 측에서 제어할 수 있으므로, 이 문제군이 근본적으로 사라집니다. Excel 파일 출력 만드는 법은 「Excel 장표 출력 만드는 법」을 참조하십시오. 참고로 서버나 서비스에서의 Excel COM 자동화는 비권장이며, 「EXCEL.EXE가 남는 문제」에서 쓴 대로 프로세스 잔류의 온상이 되므로, 라이브러리 기반 출력을 고르십시오.
  3. ="007" 형식으로 출력하는 것은 최후의 수단이다. Excel로 열면 문자열로 표시되는 편법이지만, CSV로서는 「수식이 들어간 비표준 파일」이 되어 Excel 이외의 가져오기 대상을 깨뜨립니다. 다른 시스템과 겸용하는 파일에서는 쓰지 마십시오.

6. CSV 인젝션 ── 사용자 입력을 수식으로 만들지 않는다

사용자가 입력한 값(이름·비고·주소 등)을 CSV로 출력해 Excel로 열게 하는 앱에는, CSV 인젝션이라는 공격 경로가 있습니다. =·+·-·@ 등으로 시작하는 필드를 Excel이 수식으로 해석하기 때문에, 악의 있는 입력(예: =cmd|'/C calc'!A0 처럼 외부 프로세스 기동을 노리는 수식)이 CSV를 연 다른 사용자의 환경에서 실행될 수 있다는 문제입니다7.

대책의 기본은, 출력 시 위험한 선두 문자를 무해화하는 것입니다.

// 수식으로 해석될 수 있는 필드 선두에 작은따옴표를 붙인다.
// 일본어 환경에서는 전각 =+-@ 도 수식 시작으로 해석될 수 있으므로 대상에 포함한다
static string SanitizeForExcel(string value)
{
    if (value.Length > 0 && value[0]
        is '=' or '+' or '-' or '@' or '\t' or '\r' or '\n'
        or '=' or '+' or '-' or '@')
    {
        return "'" + value;
    }
    return value;
}

선두의 작은따옴표는 Excel에서 「문자열로 다룬다」는 표시가 됩니다. 참고로 위험한 선두 문자 목록은 스프레드시트 구현·버전에 따라 달라질 수 있으므로, 이런 무해화는 「선두가 이 허용 목록(영숫자 등)에 일치하는 경우만 그대로 통과」하는 방향으로 설계하면 예상 누락에 강해집니다. 다만 다른 시스템이 그 CSV를 가져오는 경우에는 작은따옴표가 데이터로 남는 점에 주의가 필요하며, 「Excel로 여는 용」과 「시스템 가져오기용」으로 파일을 나누는 판단도 있을 수 있습니다. 입력값 검증(애초에 선두 = 를 허용하지 않는 등)과 조합할지까지 포함해, 앱 전체의 보안 설계는 「Windows 앱 보안 최소 체크리스트」를 참조하십시오.

7. 운영상의 함정 ── 포맷 이외에서 사고나는 곳

마지막으로, CSV 자체의 구문 이외에서 실무 사고가 일어나는 지점을 정리합니다.

  • 숫자·날짜 포맷을 OS 컬처에 의존시키지 않는다. value.ToString() 은 OS 지역 설정의 영향을 받습니다. 소수점이 쉼표인 지역 설정에서는 1,5 로 출력되어 CSV 구분자와 충돌합니다. 파일 연동용 변환은 CultureInfo.InvariantCulture 를 명시하고, 날짜는 yyyy-MM-dd 같은 고정 서식으로 하십시오. 날짜 설계는 「업무 앱의 일시·타임존 설계」에서 자세히 다룹니다.
  • 대량 데이터는 스트리밍으로 처리한다 ── 다만 「한 줄씩」으로 충분한지는 사양에 달렸다. File.ReadAllLines는 모든 행을 배열로 메모리에 올리므로 큰 파일에는 맞지 않습니다8. 여기서 File.ReadLines(지연 열거)나 StreamReader의 행 단위 읽기로 바꿔도 되는 것은, 필드 안의 개행을 허용하지 않는다고 사양에 명시된 파일뿐입니다. 따옴표 안 개행을 허용하는 RFC 4180 준수 CSV에서는, 행 단위 분할이 1레코드를 중간에 절단합니다. 그 경우에는 파일 경로에서 직접 읽는 TextFieldParser(전체를 메모리에 올리지 않고 처리할 수 있음)나, CSV 라이브러리의 스트리밍 읽기를 사용하십시오.
  • 「쓰기 중인 파일」을 읽히지 않는다·읽지 않는다. 출력 측은 임시 파일 이름으로 쓴 뒤 이름을 바꾸고, 가져오기 측은 배타를 확인한 뒤 읽는다──이 파일 연동의 기본형은 「파일 연동의 배타 제어 기초 지식」에, 폴더 감시로 가져오는 경우의 주의는 「FileSystemWatcher 실무 가이드」에 정리해 두었습니다.
  • 열의 증감에 대비한다. 「헤더 행의 열 이름으로 대응한다」거나 「열 순서 고정·열 수 체크로 불일치면 거부한다」를 사양으로 명시합니다. 암묵의 fields[7] 같은 매직 넘버 참조는, 연동 대상의 사소한 열 추가로 조용히 깨집니다.
  • 끝 개행과 빈 행 취급을 정해 둔다. 마지막 행 뒤에 개행이 있는지, 빈 행을 건너뛸지 거부할지는 파서 구현에 따라 동작이 갈리는 부분입니다. TextFieldParser 는 빈 행을 건너뜁니다3.

정리

CSV는 「쉼표로 구분할 뿐」인 얼굴을 한, 방언투성이 연동 포맷입니다. 실무의 요점은 네 가지──읽기는 따옴표 처리가 되는 구성 요소를 쓴다(Split로 자체 구현하지 않는다), 문자 코드는 여는 상대(특히 Excel)에서 거꾸로 잡아 정하고 BOM 유무까지 사양에 쓴다, Excel의 값 해석(0 소실·날짜화)은 CSV 쪽에서는 막을 수 없음을 안다, 사용자 입력을 출력한다면 인젝션 대책을 넣는다──이것만으로 CSV 연동 사고의 대부분은 설계 단계에서 막을 수 있습니다.

기존 CSV 연동의 문자 깨짐이나 데이터 손상 조사, 레거시 시스템과의 연동 사양 정리, CSV 가져오기·출력 기능의 신규 구현은, 실제 파일과 연동 대상 사양을 보면서 판단해야 하는 경우가 많으므로, 막히면 상담해 주십시오.

관련 기사

관련 상담 영역

合同会社小村ソフト에서는 CSV·파일 연동을 포함한 업무 앱 개발, 기존 연동의 문자 깨짐·데이터 손상 원인 조사, 레거시 시스템과의 연동 사양 정리에 관한 기술 상담을 다루고 있습니다.

참고 링크

  1. Microsoft Learn, CodePagesEncodingProvider Class. .NET Core가 기본으로 지원하는 것은 ASCII·ISO-8859-1·Unicode 계열뿐이며, Shift_JIS(코드 페이지 932) 등 코드 페이지 인코딩에는 System.Text.Encoding.CodePages 의 CodePagesEncodingProvider 를 Encoding.RegisterProvider 로 등록해야 한다는 점에 대해. ↩ ↩2

  2. IETF, RFC 4180 - Common Format and MIME Type for Comma-Separated Values (CSV) Files. CSV의 사실상 표준 사양. 필드 안의 쉼표·개행·큰따옴표의 따옴표 처리, ""로 이스케이프하는 점에 대해. ↩

  3. Microsoft Learn, TextFieldParser Class. 구분 형식·고정 길이 형식의 텍스트 파싱, HasFieldsEnclosedInQuotes를 이용한 따옴표 처리, 해석할 수 없는 행에서의 MalformedLineException과 ErrorLine/ErrorLineNumber, ReadFields가 빈 행을 건너뛴다는 점에 대해. ↩ ↩2 ↩3

  4. CsvHelper 공식 문서, CsvHelper. .NET용 CSV 읽기/쓰기 라이브러리라는 점, RFC 4180 준수를 내세운다는 점, Microsoft Public License(MS-PL)와 Apache License 2.0 듀얼 라이선스로 상용 이용이 가능하다는 점에 대해. ↩

  5. Microsoft 지원, Opening CSV UTF-8 files correctly in Excel. BOM을 붙여 저장된 UTF-8 CSV는 보통대로 열 수 있다는 점, BOM이 없는 경우에는 「데이터」 탭의 「데이터 가져오기」>「파일에서」>「텍스트 또는 CSV에서」(Power Query), 또는 가져오기 마법사로 열어야 한다는 점에 대해. ↩

  6. Microsoft Learn, UTF8Encoding Class. encoderShouldEmitUTF8Identifier 매개변수로 BOM(U+FEFF) 출력을 제어하는 점과, Encoding.UTF8 속성이 반환하는 인스턴스는 BOM을 출력하는 설정이라는 점에 대해. ↩

  7. OWASP, CSV Injection. =·+·-·@·탭·복귀 문자로 시작하는 필드가 스프레드시트에서 수식으로 해석되는 공격(Formula Injection)과 그 대책에 대해. ↩

  8. Microsoft Learn, File.ReadLines Method. ReadAllLines 가 전 행의 배열을 반환하는 데 비해, ReadLines 는 열거 가능한 컬렉션을 반환하므로 큰 파일에서도 전체를 기다리지 않고 열거를 시작할 수 있다는 점에 대해. ↩

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

CSV 문자 코드는 무엇으로 해야 합니까?
받는 쪽이 정해져 있다면 받는 쪽 사양에 맞추는 것이 대원칙입니다. 새로 정할 수 있는 경우, 사람이 Excel로 여는 파일은 BOM 포함 UTF-8, 시스템 간 연동은 BOM 없는 UTF-8을 사양서에 명시하는 것이 무난한 출발점입니다. 레거시 시스템 연동에서 Shift_JIS를 요구받은 경우에는, 기종 의존 문자나 외자 취급을 먼저 합의해 두지 않으면, 나중에 문자 깨짐의 책임 문제가 됩니다.
CSV 라이브러리는 도입해야 합니까? 아니면 자체 구현으로 충분합니까?
읽기에 관해서는, 자체 구현 String.Split로는 사양상 올바르게 처리할 수 없는 경우(필드 안의 쉼표·개행·따옴표)가 있으므로, 어떤 형태로든 전용 구성 요소를 써야 합니다. .NET 표준 범위라면 TextFieldParser가 RFC 4180에 상당하는 따옴표 처리를 지원합니다. 열과 객체의 매핑, 형식 변환, 대량 데이터의 성능까지 필요하다면, 실적이 있는 CSV 라이브러리 도입을 검토하십시오. 쓰기는 사양이 단순한 만큼 자체 구현도 현실적이지만, 따옴표 이스케이프의 공통 함수를 하나 마련해 전부 그곳을 통과하도록 하십시오.
쉼표 구분이 아니라 탭 구분(TSV)으로 해야 합니까?
데이터에 쉼표가 자주 나오는 경우(주소·성명·금액 표기 등), TSV로 하면 따옴표 처리 빈도가 줄고, 눈으로 확인하기도 쉬워집니다. 다만 탭이나 개행이 데이터에 들어갈 수 있는 점은 같고, 따옴표 처리가 불필요해지는 것은 아닙니다. 「TSV이므로 구분 문자를 고려할 필요가 없다」가 되지 않는다는 점에 주의하십시오. 연동 대상과 합의할 수 있다면, 구분 문자보다 「문자 코드·따옴표·개행 코드·헤더 유무를 사양서에 명시하는 것」이 사고 방지에 더 효과적입니다.
Excel로 여는 것이 목적이라면, CSV가 아니라 Excel 파일을 출력해야 합니까?
「사람이 Excel로 보고·인쇄하는 것」이 주목적이라면, xlsx 형식 출력을 검토할 가치가 있습니다. CSV는 Excel로 여는 순간에 앞자리 0 소실이나 날짜화가 일어나는 형식이며, 이를 CSV 쪽에서 완전히 막는 방법은 없습니다. xlsx라면 셀 서식을 출력 측에서 제어할 수 있습니다. 한편, 다른 시스템으로의 가져오기가 주목적이고 Excel 열람은 부수라면, CSV 그대로 「가져오기 절차(데이터 가져오기 기능을 사용)」를 운영 절차로 안내하는 편이 현실적입니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기