Преобразование CSV в TSV

27

У меня есть несколько больших файлов CSV, и я хотел бы, чтобы они были в формате TSV (в формате табуляции). Сложность состоит в том, что в полях файла CSV есть запятые, например:

 A,,C,"D,E,F","G",I,"K,L,M",Z

Ожидаемый результат:

 A      C   D,E,F   G   I   K,L,M   Z

(где пробелы между ними - «жесткие» вкладки)

На этом сервере установлены Perl, Python и coreutils.

Темное сердце
источник
Я бы сделал это с node.js или с Perl.
Петер говорит восстановить Монику
1
Замените запятые без кавычек на вкладки ...
cricket_007
Да, если бы у меня было больше 5 минут на этот вопрос. Но я с радостью поддержу ответчиков своими голосами. То, что я пытался сказать, что обычные вещи sed / awk, вероятно, не подходят для этого (по крайней мере, в их часто используемом использовании).
Петер говорит восстановить Монику
6
Я не уверен, что ваш пример является представителем реальных данных, но если это будут настоящие текстовые строки, не забывайте, что вам может потребоваться обработать случай, когда строка содержит вкладку ...
AC
3
Другая сложность заключается в том, что CSV - это очень слабо определенный формат, реального стандарта нет (есть RFC, но он был написан спустя годы после факта). Я написал код, который использовал предоставленный языком синтаксический анализатор CSV, а затем пришлось переписать его с помощью специального анализатора, потому что я обнаружил, что входные данные были в неправильном варианте формата CSV.
plugwash

Ответы:

37

питон

Добавить в файл с именем csv2tab.shи сделать его исполняемым

#!/usr/bin/env python
import csv, sys
csv.writer(sys.stdout, dialect='excel-tab').writerows(csv.reader(sys.stdin))

Тестовые прогоны

$ echo 'A,,C,"D,E,F","G",I,"K,L,M",Z' | ./csv2tab.sh                         
A       C   D,E,F   G   I   K,L,M   Z

$ ./csv2tab.sh < data.csv > data.tsv && head data.tsv                                                   
1A      C   D,E,F   G   I   K,L,M   Z
2A      C   D,E,F   G   I   K,L,M   Z
3A      C   D,E,F   G   I   K,L,M   Z
cricket_007
источник
5
Возможная ошибка: этот ответ не покидает внутренние вкладки.
Морген
4
@ Морген csv.writer(sys.stdout, dialect='excel-tab').writerows(csv.reader(sys.stdin))? Устраняет цикл также.
Муру
1
@chx попробуй python -c 'import csv,sys; csv.writer(sys.stdout, dialect="excel-tab").writerows(csv.reader(sys.stdin))'. Я сомневаюсь, -mчто так работает.
Муру
18

Ради прикола sed.

sed -E 's/("([^"]*)")?,/\2\t/g' file

Если ваш sedне поддерживает -E, попробуйте с -r. Если вы sedне поддерживаете \tбуквенную вкладку, попробуйте поместить буквенную вкладку (во многих оболочках, ctrl- v tab) или в Bash, используйте $'...'строку в стиле C (в этом случае обратная косая черта \2должна быть удвоена). Если вы хотите сохранить цитаты, используйте \1вместо\2 (в этом случае внутренняя пара скобок бесполезна и может быть удалена).

Это не делает попытку обработать экранированные двойные кавычки внутри двойных кавычек; некоторые диалекты CSV поддерживают это, удваивая цитируемую двойную кавычку (sic).

tripleee
источник
1
Я думаю, что я попробовал около 100 различных сценариев sed для достижения этой цели, но все мои попытки потерпели неудачу. Это круто.
Георгий
16

Используя csvkitутилиту (Python), например:

$ csvformat -T in.csv > out.txt

Потоковая передача, с правильным цитированием и экранированием CSV и TSV

Это в apt и других пакетных менеджерах

Нил Макгиган
источник
13

Одним из вариантов может быть модуль Perl Text :: CSV, например

perl -MText::CSV -lne 'BEGIN { $csv = Text::CSV->new() }
  print join "\t", $csv->fields() if $csv->parse($_)
' somefile

показывать

echo 'A,,C,"D,E,F","G",I,"K,L,M",Z' |
  perl -MText::CSV -lne 'BEGIN { $csv = Text::CSV->new() }
  print join "\t", $csv->fields() if $csv->parse($_)
'
A       C   D,E,F   G   I   K,L,M   Z
steeldriver
источник
1
Было бы неправильно, если бы поле содержало вкладку
Нил Макгиган
6

Perl

