تحليل ملف CSV باستخدام البله
سؤال
وكيف تحليل ملف CSV باستخدام البله؟ ببساطة وضع FS=","
لا يكفي، كحقل نقلت بفاصلة داخل سيتم التعامل مع حقول متعددة.
مثال باستخدام FS=","
الذي لا يعمل:
ومحتويات الملف:
one,two,"three, four",five
"six, seven",eight,"nine"
والنصي البله:
BEGIN { FS="," }
{
for (i=1; i<=NF; i++) printf "field #%d: %s\n", i, $(i)
printf "---------------------------\n"
}
والناتج سيئة:
field #1: one
field #2: two
field #3: "three
field #4: four"
field #5: five
---------------------------
field #1: "six
field #2: seven"
field #3: eight
field #4: "nine"
---------------------------
والناتج المرغوب فيه:
field #1: one
field #2: two
field #3: "three, four"
field #4: five
---------------------------
field #1: "six, seven"
field #2: eight
field #3: "nine"
---------------------------
المحلول
والجواب القصير هو "أنا لن تستخدم البله تحليل CSV CSV إذا كان يحتوي على بيانات حرج"، حيث "حرج" يعني أشياء مثل الفواصل في البيانات الميدانية CSV.
والسؤال التالي هو "ماذا معالجة البعض انت ذاهب الى ما تقوم به"، لأن ذلك سيؤثر ما هي البدائل التي تستخدمها.
وكنت على الارجح استخدام بيرل والنص :: CSV أو وحدات النص :: CSV_XS لقراءة ومعالجة البيانات. تذكر، وكتب بيرل في الأصل تأتي في إطار awk
وsed
القاتل - ومن هنا جاءت برامج a2p
وs2p
لا يزال توزيعها مع بيرل التي تحول awk
وsed
البرامج النصية (على التوالي) في بيرل
نصائح أخرى
والبله الإصدار 4 دليل يقول لاستخدام FPAT = "([^,]*)|(\"[^\"]+\")"
عند يعرف FPAT
، فإنه تعطيل FS
ويحدد المجالات التي المحتوى بدلا من فاصل.
ويمكنك استخدام الدالة المجمع بسيطة تسمى csvquote لتطهير المدخلات واستعادته بعد الانتهاء AWK معالجتها. يجب أن تعمل الأنابيب البيانات الخاصة بك من خلال ذلك في بداية ونهاية، وكل شيء طيب:
قبل:
gawk -f mypgoram.awk input.csv
وبعد:
csvquote input.csv | gawk -f mypgoram.awk | csvquote -u
https://github.com/dbro/csvquote للحصول على رمز والوثائق.
إذا يجوز، وأود أن استخدام بايثون CSV حدة، مع إيلاء خاص الانتباه إلى المستخدمة والتنسيق المعلمات المطلوبة أ > لتحليل ملف CSV لديك.
csv2delim.awk
# csv2delim.awk converts comma delimited files with optional quotes to delim separated file
# delim can be any character, defaults to tab
# assumes no repl characters in text, any delim in line converts to repl
# repl can be any character, defaults to ~
# changes two consecutive quotes within quotes to '
# usage: gawk -f csv2delim.awk [-v delim=d] [-v repl=`"] input-file > output-file
# -v delim delimiter, defaults to tab
# -v repl replacement char, defaults to ~
# e.g. gawk -v delim=; -v repl=` -f csv2delim.awk test.csv > test.txt
# abe 2-28-7
# abe 8-8-8 1.0 fixed empty fields, added replacement option
# abe 8-27-8 1.1 used split
# abe 8-27-8 1.2 inline rpl and "" = '
# abe 8-27-8 1.3 revert to 1.0 as it is much faster, split most of the time
# abe 8-29-8 1.4 better message if delim present
BEGIN {
if (delim == "") delim = "\t"
if (repl == "") repl = "~"
print "csv2delim.awk v.m 1.4 run at " strftime() > "/dev/stderr" ###########################################
}
{
#if ($0 ~ repl) {
# print "Replacement character " repl " is on line " FNR ":" lineIn ";" > "/dev/stderr"
#}
if ($0 ~ delim) {
print "Temp delimiter character " delim " is on line " FNR ":" lineIn ";" > "/dev/stderr"
print " replaced by " repl > "/dev/stderr"
}
gsub(delim, repl)
$0 = gensub(/([^,])\"\"/, "\\1'", "g")
# $0 = gensub(/\"\"([^,])/, "'\\1", "g") # not needed above covers all cases
out = ""
#for (i = 1; i <= length($0); i++)
n = length($0)
for (i = 1; i <= n; i++)
if ((ch = substr($0, i, 1)) == "\"")
inString = (inString) ? 0 : 1 # toggle inString
else
out = out ((ch == "," && ! inString) ? delim : ch)
print out
}
END {
print NR " records processed from " FILENAME " at " strftime() > "/dev/stderr"
}
test.csv
"first","second","third"
"fir,st","second","third"
"first","sec""ond","third"
" first ",sec ond,"third"
"first" , "second","th ird"
"first","sec;ond","third"
"first","second","th;ird"
1,2,3
,2,3
1,2,
,2,
1,,2
1,"2",3
"1",2,"3"
"1",,"3"
1,"",3
"","",""
"","""aiyn","oh"""
"""","""",""""
11,2~2,3
test.bat
rem test csv2delim
rem default is: -v delim={tab} -v repl=~
gawk -f csv2delim.awk test.csv > test.txt
gawk -v delim=; -f csv2delim.awk test.csv > testd.txt
gawk -v delim=; -v repl=` -f csv2delim.awk test.csv > testdr.txt
gawk -v repl=` -f csv2delim.awk test.csv > testr.txt
ولست متأكدا بالضبط ما إذا كان هذا هو الطريق الصحيح لفعل الأشياء. وأود أن العمل بدلا من ذلك على ملف CSV الذي إما كل القيم هي ما نقلت أو لا شيء. راجع للشغل، AWK يسمح regexes أن تكون فواصل الميدان. معرفة ما اذا كان يمكن أن يكون مفيدا.
{
ColumnCount = 0
$0 = $0 "," # Assures all fields end with comma
while($0) # Get fields by pattern, not by delimiter
{
match($0, / *"[^"]*" *,|[^,]*,/) # Find a field with its delimiter suffix
Field = substr($0, RSTART, RLENGTH) # Get the located field with its delimiter
gsub(/^ *"?|"? *,$/, "", Field) # Strip delimiter text: comma/space/quote
Column[++ColumnCount] = Field # Save field without delimiter in an array
$0 = substr($0, RLENGTH + 1) # Remove processed text from the raw data
}
}
وأنماط التي تتبع هذا واحد يمكن الوصول إلى الحقول في عمود []. يشير عدد الأعمدة عدد من العناصر في العمود [] التي تم العثور عليها. إن لم يكن كافة الصفوف تحتوي على نفس عدد الأعمدة، العمود [] يحتوي على بيانات اضافية بعد العمود [الأعمدة] عند معالجة الصفوف أقصر.
وهذا التطبيق بطيء، ولكن يبدو أن يحاكي ميزة FPAT
/ patsplit()
وجدت في البله> = 4.0.0 المذكورة في إجابة سابقة.
وإليك ما خطرت لي. فإن أي تعليقات و / أو حلول أفضل موضع تقدير.
BEGIN { FS="," }
{
for (i=1; i<=NF; i++) {
f[++n] = $i
if (substr(f[n],1,1)=="\"") {
while (substr(f[n], length(f[n]))!="\"" || substr(f[n], length(f[n])-1, 1)=="\\") {
f[n] = sprintf("%s,%s", f[n], $(++i))
}
}
}
for (i=1; i<=n; i++) printf "field #%d: %s\n", i, f[i]
print "----------------------------------\n"
}
والفكرة الأساسية هي أن حلقة I عبر الحقول، وأي مجال الذي يبدأ مع اقتباس لكن لا تنتهي مع اقتباس يحصل الحقل التالي إلحاق إليه.
وبيرل لديها وحدة النص :: CSV_XS وهو للتعامل مع غرابة نقلت-فاصلة بنيت لهذا الغرض.
بالتناوب محاولة وحدة النص :: CSV.
وperl -MText::CSV_XS -ne 'BEGIN{$csv=Text::CSV_XS->new()} if($csv->parse($_)){@f=$csv->fields();for $n (0..$#f) {print "field #$n: $f[$n]\n"};print "---\n"}' file.csv
وتنتج هذا الإخراج:
field #0: one
field #1: two
field #2: three, four
field #3: five
---
field #0: six, seven
field #1: eight
field #2: nine
---
وهنا نسخة للقراءة الإنسان.
احفظ parsecsv، شمود + س، وتشغيل بأنها "parsecsv file.csv"
#!/usr/bin/perl
use warnings;
use strict;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new();
open(my $data, '<', $ARGV[0]) or die "Could not open '$ARGV[0]' $!\n";
while (my $line = <$data>) {
if ($csv->parse($line)) {
my @f = $csv->fields();
for my $n (0..$#f) {
print "field #$n: $f[$n]\n";
}
print "---\n";
}
}
وقد تحتاج للإشارة إلى إصدار مختلف من بيرل على جهازك، لأن النص :: قد لا يتم تثبيت وحدة CSV_XS على نسخة افتراضية من بيرل.
Can't locate Text/CSV_XS.pm in @INC (@INC contains: /home/gnu/lib/perl5/5.6.1/i686-linux /home/gnu/lib/perl5/5.6.1 /home/gnu/lib/perl5/site_perl/5.6.1/i686-linux /home/gnu/lib/perl5/site_perl/5.6.1 /home/gnu/lib/perl5/site_perl .).
BEGIN failed--compilation aborted.
وإذا كان أي من الإصدارات الخاصة بك من بيرل يكون النص :: CSV_XS تثبيت، ستحتاج إلى:
sudo apt-get install cpanminus
sudo cpanm Text::CSV_XS