2014-12-01 Обновление: нижеприведенный ответ работает только для одного особого формата CSV. Как правильно указал DG в комментариях, это решение НЕ соответствует определению CSV RFC 4180, а также не соответствует формату MS Excel. Это решение просто демонстрирует, как можно разобрать одну (нестандартную) линию ввода CSV, содержащую сочетание типов строк, где строки могут содержать экранированные кавычки и запятые.
Как правильно указывает austincheney, вам действительно нужно разобрать строку от начала до конца, если вы хотите правильно обрабатывать строки с кавычками, которые могут содержать экранированные символы. Кроме того, ОП четко не определяет, что такое «строка CSV». Сначала мы должны определить, что представляет собой действительную строку CSV и ее отдельные значения.
Для целей этого обсуждения строка «CSV» состоит из ноль или более значений, где несколько значений разделяются запятой. Каждое значение может состоять из:
Правила / Примечания:
'that\'s cool'
. \'
в одинарных кавычках. \"
в двойных кавычках. Функция JavaScript, которая преобразует допустимая строка CSV (как определено выше) в массив строковых значений.
Регулярные выражения, используемые этим решением, являются сложными. И (IMHO) все нетривиальные регулярные выражения должны быть представлены в режиме свободного пробела с большим количеством комментариев и отступов. К сожалению, JavaScript не разрешает режим свободного пробела. Таким образом, регулярные выражения, реализованные этим решением, сначала представлены в собственном синтаксисе regex (выражаемом с помощью удобного Python: r'''...'''
синтаксиса raw-multi-line-string).
Сначала это регулярное выражение, которое подтверждает, что строка CVS удовлетворяет вышеуказанным требованиям:
re_valid = r"""
# Validate a CSV string having single, double or un-quoted values.
^ # Anchor to start of string.
\s* # Allow whitespace before value.
(?: # Group for value alternatives.
'[^'\\]*(?:\\[\S\s][^'\\]*)*' # Either Single quoted string,
| "[^"\\]*(?:\\[\S\s][^"\\]*)*" # or Double quoted string,
| [^,'"\s\\]*(?:\s+[^,'"\s\\]+)* # or Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Allow whitespace after value.
(?: # Zero or more additional values
, # Values separated by a comma.
\s* # Allow whitespace before value.
(?: # Group for value alternatives.
'[^'\\]*(?:\\[\S\s][^'\\]*)*' # Either Single quoted string,
| "[^"\\]*(?:\\[\S\s][^"\\]*)*" # or Double quoted string,
| [^,'"\s\\]*(?:\s+[^,'"\s\\]+)* # or Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Allow whitespace after value.
)* # Zero or more additional values
$ # Anchor to end of string.
"""
Если строка соответствует указанному выше выражению, то эта строка является допустимой строкой CSV (в соответствии с ранее описанными правилами) и может быть проанализирована с использованием следующего регулярного выражения. Следующее regex затем используется для сопоставления одного значения из строки CSV.
re_value = r"""
# Match one value in valid CSV string.
(?!\s*$) # Don't match empty last value.
\s* # Strip whitespace before value.
(?: # Group for value alternatives.
'([^'\\]*(?:\\[\S\s][^'\\]*)*)' # Either $1: Single quoted string,
| "([^"\\]*(?:\\[\S\s][^"\\]*)*)" # or $2: Double quoted string,
| ([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*) # or $3: Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Strip whitespace after value.
(?:,|$) # Field ends on comma or EOS.
"""
Обратите внимание, что существует одно специальное значение, которое не соответствует этому регулярному выражению - последнее значение, когда это значение пусто. Этот специальный случай «пустое последнее значение» проверяется и обрабатывается следующей js-функцией.
// Return array of string values, or NULL if CSV string not well formed.
function CSVtoArray(text) {
var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/;
var re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;
// Return NULL if input string is not well formed CSV string.
if (!re_valid.test(text)) return null;
var a = []; // Initialize array to receive values.
text.replace(re_value, // "Walk" the string using replace with callback.
function(m0, m1, m2, m3) {
// Remove backslash from \' in single quoted values.
if (m1 !== undefined) a.push(m1.replace(/\\'/g, "'"));
// Remove backslash from \" in double quoted values.
else if (m2 !== undefined) a.push(m2.replace(/\\"/g, '"'));
else if (m3 !== undefined) a.push(m3);
return ''; // Return empty string.
});
// Handle special case of empty last value.
if (/,\s*$/.test(text)) a.push('');
return a;
};
В следующих примерах фигурные скобки используются для разграничения {result strings}
. (Это поможет визуализировать ведущие / конечные пробелы и строки нулевой длины.)
// Test 1: Test string from original question.
var test = "'string, duppi, du', 23, lala";
var a = CSVtoArray(test);
/* Array hes 3 elements:
a[0] = {string, duppi, du}
a[1] = {23}
a[2] = {lala} */
// Test 2: Empty CSV string.
var test = "";
var a = CSVtoArray(test);
/* Array hes 0 elements: */
// Test 3: CSV string with two empty values.
var test = ",";
var a = CSVtoArray(test);
/* Array hes 2 elements:
a[0] = {}
a[1] = {} */
// Test 4: Double quoted CSV string having single quoted values.
var test = "'one','two with escaped \' single quote', 'three, with, commas'";
var a = CSVtoArray(test);
/* Array hes 3 elements:
a[0] = {one}
a[1] = {two with escaped ' single quote}
a[2] = {three, with, commas} */
// Test 5: Single quoted CSV string having double quoted values.
var test = '"one","two with escaped \" double quote", "three, with, commas"';
var a = CSVtoArray(test);
/* Array hes 3 elements:
a[0] = {one}
a[1] = {two with escaped " double quote}
a[2] = {three, with, commas} */
// Test 6: CSV string with whitespace in and around empty and non-empty values.
var test = " one , 'two' , , ' four' ,, 'six ', ' seven ' , ";
var a = CSVtoArray(test);
/* Array hes 8 elements:
a[0] = {one}
a[1] = {two}
a[2] = {}
a[3] = { four}
a[4] = {}
a[5] = {six }
a[6] = { seven }
a[7] = {} */
Это решение требует, чтобы строка CSV была «действительной». Например, некотируемые значения могут не содержать обратных косых черт или кавычек, например. следующая строка CSV НЕ является допустимой:
var invalid1 = "one, that's me!, escaped \, comma"
Это не является действительно ограничением, потому что любая подстрока может быть представлена как одно или двойное кавычное значение. Обратите внимание также, что это решение представляет собой только одно возможное определение для: «Comma Separated Values».
Редактировать: 2014-05-19: Добавлен отказ от ответственности. Изменить: 2014-12-01: Перемещено заявление об отказе.
Пожалуйста, попробуйте что-то вроде:
awk -F, '{
x=$1;
if (!shown[x,$10]++)
ctry[x]=ctry[x]$10"%%% ";
if (!shown[x,$6]++)
nation[x]=nation[x]$6"%%% ";
a[x]=$1","$2","$3","$4","$5
b[x]=$7","$8","$9
}
END{
for(x in a){
gsub (/%%% $/,"",nation[x]);
gsub (/%%% $/,"",ctry[x]);
print a[x]","nation[x]","b[x]","ctry[x]"\n";
}
}' list.csv > final.csv
Вы увидите, что приведенный выше скрипт основан на сценарии OP с некоторыми реорганизациями и модификациями. Пункт - линия: if (!shown[x,$10]++)
и следующая подобная, которая избегает дублирования.
В одну сторону:
awk -F, '($1 in a){x=a[$1];len=split(x,arr,",");arr[6]=arr[6]"%%% "$6;y=arr[1];arr[10]=arr[10]"%%% "$10;for(i=2;i<=len;i++){y=y","arr[i];a[$1]=y;}next;}{a[$1]=[110];}END{for(i in a){print a[i];}}' file
Попробуйте это решение Perl
$ cat ginzburg.txt
123123, Shelf Life Test,f,Other,066900,Germany,809900,Chem CMI,066900,Europe
123123, Shelf Life Test,f,Other,066900,Poland,810000,Chem CMI,066900,APAC
123123, Shelf Life Test,f,Other,066900,Spain,810100,Chem CMI,066900,APAC
123123, Shelf Life Test,f,Other,066900,France,2810200,Chem CMI,066900,North America
456456,Ammonium Citrus Esther,f,SupraTex Chem Analysis, 475000, Nigeria,814600,Chem Sensory,129475,MEA (Middle East and Africa)
789789,Archive 9 BASES II,f,HydroCare,066900,Belgium,211500,Chem CMI,066900,CIS (Commonwealth of Independent States)
$ perl -F, -lanE ' $x=join(",",@F[0..4]);$kv{$x}=join(",",@F[6..8]);@t=@{$kv2{$x}};push(@t,$F[5]);$kv2{$x}=[@t]; @p=@{$kv3{$x}};push(@p,$F[-1]);$kv3{$x}=[@p]; END { for(keys %kv) { %tv=(); %tv=map{ Попробуйте это решение Perl
[110]=>1} @{$kv3{ Попробуйте это решение Perl
[110]}};print " Попробуйте это решение Perl
[110] ",join("%%",@{$kv2{ Попробуйте это решение Perl
[110]}})," ",$kv{ Попробуйте это решение Perl
[110]},",",join("%%",keys %tv) } } ' ginzburg.txt
456456,Ammonium Citrus Esther,f,SupraTex Chem Analysis, 475000 Nigeria 814600,Chem Sensory,129475,MEA (Middle East and Africa)
123123, Shelf Life Test,f,Other,066900 Germany%%Poland%%Spain%%France 2810200,Chem CMI,066900,Europe%%North America%%APAC
789789,Archive 9 BASES II,f,HydroCare,066900 Belgium 211500,Chem CMI,066900,CIS (Commonwealth of Independent States)
$