ИТ Блог. Администрирование серверов на основе Linux (Ubuntu, Debian, CentOS, openSUSE)

Как разобрать CSV-файл в Bash?

Как разобрать CSV-файл в Bash?

Анализ файла значений, разделенных запятыми, то есть CSV-файла, из командной оболочки bash может быть сложной задачей и приводить к ошибкам в зависимости от сложности CSV-файла. Однако это частая задача во многих сценариях оболочки автоматизации или для быстрой обработки и переформатирования данных из файла, загруженного в bash.

В этом посте рассказывается о том, как разобрать CSV-файл с помощью встроенных команд Bash или с помощью команды awk для разбора более сложного формата. Решения, представленные ниже, могут быть легко адаптированы к другим форматам файлов, например, к файлу значений, разделенных табуляцией, т.е. к TSV-файлу.

Для примеров в этой статье мы используем CSV-файл из datahub.io со списком стран и их двухзначным кодом (ISO 3166-1). CSV-файл содержит два поля Name и Code с 249 записями + 1 строка заголовка, что делает его файлом в 250 строк.

$ head -n5 countries.csv 
Name,Code
Afghanistan,AF
Åland Islands,AX
Albania,AL
Algeria,DZ
...

 

Какой формат файла CSV?

CSV-файл – это файл,  C содержащий S отдельные V значения. Несмотря на то, что этот формат используется десятилетиями и в широком спектре программного обеспечения, на самом деле он не является стандартным. Для CSV-файлов нет официальной спецификации, что приводит к сложности их разбора.

Некоторые программы могут допускать различные сложные варианты использования, такие как поддержка многострочных полей или некоторых пользовательских разделителей. Наиболее близкими к спецификации являются IETF RFC 4180 и IETF RFC 7111, которые определяют mime-тип IANA для CSV как text/csv.

Вы можете найти хороший обзор предварительного общего определения формата CSV в разделе 2 RFC 4180. Краткое описание высокого уровня может быть следующим.

 

Представление такого файла с учетом вышеуказанных критериев может выглядеть следующим образом. Обозначение CRLF указывает на разрыв строки в CSV-файле. Строка записи row3 представляет поля с экранированными двойными кавычками, пробелом и разрывом строки.

header_column_1,header_column_2,header_column_3 CRLF
row1_field1,row1_field2,row1_field3 CRLF
row2_field1,row2_field2,row2_field3 CRLF
"row3""field1","row3 field2","row3 CRLF
field3" CRLF
row4_field1,row4_field2,row4_field3

 

При попытке проанализировать CSV-файл в bash важно понимать источник происхождения данных и следует ли ожидать поддержки сложных форматов. В некоторых случаях у вас может не быть другого выбора, кроме как использовать внешнюю библиотеку для поддержки некоторых сложных форматов.

 

Как разобрать CSV-файл в Bash?

Используя встроенные функции Bash

Для перебора наших выборочных данных самый простой способ – прочитать файл и использовать внутренний разделитель полей (IFS). Для чтения каждой строки CSV-файла вы можете использовать встроенную команду read, которая считывает строку из стандартного ввода и разбивает ее на поля, присваивая каждое слово переменной. Опция -r запрещает использование обратной косой черты \ для экранирования любых символов. Без опции -r неэкранированная обратная косая черта была бы удалена вместо того, чтобы быть представленной в виде символа.

Обратите внимание, что read потребуется имя переменной для каждого поля, которое вы хотите записать, и последнее указанное значение будет просто общим для всех остальных полей.

👉 Этот метод рекомендуется только для простых CSV-файлов, в которых нет текстовых полей, содержащих лишние разделители-запятые , или возвращаемые строки.

 

Ниже приведен простой пример команды IFS с разделителем полей запятой (,) формата CSV и команды read с двумя ожидаемыми полями name и code, которые были бы доступны внутри цикла while в качестве переменных $name и $code.

while IFS=, read -r name code; do
  # сделай что-нибудь... Не забудьте пропустить строку заголовка!
  [[ "$name" != "Name" ]] && echo "$name"
done < countries.csv

 

⚠️ Однако в этой методологии есть загвоздка. Она не поддерживает полную спецификацию CSV и не будет работать так, как вы ожидаете, с данным набором данных. Если вы внимательно посмотрите на выходные данные, некоторые из них возвращают неполные значения, поскольку некоторые поля в CSV-файле являются текстовыми полями, которые содержат разделитель через запятую , и заключены в двойные кавычки “.

...
United States
United States Minor Outlying Islands
Uruguay
Uzbekistan
Vanuatu
"Venezuela
Viet Nam
"Virgin Islands
"Virgin Islands
Wallis and Futuna
Western Sahara
...

 

Вы можете подсчитать, сколько у нас неверных записей, с помощью другого цикла while, простого регулярного выражения и счетчика, используя арифметическое расширение.

