Удалить ложные запятые
Клиент-идиот генерирует файлы csv, но в одном поле иногда добавляются дополнительные запятые (поле описания).
Есть ли опрятное регулярное выражение, чтобы найти эти плохие записи и заменить лишние запятые на что-то еще. Командная строка SED будет в порядке.
Пример:
A,B,C,This is a description,D,E
F,G,H,This is a description with a comma (,) in it,D,E
Мне нужен SED, который может сказать, что в строке слишком много запятых и удалить дополнительную запятую из поля 4.
У нас нет роскоши говорить глупому клиенту об изменении своего кода.
Добавлен
Я бы не стал возражать против решения, которое просто удаляет ложную запятую one, которую я должен запускать несколько раз.
Ответы
Ответ 1
Решение 1: однострочное, удаление ,
Здесь вы можете использовать однострочный переключатель SED:
sed -r 's/([^,],[^,],[^,],)(.*)(,.+,.+)/\1'"$(sed -r 's/([^,],[^,],[^,],)(.*)(,.+,.+)/\2/' <<< $myInput | sed 's/,//g')"'\3/' <<< $myInput
Вам нужно заменить <<< $myInput
на любой ваш текущий вход.
Когда вы работаете с CSV, вам может потребоваться настроить (оба входа) регулярное выражение для соответствия каждой строке вашего листа CSV.
Если ваши первые три и последние два поля больше одного char заменить [^,]
на [^,]*
.
Объяснение:
Мы используем это регулярное выражение
/([^,],[^,],[^,],)(.*)(,.+,.+)/
который захватывает первую (F,G,H,
), вторую (.*
) и последнюю часть (,D,E
) строки для нас.
Первая и третья группы захвата будут неизменными, а вторая будет заменена.
Для подстановки будем называть sed
вторым (и фактически третьим) временем. Сначала мы фиксируем только вторую группу, второй заменяем каждый ,
ничем (только в группе захвата!).
Доказательство:
![enter image description here]()
Конечно, если нет нежелательной запятой, ничего не заменяется:
![enter image description here]()
Решение 2: весь файл, строка за строкой, удаление ,
Если вы хотите указать только файл, и замена должна произойти для каждой строки файла, которую вы можете использовать
while read line; do sed -r 's/([^,],[^,],[^,],)(.*)(,.+,.+)/\1'"$(sed -r 's/([^,],[^,],[^,],)(.*)(,.+,.+)/\2/' <<< $line | sed 's/,//g')"'\3/' <<< $line; done < input.txt
где input.txt
в конце - очевидно - ваш файл.
Я просто использую команду SED сверху в while
-loop, который читает каждую строку текста. Это необходимо, потому что вам нужно отслеживать строку, которую вы читаете, так как вы вызываете sed
два раза на одном и том же входе.
![enter image description here]()
Решение 3: весь файл, заключить поле в "
Как @Łukasz L. указал в комментариях к OP, согласно RFC1480, в котором описывается формат CSV файлов, было бы лучше заключить поля, содержащие запятую в "
.
Это проще, чем другие решения:
sed -r 's/([^,],[^,],[^,],)(.*)(,.*,.*)/\1"\2"\3/' input.txt
Снова у нас есть три группы захвата. Это позволяет просто обернуть вторую группу в "
!
![enter image description here]()
Ответ 2
Если количество столбцов фиксировано, мы можем попытаться вырезать первые три и последние два столбца с помощью lookaheads ?:
и сопоставить запятые внутри остальной части строки (это описание). У меня есть что-то вроде этого:
(?:^(?:[^,]*,){3})(?:(?:[^,]*(,))*[^,]*)(?:(?:,[^,]*){2}$)
[^,]*
- это поле (без запятой), поэтому (?:^(?:[^,]*,){3})
сократит первые 3 столбца (включая следующую запятую). (?:(?:,[^,]*){2}$)
удалит последние 2 столбца, включая запятую. (?:(?:[^,]*(,))*[^,]*)
соответствует внутреннему.
В JavaScript все выражение возвращает полное описание (с запятыми) в качестве первого совпадения, а в нем запятые в качестве второго. Это дает возможность, в зависимости от двигателя Regex, либо espace, либо заменять описание (если движок дает диапазоны согласованного выражения) или нацеливать на выражение (,)
, соответствующее запятой с синтаксисом замены.
У меня нет возможности запускать и тестировать с помощью sed, но это регулярное выражение должно быть очень близко к нужному вам решению.
Ответ 3
Я попытался решить эту проблему с помощью sed, но не смог выполнить замену внутри группы соответствия. Вместо этого мне удалось сделать это с помощью рубинового однострочного терминала, который можно запустить с терминала:
cat your_file | ruby -ne '$_.scan(/^(\w+,\w+,\w+,)([^$]+)(,\w,\w)$/).each{|m|puts m[0]+m[1].gsub(",","")+m[2]}'
Это предполагает, что всегда есть 6 столбцов, а четвертый - это та, которая может содержать запятые.
Код был протестирован с ruby 1.8.7, 1.9.1 и 2.1.0.
Ответ 4
Мой подход заключается в том, чтобы вычислить, какие запятые для удаления основываться на общем количестве столбцов и том столбце, который вы должны изменить. Он принимает три аргумента, входной файл, общее количество столбцов и странный столбец с дополнительными запятыми.
Далее perl script делает некоторые расчеты. Когда существуют дополнительные запятые, будут дополнительные столбцы, поэтому они получат место, где расположены дополнительные столбцы и присоединяются к ним.
#!/usr/bin/env perl
use warnings;
use strict;
use Text::CSV_XS;
my (@columns);
open my $fh, '<', shift or die;
my ($total_columns, $weird_column) = (shift, shift);
my $csv = Text::CSV_XS->new or die;
while ( my $row = $csv->getline( $fh ) ) {
undef @columns;
if ( @$row == $total_columns ) {
@columns = @$row;
next;
}
my $extra_columns = @$row - $total_columns;
my $post_columns_index = $weird_column + $extra_columns;
@columns = (
@$row[0..($weird_column-2)],
join( '', @$row[($weird_column-1)..($post_columns_index-1)]),
@$row[$post_columns_index..$#$row]
);
}
continue {
$csv->print( \*STDOUT, \@columns );
printf "\n";
}
Предполагая входной файл как:
A,B,C,This is a description,D,E
F,G,H,This is a description with a comma (,) in it,D,E
F,G,H,This is, a description with two commas (,) in it,D,E
F,G,H,This is, a description with, three commas (,) in it,D,E
Запустите его так:
perl script.pl infile 6 4
Это дает:
A,B,C,"This is a description",D,E
F,G,H,"This is a description with a comma () in it",D,E
F,G,H,"This is a description with two commas () in it",D,E
F,G,H,"This is a description with three commas () in it",D,E
Возможно, это может завершиться ошибкой с крайними случаями (первое и последнее поле). Я не тестировал его подробно, но надеюсь, что вы поняли это. Я попытался сделать это как можно более общим.