هل يمكن أن تتعامل AWK مع ملف CSV الذي يحتوي على فاصلة داخل حقل مقتبس؟

StackOverflow https://stackoverflow.com/questions/3138363

سؤال

أنا أستخدم AWK لأداء حساب مجموع عمود واحد في ملف CSV. تنسيق البيانات مثل:

id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99

كنت أستخدم هذا البرنامج النصي AWK لحساب المبلغ:

awk -F, '{sum+=$3} END {print sum}'

تحتوي بعض القيمة في حقل الاسم على فاصلة وهذا يكسر البرنامج النصي الخاص بي. سؤالي هو: هل يمكن لحل هذه المشكلة؟ إذا كانت الإجابة بنعم ، وكيف يمكنني فعل ذلك؟

شكرًا لك.

هل كانت مفيدة؟

المحلول

تكتب وظيفة في awk مثل أدناه:

$ awk 'func isnum(x){return(x==x+0)}BEGIN{print isnum("hello"),isnum("-42")}'
0 1

يمكنك دمج هذه الوظيفة في البرنامج النصي وتحقق مما إذا كان الحقل الثالث رقميًا أم لا. إذا لم يكن رقميًا ، فانتقل إلى الحقل الرابع وإذا لم يكن الحقل الرابع في الحقل لا يتجاوز 5 ... حتى تصل إلى قيمة رقمية. ربما ستساعد الحلقة هنا ، وتضيفها إلى المبلغ.

نصائح أخرى

طريقة واحدة باستخدام GNU awk و fpat

awk 'BEGIN { FPAT = "([^, ]+)|(\"[^\"]+\")" } { sum+=$3 } END { print sum }' file.txt

نتيجة:

192

من المحتمل أن تكون أفضل حالًا في القيام بذلك في Perl مع Text :: CSV ، لأن هذا حل سريع وقوي.

يمكنك المساعدة في العمل مع حقول البيانات التي تحتوي على فواصل (أو خطوط جديدة) باستخدام برنامج نصي صغير كتبته يسمى CSVquote. يحل محل الفواصل المخالفة داخل الحقول المقتبسة بأحرف غير مطبوعة. إذا كنت بحاجة إلى ذلك ، يمكنك لاحقًا استعادة تلك الفواصل - ولكن في هذه الحالة ، لا تحتاج إلى ذلك.

ها هو الأمر:

csvquote inputfile.csv | awk -F, '{sum+=$3} END {print sum}'

نرى https://github.com/dbro/csvquote للرمز

انا استخدم

`FPAT="([^,]+)|(\"[^\"]+\")" `

لتحديد الحقول مع جوك. لقد وجدت أنه عندما يكون الحقل فارغًا ، فإن هذا لا يتعرف على العدد الصحيح للحقول. لأن "+" يتطلب حرف واحد على الأقل في الحقل. لقد غيرتها إلى:

`FPAT="([^,]*)|(\"[^\"]*\")"`

واستبدال "+" مع "*". إنه يعمل بشكل صحيح.

أجد أيضًا أن دليل مستخدم GNU AWK لديه هذه المشكلة أيضًا.https://www.gnu.org/software/gawk/manual/html_node/splitting-by-content.html

مقابل ملف إدخال بسيط كما يمكنك فقط كتابة وظيفة صغيرة لتحويل جميع FSS الحقيقية خارج عروض الأسعار إلى بعض القيمة الأخرى (اخترت RS لأن فاصل السجل لا يمكن أن يكون جزءًا من السجل) ثم استخدم ذلك كـ FS ، على سبيل المثال:

$ cat decsv.awk
BEGIN{ fs=FS; FS=RS }

{
   decsv()

   for (i=1;i<=NF;i++) {
       printf "Record %d, Field %d is <%s>\n" ,NR,i,$i
   }
   print ""
}

function decsv(         curr,head,tail)
{
   tail = $0
   while ( match(tail,/"[^"]+"/) ) {
       head = substr(tail, 1, RSTART-1);
       gsub(fs,RS,head)
       curr = curr head substr(tail, RSTART, RLENGTH)
       tail = substr(tail, RSTART + RLENGTH)
   }
   gsub(fs,RS,tail)
   $0 = curr tail
}

$ cat file
id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99

$ awk -F", " -f decsv.awk file
Record 1, Field 1 is <id>
Record 1, Field 2 is <name>
Record 1, Field 3 is <value>

Record 2, Field 1 is <1>
Record 2, Field 2 is <foo>
Record 2, Field 3 is <17>

Record 3, Field 1 is <2>
Record 3, Field 2 is <bar>
Record 3, Field 3 is <76>

Record 4, Field 1 is <3>
Record 4, Field 2 is <"I am the, question">
Record 4, Field 3 is <99>

يصبح الأمر معقدًا فقط عندما يتعين عليك التعامل مع الخطوط الجديدة المدمجة والاقتباسات المدمجة التي هربت داخل الاقتباسات ، وحتى ذلك الحين ليس صعبًا للغاية وقد تم ذلك من قبل ...

