Thay thế chuỗi chứa dòng mới trong tệp lớn


16

Bất cứ ai cũng biết về một công cụ không dựa trên dòng để tìm kiếm / thay thế chuỗi "nhị phân" theo cách có hiệu quả bộ nhớ? Xem câu hỏi này quá.

Tôi có tệp văn bản + 2GB mà tôi muốn xử lý tương tự như những gì điều này dường như làm:

sed -e 's/>\n/>/g'

Điều đó có nghĩa là, tôi muốn xóa tất cả các dòng mới xảy ra sau một >, nhưng không phải bất cứ nơi nào khác, để loại trừ tr -d.

Lệnh này (mà tôi nhận được từ câu trả lời của một câu hỏi tương tự ) không thành công với couldn't re-allocate memory:

sed --unbuffered ':a;N;$!ba;s/>\n/>/g'

Vì vậy, có phương pháp nào khác mà không dùng đến C? Tôi ghét perl, nhưng sẵn sàng tạo một ngoại lệ trong trường hợp này :-)

Tôi không biết chắc chắn về bất kỳ ký tự nào không xảy ra trong dữ liệu, vì vậy việc thay thế tạm thời \nbằng một ký tự khác là điều tôi muốn tránh nếu có thể.

Bất cứ ý tưởng tốt, bất cứ ai?


Bạn đã thử tùy chọn --unbuffered?
ctrl-alt-delor

Có hoặc không --unbufferedhết bộ nhớ
MattBianco

Không gì $!làm gì?
ctrl-alt-delor

Có gì sai với lệnh sed đầu tiên. Thứ hai dường như đang đọc mọi thứ vào không gian mẫu, tôi không biết rằng đó $!là mặc dù. Này, tôi hy vọng sẽ cần một LOT của bộ nhớ.
ctrl-alt-delor

Vấn đề là sed đọc mọi thứ dưới dạng dòng, đó là lý do tại sao lệnh đầu tiên không xóa dòng mới, vì nó xuất ra hàng văn bản một lần nữa. Lệnh thứ hai chỉ là một cách giải quyết. Tôi nghĩ rằng sedkhông phải là công cụ thích hợp trong trường hợp này.
MattBianco

Câu trả lời:


14

Điều này thực sự là tầm thường trong Perl, bạn không nên ghét nó!

perl -i.bak -pe 's/>\n/>/' file

Giải trình

  • -i: chỉnh sửa tệp tại chỗ và tạo bản sao lưu của bản gốc được gọi file.bak. Nếu bạn không muốn sao lưu, chỉ cần sử dụng perl -i -pethay thế.
  • -pe: đọc từng dòng tệp đầu vào và in từng dòng sau khi áp dụng tập lệnh được cung cấp dưới dạng -e.
  • s/>\n/>/: sự thay thế, giống như sed.

Và đây là một awkcách tiếp cận:

awk  '{if(/>$/){printf "%s",$0}else{print}}' file2 

3
+1. awk golf:awk '{ORS=/>$/?"":"\n"}1'
glenn jackman

1
Tại sao tôi không thích perl nói chung là lý do tương tự tại sao tôi chọn câu trả lời này (hoặc thực sự là nhận xét của bạn cho câu trả lời của Gnouc): khả năng đọc. Sử dụng perl -pe với một "mẫu sed" đơn giản là cách dễ đọc hơn một biểu thức sed phức tạp.
MattBianco

3
@MattBianco đủ công bằng nhưng, chỉ để bạn biết, điều đó không liên quan gì đến Perl. Giao diện mà Gnouc sử dụng là một tính năng của một số ngôn ngữ biểu thức chính quy (bao gồm, nhưng không giới hạn ở PCRE), không phải lỗi của Perl. Ngoài ra, sau khi thể hiện sự quái dị này ':a;N;$!ba;s/>\n/>/g'trong câu hỏi của bạn, bạn đã từ bỏ quyền khiếu nại về khả năng đọc! : P
terdon

@glennjackman tốt đẹp! Tôi đã chơi với foo ? bar : bazcấu trúc nhưng không thể làm cho nó hoạt động.
terdon

@terdon: Yeap, lỗi của tôi. Xóa đi.
cuonglm

7

Một perlgiải pháp:

$ perl -pe 's/(?<=>)\n//'

Giải thích

  • s/// được sử dụng để thay thế chuỗi.
  • (?<=>) là mô hình lookbehind.
  • \n phù hợp với dòng mới.

