Вопрос или проблема
Я работаю с CSV-файлами и иногда мне нужно быстро проверить содержимое строки или столбца из командной строки. В большинстве случаев команды cut
, head
, tail
и подобные выполнят свою задачу; однако cut
не может легко справляться с такими ситуациями, как
"это, первая запись", это вторая, 34.5
Здесь первая запятая – часть первого поля, но cut -d, -f1
с этим не согласен. Прежде чем я начну писать решение сам, мне было интересно, знает ли кто-нибудь хороший инструмент, который уже существует для этой работы. Он должен, по крайней мере, уметь обрабатывать приведённый выше пример и возвращать столбец из CSV-файла. Другие желаемые функции включают возможность выбирать столбцы на основе имен столбцов, указанных в первой строке, поддержку других стилей кавычек и поддержку файлов, разделённых табуляцией.
Если вы не знаете такого инструмента, но у вас есть предложения по реализации такой программы на Bash, Perl, Python или других распространённых языках сценариев, я бы не против таких предложений.
Я, вероятно, немного запоздал, но есть ещё один инструмент, который стоит упомянуть: csvkit.
У него много инструментов командной строки, которые могут:
- реформатировать CSV-файлы,
- конвертировать в и из CSV из различных форматов (JSON, SQL, XLS),
- являться эквивалентом
cut
,grep
,sort
и других, но с учётом CSV, - соединять разные CSV-файлы,
- делать общие SQL запросы на данных из CSV-файлов.
Вы можете использовать модуль csv
в Python.
Простой пример:
import csv
reader = csv.reader(open("test.csv", "r"))
for row in reader:
for col in row:
print(col)
Miller – это ещё один отличный инструмент для манипуляции данными, основанными на именах, включая CSV (с заголовками). Чтобы извлечь первый столбец из CSV-файла, не заботясь о его имени, можно использовать что-то вроде
printf '"первый,столбец",второй,третий\n1,2,3\n' |
mlr --csv -N cut -f 1
Это звучит как работа для Perl с Text::CSV
.
perl -MText::CSV -pe '
BEGIN {$csv = Text::CSV->new();}
$csv->parse($_) or die;
@fields = $csv->fields();
print @fields[1,3];
'
Смотрите документацию для обработки имен столбцов. Разделитель и стиль кавычек можно настроить с помощью параметров для new
. Также смотрите Text::CSV::Separator
для угадывания разделителей.
Я бы рекомендовал xsv, «быстрый инструмент командной строки для работы с CSV, написанный на Rust».
Написан автором Ripgrep.
Известен в Как мы сделали обработку CSV в 142 раза быстрее (поток Reddit).
Я нашёл csvfix, инструмент командной строки, который хорошо справляется с задачей. Однако вам придётся создать его самостоятельно:
http://neilb.bitbucket.org/csvfix
Он выполняет все ожидаемые функции: упорядочивает/выбирает столбцы, разбивает/объединяет и многие другие, например, генерирует SQL вставки из данных CSV и делает разницу между данными CSV.
Если вы хотите использовать командную строку (и не хотите создавать целую программу для выполнения задачи), вам стоит обратить внимание на rows, проект, над которым я работаю: это интерфейс командной строки для табличных данных, а также библиотека Python для использования в ваших программах. С помощью интерфейса командной строки вы можете красиво выводить любые данные в CSV, XLS, XLSX, HTML или в любой другой табличный формат, поддерживаемый библиотекой, с помощью простой команды:
rows print myfile.csv
Если myfile.csv
выглядит так:
state,city,inhabitants,area
RJ,Angra dos Reis,169511,825.09
RJ,Aperibé,10213,94.64
RJ,Araruama,112008,638.02
RJ,Areal,11423,110.92
RJ,Armação dos Búzios,27560,70.28
Тогда rows выведет содержимое в красивом виде, как это:
+-------+-------------------------------+-------------+---------+
| state | city | inhabitants | area |
+-------+-------------------------------+-------------+---------+
| RJ | Angra dos Reis | 169511 | 825.09 |
| RJ | Aperibé | 10213 | 94.64 |
| RJ | Araruama | 112008 | 638.02 |
| RJ | Areal | 11423 | 110.92 |
| RJ | Armação dos Búzios | 27560 | 70.28 |
+-------+-------------------------------+-------------+---------+
Установка
Если вы разработчик на Python и уже установили pip
на своей машине, просто выполните внутри виртуального окружения или с sudo
:
pip install rows
Если вы используете Debian:
sudo apt-get install rows
Другие интересные функции
Конвертация форматов
Вы можете конвертировать между любыми поддерживаемыми форматами:
rows convert myfile.xlsx myfile.csv
Запросы
Да, вы можете использовать SQL в CSV-файле:
$ rows query 'SELECT city, area FROM table1 WHERE inhabitants > 100000' myfile.csv
+----------------+--------+
| city | area |
+----------------+--------+
| Angra dos Reis | 825.09 |
| Araruama | 638.02 |
+----------------+--------+
Конвертация результата запроса в файл вместо stdout также возможна с помощью параметра --output
.
Как библиотека Python
Вы также можете использовать rows в своих Python-программах:
import rows
table = rows.import_from_csv('myfile.csv')
rows.export_to_txt(table, 'myfile.txt')
# `myfile.txt` будет содержать такой же контент, как вывод `rows print`
Надеюсь, вам это понравится!
Если вам нужен визуальный / интерактивный инструмент в терминале, я искренне рекомендую VisiData.
У него есть таблицы частот (показанные выше), сводные таблицы, “плавление”, графики рассеяния, фильтрация / вычисление с использованием Python и многое другое.
Вы можете передавать CSV-файлы следующим образом
vd hello.csv
Есть специфические параметры для CSV: --csv-dialect
, --csv-delimiter
, --csv-quotechar
и --csv-skipinitialspace
для тонкой настройки обработки CSV-файлов.
Я использовал csvtool однажды, и он сэкономил мне много времени и усилий. Вызывается из оболочки.
R – не мой любимый язык программирования, но он хорош для таких вещей.
Если ваш CSV-файл выглядит так
***********
foo.csv
***********
col1, col2, col3
"это, первая запись", это вторая, 34.5
"немного больше", "путаница", вещи
Внутри интерпретатора R введите
> x=read.csv("foo.csv", header=FALSE)
> x
col1 col2 col3
1 это, первая запись это вторая 34.5
2 "немного больше" путаница вещи
> x[1] # первый столбец
col1
1 это, первая запись
2 "немного больше"
> x[1,] # первая строка
col1 col2 col3
1 это, первая запись это вторая 34.5
Что касается ваших других запросов, для “возможности выбирать столбцы на основе имен столбцов, указанных в первой строке” смотрите
> x["col1"]
col1
1 это, первая запись
2 "немного больше"
Что касается “поддержки других стилей кавычек”, смотрите аргумент quote
для read.csv (и связанных функций).
Что касается “поддержки файлов, разделённых табуляцией”, смотрите аргумент sep
для read.csv (установите sep
на ‘\t’).
Для получения дополнительной информации смотрите онлайн справку.
> help(read.csv)
Одним из лучших инструментов является Miller. Это как awk, sed, cut, join и sort для данных с именными индексами, таких как CSV, TSV и табличный JSON.
Например
echo '"это, первая запись", это вторая, 34.5' | \
mlr --icsv --implicit-csv-header cat
даёт вам
1=это, первая запись,2= это вторая,3= 34.5
Если вы хотите TSV
echo '"это, первая запись", это вторая, 34.5' | \
mlr --c2t --implicit-csv-header cat
даёт вам (возможно убрать заголовок)
1 2 3
это, первая запись это вторая 34.5
Если вы хотите первый и третий столбцы, меняя их порядок
echo '"это, первая запись", это вторая, 34.5' | \
mlr --csv --implicit-csv-header --headerless-csv-output cut -o -f 3,1
даёт вам
34.5,"это, первая запись"
cissy также позволяет обрабатывать CSV через командную строку. Он написан на C (маленький/лёгкий) с доступными rpm и deb пакетами для большинства дистрибутивов.
Используя пример:
echo '"это, первая запись", это вторая, 34.5' | cissy -c 1
"это, первая запись"
или
echo '"это, первая запись", это вторая, 34.5' | cissy -c 2
это вторая
или
echo '"это, первая запись", это вторая, 34.5' | cissy -c 2-
это вторая, 34.5
Также ознакомьтесь с GNU Recutils и crush-tools.
Чтобы использовать Python из командной строки, вы можете ознакомиться с pythonpy (https://github.com/Russell91/pythonpy):
$ echo $'a,b,c\nd,e,f' | py '[x[1] for x in csv.reader(sys.stdin)]'
b
e
В репозитории GitHub Structured Text Tools есть полезный список соответствующих инструментов командной строки для Linux. В частности, раздел Delimiter Separated Values перечисляет несколько инструментов, способных работать с CSV, которые непосредственно поддерживают запрашиваемые операции.
Используя Raku (ранее известный как Perl_6)
Эти ответы используют язык программирования Raku, в частности в сочетании с модулем Text::CSV
. Ответ ниже состоит из двух частей: первая часть читает файл построчно, вторая часть читает файл целиком. Любой из методов можно использовать для обработки текста, создавая файлы, соответствующие RFC-4180 (и, возможно, RFC-4180/RFC-7111).
Построчно:
Чтение файла построчно, как “однострочник”, принимающий ввод командной строки:
~$ raku -MText::CSV -ne 'my $csv = Text::CSV.new; $csv.parse($_); put $csv.strings.raku;' MS.csv
Пример ввода:
https://www.microsoft.com/en-us/download/details.aspx?id=45485
Пример вывода:
["Имя пользователя", "Имя", "Фамилия", "Отображаемое имя", "Должность", "Отдел", "Номер офиса", "Телефон офиса", "Мобильный телефон", "Факс", "Адрес", "Город", "Штат или провинция", "Почтовый индекс", "Страна или регион"]
["chris@contoso.com", "Chris", "Green", "Chris Green", "IT Manager", "Information Technology", "123451", "123-555-1211", "123-555-6641", "123-555-9821", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
["ben@contoso.com", "Ben", "Andrews", "Ben Andrews", "IT Manager", "Information Technology", "123452", "123-555-1212", "123-555-6642", "123-555-9822", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
["david@contoso.com", "David", "Longmuir", "David Longmuir", "IT Manager", "Information Technology", "123453", "123-555-1213", "123-555-6643", "123-555-9823", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
["cynthia@contoso.com", "Cynthia", "Carey", "Cynthia Carey", "IT Manager", "Information Technology", "123454", "123-555-1214", "123-555-6644", "123-555-9824", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
["melissa@contoso.com", "Melissa", "MacBeth", "Melissa MacBeth", "IT Manager", "Information Technology", "123455", "123-555-1215", "123-555-6645", "123-555-9825", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
Выше используется флаг построчного ввода -ne
для принятия ввода построчно и вывода разобранных строк. Выше, уберите вызов .raku
, чтобы вывести разобранный текст без двойных кавычек/экранирований. Либо код ниже принимает ввод построчно, используя $csv.getline()
, который хранит данные внутренне как массив Raku, который затем выводится:
Построчно: Код Raku в виде скрипта (полезный для обработки данных):
use Text::CSV;
my @rows;
my $csv = Text::CSV.new;
my $fh = open "MS.csv", :r, :!chomp;
while ($csv.getline($fh)) -> $row {
@rows.push: $row;
}
$fh.close;
$_.raku.put for @rows;
Построчно: Код Raku как “однострочник”, принимающий STDIN
:
~$ raku -MText::CSV -e 'my @rows; my $csv = Text::CSV.new; \
while ($csv.getline($*IN)) -> $row { @rows.push: $row; }; \
$_.raku.put for @rows;' < MS.csv
Пример вывода (последние два приведённых выше примера показывают внутреннее представление данных Raku):
$["Имя пользователя", "Имя", "Фамилия", "Отображаемое имя", "Должность", "Отдел", "Номер офиса", "Телефон офиса", "Мобильный телефон", "Факс", "Адрес", "Город", "Штат или провинция", "Почтовый индекс", "Страна или регион"]
$["chris@contoso.com", "Chris", "Green", "Chris Green", "IT Manager", "Information Technology", "123451", "123-555-1211", "123-555-6641", "123-555-9821", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
$["ben@contoso.com", "Ben", "Andrews", "Ben Andrews", "IT Manager", "Information Technology", "123452", "123-555-1212", "123-555-6642", "123-555-9822", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
$["david@contoso.com", "David", "Longmuir", "David Longmuir", "IT Manager", "Information Technology", "123453", "123-555-1213", "123-555-6643", "123-555-9823", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
$["cynthia@contoso.com", "Cynthia", "Carey", "Cynthia Carey", "IT Manager", "Information Technology", "123454", "123-555-1214", "123-555-6644", "123-555-9824", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
$["melissa@contoso.com", "Melissa", "MacBeth", "Melissa MacBeth", "IT Manager", "Information Technology", "123455", "123-555-1215", "123-555-6645", "123-555-9825", "1 Microsoft way", "Redmond", "Wa", "98052", "Соединённые Штаты"]
Если вам нужны только первые две строки (например, заголовок и первая строка данных), измените:
while ($csv.getline($*IN))
...на... for ($csv.getline($*IN) xx 2)
Вывод только одной строки (например, заголовка) вернёт список, разделённый \n
(полезно для проверки правильности разбора):
"Имя пользователя"
"Имя"
"Фамилия"
"Отображаемое имя"
"Должность"
"Отдел"
"Номер офиса"
"Телефон офиса"
"Мобильный телефон"
"Факс"
"Адрес"
"Город"
"Штат или провинция"
"Почтовый индекс"
"Страна или регион"
Конечно, вы также можете выводить отдельные столбцы. Просто измените окончательное выражение put
на:
.[2].raku.put for @rows;
...чтобы вывести 3-й столбец ("Фамилия", нумерация начинается с 0):
"Фамилия"
"Green"
"Andrews"
"Longmuir"
"Carey"
"MacBeth"
Чтение файла целиком: Модуль Raku Text::CSV
предоставляет высокоуровневую функцию csv(…)
для изменения/валидации текста CSV/TSV и возвращения файлов, соответствующих RFC-4180 (и, возможно, RFC-4180/RFC-7111).
Конвертация CSV-файла в TSV-файл (читает stdin
, выводит столбцы с внутренними пробелами как заключённые в двойные кавычки по умолчанию):
$ cat MS.csv | raku -MText::CSV -e 'my @a = csv(in => $*IN); csv(in => @a, sep => "\t", out => $*OUT);' > MS.tsv
Пример вывода (сохранён и повторно открыт в Vim с модулем CSV):
"Имя пользователя" "Имя" "Фамилия" "Отображаемое имя" "Должность" Отдел "Номер офиса" "Телефон офиса" "Мобильный телефон" Факс Адрес Город "Штат или провинция" "Почтовый индекс" "Страна или регион"
[email protected] Chris Green "Chris Green" "IT Manager" "Information Technology" 123451 123-555-1211 123-555-6641 123-555-9821 "1 Microsoft way" Redmond Wa 98052 "Соединённые Штаты"
[email protected] Ben Andrews "Ben Andrews" "IT Manager" "Information Technology" 123452 123-555-1212 123-555-6642 123-555-9822 "1 Microsoft way" Redmond Wa 98052 "Соединённые Штаты"
[email protected] David Longmuir "David Longmuir" "IT Manager" "Information Technology" 123453 123-555-1213 123-555-6643 123-555-9823 "1 Microsoft way" Redmond Wa 98052 "Соединённые Штаты"
[email protected] Cynthia Carey "Cynthia Carey" "IT Manager" "Information Technology" 123454 123-555-1214 123-555-6644 123-555-9824 "1 Microsoft way" Redmond Wa 98052 "Соединённые Штаты"
[email protected] Melissa MacBeth "Melissa MacBeth" "IT Manager" "Information Technology" 123455 123-555-1215 123-555-6645 123-555-9825 "1 Microsoft way" Redmond Wa 98052 "Соединённые Штаты"
Модуль Raku Text::CSV
может обрабатывать альтернативные разделители столбцов (например, \t
), двойные кавычки для значений столбцов с встроенными запятыми, переводами строк и т. д. Самое главное, в процессе обработки ввода текста у вас будет доступ к мощному интерфейсу для работы с регулярными выражениями Raku, если вам нужно фильтровать/заменять символы (например, имена столбцов), строки или столбцы. Каждый приведённый выше пример кода Raku правильно разбирает короткий текстовый пример и выводит следующее (или подобное; укажите Text::CSV.new(:allow_whitespace)
, чтобы обрезать пробелы до/после каждого поля):
["это, первая запись", "это вторая", "34.5"]
Смотрите приведенные ниже URL для хранения данных в хэше (или массиве хэшей), и/или для задания column-names
, sep-char
, escape-char
, formula-handling
, binary
, strict
параметров и др.
https://raku.land/github:Tux/Text::CSV
https://github.com/Tux/CSV
https://raku.org
csvq - это инструмент командной строки, который использует SQL для запроса CSV файлов:
$ echo '"это, первая запись", это вторая, 34.5' |
csvq --no-header "SELECT * FROM STDIN"
+--------------------------+----------------------+--------+
| c1 | c2 | c3 |
+--------------------------+----------------------+--------+
| это, первая запись | это вторая | 34.5 |
+--------------------------+----------------------+--------+
Обратите внимание, что поскольку CSV-файл не содержит заголовка, csvq задаёт свои собственные внутренние пронумерованные имена столбцов.
Чтобы выбрать первый столбец, укажите имя столбца c1
:
$ echo '"это, первая запись", это вторая, 34.5' |
csvq --no-header "SELECT c1 FROM STDIN"
+--------------------------+
| c1 |
+--------------------------+
| это, первая запись |
+--------------------------+
Решение на awk
awk -vq='"' '
func csv2del(n) {
for(i=n; i<=c; i++)
{if(i%2 == 1) gsub(/,/, OFS, a[i])
else a[i] = (q a[i] q)
out = (out) ? out a[i] : a[i]}
return out}
{c=split($0, a, q); out=X;
if(a[1]) $0=csv2del(1)
else $0=csv2del(2)}1' OFS='|' file
Добавляем ещё один инструмент в списке...
Он работает как printf(1)
.
Например, с таким вводом:
Имя,Фамилия,Идентификационный номер
Джордж,Вашингтон,1
Бетси,Росс,2
И с такой командной строкой:
$ cat input.csv | csvprintf -i '%{IdNumber}04d: %{LastName}s, %{FirstName}s\n'
Вы получите такой вывод:
0001: Вашингтон, Джордж
0002: Росс, Бетси
Отказ от ответственности: я написал это