sed: đọc toàn bộ tệp vào không gian mẫu mà không bị lỗi khi nhập một dòng


9

Đọc toàn bộ tệp vào không gian mẫu rất hữu ích để thay thế các dòng mới, & c. và có nhiều trường hợp tư vấn như sau:

sed ':a;N;$!ba; [commands...]'

Tuy nhiên, nó không thành công nếu đầu vào chỉ chứa một dòng.

Ví dụ, với hai dòng đầu vào, mọi dòng đều phải tuân theo lệnh thay thế:

$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt

Nhưng, với đầu vào dòng đơn, không có sự thay thế nào được thực hiện:

$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc

Làm thế nào để một người viết một sedlệnh để đọc tất cả các đầu vào cùng một lúc và không có vấn đề này?


Tôi đã chỉnh sửa câu hỏi của bạn để nó chứa một câu hỏi thực tế. Bạn có thể đợi câu trả lời khác nếu bạn thích nhưng cuối cùng đánh dấu câu trả lời tốt nhất là được chấp nhận (Xem nút ống ở bên trái câu trả lời, ngay bên dưới các nút mũi tên lên xuống).
John1024

@ John1024 Cảm ơn, tốt để có một ví dụ. Tìm thấy loại điều này có xu hướng nhắc nhở tôi rằng "mọi thứ đều sai" nhưng tôi vui vì một số người trong chúng ta không từ bỏ. :}
Dicktyr

2
Có một lựa chọn thứ ba! Sử dụng sed -ztùy chọn của GNU . Nếu tệp của bạn không có giá trị, nó sẽ đọc cho đến khi kết thúc tệp! Tìm thấy từ đây: stackoverflow.com/a/30049447/582917
CMCDragonkai

Câu trả lời:


13

Có tất cả các loại lý do tại sao đọc toàn bộ tệp vào không gian mẫu có thể sai. Vấn đề logic trong câu hỏi xung quanh dòng cuối cùng là một vấn đề phổ biến. Nó có liên quan đến sedchu kỳ dòng - khi không còn dòng nào nữa và sedgặp EOF thì nó đã qua - nó thoát khỏi quá trình xử lý. Và vì vậy, nếu bạn đang ở dòng cuối cùng và bạn hướng dẫn sedđể có được một thứ khác, nó sẽ dừng ngay tại đó và không làm gì nữa.

Điều đó nói rằng, nếu bạn thực sự cần phải đọc toàn bộ tệp vào không gian mẫu, thì có lẽ đáng để xem xét một công cụ khác. Thực tế là, sedcó nghĩa là trình soạn thảo luồng - nó được thiết kế để hoạt động một dòng - hoặc một khối dữ liệu logic - tại một thời điểm.

Có nhiều công cụ tương tự được trang bị tốt hơn để xử lý các khối tệp đầy đủ. edex, ví dụ, có thể thực hiện nhiều việc sedcó thể làm và với cú pháp tương tự - và nhiều thứ khác bên cạnh - nhưng thay vì chỉ hoạt động trên luồng đầu vào trong khi chuyển đổi nó thành đầu ra sed, chúng cũng duy trì các tệp sao lưu tạm thời trong hệ thống tệp . Công việc của họ được đệm vào đĩa khi cần và họ không thoát đột ngột vào cuối tệp (và có xu hướng nổ tung ít thường xuyên hơn dưới sự căng thẳng của bộ đệm) . Ngoài ra, họ cung cấp nhiều chức năng hữu ích mà sedkhông - thuộc loại đơn giản là không có ý nghĩa trong ngữ cảnh luồng - như dấu dòng, hoàn tác, bộ đệm được đặt tên, tham gia, v.v.

sedThế mạnh chính của nó là khả năng xử lý dữ liệu ngay khi đọc nó - nhanh chóng, hiệu quả và trong luồng. Khi bạn nhét một tập tin bạn vứt nó đi bạn có xu hướng gặp phải những khó khăn trong trường hợp như vấn đề dòng cuối cùng mà bạn đề cập, và bộ đệm tràn ngập, và hiệu suất kinh khủng - vì dữ liệu mà nó phân tích tăng theo thời gian xử lý của công cụ regrec khi liệt kê các kết quả khớp tăng theo cấp số nhân .