نرى ما هي الطريقة الأكثر قوة لتحليل CSV بكفاءة باستخدام AWK؟ للمزيد من المعلومات.

يمكنك دائمًا معالجة المشكلة من المصدر. ضع اقتباسات حول حقل الاسم ، تمامًا مثل حقل "أنا ، السؤال". هذا أسهل بكثير من قضاء وقتك في حلول الترميز لذلك.

تحديث(كما طلب دينيس). مثال بسيط

$ s='id, "name1,name2", value 1, foo, 17 2, bar, 76 3, "I am the, question", 99'

$ echo $s|awk -F'"' '{ for(i=1;i<=NF;i+=2) print $i}'
id,
, value 1, foo, 17 2, bar, 76 3,
, 99

$ echo $s|awk -F'"' '{ for(i=2;i<=NF;i+=2) print $i}'
name1,name2
I am the, question

كما ترون ، من خلال تعيين المحدد على اقتباس مزدوج ، فإن الحقول التي تنتمي إلى "عروض الأسعار" هي دائمًا على عددسك. نظرًا لأن OP ليس لديه رفاهية تعديل البيانات المصدر ، فإن هذه الطريقة لن تكون مناسبة له.

إذا كنت تعرف بالتأكيد أن عمود "القيمة" هو دائمًا العمود الأخير:

awk -F, '{sum+=$NF} END {print sum}'

يمثل NF عدد الحقول ، لذا فإن $ nf هو العمود الأخير

ساعدتني هذه المقالة في حل مشكلة حقل البيانات نفسها. سوف تضع معظم CSV اقتباسًا حول الحقول ذات المساحات أو الفواصل داخلها. هذا يفسد عدد الحقل لـ AWK إلا إذا قمت بتصفيةها.

إذا كنت بحاجة إلى البيانات داخل تلك الحقول التي تحتوي على القمامة ، فهذا ليس لك. ghostdog74 قدمت الإجابة ، التي تفرغ هذا الحقل ولكنها تحافظ على إجمالي عدد الحقول في النهاية ، وهو مفتاح الحفاظ على إخراج البيانات ثابتًا. لم يعجبني كيف قدم هذا الحل خطوطًا جديدة. هذا هو إصدار هذا الحل الذي استخدمته. لم يكن للحقول الثلاثة القبضة هذه المشكلة في البيانات. غالبًا ما كان الحقل الرابع الذي يحتوي على اسم العميل قد حدث ، لكنني كنت بحاجة إلى تلك البيانات. الحقول المتبقية التي تظهر المشكلة التي يمكنني التخلص منها دون مشكلة لأنها لم تكن ضرورية في إخراج تقريري. لذلك قمت أولاً بإخراج القمامة في الحقل الرابع على وجه التحديد وأزيل أول حالتين من عروض الأسعار. ثم أطبق ماذا ghostdog74أعطيت لتفريغ الحقول المتبقية التي لها فواصل داخلها - وهذا يزيل أيضًا عروض الأسعار ، لكنني أستخدم printfللحفاظ على البيانات في سجل واحد. أبدأ بـ 85 حقلًا وينتهي به المطاف مع 85 حقلًا في جميع الحالات من 8000 خطوط من البيانات الفوضوية. درجة مثالية!

grep -i $1 $dbfile | sed 's/\, Inc.//;s/, LLC.//;s/, LLC//;s/, Ltd.//;s/\"//;s/\"//' | awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}' > $tmpfile

الحل الذي يفرغ الحقول مع الفواصل داخلها ولكنه يحافظ أيضًا على السجل ، بالطبع هو:

awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}

Megs من شكر Ghostdog74 على الحل العظيم!

Netsguy256/

FPAT هو الحل الأنيق لأنه يمكنه التعامل مع الفواصل المروعة ضمن مشكلة اقتباسات ، ولكن لتلخيص عمود من الأرقام في العمود الأخير بغض النظر عن عدد الفواصل السابقة ، يعمل $ nf بشكل جيد:

awk -F"," '{sum+=$NF} END {print sum}'

للوصول إلى العمود الثاني إلى الأخير ، يمكنك استخدام هذا:

awk -F"," '{sum+=$(NF-1)} END {print sum}'

محلات CSV من CSV تمامًا مثل Perl's Text::CSV_XS مصممة خصيصًا للتعامل مع هذا النوع من الغرابة.

perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new({allow_whitespace => 1})} if($csv->parse($_)){@f=$csv->fields();$sum+=$f[2]} END{print $sum}' file

allow_whitespace هناك حاجة لأن بيانات الإدخال تحتوي على مساحة بيضاء تحيط بفواصل الفاصلة. إصدارات قديمة جدا من Text::CSV_XS قد لا تدعم هذا الخيار.

قدمت المزيد من التفسير ل Text::CSV_XS ضمن إجابتي هنا: تحليل ملف CSV باستخدام Gawk

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top