Видаліть кома між цитатами лише у файлі з комою


23

У мене вхідний файл, розміщений комами ( ,). Є кілька полів, укладених у подвійні лапки, у яких є кома. Ось зразок рядка

123,"ABC, DEV 23",345,534.202,NAME

Мені потрібно видалити всі кома, що виникає всередині подвійних лапок, а також подвійних лапок. Отже, вищенаведений рядок повинен бути розібраний, як показано нижче

123,ABC DEV 23,345,534.202,NAME

Я спробував наступне, використовуючи, sedале не даючи очікуваних результатів.

sed -e 's/\(".*\),\(".*\)/\1 \2/g'

Будь-які швидкі фокуси з sed, awkабо будь-який інший утиліти Unix , будь ласка?


Я не впевнений, що ви намагаєтеся зробити, але утиліта "csvtool" набагато краща для розбору csv, ніж загальні інструменти, такі як sed або awk. Це майже у кожному дистрибутиві Linux.
figtrap

Відповіді:


32

Якщо лапки врівноважені, ви хочете видалити коми між усіма іншими цитатами, це може бути виражено awkтак:

awk -F'"' -v OFS='' '{ for (i=2; i<=NF; i+=2) gsub(",", "", $i) } 1' infile

Вихід:

123,ABC DEV 23,345,534.202,NAME

Пояснення

В -F"марці AWK розділити рядок в подвійних лапках знаків, що означає кожне інше поле буде між лапками текстом. Запуск циклу for-циклу gsub, короткий для глобальної підстановки, у кожному іншому полі, замінюючи кома ( ",") нічим ( ""). 1В кінці викликає кодовий блок за замовчуванням: { print $0 }.


1
Будь ласка, можете розробити gsubта пояснити коротко, як працює цей один лайнер ?? будь ласка.
mtk

Дякую! Цей сценарій працює дуже добре, але ви могли б пояснити самотній 1 в кінці сценарію? -} 1 '-
Какао

@CocoaEv: виконується { print $0 }. Я також додав це до пояснення.
Тор

