Làm cách nào để thao tác với tệp CSV bằng sed hoặc awk?


23

Làm cách nào tôi có thể thực hiện các thao tác sau với tệp CSV bằng cách sử dụng sedhoặc awk?

  • Xóa một cột
  • Nhân đôi một cột
  • Di chuyển một cột

Tôi có một bàn lớn với hơn 200 hàng và tôi không quen thuộc lắm sed.


1
Cross được đăng trên AskUbfox
enzotib

@enzotib bạn có thể gửi liên kết?
n0pe

@MaxMackie askubfox.com/questions/88142/ . Tôi không thể nhận được một bản mod ở đó vào giờ này, vì vậy tôi đã gắn cờ nó yêu cầu họ di chuyển nếu họ sẵn sàng; nó đã có câu trả lời được chấp nhận vì vậy tôi không chắc họ có làm không
Michael Mrozek

@MichaelMrozek, hmmm điều gì thường xảy ra trong những tình huống này? Chúng ta chỉ đơn giản là giữ các bản sao?
n0pe

1
Trừ khi bạn cần chạy trên một hệ thống chỉ có sẵn các công cụ cơ bản, hãy xem Có công cụ dòng lệnh mạnh mẽ nào để xử lý tệp csv không?
Gilles 'SO- ngừng trở nên xấu xa'

Câu trả lời:


7

Ngoài cách cắt và sắp xếp lại các trường (được nêu trong các câu trả lời khác), còn có vấn đề về các trường CSV kỳ quặc.

Nếu dữ liệu của bạn rơi vào danh mục "kỳ quặc" này, một chút lọc trướcsau có thể xử lý nó. Các bộ lọc hiển thị dưới đây yêu cầu các nhân vật \x01, \x02, \x03, \x04để không xuất hiện bất cứ nơi nào trong dữ liệu của bạn.

Dưới đây là các bộ lọc bao quanh một awkbãi chứa trường đơn giản .

Lưu ý: trường năm có bố cục "trường được trích dẫn" không hợp lệ / không đầy đủ, nhưng nó là lành tính ở cuối hàng (tùy thuộc vào trình phân tích cú pháp CSV). Nhưng, tất nhiên, nó sẽ gây ra kết quả không có vấn đề nếu nó bị tráo đổi khỏi vị trí cuối hàng hiện tại .

Cập nhật; user121196 đã chỉ ra một lỗi khi dấu phẩy đứng trước dấu ngoặc kép . Đây là cách khắc phục.

Dữ liệu

cat <<'EOF' >file
field one,"fie,ld,two",field"three","field,\",four","field,five
"15111 N. Hayden Rd., Ste 160,",""
EOF

Mật mã

sed -r 's/^/,/; s/\\"/\x01/g; s/,"([^"]*)"/,\x02\1\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g; tMC; s/^,// ' file |
  awk -F, '{ for(i=1; i<=NF; i++) printf "%s\n", $i; print NL}' |
    sed -r 's/\x01/\\"/g; s/(\x02|\x03)/"/g; s/\x04/,/g' 

Đầu ra:

field one
"fie,ld,two"
field"three"
"field,\",four"
"field,five

"15111 N. Hayden Rd., Ste 160,"
""

Đây là bộ lọc trước , mở rộng với ý kiến.
Bộ lọc bài chỉ là một sự đảo ngược của \x01. \x02, \x03,\x04

sed -r '
    s/^/,/                # add a leading comma delimiter
    s/\\"/\x01/g          # obfuscate escaped quotation-mark (\")
    s/,"([^"]*)"/,\x02\1\x03/g    # obfuscate quotation-marks
    s/,"/,\x02/           # when no trailing quote on last field  
    :MC                   # obfuscate commas embedded in quotes
    s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g
    tMC
    s/^,//                # remove spurious leading delimiter
'

Làm thế nào bạn có thể xóa cột thứ n dựa trên bộ lọc này?
dùng121196

@ user121196 - Như đã đề cập trong câu mở đầu, câu trả lời này cho thấy một cách để làm cho dữ liệu CSV phù hợp hơn .. ví dụ: bằng cách thay thế tạm thời dấu phẩy được nhúng bằng ký tự mã thông báo trung tính ... và sau đó hoàn nguyên lại thành dấu phẩy sau khi di chuyển / cắt / xóa. Một lần nữa, như đã đề cập, bước di chuyển / cắt / xóa được thay thế bằng kết xuất trường awk đơn giản .
Peter.O

1
nó không thành công trong trường hợp này: "15111 N. Hayden Rd., Ste 160,", ""
user121196

@ user121196: Cảm ơn bạn đã chỉ ra điều đó. Tôi đã cập nhật câu trả lời với một sửa chữa.
Peter.O

15

Điều này phụ thuộc vào việc tệp CSV của bạn chỉ sử dụng dấu phẩy cho dấu phân cách hoặc nếu bạn có sự điên rồ như:

trường một, "trường, hai", trường ba

Điều này giả sử bạn đang sử dụng tệp CSV đơn giản:

Xóa một cột

