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 sed
chu kỳ dòng - khi không còn dòng nào nữa và sed
gặ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à, sed
có 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 đủ. ed
và ex
, ví dụ, có thể thực hiện nhiều việc sed
có 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à sed
khô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.
sed
Thế 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 và 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/g
trườ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 g
thay thế một nhân vật cho một nhân vật khác, thì y
có 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 sed
trong phần BUGS BÁO CÁO GIAO DỊCH :
Biến POSIXLY_CORRECT
môi trường được đề cập vì POSIX chỉ định rằng nếu sed
gặp EOF khi thử, N
nó 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 N
hành vi của:
N
Nối dòng đầu vào tiếp theo, trừ \n
ewline kết thúc của nó vào không gian mẫu, sử dụng \n
ewline 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, b
trang 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ỳ sed
lệnh nào chấp nhận một tham số tùy ý được hiểu là phân định tại một \n
ewline 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 sed
triể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 đó -e
câu lệnh xecut mới là viết tắt của \n
dấu phân cách ewline. Vì vậy, nơi info
văn bản GNU gợi ý cách triển khai truyền thống sed
sẽ 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ì t
lệnh est - giống như hầu hết sed
cá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 H
cũ, 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 h
không gian cũ. Các bộ đệm được x
thay đổ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/&/3p
cố 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 đó t
thành công, tập lệnh sẽ chuyển sang nhãn n
ot d
elete - một l
tậ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 đó //!g
sẽ 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 \n
ewline 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/&/3p
khô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 sed
kị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 N
ext và n
ext 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ì sed
dù 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ề N
và 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 N
khi đọ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.