LPI Linux Essentials study materials
LPI Linux Essentials study resources

CSV revenue totals broken by BOM, CRLF and quoted commas

LPI Linux Essentials · Put your knowledge to work in context.

All case studies

CSV revenue totals broken by BOM, CRLF and quoted commas

finance exports a 380 MB CSV named sales.csv with header region,product,units,revenue and about 1.1 million rows. Product names can contain commas inside double quotes, for example a product called Widget, large, and embedded double quotes are escaped by doubling, for example a product called 12-inch screen written with doubled quotes. The file was produced on Windows: CRLF line endings, and it begins with a UTF-8 BOM. A colleague's pipeline:

cut -d, -f1 sales.csv | sort | uniq -c | sort -rn

shows a phantom region entry with an invisible leading character, which is the header being counted as if it were a region, and a per-region revenue sum built with awk -F, gives impossible totals for rows whose product contains a comma. The file has one header row. The colleague wants a one-line fix before the monthly close in two hours, and cannot install new packages on the reporting host (only coreutils, gawk 5.2, sed and python3 are present).

1. Diagnose the three distinct data-format problems and explain how each corrupts the output. Give a quick diagnostic command for each.

Read the worked answer

Three separate problems, each with a different symptom.

  • BOM. The file starts with the three bytes EF BB BF, which represent U+FEFF. cut treats them as part of the first field of the first line, so the header's first field becomes a name with an invisible leading character and is counted as a region. Diagnostic: head -c 3 sales.csv | od -An -tx1 or file sales.csv.
  • CRLF. Windows line endings leave a carriage return at the end of the last field. awk -F, sees the revenue value with a trailing control character, so numeric conversion and sums fail or the last column is treated as a string. Diagnostic: head -1 sales.csv | cat -A shows ^M$ at the end of the line.
  • Commas inside quoted fields. cut -d, and awk -F, are not CSV parsers. In a field such as a quoted Widget, large, the comma inside the quotes is a field separator to them, so every later column shifts. Diagnostic: compare the number of fields per row with the header using awk -F, '{print NF}' sales.csv | sort | uniq -c; rows with quoted commas have one more field than the header.

The invisible leading character and the impossible revenue totals are therefore two different failures: encoding and line-ending handling, and quoting.

2. Provide a correct per-region revenue calculation under the two-hour and no-install constraints, and explain why it is correct where cut and simple awk -F, are not.

Read the worked answer

Use Python's standard csv module, which is already present and understands quoting correctly:

python3 - <<'PY'
import csv, collections
from decimal import Decimal
totals = collections.defaultdict(Decimal)
with open('sales.csv', newline='', encoding='utf-8-sig') as f:
    reader = csv.DictReader(f)
    for row in reader:
        region = row['region'].strip()
        totals[region] += Decimal(row['revenue'].strip())
for region in sorted(totals):
    print(region, totals[region])
PY

Why it is correct:

  • utf-8-sig consumes the BOM, so the first header name is clean.
  • newline='' lets the CSV module handle CRLF instead of leaving carriage returns in the last field.
  • csv.DictReader maps values by header name, so a comma inside a quoted product does not shift the revenue field.
  • The CSV module implements the quoting rules, including doubled double quotes, as a state machine rather than a fragile regular expression.
  • Decimal avoids floating-point rounding in a financial report.

If you must stay in awk, gawk 5.2 has a CSV mode: gawk --csv 'NR>1 {rev[$1]+=$4} END {for (r in rev) print r, rev[r]}' sales.csv. Validate either output against wc -l before publishing.

3. The colleague asks whether sed can remove all commas inside quotes before cut. Evaluate this idea.

Read the worked answer

Not reliably. A regex such as sed -E ':a; s/("[^",]*),([^"]*")/\1\2/g; ta' can handle a single simple quoted comma, but it breaks on doubled quotes, on multiple commas inside one field, on quoted fields containing both quotes and commas, and on any multiline field. CSV quoting is a small state machine: inside quotes, a doubled quote is a literal quote, and a comma is data. sed has no robust persistent in-quote state across the line for this grammar.

The dangerous part is the failure mode: it will work for most rows and silently corrupt a subset, producing plausible but wrong totals. That is worse than a visible error during a monthly close. Use the Python csv module, or gawk --csv if you must stay in awk, and validate the row count and a sample of high-value rows against the source. Do not spend the two-hour window debugging a sed expression.