Bạn có thể thoát khỏi một cột duy nhất theo nhiều cách; Tôi đã sử dụng cột 2 làm ví dụ. Cách dễ nhất có lẽ là sử dụng cut, cho phép bạn chỉ định một dấu phân cách -dvà trường nào bạn muốn in -f; điều này bảo nó phân chia trên dấu phẩy và trường đầu ra 1 và trường 3 đến hết:

$ cut -d, -f1,3- /path/to/your/file

Nếu bạn thực sự cần sử dụng sed, bạn có thể viết một biểu thức chính quy khớp với các n-1trường đầu tiên , trường nthứ nhất và phần còn lại và bỏ qua đầu ra n(đây nlà 2, vì vậy nhóm đầu tiên được khớp với 1thời gian \{1\}:):

$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)/\1\3/' /path/to/your/file

Có một số cách để làm điều này awk, không có cách nào đặc biệt thanh lịch. Bạn có thể sử dụng một forvòng lặp, nhưng xử lý dấu phẩy là một nỗi đau; bỏ qua rằng nó sẽ là một cái gì đó như:

$ awk -F, '{for(i=1; i<=NF; i++) if(i != 2) printf "%s,", $i; print NL}' /path/to/your/file

Tôi thấy việc xuất trường 1 dễ dàng hơn và sau đó sử dụng substrđể loại bỏ mọi thứ sau trường 2:

$ awk -F, '{print $1 "," substr($0, length($1)+length($2)+3)}' /path/to/your/file

Điều này gây khó chịu cho các cột hơn nữa dọc theo mặc dù

Sao chép một cột

Về sedcơ bản, đây là biểu thức giống như trước đây, nhưng bạn cũng nắm bắt được cột mục tiêu và bao gồm nhóm đó nhiều lần trong thay thế:

$ sed 's/\(\([^,]\+,\)\{1\}\)\([^,]\+,\)\(.*\)/\1\3\3\4/' /path/to/your/file

Theo awkcách lặp for, nó sẽ giống như (một lần nữa bỏ qua dấu phẩy):

$ awk -F, '{
for(i=1; i<=NF; i++) {
    if(i == 2) printf "%s,", $i;
    printf "%s,", $i
}
print NL
}' /path/to/your/file

Các substrcách:

$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file

(tcdyl đã đưa ra một phương pháp tốt hơn trong câu trả lời của mình )

Di chuyển một cột

Tôi nghĩ rằng sedgiải pháp theo tự nhiên từ những người khác, nhưng nó bắt đầu dài ra một cách lố bịch


Đó là một câu trả lời được tải! +1 :)
jaypal singh


12

awklà đặt cược tốt nhất của bạn. awkin các trường theo số, vì vậy ...

awk 'BEGIN { FS=","; OFS=","; } {print $1,$2,$3}' file

Để xóa một cột, không in nó:

 awk 'BEGIN { FS=","; OFS=","; } {print $1,$3}' file

Để thay đổi thứ tự:

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file

Chuyển hướng đến một tập tin đầu ra.

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file > output.file

awk có thể định dạng đầu ra là tốt.

Đầu ra định dạng Awk


Vì đó là CSV, bạn cũng sẽ cần BEGIN { FS=","; OFS=","; }.

1
Tôi nghĩ thậm chí FS = OFS = "," sẽ hoạt động.

5

Cho một tệp được phân tách bằng dấu cách theo định dạng sau:

1 2 3 4 5

Bạn có thể xóa trường 2 bằng awk như vậy:

awk '{ sub($2,""); print}' file

trả về

1  3 4 5

Thay cột 2 bằng cột n khi thích hợp.

Để nhân đôi cột 2,

awk '{ col = $2 " " $2; $2 = col; print }' file

trả về

1 2 2 3 4 5

Để chuyển cột 2 và 3,

awk '{temp = $2; $2 = $3; $3 = temp; print}'

trả về

1 3 2 4 5

awk thường rất giỏi trong việc xử lý khái niệm về các lĩnh vực . Nếu bạn đang xử lý CSV và không phải là tệp được phân tách bằng dấu cách, bạn chỉ cần sử dụng

awk -F,

để xác định trường của bạn dưới dạng dấu phẩy, thay vì dấu cách (là mặc định). Có một số tài nguyên awk tốt trực tuyến, một trong số đó tôi liệt kê dưới dạng nguồn dưới đây.

Nguồn cho # 3


Tôi không biết nhiều về awknó, nhưng dường như đầu ra được phân tách bằng dấu cách ngay cả khi dấu tách trường là ,(dấu tách trường chỉ kiểm soát cách nó xử lý đầu vào)
Michael Mrozek

@MichaelMrozek: vâng, đó là biến OFS awk điều khiển dấu tách trường đầu ra.
enzotib

Có, và như tôi đã đề cập trong câu trả lời của mình, bạn có thể chuyển tùy chọn -F sang awk để thay đổi dấu phân cách (ví dụ -F,)
tcdyl

0

Điều này sẽ làm việc để xóa

awk '{$2="";$0=$0;$1=$1}1'

Đầu vào

a b c d

Đầu ra

a c d
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.