ลบเครื่องหมายจุลภาคระหว่างเครื่องหมายคำพูดเฉพาะในไฟล์ที่คั่นด้วยเครื่องหมายจุลภาค


23

ฉันมีไฟล์อินพุตคั่นด้วยเครื่องหมายจุลภาค ( ,) มีบางฟิลด์อยู่ในเครื่องหมายคำพูดคู่ที่มีเครื่องหมายจุลภาคอยู่ นี่คือแถวตัวอย่าง

123,"ABC, DEV 23",345,534.202,NAME

ฉันต้องลบเครื่องหมายจุลภาคทั้งหมดที่เกิดขึ้นภายในเครื่องหมายคำพูดคู่และเครื่องหมายคำพูดคู่ด้วย ดังนั้นบรรทัดข้างต้นควรได้รับการแยกวิเคราะห์ตามที่แสดงด้านล่าง

123,ABC DEV 23,345,534.202,NAME

ฉันลองใช้สิ่งต่อไปนี้sedแต่ไม่ให้ผลลัพธ์ที่คาดหวัง

sed -e 's/\(".*\),\(".*\)/\1 \2/g'

เทคนิคใด ๆ อย่างรวดเร็วด้วยsed, awkหรือยูทิลิตี้ยูนิกซ์อื่นใดโปรด?


ฉันไม่แน่ใจว่าคุณกำลังพยายามทำอะไร แต่ยูทิลิตี้ "csvtool" ดีกว่าสำหรับการแยกวิเคราะห์ csv มากกว่าเครื่องมือทั่วไปเช่น sed หรือ awk มันอยู่ใน linux distro ของทุก ๆ
รูปที่

คำตอบ:


32

หากคำพูดมีความสมดุลคุณจะต้องลบเครื่องหมายจุลภาคระหว่างคำพูดอื่น ๆ ทั้งหมดนี้สามารถแสดงในawkลักษณะนี้:

awk -F'"' -v OFS='' '{ for (i=2; i<=NF; i+=2) gsub(",", "", $i) } 1' infile

เอาท์พุท:

123,ABC DEV 23,345,534.202,NAME

คำอธิบาย

-F"ทำให้ awk แยกเส้นที่สัญญาณอ้างดับเบิลซึ่งหมายความว่าทุกสาขาอื่น ๆ จะเป็นข้อความระหว่างอ้าง for-loop ทำงานแบบgsubย่อสำหรับการทดแทนแบบโกลบอลบนทุก ๆ ฟิลด์โดยแทนที่คอมมา ( ",") ด้วย nothing ( "") ที่สิ้นสุดจะเรียกรหัสบล็อกเริ่มต้น:1{ print $0 }


1
โปรดgsubอธิบายและอธิบายสั้น ๆ ว่าสายการบินนี้ทำงานอย่างไร? โปรด.
mtk

ขอขอบคุณ! สคริปต์นี้ใช้งานได้ดีจริงๆ แต่คุณสามารถอธิบาย 1 ที่อ้างว้างในตอนท้ายของสคริปต์ได้ไหม -} 1 '-
CocoaEv

@CocoaEv: { print $0 }มันรัน ฉันเพิ่มไปที่คำอธิบายเช่นกัน
ธ อร์

