Làm cách nào tôi có thể trích xuất / thay đổi dòng trong tệp văn bản có dữ liệu được tách thành các trường?


9

Làm cách nào tôi có thể thao tác dữ liệu dựa trên trường từ dòng lệnh? Ví dụ

  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ N foo?
  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ N không foo?
  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ N khớp foo?
  • Làm cách nào để thay đổi trường N thành foo?

Có một cách tiếp cận tiêu chuẩn hoặc bộ công cụ hỗ trợ thao tác dữ liệu dựa trên trường trên các hệ thống * nix không?



4
Không, đây không phải là một bản dupe. Nó liên quan đến trường hợp cụ thể của dữ liệu được tổ chức trong các trường và không chỉ về việc thay thế. Nó cũng là về việc in một tập hợp con của tập tin.
terdon

Tách vào các lĩnh vực như thế nào? Câu trả lời gợi ý giới hạn bởi dấu phẩy (csv), tab, dấu cách hoặc tương tự. Tôi nghĩ rằng một từ dành cho các thư viện chuyên ngành như gấu trúc của R hoặc Python chuyển đổi các tệp csv, v.v. thành các khung dữ liệu cũng theo thứ tự. Đó có lẽ là những giải pháp tốt nhất tổng thể. Và vâng, tôi biết nó nói "từ dòng lệnh".
Faheem Mitha

@FaheemMitha Tôi chỉ muốn đưa ra một đoạn trích giới thiệu các khả năng của các công cụ như awk, perl và sed để xử lý dữ liệu dựa trên trường. Tôi không muốn bao gồm nhiều kỹ thuật nâng cao hơn trong câu trả lời của mình vì tôi muốn giữ cho nó đơn giản. Tôi muốn đọc nó mặc dù vậy, vui lòng thêm câu trả lời của riêng bạn bằng R hoặc python.
terdon

Câu trả lời:


9

Có hai cách tiếp cận cơ bản người ta có thể sử dụng khi xử lý các trường: i) sử dụng một công cụ hiểu các trường; ii) sử dụng biểu thức chính quy. Trong hai, trước đây thường mạnh mẽ hơn và đơn giản hơn.

Nhiều công cụ phổ biến có sẵn trên * nix được thiết kế rõ ràng để xử lý các trường hoặc có các thủ thuật tiện lợi để tạo điều kiện thuận lợi cho nó.

1. Sử dụng một công cụ hiểu các lĩnh vực

1.1

Công cụ cổ điển ở đây là awk. Nó sẽ tự động tách mỗi dòng đầu vào vào các lĩnh vực (tách lĩnh vực là khoảng trắng theo mặc định nhưng có thể được thay đổi bằng cách sử dụng -Flá cờ) và các lĩnh vực này sau đó được sẵn sàng cho awkkịch bản như nơi là số lĩnh vực. Trường thứ 1 là , trường thứ hai, v.v.$nn$1$2

  • Dòng in có trường thứ 3 là foo.

    awk '$3=="foo"' file

    Thay đổi dấu phân cách thành :

    awk -F":" '$3=="foo"' file

    Hành động mặc định awklà in. Do đó, các lệnh trên sẽ in tất cả các dòng có trường thứ 3 foo. Khi sử dụng -F, bạn có thể đặt các dấu tách trường tùy ý và thậm chí sử dụng các biểu thức thông thường.

  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 3 không foo?

    awk '$3!="foo"' file
  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 3 khớp foo?

    Nếu bạn chỉ tìm kiếm các trường khớp với một mẫu (ví dụ: fookhớp foobar), hãy sử dụng ~thay vì ==:

    awk '$3~/foo/' file
  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 3 không khớp foo?

    awk '$3!~/foo/' file
  • Làm cách nào để thay đổi trường thứ 3 thành foo?

    awk '$3="foo"' file

1,2 Perl

Một sự lựa chọn khác là perlmột lớp lót. Giống như awk, Perl là một ngôn ngữ kịch bản đầy đủ tính năng nhưng cũng có thể được chạy như một chương trình dòng lệnh lấy kịch bản làm đầu vào. Hành vi của nó được sửa đổi bằng các công tắc dòng lệnh, liên quan nhất đến câu hỏi này là:

  • -e: tập lệnh perlnên chạy;
  • -n : đọc dòng tệp đầu vào theo dòng;
  • -p: in từng dòng đầu vào sau khi áp dụng tập lệnh được cung cấp bởi -e;
  • -l: xóa các dòng mới theo dõi từ mỗi dòng đầu vào và thêm một dòng mới vào mỗi printcuộc gọi;
  • -a: awk-mode, chia từng dòng đầu vào thành mảng @F;
  • -F: dấu phân cách trường cho -a.

Một sự khác biệt quan trọng awklà công tắc perlcủa nó -achia các tệp thành một mảng. Trong Perl, mảng bắt đầu từ 0, không phải 1. Điều này có nghĩa là trường thứ 2 thực sự $F[1]và không $F[2]. Với tất cả những điều này, perltương đương với những điều trên là:

  • Dòng in có trường thứ 3 là foo.

    perl -ane 'print if $F[2] eq "foo"' file

    Thay đổi dấu phân cách thành :

    perl -F":" -ane 'print if $F[2] eq "foo"' file

    Không giống như awk, perlkhông thể sử dụng biểu thức chính quy làm dấu phân cách trường. Họ cần phải là một nhân vật hoặc chuỗi cụ thể.

  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 3 không foo?

    perl -ane 'print unless $F[2] eq "foo"' file
  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 3 khớp foo?

    perl -ane 'print if $F[2]=~/foo/' file
  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 3 không khớp foo?

    perl -lane 'print unless $F[2]=~/foo/' file
  • Làm cách nào để thay đổi trường thứ 3 thành foo?

    Điều này là một chút rườm rà hơn trong Perl. Cách tiếp cận thông thường là thay đổi giá trị trong @Fmảng và sau đó in mảng. Với các tệp được phân tách bằng dấu cách đơn giản, điều này thật dễ dàng:

    perl -lane '$F[2]="foo"; print "@F"' file

    Với một dấu phân cách khác, bạn sẽ cần đến joinmảng. Nếu không, nó sẽ được in tách biệt không gian:

    perl -F: -lane '$F[2]="foo"; print join ":",@F' file

