Xóa dấu phẩy giữa các dấu ngoặc kép trong tệp được phân cách bằng dấu phẩy


23

Tôi có một tệp đầu vào được phân tách bằng dấu phẩy ( ,). Có một số trường được đặt trong dấu ngoặc kép có dấu phẩy trong đó. Đây là hàng mẫu

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

Tôi cần phải xóa tất cả dấu phẩy xảy ra bên trong dấu ngoặc kép và dấu ngoặc kép là tốt. Vì vậy, dòng trên nên được phân tích cú pháp như dưới đây

123,ABC DEV 23,345,534.202,NAME

Tôi đã thử cách sử dụng sau đây sednhưng không cho kết quả như mong đợi.

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

Bất kỳ thủ thuật nhanh chóng với sed, awkhoặc bất kỳ tiện ích unix khác xin vui lòng?


Tôi không chắc chắn những gì bạn đang cố gắng để làm, nhưng tiện ích "csvtool" tốt hơn nhiều để phân tích csv so với các công cụ chung chung như sed hoặc awk. Nó chỉ là về mọi bản phân phối của linux.
figtrap

Câu trả lời:


32

Nếu các trích dẫn được cân bằng, bạn sẽ muốn xóa dấu phẩy giữa mỗi trích dẫn khác, điều này có thể được thể hiện awknhư sau:

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

Đầu ra:

123,ABC DEV 23,345,534.202,NAME

Giải trình

Việc -F"làm cho awk tách dòng tại các dấu ngoặc kép, có nghĩa là mọi lĩnh vực khác sẽ là văn bản trích dẫn. Vòng lặp for chạy gsub, viết tắt của thay thế toàn cầu, trên mọi trường khác, thay thế dấu phẩy ( ",") bằng không có gì ( ""). Các 1cuối cùng gọi mặc định mã khối: { print $0 }.


1
Xin vui lòng bạn có thể gsubgiải thích và giải thích ngắn gọn, làm thế nào một lớp lót này hoạt động ?? xin vui lòng.
mtk

Cảm ơn bạn! Kịch bản này hoạt động thực sự tốt, nhưng bạn có thể giải thích 1 cô đơn ở cuối kịch bản không? -} 1 '-
Ca caoEv

@CocoaEv: Nó thi hành { print $0 }. Tôi đã thêm vào đó để giải thích là tốt.
Thor