Về điểm cuối cùng, nhân tiện: trong khi tôi hiểu s/a/A/gtrường hợp ví dụ rất có thể chỉ là một ví dụ ngây thơ và có lẽ không phải là kịch bản thực tế mà bạn muốn thu thập trong một đầu vào, bạn có thể thấy nó đáng để bạn làm quen với y///. Nếu bạn thường thấy mình gthay thế một nhân vật cho một nhân vật khác, thì ycó thể rất hữu ích cho bạn. Nó là một sự biến đổi trái ngược với sự thay thế và nhanh hơn rất nhiều vì nó không bao hàm một biểu thức chính quy. Điểm thứ hai này cũng có thể làm cho nó hữu ích khi cố gắng giữ và lặp lại các //địa chỉ trống vì nó không ảnh hưởng đến chúng nhưng có thể bị ảnh hưởng bởi chúng. Trong mọi trường hợp, y/a/A/là một phương tiện đơn giản hơn để thực hiện tương tự - và hoán đổi cũng có thể như:y/aA/Aa/ mà sẽ trao đổi tất cả chữ hoa / chữ thường như trên một dòng cho nhau.

Bạn cũng nên lưu ý rằng hành vi bạn mô tả thực sự không phải là những gì được cho là xảy ra.

Từ GNU info sedtrong phần BUGS BÁO CÁO GIAO DỊCH :

  • N lệnh trên dòng cuối cùng

    • Hầu hết các phiên bản sedthoát mà không in bất cứ điều gì khi Nlệnh được ban hành trên dòng cuối cùng của tệp. GNU sedin không gian mẫu trước khi thoát trừ khi tất nhiên công -ntắc lệnh đã được chỉ định. Sự lựa chọn này là do thiết kế.

    • Ví dụ, hành vi của sed N foo barsẽ phụ thuộc vào việc foo có số dòng chẵn hay số lẻ. Hoặc, khi viết một kịch bản để đọc một vài dòng tiếp theo sau một mẫu phù hợp, việc triển khai truyền thống sedsẽ buộc bạn phải viết một cái gì đó giống như /foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }thay vì chỉ /foo/{ N;N;N;N;N;N;N;N;N; }.

    • Trong mọi trường hợp, cách giải quyết đơn giản nhất là sử dụng $d;Ntrong các tập lệnh dựa trên hành vi truyền thống hoặc đặt POSIXLY_CORRECTbiến thành giá trị không trống.

Biến POSIXLY_CORRECTmôi trường được đề cập vì POSIX chỉ định rằng nếu sedgặp EOF khi thử, Nnó sẽ thoát mà không có đầu ra, nhưng phiên bản GNU cố tình phá vỡ tiêu chuẩn trong trường hợp này. Cũng lưu ý rằng ngay cả khi hành vi được chứng minh ở trên giả định là trường hợp lỗi là một trong những chỉnh sửa luồng - không đưa toàn bộ tệp vào bộ nhớ.

Do đó, tiêu chuẩn xác định Nhành vi của:

  • N

    • Nối dòng đầu vào tiếp theo, trừ \newline kết thúc của nó vào không gian mẫu, sử dụng \newline nhúng để tách vật liệu được nối với vật liệu ban đầu. Lưu ý rằng số dòng hiện tại thay đổi.

    • Nếu không có dòng đầu vào tiếp theo khả dụng, Nđộng từ lệnh sẽ phân nhánh đến cuối tập lệnh và thoát mà không bắt đầu một chu kỳ mới hoặc sao chép không gian mẫu vào đầu ra tiêu chuẩn.

Trên lưu ý đó, có một số GNU-isms khác được thể hiện trong câu hỏi - đặc biệt là việc sử dụng :nhãn, btrang trại và {dấu ngoặc theo ngữ cảnh chức năng }. Như một quy tắc tự nhiên, bất kỳ sedlệnh nào chấp nhận một tham số tùy ý được hiểu là phân định tại một \newline trong tập lệnh. Vì vậy, các lệnh ...

:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...

... Tất cả đều rất có khả năng thực hiện thất thường tùy thuộc vào việc sedtriển khai đọc chúng. Có thể viết chúng:

...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}

Điều này cũng đúng đối với r, w, t, a, i, và c (và có thể một vài chi tiết mà tôi quên tại thời điểm này) . Trong hầu hết mọi trường hợp, chúng cũng có thể được viết:

sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
    "//{ do arbitrary list of commands" -e \}

... Trong đó -ecâu lệnh xecut mới là viết tắt của \ndấu phân cách ewline. Vì vậy, nơi infovăn bản GNU gợi ý cách triển khai truyền thống sedsẽ buộc bạn phải thực hiện :

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }

... đúng hơn là ...

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}

... Tất nhiên, điều đó cũng không đúng. Viết kịch bản theo cách đó là một chút ngớ ngẩn. Có nhiều cách đơn giản hơn để làm tương tự, như:

printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
         //!g;x;$!d;:nd' -e 'l;$a\' \
     -e 'this is the last line' 

... mà in:

foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line

... bởi vì tlệnh est - giống như hầu hết sedcác lệnh - phụ thuộc vào chu kỳ dòng để làm mới thanh ghi trả về của nó và ở đây chu trình dòng được phép thực hiện hầu hết công việc. Đó là một sự đánh đổi khác mà bạn thực hiện khi bạn nhét một tệp - chu trình dòng không được làm mới một lần nữa và rất nhiều bài kiểm tra sẽ hoạt động bất thường.

Lệnh trên không có nguy cơ vượt quá đầu vào vì nó chỉ thực hiện một số thử nghiệm đơn giản để xác minh những gì nó đọc khi đọc. Với Hcũ, tất cả các dòng được gắn vào không gian giữ, nhưng nếu một dòng khớp với /foo/nó sẽ ghi đè lên hkhông gian cũ. Các bộ đệm được xthay đổi tiếp theo và một s///ubstlation có điều kiện được thử nếu nội dung của bộ đệm khớp với //mẫu cuối cùng được xử lý. Nói cách khác, //s/\n/&/3pcố gắng thay thế dòng mới thứ ba trong không gian giữ bằng chính nó và in kết quả nếu không gian giữ hiện tại khớp /foo/. Nếu điều đó tthành công, tập lệnh sẽ chuyển sang nhãn not delete - một ltập lệnh và kết thúc tập lệnh.

Trong trường hợp cả hai /foo/và một dòng mới thứ ba không thể được khớp với nhau trong không gian giữ, sau đó //!gsẽ ghi đè lên bộ đệm nếu /foo/không khớp, hoặc, nếu nó được khớp, nó sẽ ghi đè lên bộ đệm nếu \newline không khớp (do đó thay thế /foo/bằng chính nó) . Thử nghiệm tinh tế nhỏ này giữ cho bộ đệm không bị lấp đầy một cách không cần thiết trong thời gian dài không /foo/và đảm bảo quá trình vẫn ổn định vì đầu vào không chồng chất. Tiếp theo trong trường hợp không /foo/hoặc //s/\n/&/3pkhông thành công, bộ đệm lại được hoán đổi và mỗi dòng cuối cùng đều bị xóa.

Cuối cùng - dòng cuối cùng $!d- là một minh chứng đơn giản về cách một sedkịch bản từ trên xuống có thể được thực hiện để xử lý nhiều trường hợp một cách dễ dàng. Khi phương pháp chung của bạn là loại bỏ các trường hợp không mong muốn bắt đầu bằng cách chung nhất và xử lý các trường hợp cụ thể nhất thì các trường hợp cạnh có thể dễ dàng xử lý hơn vì chúng chỉ đơn giản được phép rơi vào cuối tập lệnh với dữ liệu mong muốn khác của bạn và khi nào tất cả kết thúc tốt đẹp bạn chỉ còn lại dữ liệu bạn muốn. Tuy nhiên, việc phải lấy các trường hợp cạnh như vậy ra khỏi một vòng khép kín có thể khó thực hiện hơn nhiều.