2
วิธีการนี้มีปัญหา: บางครั้ง csv มีแถวที่ยาวหลายบรรทัดเช่น: prefix,"something,otherthing[newline]something , else[newline]3rdline,and,things",suffix (เช่น: หลายบรรทัดและซ้อนกัน "," ที่ใดก็ได้ภายในการอ้างอิงสองบรรทัดหลายบรรทัด: "...."ส่วนทั้งหมดควรเข้าร่วมและ,ควรอยู่ภายในแทนที่ / ลบ ... ): สคริปต์ของคุณจะไม่เห็นคู่ของคำพูดคู่ในกรณีนั้นและมันไม่ง่ายเลยที่จะแก้ (ต้อง "เข้าร่วมใหม่" บรรทัดที่อยู่ใน "เปิด" (เช่นเลขคี่) อ้างสองครั้ง ... + ใช้ความระมัดระวังเป็นพิเศษหากมีการหลบหนี\" อยู่ภายในสตริง)
Olivier Dulac

1
ชอบวิธีแก้ปัญหานี้ แต่ฉัน tweaked มันเพราะฉันมักจะเก็บเครื่องหมายจุลภาค แต่ยังต้องการที่จะกำหนดขอบเขต แต่ฉันเปลี่ยนเครื่องหมายจุลภาคนอกเครื่องหมายคำพูดเป็นไพพ์แปลง csv เป็นไฟล์ psv:awk -F'"' -v OFS='"' '{ for (I=1; i<=NF; i+=2) gsub(",", "|", $i) } 1' infile
Danton Noriega

7

มีการตอบสนองที่ดีโดยใช้ sed เพียงครั้งเดียวด้วยการวนซ้ำ :

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME'|
  sed ':a;s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 /;ta'
123,"ABC  DEV 23",345,534,"some more  comma-separated  words",202,NAME

คำอธิบาย:

  • :a; เป็นฉลากสำหรับสาขาเฟอร์ฟูร์
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 / อาจมี 3 ส่วนที่ปิดล้อม
    • อันดับแรกอันดับที่ 2: [^"]*,\?\|"[^",]*",\?จับคู่สตริงที่ไม่มีเครื่องหมายอัญประกาศคู่อาจตามด้วยโคม่าหรือสตริงที่ล้อมรอบด้วยเครื่องหมายคำพูดคู่สองตัวโดยไม่มีโคม่าและอาจตามด้วยโคม่า
    • กว่าส่วน REแรกนั้นประกอบด้วยการทำซ้ำหลายครั้งของส่วนที่อธิบายไว้ก่อนหน้า 2 ตามด้วยเครื่องหมายคำพูด 1 คู่และ caracteres บางส่วน แต่ไม่มีเครื่องหมายคำพูดคู่หรือ comas
    • ส่วน RE แรกที่จะตามด้วยอาการโคม่า
    • Nota ส่วนที่เหลือของบรรทัดไม่จำเป็นต้องแตะต้อง
  • taจะวนซ้ำ:aหากs/คำสั่งก่อนหน้าเปลี่ยนไปบ้าง

ทำงานได้กับคำพูดซ้อน เยี่ยมมากขอบคุณ!
tricasse

5

วิธีแก้ปัญหาทั่วไปที่ยังสามารถจัดการกับเครื่องหมายจุลภาคหลายตัวระหว่างคำพูดที่สมดุลต้องการการทดแทนที่ซ้อนกัน ฉันใช้โซลูชันใน perl ซึ่งประมวลผลทุกบรรทัดของอินพุตที่กำหนดและใช้คอมม่าแทนเครื่องหมายคำพูดคู่อื่น ๆ ทุกคู่:

perl -pe 's/ "  (.+?  [^\\])  "               # find all non escaped 
                                              # quoting pairs
                                              # in a non-greedy way

           / ($ret = $1) =~ (s#,##g);         # remove all commas within quotes
             $ret                             # substitute the substitution :)
           /gex'

หรือในระยะสั้น

perl -pe 's/"(.+?[^\\])"/($ret = $1) =~ (s#,##g); $ret/ge'

คุณสามารถไพพ์ข้อความที่คุณต้องการประมวลผลคำสั่งหรือระบุไฟล์ข้อความที่จะประมวลผลเป็นอาร์กิวเมนต์บรรทัดคำสั่งล่าสุด


1
[^\\]จะมีผลกระทบที่ไม่พึงประสงค์ของการจับคู่ตัวอักษรตัวสุดท้ายภายในคำพูดและถอดมันออกมา (ไม่ใช่ \ ตัวอักษร) คือคุณไม่ควรใช้ตัวอักษรที่ ลอง(?<!\\)แทน
tojrobinson

ขอบคุณสำหรับคำคัดค้านของคุณฉันได้แก้ไขมันแล้ว อย่างไรก็ตามฉันคิดว่าเราไม่จำเป็นต้องดูการยืนยันที่นี่หรือทำเรา!
user1146332

1
การรวมที่ไม่ใช่ \ ในกลุ่มการดักจับของคุณจะให้ผลลัพธ์ที่เทียบเท่ากัน +1
tojrobinson

1
+1 หลังจากลองสิ่งเล็กน้อยกับ sed ฉันตรวจสอบเอกสารของ sed และยืนยันว่าไม่สามารถใช้การแทนที่กับส่วนที่ตรงกันของบรรทัด ... ดังนั้นเลิกและลอง perl จบลงด้วยวิธีการที่คล้ายกันมาก แต่รุ่นนี้ใช้งาน[^"]*ที่จะทำให้การแข่งขันไม่โลภ (เช่นตรงกับทุกอย่างจากที่หนึ่ง"ไปยังถัดไป ):" perl -pe 's/"([^"]+)"/($match = $1) =~ (s:,::g);$match;/ge;'มันไม่ยอมรับความคิดต่างชาติที่อ้างอาจจะหนีไปกับเครื่องหมาย :-)
CAS

ขอบคุณสำหรับความคิดเห็นของคุณ จะน่าสนใจถ้า[^"]*วิธีการหรือวิธีการที่ไม่โลภใช้เวลา cpu น้อยลง
user1146332

3

ฉันจะใช้ภาษาที่มีตัวแยกวิเคราะห์ CSV ที่เหมาะสม ตัวอย่างเช่น:

ruby -r csv -ne '
  CSV.parse($_) do |row|
    newrow = CSV::Row.new [], []
    row.each {|field| newrow << field.delete(",")}
    puts newrow.to_csv
  end
' < input_file

ในขณะที่ผมชอบวิธีนี้ในตอนแรกมันเปิดออกมาเป็นที่น่าทึ่งช้าสำหรับไฟล์ขนาดใหญ่ ...
KIC

3

คำพูดที่สองของคุณถูกใส่ผิดที่:

sed -e 's/\(".*\),\(.*"\)/\1 \2/g'

นอกจากนี้การใช้นิพจน์ทั่วไปมักจะจับคู่กับส่วนที่ยาวที่สุดของข้อความซึ่งหมายความว่าสิ่งนี้จะไม่ทำงานหากคุณมีฟิลด์ที่ยกมามากกว่าหนึ่งในสตริง

วิธีที่จัดการกับเขตข้อมูลที่ยกมาหลายใน sed

sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

นี่เป็นวิธีที่จะแก้ปัญหานี้ด้วยอินพุตที่อาจมีมากกว่าหนึ่งคอมม่าต่อฟิลด์ที่ยกมาการแสดงออกครั้งแรกใน sed จะต้องทำซ้ำหลาย ๆ ครั้งเป็นเนื้อหาคอมม่าสูงสุดในฟิลด์เดียวหรือจนกว่ามันจะ ไม่เปลี่ยนผลลัพธ์เลย

การใช้งาน sed ด้วยนิพจน์มากกว่าหนึ่งนิพจน์ควรมีประสิทธิภาพมากกว่าการทำงานแบบหลายขั้นตอนและ "tr" ทั้งหมดทำงานโดยใช้ไพพ์เปิด

อย่างไรก็ตามสิ่งนี้อาจมีผลกระทบที่ไม่พึงประสงค์หากรูปแบบการป้อนข้อมูลไม่ถูกต้อง เช่นคำพูดซ้อนกัน

ใช้ตัวอย่างการรัน:

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME' \
| sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' \
-e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

เอาท์พุท:

123,ABC  DEV 23,345,534,some more  comma-separated  words,202,NAME

คุณสามารถทำให้มันทั่วไปมากขึ้นด้วยการกำหนดการทำงานตามเงื่อนไขและอื่น ๆ สามารถอ่านได้ด้วย ERE เช่นกับ GNU sed -r ':r; s/("[^",]+),([^",]*)/\1 \2/g; tr; s/"//g'sed:
Thor

2

ใน Perl - คุณสามารถใช้Text::CSVในการแยกวิเคราะห์นี้และทำมันเล็กน้อย:

#!/usr/bin/env perl
use strict;
use warnings;

use Text::CSV; 

my $csv = Text::CSV -> new();

while ( my $row = $csv -> getline ( \*STDIN ) ) {
    #remove commas in each field in the row
    $_ =~ s/,//g for @$row;
    #print it - use print and join, rather than csv output because quotes. 
    print join ( ",", @$row ),"\n";
}

คุณสามารถพิมพ์ด้วยText::CSVแต่มีแนวโน้มที่จะรักษาคำพูดถ้าคุณทำ (แม้ว่าฉันจะแนะนำ - แทนที่จะลอกราคาสำหรับผลลัพธ์ของคุณคุณสามารถแยกวิเคราะห์การใช้Text::CSVในสถานที่แรก)


0

ฉันสร้างฟังก์ชั่นเพื่อวนรอบอักขระทุกตัวในสตริง
หากตัวละครเป็นอัญประกาศการตรวจสอบ (b_in_qt) จะถูกทำเครื่องหมายเป็นจริง
แม้ว่า b_in_qt จะเป็นจริงเครื่องหมายจุลภาคทั้งหมดจะถูกแทนที่ด้วยช่องว่าง
b_in_qt ถูกตั้งค่าเป็นเท็จเมื่อพบเครื่องหมายจุลภาคถัดไป

FUNCTION f_replace_c (str_in  VARCHAR2) RETURN VARCHAR2 IS
str_out     varchar2(1000)  := null;
str_chr     varchar2(1)     := null;
b_in_qt     boolean         := false;

BEGIN
    FOR x IN 1..length(str_in) LOOP
      str_chr := substr(str_in,x,1);
      IF str_chr = '"' THEN
        if b_in_qt then
            b_in_qt := false;
        else
            b_in_qt := true;
        end if;
      END IF;
      IF b_in_qt THEN
        if str_chr = ',' then
            str_chr := ' ';
        end if;
      END IF;
    str_out := str_out || str_chr;
    END LOOP;
RETURN str_out;
END;

str_in := f_replace_c ("blue","cat,dog,horse","",yellow,"green")

RESULTS
  "blue","cat dog horse","",yellow,"green"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.