Toàn bộ ý nghĩa mẫu loại bỏ tất cả các dòng mới có >trước nó.


2
quan tâm để nhận xét những phần của chương trình làm gì? Tôi luôn tìm cách học hỏi.
MattBianco

2
Tại sao phải bận tâm với cái nhìn? Tại sao không chỉ s/>\n/>/?
terdon

1
hoặc s/>\K\n//cũng sẽ làm việc
glenn jackman

@terdon: Chỉ là điều đầu tiên tôi mặc dù, loại bỏ thay vì thay thế
cuonglm

@glennjackman: điểm tốt!
cuonglm

3

Còn cái này thì sao:

sed ':loop
  />$/ { N
    s/\n//
    b loop
  }' file

Đối với GNU sed, bạn cũng có thể thử thêm tùy chọn -u( --unbuffered) theo câu hỏi. GNU sed cũng hài lòng với điều này như một lớp lót đơn giản:

sed ':loop />$/ { N; s/\n//; b loop }' file

Điều đó sẽ không xóa lần cuối \nnếu tập tin kết thúc >\n, nhưng dù sao thì điều đó có lẽ tốt hơn.
Stéphane Chazelas

@ StéphaneChazelas, tại sao việc đóng lại }cần phải ở một biểu thức riêng biệt? điều này sẽ không hoạt động như một biểu thức multiline?
Graeme

1
Điều đó sẽ hoạt động trong các sed POSIX có b loop\n}hoặc -e 'b loop' -e '}'không b loop;}và chắc chắn là không và b loop}vì nó hợp lệ trong các tên nhãn (mặc dù không ai trong tâm trí của họ sẽ sử dụng nó. Và điều đó có nghĩa là GNU sed không tuân thủ POSIX) và lệnh cần phải được tách ra từ lệnh. };}b
Stéphane Chazelas

@ StéphaneChazelas, GNU sedhài lòng với tất cả những điều trên ngay cả với --posix! Các tiêu chuẩn cũng có sau đây cho biểu thức niềng răng - The list of sed functions shall be surrounded by braces and separated by <newline>s. Điều này không có nghĩa là dấu chấm phẩy chỉ nên được sử dụng bên ngoài niềng răng?
Graeme

@mikeerv, vòng lặp là cần thiết để xử lý các dòng liên tiếp kết thúc bằng >. Bản gốc không bao giờ có một, điều này đã được Stéphane chỉ ra.
Graeme

1

Bạn sẽ có thể sử dụng sedvới Nlệnh, nhưng mẹo sẽ là xóa một dòng khỏi không gian mẫu mỗi lần bạn thêm một dòng khác (để không gian mẫu luôn chỉ chứa 2 dòng liên tiếp, thay vì cố gắng đọc toàn bộ tập tin) - thử

sed ':a;$!N;s/>\n/>/;P;D;ba'

EDIT: sau khi đọc lại bài hát Một người nổi tiếng quyến rũ của Peteris Krumins Giải thích tôi tin rằng một sedgiải pháp tốt hơn sẽ là

sed -e :a -e '/>$/N; s/\n//; ta'