2
у цього підходу є проблема: іноді в csv є рядки, що охоплюють кілька рядків, таких як: prefix,"something,otherthing[newline]something , else[newline]3rdline,and,things",suffix (тобто: кілька рядків і вкладених "," в будь-якому місці багаторядкового подвійного цитування: вся "...."частина повинна бути знову з'єднана, а всередині ,повинна бути замінено / вилучено ...): ваш сценарій не побачить пар подвійних лапок у такому випадку, і це не дуже легко вирішити (потрібно "знову приєднатися" до рядків, які знаходяться у "відкритому" (тобто, непарному номері) подвійні лапки ... + виявляти особливу обережність , якщо є і втекли \" всередині рядка)
Олів'є Дюлак

1
Сподобалось це рішення, але я підмінив його, оскільки я часто люблю зберігати коми, але все ще хочу розмежувати. Натомість я переключив коми поза цитатами на pipe, перетворивши csv у файл psv:awk -F'"' -v OFS='"' '{ for (I=1; i<=NF; i+=2) gsub(",", "|", $i) } 1' infile
Danton Noriega

7

Є хороша відповідь, використовуючи sed просто один раз з циклом :

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME'|
  sed ':a;s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 /;ta'
123,"ABC  DEV 23",345,534,"some more  comma-separated  words",202,NAME

Пояснення:

  • :a; є ярликом для більш тонкої гілки
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 / може містити 3 вкладені частини
    • спочатку 2-й: [^"]*,\?\|"[^",]*",\?відповідність рядку, що не містить подвійної лапки, може слідувати кома або рядок, укладений двома подвійними цитатами, без коми, а може бути, слідом за комою.
    • ніж перша частина RE складається з такої кількості повторень раніше описаної частини 2, за якою слідує 1 подвійна цитата та кілька каратек, але жодної подвійної цитати, а також коми.
    • Перша частина RE повинна слідувати комі.
    • Нота, решту рядка не потрібно чіпати
  • taбуде циклічно, :aякщо попередня s/команда внесла деякі зміни.

Працює також з вкладеними цитатами. Дивовижне, дякую!
трикассе

5

Загальне рішення, яке також може вирішити декілька косів між збалансованими лапками, потребує вкладеної заміни. Я реалізую рішення в perl, яке обробляє кожен рядок заданого вводу і лише замінює коми у всіх інших парах лапок:

perl -pe 's/ "  (.+?  [^\\])  "               # find all non escaped 
                                              # quoting pairs
                                              # in a non-greedy way

           / ($ret = $1) =~ (s#,##g);         # remove all commas within quotes
             $ret                             # substitute the substitution :)
           /gex'

або коротше

perl -pe 's/"(.+?[^\\])"/($ret = $1) =~ (s#,##g); $ret/ge'

Ви можете передавати текст команді, яку ви хочете обробити, або вказати текстовий файл, який обробляється як аргумент останнього командного рядка.


1
[^\\]Матиме небажаний ефект зіставлення останнього символу всередині лапок і видаляючи його (НЕ \ символ), тобто, ви не повинні споживати цей символ. Спробуйте (?<!\\)замість цього.
цейробинсон

Дякую за ваше заперечення, я це виправив. Тим не менш, я думаю, нам тут не потрібно дивитися на твердження, чи ми !?
користувач1146332

1
Включення non \ у вашу групу захоплення дає еквівалентний результат. +1
цейробінсон

1
+1. спробувавши кілька речей з sed, я перевірив документи sed і підтвердив, що він не може застосувати заміну лише до відповідної частини рядка ... тому відмовився і спробував perl. Закінчений з дуже схожим підходом , але дана версія використовується , [^"]*щоб зробити матч не жадібним (тобто відповідає всім від одного "до наступного " ): perl -pe 's/"([^"]+)"/($match = $1) =~ (s:,::g);$match;/ge;'. Це не визнає чудової ідеї про те, що цитата може бути уникнута з зворотним нахилом :-)
cas

Дякуємо за ваш коментар Було б цікаво, якщо або [^"]*підхід, або явний не жадібний підхід вимагає менше процесорного часу.
користувач1146332

3

Я б використовував мову з належним аналізатором CSV. Наприклад:

ruby -r csv -ne '
  CSV.parse($_) do |row|
    newrow = CSV::Row.new [], []
    row.each {|field| newrow << field.delete(",")}
    puts newrow.to_csv
  end
' < input_file

в той час як спочатку мені сподобалось це рішення, для великих файлів виявилося неймовірно повільним ...
KIC

3

Ваші другі цитати не вказано:

sed -e 's/\(".*\),\(.*"\)/\1 \2/g'

Крім того, використання регулярних виразів, як правило, відповідає найдовшій частині тексту, тобто це не працюватиме, якщо у вас є більше одного цитованого поля в рядку.

Спосіб, що обробляє кілька цитованих полів у sed

sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

Це також спосіб вирішити це, однак, при введенні, яке може містити більше однієї коми на котируване поле, перший вираз у sed повинен повторюватися стільки разів, скільки максимальний вміст коми в одному полі, або поки воно не буде зовсім не змінює вихід.

Запуск sed з більш ніж одним виразом повинен бути більш ефективним, ніж кілька запущених процесів sed і "tr", що працюють з відкритими трубами.

Однак це може мати небажані наслідки, якщо введення неправильно відформатовано. тобто вкладені котирування, невстановлені цитати.

Використовуючи запущений приклад:

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME' \
| sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' \
-e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

Вихід:

123,ABC  DEV 23,345,534,some more  comma-separated  words,202,NAME

Ви можете зробити його більш загальним із умовним розгалуженням і легше читати з ERE, наприклад, з GNU sed : sed -r ':r; s/("[^",]+),([^",]*)/\1 \2/g; tr; s/"//g'.
Тор

2

У perl - ви можете скористатися Text::CSVцим розбором і зробити це тривіально:

#!/usr/bin/env perl
use strict;
use warnings;

use Text::CSV; 

my $csv = Text::CSV -> new();

while ( my $row = $csv -> getline ( \*STDIN ) ) {
    #remove commas in each field in the row
    $_ =~ s/,//g for @$row;
    #print it - use print and join, rather than csv output because quotes. 
    print join ( ",", @$row ),"\n";
}

Ви можете друкувати за допомогою, Text::CSVале це, як правило, зберігає цитати, якщо це зробити. (Хоча, я б запропонував - замість зачистки лапки для виведення, ви можете просто розібрати , використовуючи Text::CSVв першу чергу).


0

Я створив функцію для циклу проходження кожного символу в рядку.
Якщо символ є цитатами, то чек (b_in_qt) позначається істинним.
У той час як b_in_qt відповідає дійсності, всі коми замінюються пробілом.
b_in_qt встановлюється значення false, коли буде знайдена наступна кома.

FUNCTION f_replace_c (str_in  VARCHAR2) RETURN VARCHAR2 IS
str_out     varchar2(1000)  := null;
str_chr     varchar2(1)     := null;
b_in_qt     boolean         := false;

BEGIN
    FOR x IN 1..length(str_in) LOOP
      str_chr := substr(str_in,x,1);
      IF str_chr = '"' THEN
        if b_in_qt then
            b_in_qt := false;
        else
            b_in_qt := true;
        end if;
      END IF;
      IF b_in_qt THEN
        if str_chr = ',' then
            str_chr := ' ';
        end if;
      END IF;
    str_out := str_out || str_chr;
    END LOOP;
RETURN str_out;
END;

str_in := f_replace_c ("blue","cat,dog,horse","",yellow,"green")

RESULTS
  "blue","cat dog horse","",yellow,"green"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.