2. Sử dụng biểu thức chính quy

Ý tưởng ở đây là sử dụng một biểu thức chính quy (viết tắt là "regex") để xác định vị trí của chuỗi mục tiêu trong dòng. Ví dụ: trong một tệp có các trường được phân tách bằng nhau :, chúng ta có thể tìm trường thứ 2 bằng cách khớp mọi thứ với trường thứ 1 :(trường thứ 1) và sau đó tìm kiếm trường thứ hai:

^[^:]*:[^:]*:

Regex này có nghĩa là:

  • ^ : đầu dòng;
  • [^]: một lớp nhân vật phủ định. [^:]có nghĩa là "bất cứ điều gì nhưng :";
  • * : 0 hoặc nhiều hơn mẫu trước đó;
  • :: một nghĩa đen :;

Được kết hợp với nhau, điều này có nghĩa là trường đầu tiên [^:]*là trường thứ nhất và trường thứ hai là trường thứ hai. Rõ ràng, điều này không thực tế lắm nếu bạn đang tìm kiếm trường thứ 14 nhưng nó có thể hữu ích cho những điều đơn giản hơn. Vì vậy, làm thế nào để chúng tôi thực hiện điều này để thao túng dữ liệu của chúng tôi? Có nhiều công cụ khác nhau có thể làm điều này; trong những ví dụ này tôi sẽ sử dụng sednhưng bạn có thể làm những việc rất giống với awk, perlhoặc python.

  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 2 foo?

    sed -n '/^[^:]*:foo:/p' file

    Việc -nloại bỏ đầu ra bình thường và /regex/pcó nghĩa là "in bất kỳ dòng nào mà biểu thức chính quy phù hợp.

  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 2 không foo?

    sed '/^[^:]*:foo:/d' file

    Các nghịch đảo logic của ở trên. Ở đây, /regex/dcó nghĩa là "xóa bất kỳ dòng nào mà regex khớp.

  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 2 khớp foo?

    sed -n '/^[^:]*:[^:]*foo/p' file
  • Làm cách nào tôi chỉ có thể in các dòng có trường thứ 2 không khớp foo?

    sed '/^[^:]*:[^:]*foo/d' file
  • Làm cách nào để thay đổi trường thứ 2 thành foo?

    sed 's/\([^:]*:\)[^:]*/\1foo/' file 

    Hoặc, vì sedsự thay thế có thể trực tiếp giải quyết một sự xuất hiện của các mẫu bằng sự lặp lại của nó với một cờ số đơn giản:

    sed 's/[^:]*/foo/2' file

(1) Có bất kỳ tiêu chuẩn, vườn nhiều * nix công cụ mà hiểu delimiters nhúng trong các lĩnh vực, ví dụ như, Capt. Kirk, Mr. Spock, "Dr. McCoy, MD", Scotty? (2) Tôi gặp rắc rối bởi thực tế là (i) → 2 và (ii) → 1 , nhưng tôi không muốn thay đổi bất cứ điều gì quan trọng. (3) Nếu đây đang được đề cử là một câu hỏi kinh điển, nó không nên liên kết đến một số tài liệu tham khảo kỹ lưỡng và có thẩm quyền về biểu thức thông thường (ví dụ, Wikipedia , Regular-Expressions.info , RegexPlanet , vv)?
G-Man nói 'Phục hồi Monica'

@ G-Man 1) không xa như tôi biết 2) đủ công bằng, đã sửa 3) Tôi chỉ muốn viết một đoạn mồi đơn giản về hai cách tiếp cận chính để xử lý các trường. Tôi không muốn đi sâu vào chi tiết và chắc chắn nó không nhằm mục đích trở thành một tài liệu tham khảo về các biểu thức thông thường. Cảm ơn vì đã ăn táo bằng cách này :)
terdon

Trong trường hợp bạn quan tâm: s/:[^:]*/:foo/cũng hoạt động., Nhưng backref có giá trị bao gồm trong một bài viết kinh điển về regrec , có lẽ.
mikeerv

@ G-Man - sedcó thể thực hiện các dấu phân cách được nhúng và thực sự, bất kỳ công cụ có khả năng BRE hoàn toàn nào cũng có thể. Với danh sách mà bạn đã đưa ra, đang thực hiện: sed 's/[^,"]*\("[^"]*\)\{0,1\}[^,]*,//;s///2' <listsẽ in Mr. Spock, Scotty (với khoảng trắng ở đầu) vì thay thế đầu tiên sẽ loại bỏ trường đầu tiên và lần thứ hai xóa Bones.
mikeerv

Bạn nói Unlike awk, perl can't use regular expressions as field delimiters. - điều này chỉ đúng nếu bạn sử dụng -ađể tự động phân tách. Bạn vẫn có thể sử dụng split()chính mình nếu bạn muốn. ví dụperl -p -e 'my @F = split /regexp/ ; print if $F[2] =~ /foo/'
cas
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.