Как обрабатывать запятые в 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