perl -lne '
   my $re = qr/,(?=(?:[^"]*"[^"]*")*(?![^"]*"))/;
   print join "\t", map { s/(?<!\\)"//gr =~ s/\\"/"/gr } split $re;
'

Awk

awk -v Q=\" -v FPAT="([^,]*)|(\"[^\"]+\")" -v OFS="\t" '{
   for (i=1; i<=NF; ++i)
      if ( substr($i, 1, 1) == Q )
         $i = substr($i, 2, length($i) - 2)
   print $1, $2, $3, $4, $5, $6, $7, $8
}'

Результат:

A               C       D,E,F   G       I       K,L,M   Z

источник
+1 Perl версия работает как шарм
ATorras
4

Решение термоядерной мухобойки должно использовать libreoffice. Хотя https://ask.libreoffice.org/en/question/19042/is-is-possible-to-convert-comma-separated-value-csv-to-tab-separated-value-tsv-via-headless-mode / предполагает, что это невозможно, но это неправильно (или просто устарело?), и моя команда работает на 5.3:

loffice "-env:UserInstallation=file:///tmp/LibO_Conversion" --convert-to csv:"Text - txt - csv (StarCalc)":9,34,UTF8 --headless --outdir some/path --infilter='csv:44,34,UTF8' *.csv

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

СНХ
источник
2
Я думаю, что настоящая термоядерная мухобойка написала бы утилиту Java, чтобы сделать это через UNO API LibreOffice :).
Пон
3

Если у вас есть или можете установить csvtoolутилиту:

csvtool -t COMMA -u TAB cat in.csv > out.ctv

Обратите внимание, что по какой-то причине csvtoolнет справочной страницы, но csvtool --helpнапечатает пару сотен строк документации.

Кит Томпсон
источник
3

Использование mlrпочти лаконично, но отключение заголовков требует длинных опций:

mlr --c2t --implicit-csv-header --headerless-csv-output cat file.csv 

Выход:

A       C   D,E,F   G   I   K,L,M   Z
АРУ
источник
3

Я создал конвертер CSV в TSV с открытым исходным кодом, который обрабатывает описанные преобразования. Это довольно быстро, может быть стоит посмотреть, если есть необходимость конвертировать большие файлы CSV. Инструмент является частью набора утилит eBay TSV (документация по csv2tsv здесь ). Для описанного ввода достаточно параметров по умолчанию:

$ csv2tsv file.csv > file.tsv
JonDeg
источник
2

напор

Просто для удовольствия, в Vim можно выполнять замены регулярных выражений . Вот потенциальное четырехстрочное решение, адаптированное с: /programming/33332871/remove-all-commas-between-quotes-with-a-vim-regex

  1. Запятые между кавычками сначала заменяются подчеркиванием (или другим отсутствующим символом),
  2. Все остальные запятые заменяются на вкладки,
  3. Подчеркивания внутри кавычек восстанавливаются до запятых,
  4. Кавычки удалены.

    :%s/".\{-}"/\=substitute(submatch(0), ',', '_' , 'g')/g
    :%s/,/\t/g
    :%s/_/,/g
    :%s/"//g

Чтобы написать сценарий решения, четыре строки выше (без двоеточия) могут быть сохранены в файл, например to_tsv.vim. Откройте каждый CSV для редактирования с Vim и sourceв to_tsv.vimскрипте на Vim командной строки (адаптировано из /programming/3374179/run-vim-script-from-vim-commandline/8806874#8806874 ):

    :source /path/to/vim/filename/to_tsv.vim
jubilatious1
источник
1

Вот пример преобразования CSV в TSV с помощью jqутилиты :

$ jq -rn '@tsv "\(["A","","C","D,E,F","G","I","K,L,M","Z"])"'
A       C   D,E,F   G   I   K,L,M   Z

или:

$ echo '["A","","C","D,E,F","G","I","K,L,M","Z"]' | jq -r @tsv
A       C   D,E,F   G   I   K,L,M   Z

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

Источник: Простой выходной формат TSV .

kenorb
источник
1

При perlусловии, что в полях csv нет встроенных "или новых строк или вкладок:

perl -pe 's{"(.*?)"|,}{$1//"\t"}ge'
Стефан Шазелас
источник
0

Следующее является просто исправлением ответа от @tripleee, так что он удаляет все кавычки из конечного поля так же, как и все остальные поля.

Чтобы показать, что исправляется, ниже приведен ответ тройного пользователя , а также небольшая модификация примерных данных ОП с добавлением кавычек вокруг последнего поля « Z ».

echo 'A,,C,"D,E,F","G",I,"K,L,M","Z"' |  sed -r -e 's/("([^"]*)")?,/\2\t/g'
A       C   D,E,F   G   I   K,L,M   "Z"

Вы можете видеть, что « Z » остается с кавычками вокруг него. Это отличается от того, как обрабатываются внутренние поля. Например, у ' G ' нет кавычек.

Следующая команда использует вторую замену для очистки последнего столбца:

echo 'A,,C,"D,E,F","G",I,"K,L,M","Z"' |  sed -r -e 's/("([^"]*)")?,/\2\t/g' \
                                                -e 's/\t"([^"]*)"$/\t\1/'
A       C   D,E,F   G   I   K,L,M   Z
Fonnae
источник
1
Когда входные данные 'A,,C,"D,E,F","G",I,"K,L,M","Z,A"'вводятся в этот ответ, то "Z,A"неверно заменяется Z Aвместо правильного Z,A.
АРУ