awk: объединить строки с уникальным содержимым в каждой строке с одинаковым идентификатором

Отказ от ответственности

2014-12-01 Обновление: нижеприведенный ответ работает только для одного особого формата CSV. Как правильно указал DG в комментариях, это решение НЕ соответствует определению CSV RFC 4180, а также не соответствует формату MS Excel. Это решение просто демонстрирует, как можно разобрать одну (нестандартную) линию ввода CSV, содержащую сочетание типов строк, где строки могут содержать экранированные кавычки и запятые.

Нестандартное CSV-решение

Как правильно указывает austincheney, вам действительно нужно разобрать строку от начала до конца, если вы хотите правильно обрабатывать строки с кавычками, которые могут содержать экранированные символы. Кроме того, ОП четко не определяет, что такое «строка CSV». Сначала мы должны определить, что представляет собой действительную строку CSV и ее отдельные значения.

Учитывая: «CSV String» Определение

Для целей этого обсуждения строка «CSV» состоит из ноль или более значений, где несколько значений разделяются запятой. Каждое значение может состоять из:

  1. Строка с двойными кавычками. (может содержать неизолированные одинарные кавычки.)
  2. Одиночная кавычка. (может содержать неэксклюзивные двойные кавычки.)
  3. Некабельная строка. (не может содержать кавычек, запятых или обратных косых черт.)
  4. Пустое значение. (Все пробельные значения считаются пустыми.)

Правила / Примечания:

  • Цитированные значения могут содержать запятые.
  • Цитируемые значения может содержать экранированное-что-либо, например 'that\'s cool'.
  • Должны быть указаны значения, содержащие кавычки, запятые или обратные косые черты.
  • Значения, содержащие ведущие или конечные пробелы, должны быть указаны.
  • Обратная косая черта удаляется из всех: \' в одинарных кавычках.
  • Обратная косая черта удаляется из всех: \" в двойных кавычках.
  • Строки без кавычек обрезаются из любого ведущего и конечные пробелы.
  • Разделитель запятой может иметь смежные пробелы (которые игнорируются).

Найти:

Функция JavaScript, которая преобразует допустимая строка CSV (как определено выше) в массив строковых значений.

Решение:

Регулярные выражения, используемые этим решением, являются сложными. И (IMHO) все нетривиальные регулярные выражения должны быть представлены в режиме свободного пробела с большим количеством комментариев и отступов. К сожалению, JavaScript не разрешает режим свободного пробела. Таким образом, регулярные выражения, реализованные этим решением, сначала представлены в собственном синтаксисе regex (выражаемом с помощью удобного Python: r'''...''' синтаксиса raw-multi-line-string).

Сначала это регулярное выражение, которое подтверждает, что строка CVS удовлетворяет вышеуказанным требованиям:

Regex для проверки «строки CSV»:

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.

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-функцией.

Функция JavaScript для разбора строки CSV:

// 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: Перемещено заявление об отказе.

0
задан Ginzburg 17 January 2019 в 10:33
поделиться

3 ответа

Пожалуйста, попробуйте что-то вроде:

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]++) и следующая подобная, которая избегает дублирования.

0
ответ дан tshiono 17 January 2019 в 10:33
поделиться

В одну сторону:

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
0
ответ дан Guru 17 January 2019 в 10:33
поделиться

Попробуйте это решение 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) $
0
ответ дан stack0114106 17 January 2019 в 10:33
поделиться
Другие вопросы по тегам:

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