Ответ 1
Если ваш CSV не может содержать символы новой строки или экранированные двойные кавычки, тогда все, что вам нужно, это (с GNU awk для FPAT
):
$ echo 'foo,"field,with,commas",bar' |
awk -v FPAT='[^,]*|"[^"]+"' '{for (i=1; i<=NF;i++) print i, "<" $i ">"}'
1 <foo>
2 <"field,with,commas">
3 <bar>
В противном случае, однако, более общее, надежное, портативное решение, которое будет работать с любым современным awk:
$ cat decsv.awk
function buildRec( i,orig,fpat,done) {
$0 = PrevSeg $0
if ( gsub(/"/,"&") % 2 ) {
PrevSeg = $0 RS
done = 0
}
else {
PrevSeg = ""
gsub(/@/,"@A"); gsub(/""/,"@B") # <"[email protected]""bar"> -> <"[email protected]@Bbar">
orig = $0; $0 = "" # Save $0 and empty it
fpat = "([^" FS "]*)|(\"[^\"]+\")" # Mimic GNU awk FPAT meaning
while ( (orig!="") && match(orig,fpat) ) { # Find the next string matching fpat
$(++i) = substr(orig,RSTART,RLENGTH) # Create a field in new $0
gsub(/@B/,"\"",$i); gsub(/@A/,"@",$i) # <"[email protected]@Bbar"> -> <"[email protected]"bar">
gsub(/^"|"$/,"",$i) # <"[email protected]"bar"> -> <[email protected]"bar>
orig = substr(orig,RSTART+RLENGTH+1) # Move past fpat+sep in orig $0
}
done = 1
}
return done
}
BEGIN { FS=OFS="," }
!buildRec() { next }
{
printf "Record %d:\n", ++recNr
for (i=1;i<=NF;i++) {
# To replace newlines with blanks add gsub(/\n/," ",$i) here
printf " $%d=<%s>\n", i, $i
}
print "----"
}
,
$ awk -f decsv.awk file.csv
Record 1:
$1=<rec1, fld1>
$2=<>
$3=<rec1","fld3.1
",
fld3.2>
$4=<rec1
fld4>
----
Record 2:
$1=<rec2, fld1.1
fld1.2>
$2=<rec2 fld2.1"fld2.2"fld2.3>
$3=<>
$4=<rec2 fld4>
----
Выше предполагается, что окончания строки UNIX \n
. В Windows \r\n
окончания строк это намного проще, так как "новые строки" внутри каждого поля будут фактически просто переводами строк (то есть \n
s), и поэтому вы можете установить RS="\r\n"
а затем \n
пределах поля не будут обрабатываться как окончания строк.
Он работает путем простого подсчета количества "
присутствующих" в текущей записи всякий раз, когда он встречает RS
- если это нечетное число, тогда RS
(предположительно \n
но не обязательно) находится в середине поля, и поэтому мы продолжаем строит текущую запись, но если это даже тогда, это конец текущей записи, и поэтому мы можем продолжить с остальной частью сценария, обрабатывающей теперь завершенную запись.
gsub(/@/,"@A"); gsub(/""/,"@B")
gsub(/@/,"@A"); gsub(/""/,"@B")
преобразует каждую пару двойных кавычек, пересекающих всю запись (имейте в виду, что эти пары ""
могут применяться только в полях в кавычках) в строку @B
, которая не содержит двойных кавычек, поэтому что когда мы разделяем запись на поля, match() не срабатывает из-за кавычек, появляющихся внутри полей. gsub(/@B/,"\"",$i); gsub(/@A/,"@",$i)
восстанавливает кавычки внутри каждого поля по отдельности, а также преобразует ""
в "
они действительно представляют,
См. Также Как использовать awk в cygwin для печати полей из таблицы Excel? о том, как создавать CSV из таблиц Excel.