2
Cách tiếp cận này có một vấn đề: đôi khi csv có các hàng trải dài một vài dòng, chẳng hạn như: prefix,"something,otherthing[newline]something , else[newline]3rdline,and,things",suffix (ví dụ: một vài dòng và lồng nhau "," bất cứ nơi nào trong trích dẫn kép nhiều dòng: toàn bộ "...."phần nên được nối lại và bên trong ,phải được nối lại đã thay thế / xóa ...): tập lệnh của bạn sẽ không thấy các cặp dấu ngoặc kép trong trường hợp đó và thực sự không dễ giải quyết (cần phải "nối lại" các dòng trong một "mở" (nghĩa là số lẻ) trích dẫn gấp đôi ... + hãy cẩn thận hơn nếu \" bên trong chuỗi cũng có một lối thoát )
Olivier Dulac

1
Yêu thích giải pháp này nhưng tôi đã tinh chỉnh nó vì tôi thường thích giữ dấu phẩy nhưng vẫn muốn phân định. Thay vào đó, tôi chuyển dấu phẩy bên ngoài dấu ngoặc kép thành ống dẫn, chuyển đổi csv thành tệp psv:awk -F'"' -v OFS='"' '{ for (I=1; i<=NF; i+=2) gsub(",", "|", $i) } 1' infile
Danton Noriega

7

Có một phản hồi tốt , sử dụng sed chỉ đơn giản là một lần với một vòng lặp :

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

Giải trình:

  • :a; là một nhãn cho chi nhánh
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 / có thể chứa 3 phần kèm theo
    • đầu tiên là lần 2: [^"]*,\?\|"[^",]*",\?khớp với một chuỗi không có trích dẫn kép, có thể theo sau là hôn mê hoặc chuỗi được bao quanh bởi hai trích dẫn kép, không hôn mê và có thể theo sau là hôn mê.
    • hơn phần RE đầu tiên được sáng tác bởi nhiều lần lặp lại của phần 2 được mô tả trước đó, tiếp theo là 1 trích dẫn kép và một số caracteres, nhưng không có trích dẫn kép, cũng không hôn mê.
    • Phần RE đầu tiên được theo sau là hôn mê.
    • Đáng chú ý, phần còn lại của dòng không cần phải được chạm vào
  • tasẽ lặp lại :anếu s/lệnh trước đó đã thực hiện một số thay đổi.

Hoạt động cũng với dấu ngoặc kép. Thật sự cảm ơn!
tricasse

5

Một giải pháp chung cũng có thể xử lý một số dấu phẩy giữa các trích dẫn cân bằng cần một sự thay thế lồng nhau. Tôi triển khai một giải pháp trong perl, xử lý mọi dòng của một đầu vào nhất định và chỉ thay thế dấu phẩy trong mỗi cặp dấu ngoặc kép khác:

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'

hoặc trong ngắn hạn

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

Bạn có thể dẫn văn bản bạn muốn xử lý vào lệnh hoặc chỉ định tệp văn bản sẽ được xử lý làm đối số dòng lệnh cuối cùng.


1
Điều [^\\]này sẽ có tác dụng không mong muốn là khớp ký tự cuối cùng bên trong dấu ngoặc kép và loại bỏ nó (không phải \ ký tự), nghĩa là bạn không nên sử dụng ký tự đó. Hãy thử (?<!\\)thay thế.
tojrobinson

Cảm ơn sự phản đối của bạn, tôi đã sửa nó. Tuy nhiên, tôi nghĩ rằng chúng ta không cần phải nhìn phía sau khẳng định ở đây, hay chúng ta!?
dùng1146332

1
Bao gồm không \ trong nhóm chụp của bạn tạo ra kết quả tương đương. +1
tojrobinson

1
+1. Sau khi thử một vài thứ với sed, tôi đã kiểm tra tài liệu của sed và xác nhận rằng nó không thể áp dụng thay thế cho chỉ phần phù hợp của một dòng ... vì vậy đã từ bỏ và thử perl. Kết thúc với một cách tiếp cận rất giống nhau nhưng phiên bản này sử dụng [^"]*để làm cho trận đấu không tham lam (tức là khớp mọi thứ từ cái này "sang cái khác " ) : perl -pe 's/"([^"]+)"/($match = $1) =~ (s:,::g);$match;/ge;'. Nó không thừa nhận ý tưởng kỳ quặc rằng một trích dẫn có thể được thoát ra bằng dấu gạch chéo ngược :-)
cas

Cám ơn bạn đã góp ý. Sẽ rất thú vị nếu [^"]*cách tiếp cận hoặc cách tiếp cận không tham lam rõ ràng tiêu tốn ít thời gian cpu hơn.
dùng1146332

3

Tôi sẽ sử dụng ngôn ngữ với trình phân tích cú pháp CSV thích hợp. Ví dụ:

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

Mặc dù ban đầu tôi thích giải pháp này, nhưng nó lại chậm đến mức khó tin đối với các tệp lớn ...
KIC

3

Trích dẫn thứ hai của bạn bị đặt sai chỗ:

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

Ngoài ra, sử dụng các biểu thức thông thường có xu hướng khớp với phần dài nhất có thể của văn bản, có nghĩa là điều này sẽ không hoạt động nếu bạn có nhiều hơn một trường được trích dẫn trong chuỗi.

Một cách xử lý nhiều trường được trích dẫn trong sed

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

Tuy nhiên, đây cũng là một cách để giải quyết vấn đề này, với đầu vào có thể chứa nhiều dấu phẩy trên mỗi trường được trích dẫn, biểu thức đầu tiên trong sed sẽ phải được lặp lại nhiều lần so với nội dung dấu phẩy tối đa trong một trường hoặc cho đến khi không thay đổi đầu ra.

Chạy sed với nhiều hơn một biểu thức sẽ hiệu quả hơn so với một số quy trình sed đang chạy và một "tr" tất cả chạy với các ống mở.

Tuy nhiên, điều này có thể có hậu quả không mong muốn nếu đầu vào không được định dạng đúng. tức là dấu ngoặc kép, dấu ngoặc kép.

Sử dụng ví dụ đang chạy:

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'

Đầu ra:

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

Bạn có thể làm cho nó tổng quát hơn với phân nhánh có điều kiện và dễ đọc hơn với ERE, ví dụ như với GNU sed : sed -r ':r; s/("[^",]+),([^",]*)/\1 \2/g; tr; s/"//g'.
Thor

2

Trong perl - bạn có thể sử dụng Text::CSVđể phân tích cú pháp này và thực hiện nó một cách tầm thường:

#!/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";
}

Bạn có thể in với Text::CSVnhưng nó có xu hướng giữ nguyên dấu ngoặc kép nếu bạn làm như vậy. (Mặc dù, tôi đề nghị - thay vì tước trích dẫn cho đầu ra của bạn, bạn chỉ có thể phân tích cú pháp bằng cách sử dụng Text::CSVở vị trí đầu tiên).


0

Tôi đã tạo một hàm để lặp thông qua mọi ký tự trong chuỗi.
Nếu ký tự là một trích dẫn thì kiểm tra (b_in_qt) được đánh dấu là đúng.
Trong khi b_in_qt là đúng, tất cả dấu phẩy được thay thế bằng dấu cách.
b_in_qt được đặt thành false khi tìm thấy dấu phẩy tiếp theo.

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"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.