Преобразование файла CSV с помощью sed

4968
middus

Чтобы иметь возможность импортировать некоторые данные в определенный инструмент, мне нужно преобразовать файл CSV из этого формата.

"data","data","data data","data","123" 

в этот формат

data;data;data data;data;123 

Столбцы никогда не содержат какой - либо ", ;или, ,но могут быть пробелы. В настоящее время я использую следующее

sed -e 's/","/;/g' -e 's/"//g' input.csv > output.csv 

Хотя это прекрасно работает, мне интересно, если это можно сделать более элегантно, то есть

  • Является ли sed правильным (стандартным Unix) инструментом для работы?
  • Можно ли объединить оба выражения в одно?

Спасибо за ваш вклад!

7

3 ответа на вопрос

6
ayrnieu
( tr, ';' | tr -d '"' ) < input.csv > output.csv 

Я бы использовал Perl

perl -pe 'tr/,"/;/d' input.csv > output.csv 

- но эта конкретная задача не выходит за рамки SED. Вы не можете объединить два выражения.

Спасибо за ваш ответ, ИМХО два приятных решения. Не могли бы вы объяснить. в том, который использует tr? Это не то же самое, что [: punct:], верно? Человек тр не помогает мне. Кажется, что это вопрос вкуса, ответ на который является лучшим. Если авторы других ответов не возражают, я выберу это как принятый ответ, потому что он выглядит очень элегантно для меня, и сообщество оценило его как самое высокое до сих пор. middus 14 лет назад 0
я не против Я неравнодушен к версии Perl сам. Перл Тр quack quixote 14 лет назад 0
Извините - это должно быть, ayrnieu 14 лет назад 0
5
quack quixote

Что вы предпочитаете (perl, sed, awk), зависит от вас; они все сделают работу. Так как вы попросили sed, а остальные опубликованы, вот и вы. Это более простая форма вашего регулярного выражения и работает с вашей строкой примера:

$ sed -e 's/"//g; s/,/;/g' infile.csv > outfile.csv 

Обратите внимание, что вы можете объединить два выражения с точкой с запятой после каждой замены. Протестировано с GNU sed v4.1.5.

Вот ваши оригинальные выражения присоединились:

$ sed -e 's/","/;/g; s/"//g' infile.csv > outfile.csv 

Я вполне уверен, что возможно объединить две замены. Не уверен, что это было бы не случайно, и я уверен, что результат будет гораздо менее читабельным, чем сценарий вверху. Если я что-то придумаю (или кто-то еще взвешивает в комментариях), я добавлю это сюда.

«Вы ** можете ** присоединиться к двум разделам» - вы, вы не можете. Вы взяли два выражения и заменили их двумя выражениями. ayrnieu 14 лет назад 0
его оригинал был '-e "foo" -e "bar" ", я присоединил их к" -e "foo; bar" ". -e - это выражение, на которое я ссылаюсь, и предположил, что он имел в виду. Вы можете быть правы - я неправильно истолковал то, что он просит, - но вы также неправильно понимаете мое утверждение. quack quixote 14 лет назад 0
выяснены. я надеюсь. :) quack quixote 14 лет назад 0
Это круто, я не знал, что вы можете просто присоединиться к таким выражениям. Спасибо за Ваш ответ! middus 14 лет назад 0
4
wfaulk

Поскольку вы имеете дело с записями, awkимеет больше смысла. Тем не менее, это не очень хорошо в CSV, так как разделители полей несколько изменчивы. Но если вы уверены, что все поля заключены в двойные кавычки, это будет работать:

awk -F'","' 'BEGIN { gsub(/(^")|("$)/, ""); $1=$1; print }' 

Это устанавливает в качестве разделителя поля ввода awk значение " ","" (включая внутренний набор двойных кавычек). Это почти работает, за исключением того, что вам приходится иметь дело с ведущими и конечными двойными кавычками, которые удаляются с помощью gsubфункции. Эти $1=$1силы это перекомпилировать запись с новым полем выходного сепаратора, который был определен как ;в блоке BEGIN, . Затем printраспечатывает всю запись.

Это немного аккуратнее:

awk -F '(^")|(",")|("$)' 'BEGIN { $1=$1; print }' 

Он устанавливает в качестве разделителя поля ввода регулярное выражение, которое включает в себя двойные кавычки в начале и конце записи, но также заставляет его печатать пустое поле начала и конца поля. Вы можете легко избавиться от конечного поля:

awk -F '(^")|(",")|("$)' 'BEGIN { NF=NF-1; $1=$1; print }' 

NFчисло полей, и уменьшение его на единицу отрывает от последнего поля. Но я не могу придумать, как отрубить первое поле.

Если вы знаете, что у ввода всегда есть пять полей, вы можете сделать это:

awk -F '(^")|(",")|("$)' 'BEGIN { print $2,$3,$4,$5,$6 }' 

Обратите внимание, что это избавляет от $1=$1конструкции, которая нам нужна, только если мы печатаем (подразумевается) $ 0.

Все это говорит о том, что я, вероятно, в конечном итоге использовал Perl и один из многих доступных модулей CSV на CPAN .

Хорошо, это выглядит немного сложнее, чем другие решения, и не слишком читабельно. Если бы я столкнулся с этим через год, мне, вероятно, пришлось бы задуматься над тем, что он делает. Тем не менее, приятно видеть, что несколько разных инструментов (awk, sed ...) подходят для этой задачи. Спасибо за ваш подробный ответ. Я возьму это как точку входа, чтобы посмотреть в awk для других проблем. middus 14 лет назад 0
это выглядит хуже, чем есть. как только вы начинаете изучать немного awk, становится легче читать. :) quack quixote 14 лет назад 0
Это сложнее, потому что это умнее, пытаясь работать с записями в виде записей, а не со строками, которые выглядят как CSV, как строки. Это страдает от гораздо меньшей «стены сложности» - точки, в которой небольшое добавление к описанию проблемы приводит к огромным изменениям в решении (например, выбрасывание всего решения и создание другого с нуля). ayrnieu 14 лет назад 0

Похожие вопросы