Và đây là điều cuối cùng tôi phải nói: nếu bạn thực sự phải lấy toàn bộ tập tin, thì bạn có thể làm ít việc hơn bằng cách dựa vào chu trình dòng để làm điều đó cho bạn. Thông thường, bạn sẽ sử dụng Next và next cho lookahead - bởi vì chúng đi trước chu kỳ dòng. Thay vì thực hiện dự phòng một vòng khép kín trong một vòng lặp - vì seddù sao thì chu trình dòng chỉ là một vòng đọc đơn giản - nếu mục đích của bạn chỉ là thu thập dữ liệu đầu vào một cách bừa bãi, thì có lẽ dễ thực hiện hơn:

sed 'H;1h;$!d;x;...'

... sẽ tập hợp toàn bộ tập tin hoặc cố gắng phá sản.


một ghi chú bên lề Nvà hành vi cuối cùng ...

trong khi tôi không có sẵn các công cụ để kiểm tra, hãy xem xét rằng Nkhi đọc và chỉnh sửa tại chỗ sẽ hoạt động khác đi nếu tệp được chỉnh sửa là tệp script cho lần đọc tiếp theo.


1
Đặt điều kiện Hđầu tiên là đáng yêu.
jthill

@mikeerv Cảm ơn bạn đã đóng góp. Tôi có thể thấy lợi ích tiềm năng trong việc duy trì chu kỳ dòng, nhưng làm thế nào nó ít hoạt động hơn?
Dicktyr

@dicktyr tốt, cú pháp sử dụng một số phím tắt :a;$!{N;ba}như tôi đã đề cập ở trên - việc sử dụng biểu mẫu tiêu chuẩn trong thời gian dài sẽ dễ dàng hơn khi bạn cố chạy regexps trên các hệ thống lạ. Nhưng đó không thực sự là điều tôi muốn nói: Bạn thực hiện một vòng khép kín - bạn không thể dễ dàng đi vào giữa đó khi bạn muốn như bạn có thể thay vào đó bằng cách phân nhánh - cắt tỉa dữ liệu không mong muốn - và để chu kỳ xảy ra. Nó giống như một thứ từ trên xuống - mọi thứ đều sedlà kết quả trực tiếp của những gì nó vừa làm. Có thể bạn thấy nó khác đi - nhưng nếu bạn thử nó, bạn có thể thấy kịch bản đến dễ dàng hơn.
mikeerv

11

Không thành công vì Nlệnh xuất hiện trước khớp mẫu $!(không phải dòng cuối cùng) và thoát khỏi sed trước khi thực hiện bất kỳ công việc nào:

N

Thêm một dòng mới vào không gian mẫu, sau đó nối dòng đầu vào tiếp theo vào không gian mẫu. Nếu không có thêm đầu vào thì sed thoát ra mà không xử lý thêm lệnh nào .

Điều này có thể dễ dàng được sửa để làm việc với đầu vào một dòng (và thực sự rõ ràng hơn trong mọi trường hợp) bằng cách đơn giản nhóm Nbcác lệnh sau mẫu:

sed ':a;$!{N;ba}; [commands...]'

Nó hoạt động như sau:

  1. :a tạo nhãn có tên 'a'
  2. $! nếu không phải là dòng cuối cùng, thì
  3. Nnối dòng tiếp theo vào không gian mẫu (hoặc thoát nếu không có dòng tiếp theo) và banhãn (đi đến) nhãn 'a'

Thật không may, nó không khả dụng (vì nó phụ thuộc vào các phần mở rộng GNU), nhưng giải pháp thay thế sau (được đề xuất bởi @mikeerv) là di động:

sed 'H;1h;$!d;x; [commands...]'

Tôi đã đăng nó ở đây vì tôi không tìm thấy thông tin ở nơi khác và tôi muốn làm cho nó có sẵn để những người khác có thể tránh rắc rối với sự phổ biến :a;N;$!ba;.
Dicktyr

Cảm ơn vì đăng! Hãy nhớ rằng chấp nhận câu trả lời của riêng bạn cũng tốt. Bạn chỉ cần đợi một lúc trước khi hệ thống cho phép bạn làm điều đó.
terdon
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.