trong đó chỉ nối dòng sau trong trường hợp đã >kết thúc khớp và nên lặp lại một cách có điều kiện để xử lý trường hợp các dòng khớp liên tiếp (đó là 39 của Krumin. Nối một dòng vào dòng tiếp theo nếu kết thúc bằng dấu gạch chéo ngược "\" chính xác ngoại trừ việc thay thế >cho \là ký tự nối và thực tế là ký tự nối được giữ lại ở đầu ra).


2
Điều đó không hoạt động nếu 2 dòng liên tiếp kết thúc >(cũng cụ thể là GNU)
Stéphane Chazelas

1

sedkhông cung cấp cách phát ra đầu ra mà không có dòng mới. Cách tiếp cận của bạn sử dụng Ncác công việc cơ bản, nhưng lưu trữ các dòng không đầy đủ trong bộ nhớ và do đó có thể thất bại nếu các dòng trở nên quá dài (ngụ ý sed thường không được thiết kế để xử lý các dòng cực dài).

Bạn có thể sử dụng awk thay thế.

awk '{if (/<$/) printf "%s", $0; else print}'

Một cách tiếp cận khác là sử dụng trđể hoán đổi nhân vật dòng mới với một nhân vật nhàm chán, thường xuyên xuất hiện. Không gian có thể hoạt động ở đây - chọn một ký tự có xu hướng xuất hiện trên mỗi dòng hoặc ít nhất là một tỷ lệ lớn các dòng trong dữ liệu của bạn.

tr ' \n' '\n ' | sed 's/> />/g' | tr '\n ' ' \n'

Cả hai phương pháp đã được chứng minh ở đây để có hiệu quả tốt hơn trong các câu trả lời khác. Và cách tiếp cận của anh sedta không hoạt động nếu không có bộ đệm 2,5gabyte.
mikeerv

Có ai nhắc đến awk không? Ồ, tôi đã bỏ lỡ nó, tôi chỉ nhận thấy perl trong câu trả lời của terdon vì một số lý do. Không ai đề cập đến trcách tiếp cận - mikeerv, bạn đã đăng một cách tiếp cận khác (hợp lệ, nhưng ít chung chung) cũng xảy ra để sử dụng tr.
Gilles 'SO- ngừng trở nên xấu xa'

Đối với tôi, âm thanh hợp lệ nhưng ít chung chung như bạn vừa gọi nó là một giải pháp được nhắm mục tiêu hoạt động. Tôi nghĩ thật khó để tranh luận rằng một thứ như vậy không hữu ích , điều này thật kỳ quặc bởi vì nó có 0 upvote. Sự khác biệt lớn nhất tôi có thể thấy giữa giải pháp của riêng tôi và đề nghị chung chung hơn của bạn , đó là giải pháp của tôi đặc biệt giải quyết vấn đề, trong khi nói chung là của bạn . Điều đó có thể làm cho nó đáng giá - và tôi thậm chí có thể đảo ngược phiếu bầu của mình - nhưng cũng có vấn đề rắc rối trong 7 giờ giữa họ và chủ đề định kỳ của câu trả lời của bạn bắt chước người khác. Bạn có thể giải thích điều này?
mikeerv



-1

Có rất nhiều cách để làm điều này, và hầu hết ở đây thực sự tốt, nhưng tôi nghĩ đây là cách yêu thích của tôi:

tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'

Hoặc thậm chí:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n'

Tôi không thể có câu trả lời đầu tiên của bạn để làm việc cả. Trong khi tôi ngưỡng mộ sự thanh lịch của cái thứ hai, tôi tin rằng bạn cần phải loại bỏ *. Hiện tại, nó sẽ xóa bất kỳ dòng trống nào sau một dòng kết thúc bằng a >. Mạnh Hmm. Nhìn lại câu hỏi, tôi thấy rằng nó hơi mơ hồ. Câu hỏi nói rằng, tôi muốn xóa tất cả các dòng mới xảy ra sau một >, phạm lỗi tôi giải thích điều đó có nghĩa là >\n\n\n\n\nfoonên thay đổi thành \n\n\n\nfoo, nhưng tôi cho rằng foocó thể là đầu ra mong muốn.
Scott

@Scott - Tôi đã thử nghiệm với các biến thể sau: printf '>\n>\n\n>>\n>\n>>>\n>\nf\n\nff\n>\n' | tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'- kết quả >>>>>>>>>>f\n\nff\n\ncho tôi với câu trả lời đầu tiên. Tôi tò mò mặc dù những gì bạn đang làm để phá vỡ nó, bởi vì tôi muốn sửa nó. Về điểm thứ hai - tôi không đồng ý rằng nó không rõ ràng. OP không yêu cầu để loại bỏ tất cả > trước một \newline, nhưng thay vì để loại bỏ tất cả \n ewlines sau một >.
mikeerv

1
Có, nhưng một cách giải thích hợp lệ là, trong >\n\n\n\n\n, chỉ có dòng mới đầu tiên là sau a >; tất cả những người khác đang theo dõi các dòng mới khác. Lưu ý rằng OP của OP đây là những gì tôi muốn, nếu chỉ có nó hoạt động thì đề xuất này là sed -e 's/>\n/>/g'không sed -e 's/>\n*/>/g'.
Scott

1
@Scott - đề xuất không hoạt động và không bao giờ có thể. Tôi không tin rằng đề xuất mã của một người không hiểu đầy đủ về mã có thể được coi là một điểm phiên dịch hợp lệ như ngôn ngữ đơn giản mà người đó cũng sử dụng. Và bên cạnh đó, đầu ra - nếu nó thực sự làm việc - của s/>\n/>/trên >\n\n\n\n\nsẽ vẫn là cái gì đó s/>\n/>/sẽ sửa.
mikeerv
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.