count=0
while IFS=, read -r name code; do
  # сделай что-нибудь...
  [[ "$code" == *","* ]] && echo "$name $code" && ((++count))
done < countries.csv; \
echo ">> мы нашли ${count} плохих записей"

"Bolivia  Plurinational State of",BO
"Bonaire  Sint Eustatius and Saba",BQ
"Congo  the Democratic Republic of the",CD
"Iran  Islamic Republic of",IR
"Korea  Democratic People's Republic of",KP
"Korea  Republic of",KR
"Macedonia  the Former Yugoslav Republic of",MK
"Micronesia  Federated States of",FM
"Moldova  Republic of",MD
"Palestine  State of",PS
"Saint Helena  Ascension and Tristan da Cunha",SH
"Taiwan  Province of China",TW
"Tanzania  United Republic of",TZ
"Venezuela  Bolivarian Republic of",VE
"Virgin Islands  British",VG
"Virgin Islands  U.S.",VI
>> мы нашли 16 плохих записей

 

Более 6% записей вернут неполные данные. Итак, если вы не уверены, что у вас нет таких текстовых полей, мы бы не рекомендовали использовать этот первый метод.

В этом примере использовалась конструкция оператора If в Bash.

 

С помощью команды awk

Awk – это специализированный язык, предназначенный для обработки текста. Он доступен в большинстве Unix-подобных систем, к сожалению, между реализациями и версиями может быть много различий. В нашем примере мы будем использовать мощный GNU awk, который, вероятно, является наиболее полной реализацией awk.

👉 Этот метод рекомендуется для сложных CSV-файлов, в которых нет текстовых полей, содержащих разделители новой строки, такие как символы \n или \r.

 

Используя тот же набор данных countries.csv, что и в нашем первом примере, теперь мы собираемся проанализировать наш CSV с помощью реализации, использующей шаблоны полей (FPAT). Мы будем внимательно следить за тем, чтобы поля разделялись запятыми (,), игнорируя те, которые находятся в полях, заключенных в кавычки “. FPAT = “([^,]+)|(\”[^\”]+\”)” Определение можно разбить следующим образом:

или

 

Ниже приведен пример реализации с использованием awk для разбора CSV-файла с помощью FPAT.

gawk '
BEGIN {
    FPAT = "([^,]+)|(\"[^\"]+\")"
    count=0
}
{
  if ($1 != "Name") { # Не забудьте пропустить строку заголовка!
    printf("%s\n", $1)
    ++count
  }
}
END {
  printf("Количество записей: %s\n", count)
}
' countries.csv

 

Теперь мы правильно заполняем названия всех стран.

⚠️ Этот подход по-прежнему не поддерживает полную спецификацию CSV. Если ваши текстовые поля содержат возвращаемые строки или другие странности, то этот синтаксический анализ завершится неудачей. В большинстве случаев это может быть нормально, если формат содержимого известен заранее. Если некоторые поля содержат ручные пользовательские записи, вы рискуете допустить ошибки.

 

Расширенная поддержка CSV

Не существует простого способа поддерживать полные реализации CSV с помощью только встроенных функций bash или awk, учитывая многочисленные спецификации и реализации CSV. Несмотря на широкое распространение этого формата, сложно должным образом поддерживать многие граничные случаи. Для поддержки полной реализации CSV из вашего сценария оболочки вам нужно будет использовать более продвинутое решение.

Первой альтернативой разбору сложного CSV-файла из сценария оболочки является использование csvkit. Например, вы можете обработать данные с помощью csvkit, чтобы преобразовать их в формат JSON, а затем выполнить более сложную работу с помощью такого инструмента, как jq легкого и гибкого JSON-процессора командной строки. csvkit предоставляет множество утилит командной строки для импорта, экспорта, анализа, сортировки, объединения, очистки и форматирования CSV-файлов. Официальное руководство довольно полное.

Другой вариант – использовать стандартный csv-модуль в python. Это довольно просто реализовать. Вы можете использовать класс reader или DictReader из csv модуля python. Возможно, если вы не хотите реализовывать все на python, вы можете просто предварительно обработать свои CSV-файлы и очистить поля, чтобы убедиться, что они отформатированы так, как вы ожидаете. Затем вы все равно можете обработать чистый вывод CSV с помощью bash или awk, как в наших предыдущих примерах.

#!/usr/local/bin/python
# csv-reader.py: Пример синтаксического анализа CSV в python
import csv 

with open('countries.csv', 'r') as csvfile:
    reader = csv.DictReader(csvfile)
    for row in reader:
        print('Код страны', row['Code'], 'is for', row['Name'])

 

Ниже приведен пример вывода csv-reader.py скрипта на примере CSV-файла с названием страны и кодом.

[me@linux: ~]$ python3 csv-reader.py | head -n 5
Код страны AF is for Afghanistan
Код страны AX is for Åland Islands
Код страны AL is for Albania
Код страны DZ is for Algeria
Код страны AS is for American Samoa
Exit mobile version