Как обрабатывать запятые в CSV-файле, читаемом скриптом bash

Я создаю скрипт bash для генерации некоторого вывода из CSV-файла (у меня есть более 1000 записей и не хочу делать это вручную...).

содержимое CSV-файла выглядит примерно так:

Australian Capital Territory,AU-ACT,20034,AU,Australia
Piaui,BR-PI,20100,BR,Brazil
"Adygeya, Republic",RU-AD,21250,RU,Russian Federation

у меня есть код, который может отделять поля, используя запятую в качестве разделителя, но некоторые значения фактически содержат запятые, такие как Adygeya, Republic. Эти значения окружены кавычками, чтобы указать, что символы внутри должны рассматриваться как часть поля, но я не знать, как разобрать его, чтобы принять это во внимание.

В настоящее время у меня есть этот цикл:

while IFS=, read province provinceCode criteriaId countryCode country
do
    echo "[$province] [$provinceCode] [$criteriaId] [$countryCode] [$country]"
done < $input

который производит этот вывод для приведенных выше данных выборки:

[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
["Adygeya] [ Republic"] [RU-AD] [21250] [RU,Russian Federation]

Как видите, третья запись-это неверно. Я хочу, чтобы он выводил

[Adygeya Republic] [RU-AD] [21250] [RU] [Russian Federation]

6 ответов


если вы хотите сделать все это в awk (GNU awk 4 требуется, чтобы этот скрипт работал по назначению):

awk '{ 
 for (i = 0; ++i <= NF;) {
   substr($i, 1, 1) == "\"" && 
     $i = substr($i, 2, length($i) - 2)
   printf "[%s]%s", $i, (i < NF ? OFS : RS)
    }   
 }' FPAT='([^,]+)|("[^"]+")' infile

пример вывода:

% cat infile
Australian Capital Territory,AU-ACT,20034,AU,Australia
Piaui,BR-PI,20100,BR,Brazil
"Adygeya, Republic",RU-AD,21250,RU,Russian Federation
% awk '{    
 for (i = 0; ++i <= NF;) {
   substr($i, 1, 1) == "\"" &&
     $i = substr($i, 2, length($i) - 2)
   printf "[%s]%s", $i, (i < NF ? OFS : RS)
    }
 }' FPAT='([^,]+)|("[^"]+")' infile
[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
[Adygeya, Republic] [RU-AD] [21250] [RU] [Russian Federation]

С Perl:

perl -MText::ParseWords -lne'
 print join " ", map "[$_]", 
   parse_line(",",0, $_);
  ' infile 

это должно работать с вашей версией awk (на основе этой Си.у.з. post, также удалены встроенные запятые).

awk '{
 n = parse_csv(, data)
 for (i = 0; ++i <= n;) {
    gsub(/,/, " ", data[i])
    printf "[%s]%s", data[i], (i < n ? OFS : RS)
    }
  }
function parse_csv(str, array,   field, i) { 
  split( "", array )
  str = str ","
  while ( match(str, /[ \t]*("[^"]*(""[^"]*)*"|[^,]*)[ \t]*,/) ) { 
    field = substr(str, 1, RLENGTH)
    gsub(/^[ \t]*"?|"?[ \t]*,$/, "", field)
    gsub(/""/, "\"", field)
    array[++i] = field
    str = substr(str, RLENGTH + 1)
  }
  return i
}' infile

после @Dimitre это решение здесь. Вы можете сделать что-то вроде этого -

#!/usr/local/bin/gawk -f

BEGIN {
    FS="," 
    FPAT="([^,]+)|(\"[^\"]+\")"
    }

      {
    for (i=1;i<=NF;i++) 
        printf ("[%s] ",$i);
    print ""
    } 

подумав о проблеме, я понял, что, поскольку запятая в строке не важна для меня, было бы проще просто удалить ее из ввода перед разбором.

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

это решение работает только где строка содержит одну запятую между двойными кавычками.

неотвратимое регулярное выражение

(")(.*)(,)(.*)(")

первая, третья и пятая пары скобок фиксируют открытие двойной кавычки, запятую и закрытие двойной кавычки соответственно.

вторая и третья пары скобок фиксируют фактическое содержимое поля, которое мы хотим сохранить.

sed Команда Для Удаления Запятой:

echo "$input" | sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)//' 

sed Команда для удаления запятой и двойных кавычек:

echo "$input" | sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)//' 

Обновленный Код:

tmpFile=$input"Temp"
sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)//' < $input > $tmpFile
while IFS=, read province provinceCode criteriaId countryCode country
do
    echo "[$province] [$provinceCode] [$criteriaId] [$countryCode] [$country]"
done < $tmpFile
rm $tmpFile

выход:

[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
[Adygeya Republic] [RU-AD] [21250] [RU] [Russian Federation]
[Bío-Bío] [CL-BI] [20154] [CL] [Chile]

из-за слегка устаревшей версии awk в моей системе и личном предпочтении придерживаться сценария Bash я пришел к немного другому решению.

Я создал скрипт утилиты на основе этот блог который анализирует CSV-файл и заменяет разделители разделителем по вашему выбору, чтобы выходные данные могли быть захвачены и использованы для легкой обработки данных. Скрипт уважает строки с кавычками и встроенные запятые, но удалит double кавычки он находит и не работает с экранированными двойными кавычками в полях.

#!/bin/bash

input=
delimiter=

if [ -z "$input" ];
then
    echo "Input file must be passed as an argument!"
    exit 98
fi

if ! [ -f $input ] || ! [ -e $input ];
then
    echo "Input file '"$input"' doesn't exist!"
    exit 99
fi

if [ -z "$delimiter" ];
then
    echo "Delimiter character must be passed as an argument!"
    exit 98
fi

gawk '{
    c=0
    =","                                   # yes, cheating
    while() {
        delimiter=""
        if (c++ > 0) # Evaluate and then increment c
        {
            delimiter="'$delimiter'"
        }

        match(,/ *"[^"]*" *,|[^,]*,/)
        s=substr(,RSTART,RLENGTH)             # save what matched in f
        gsub(/^ *"?|"? *,$/,"",s)               # remove extra stuff
        printf (delimiter s)
        =substr(,RLENGTH+1)                 # "consume" what matched
    }
    printf ("\n")
}' $input

просто разместите его на случай, если кто-то еще найдет его полезным.


Если вы можете терпеть, чтобы окружающие кавычки сохранялись в выходных данных, вы можете использовать небольшой скрипт, который я написал под названием csvquote, чтобы включить awk и cut (и другие текстовые инструменты UNIX) для правильной обработки полей с кавычками, содержащих запятые. Вы обертываете команду следующим образом:

csvquote inputfile.csv | awk -F, '{print "[""] [""] [""] [""] [""]"}' | csvquote -u

см.https://github.com/dbro/csvquote для кода и документации


используя Решение Дмитрия (спасибо за это), я заметил, что его программа игнорирует пустые поля.

вот исправление:

awk '{ 
 for (i = 0; ++i <= NF;) {
   substr($i, 1, 1) == "\"" && 
     $i = substr($i, 2, length($i) - 2)
   printf "[%s]%s", $i, (i < NF ? OFS : RS)
    }   
 }' FPAT='([^,]*)|("[^"]+")' infile