Làm thế nào để sử dụng sed để chỉ thay thế lần xuất hiện đầu tiên trong một tập tin?


217

Tôi muốn cập nhật một số lượng lớn các tệp nguồn C ++ với một lệnh bổ sung bao gồm trước bất kỳ #inc loại nào hiện có. Đối với loại nhiệm vụ này, tôi thường sử dụng một tập lệnh bash nhỏ với sed để viết lại tập tin.

Làm cách nào để tôi sedchỉ thay thế lần xuất hiện đầu tiên của chuỗi trong tệp thay vì thay thế mỗi lần xuất hiện?

Nếu tôi sử dụng

sed s/#include/#include "newfile.h"\n#include/

nó thay thế tất cả #inc loại.

Đề xuất thay thế để đạt được điều tương tự cũng được hoan nghênh.

Câu trả lời:


135
 # sed script to change "foo" to "bar" only on the first occurrence
 1{x;s/^/first/;x;}
 1,/foo/{x;/first/s///;x;s/foo/bar/;}
 #---end of script---

hoặc, nếu bạn thích: Ghi chú của biên tập viên: chỉ hoạt động với GNU sed .

sed '0,/foo/s//bar/' file 

Nguồn


86
Tôi nghĩ rằng tôi thích giải pháp 'hoặc nếu bạn thích'. Nó cũng sẽ là tốt để giải thích các câu trả lời - và để làm cho câu trả lời giải quyết câu hỏi trực tiếp, và sau đó khái quát hóa, thay vì chỉ khái quát hóa. Nhưng câu trả lời tốt.
Jonathan Leffler

7
FYI cho người dùng Mac, bạn phải thay 0 bằng 1, vì vậy: tệp sed '1, / RE / s // to_that /'
mhost

1
@mhost Không, nó sẽ thay thế nếu mẫu được tìm thấy ở dòng # 1 và không phải nếu mẫu ở dòng khác, nhưng vẫn là mẫu được tìm thấy đầu tiên. PS nên được đề cập rằng điều này 0,chỉ hoạt động vớignu sed
Jotne

11
Ai đó có thể vui lòng giải thích giải pháp 'hoặc nếu bạn prefeer'? Tôi không biết đặt mẫu "từ" ở đâu.
Jean-Luc Nacif Coelho

3
@ Jean-LucNacifCoelho: bằng cách sử dụng s//- tức là một regex trống - có nghĩa là regex được áp dụng gần đây nhất được sử dụng lại hoàn toàn; trong trường hợp này, RE. Phím tắt tiện lợi này có nghĩa là bạn không phải sao chép regex kết thúc phạm vi trong scuộc gọi của mình .
mkuity0

289

Một sedtập lệnh sẽ chỉ thay thế lần xuất hiện đầu tiên của "Apple" bằng "Banana"

Thí dụ

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

Đây là tập lệnh đơn giản: Ghi chú của biên tập viên: chỉ hoạt động với GNU sed .

sed '0,/Apple/{s/Apple/Banana/}' input_filename

Hai tham số đầu tiên 0/Apple/là bộ xác định phạm vi. Đây s/Apple/Banana/là những gì được thực hiện trong phạm vi đó. Vì vậy, trong trường hợp này "trong phạm vi bắt đầu ( 0) cho đến phiên bản đầu tiên của Apple, thay thế Applebằng Banana. Chỉ lần đầu tiên Applesẽ được thay thế.

Bối cảnh: Trong truyền thống, sedbộ xác định phạm vi cũng là "bắt đầu từ đây" và "kết thúc tại đây" (bao gồm). Tuy nhiên, "bắt đầu" thấp nhất là dòng đầu tiên (dòng 1) và nếu "kết thúc ở đây" là một biểu thức chính quy, thì nó chỉ được thử khớp với dòng tiếp theo sau "bắt đầu", vì vậy kết thúc sớm nhất có thể là dòng 2. Vì phạm vi bao gồm, phạm vi nhỏ nhất có thể là "2 dòng" và phạm vi bắt đầu nhỏ nhất là cả dòng 1 và 2 (nghĩa là nếu có sự xuất hiện trên dòng 1, thì sự xuất hiện trên dòng 2 cũng sẽ được thay đổi, không mong muốn trong trường hợp này ). GNUsed thêm phần mở rộng riêng của nó cho phép chỉ định bắt đầu là "giả" line 0để kết thúc phạm vi có thể line 1, cho phép phạm vi "chỉ dòng đầu tiên"

Hoặc một phiên bản đơn giản hóa (một RE like trống //có nghĩa là sử dụng lại phiên bản được chỉ định trước nó, vì vậy đây là tương đương):

sed '0,/Apple/{s//Banana/}' input_filename

Và các dấu ngoặc nhọn là tùy chọn cho slệnh, vì vậy điều này cũng tương đương:

sed '0,/Apple/s//Banana/' input_filename

Tất cả những thứ này chỉ hoạt động trên GNU sed.

Bạn cũng có thể cài đặt GNU sed trên OS X bằng homebrew brew install gnu-sed.


166
được dịch sang ngôn ngữ của con người: bắt đầu ở dòng 0, tiếp tục cho đến khi bạn khớp với 'Apple', thực hiện thay thế trong dấu ngoặc nhọn. cfr: grymoire.com/Unix/Sed.html#uh-29
mariotomo

8
Trên OS X, tôi nhận đượcsed: 1: "…": bad flag in substitute command: '}'
ELLIOTTCABLE 7/1/2015

5
@ELLIOTTCABLE trên OS X, sử dụng sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'. Từ câu trả lời của @ MikhailVS (hiện tại) xuống bên dưới.
djb

8
Hoạt động mà không có dấu ngoặc quá:sed '0,/foo/s/foo/bar/'
Innokenty

5
Tôi hiểu sed: -e expression #1, char 3: unexpected , '`với điều này
Jonathan

56
sed '0,/pattern/s/pattern/replacement/' filename

Điều này làm việc cho tôi.

thí dụ

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

Lưu ý của biên tập viên: cả hai chỉ hoạt động với GNU sed .


2
@Landys điều này vẫn thay thế các trường hợp trong các dòng khác quá; không chỉ là trường hợp đầu tiên
sarat

1
@sarat Vâng, bạn nói đúng. sed '1,/pattern/s/pattern/replacement/' filenamechỉ hoạt động nếu "mẫu sẽ không xảy ra trên dòng đầu tiên" trên Mac. Tôi sẽ xóa nhận xét trước đây của tôi vì nó không chính xác. Chi tiết có thể được tìm thấy ở đây ( linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/ tựa ). Câu trả lời của Andy chỉ hoạt động với GNU sed, nhưng không phải trên Mac.
Landys

45

Một cái nhìn tổng quan của nhiều hữu ích câu trả lời hiện có , bổ sung với lời giải thích :

Các ví dụ ở đây sử dụng trường hợp sử dụng được đơn giản hóa: thay thế từ 'foo' bằng 'bar' trong dòng khớp đầu tiên.
Do sử dụng các chuỗi ANSI C-trích dẫn ( $'...') để cung cấp các dòng đầu vào mẫu, bash, ksh, hoặc zshđược giả định như vỏ.


sedChỉ GNU :

Anwswer của Ben Hoffstein cho chúng ta thấy rằng GNU cung cấp một phần mở rộng cho đặc tả POSIX chosed phép biểu mẫu 2 địa chỉ sau: 0,/re/( rebiểu thị một biểu thức chính quy tùy ý ở đây).

0,/re/cho phép regex khớp trên dòng đầu tiên . Nói cách khác: một địa chỉ như vậy sẽ tạo ra một phạm vi từ dòng thứ 1 trở lên và bao gồm cả dòng phù hợp re- cho dù rexảy ra trên dòng thứ nhất hoặc trên bất kỳ dòng tiếp theo nào.

  • Ngược lại điều này với các hình thức POSIX-compliant 1,/re/, mà tạo ra một phạm vi mà các trận đấu từ dòng 1 lên đến và bao gồm cả dòng đó trận đấu retrên tiếp theo dòng; nói cách khác: điều này sẽ không phát hiện sự xuất hiện đầu tiên của retrận đấu nếu nó xảy ra trên dòng thứ nhất và cũng ngăn chặn việc sử dụng tốc ký// để sử dụng lại biểu thức chính được sử dụng gần đây nhất (xem điểm tiếp theo). 1

Nếu bạn kết hợp một 0,/re/địa chỉ với một s/.../.../cuộc gọi (thay thế) sử dụng cùng một biểu thức chính quy, lệnh của bạn sẽ chỉ thực hiện thay thế trên dòng đầu tiên phù hợp re.
sedcung cấp một lối tắt thuận tiện để sử dụng lại biểu thức chính quy được áp dụng gần đây nhất : một cặp dấu phân cách trống ,// .

$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

Chỉ có các tính năng POSIX sednhư BSD (macOS)sed (cũng sẽ hoạt động với GNU sed ):

0,/re/không thể được sử dụng và biểu mẫu 1,/re/sẽ không phát hiện renếu nó xảy ra trên dòng đầu tiên (xem ở trên), nên cần xử lý đặc biệt cho dòng thứ nhất .

Câu trả lời của MikhailVS đề cập đến kỹ thuật, đưa vào một ví dụ cụ thể ở đây:

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

Ghi chú:

  • //Phím tắt regex trống được sử dụng hai lần ở đây: một lần cho điểm cuối của phạm vi và một lần trong scuộc gọi; trong cả hai trường hợp, regex foođược sử dụng lại hoàn toàn, cho phép chúng tôi không phải sao chép nó, điều này làm cho cả hai mã ngắn hơn và dễ bảo trì hơn.

  • POSIX sedcần các dòng mới thực sự sau một số chức năng nhất định, chẳng hạn như sau tên của nhãn hoặc thậm chí thiếu sót của nó, như trường hợp tở đây; chiến lược phân tách tập lệnh thành nhiều -etùy chọn là một cách thay thế cho việc sử dụng một dòng mới thực sự: kết thúc mỗi -eđoạn kịch bản mà một dòng mới thường cần phải đi.

1 s/foo/bar/chỉ thay thế footrên dòng 1, nếu tìm thấy ở đó. Nếu vậy, tcác nhánh đến cuối tập lệnh (bỏ qua các lệnh còn lại trên dòng). ( tHàm chỉ phân nhánh nhãn nếu scuộc gọi gần đây nhất thực hiện thay thế thực tế; trong trường hợp không có nhãn, như trường hợp ở đây, phần cuối của tập lệnh được phân nhánh).

Khi điều đó xảy ra, địa chỉ phạm vi 1,//, thường tìm thấy lần xuất hiện đầu tiên bắt đầu từ dòng 2 , sẽ không khớp và phạm vi sẽ không được xử lý, vì địa chỉ được đánh giá khi dòng hiện tại đã có 2.

Ngược lại, nếu không có trận đấu nào trên dòng thứ nhất, 1,// sẽ được nhập và sẽ tìm thấy kết quả khớp đầu tiên thực sự.

Ảnh hưởng thực là giống như với GNU sed's 0,/re/: chỉ có sự xuất hiện lần đầu tiên được thay thế, cho dù nó xảy ra trên dòng 1 hoặc bất kỳ khác.


Phương pháp không phạm vi

câu trả lời Potong của chứng minh vòng lặp kỹ thuậtbỏ qua sự cần thiết của một loạt ; vì anh ta sử dụng cú pháp GNU sed , đây là các tương đương POSIX :

Kỹ thuật lặp 1: Trong trận đấu đầu tiên, thực hiện thay thế, sau đó nhập một vòng lặp chỉ đơn giản là in các dòng còn lại như vốn có :

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

Kỹ thuật lặp 2, chỉ dành cho các tệp nhỏ : đọc toàn bộ đầu vào vào bộ nhớ, sau đó thực hiện một thay thế duy nhất trên đó .

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

1 1.61804 cung cấp các ví dụ về những gì xảy ra với 1,/re/, có và không có tiếp theo s//:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'sản lượng $'1bar\n2bar'; tức là, cả hai dòng đã được cập nhật, vì số dòng 1khớp với dòng thứ nhất và regex /foo/- phần cuối của phạm vi - sau đó chỉ được tìm kiếm để bắt đầu trên dòng tiếp theo . Do đó, cả hai dòng được chọn trong trường hợp này và sự s/foo/bar/thay thế được thực hiện trên cả hai dòng.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' thất bại : với sed: first RE may not be empty(BSD / macOS) và sed: -e expression #1, char 0: no previous regular expression(GNU), bởi vì, tại thời điểm dòng thứ 1 đang được xử lý (do số dòng 1bắt đầu phạm vi), chưa có regex nào được áp dụng, vì vậy//không đề cập đến bất cứ điều gì.
Ngoại trừ cú pháp sedđặc biệt của GNU 0,/re/, bất kỳ phạm vi nào bắt đầu bằng số dòng đều không được sử dụng //.


25

Bạn có thể sử dụng awk để làm một cái gì đó tương tự ..

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

Giải trình:

/#include/ && !done

Chạy câu lệnh hành động giữa {} khi dòng khớp với "#include" và chúng tôi chưa xử lý nó.

{print "#include \"newfile.h\""; done=1;}

Điều này in #include "newfile.h", chúng ta cần thoát khỏi dấu ngoặc kép. Sau đó, chúng tôi đặt biến thực hiện thành 1, vì vậy chúng tôi không thêm nhiều hơn bao gồm.

1;

Điều này có nghĩa là "in ra dòng" - một hành động trống mặc định để in $ 0, in ra toàn bộ dòng. Một lót và dễ hiểu hơn sed IMO :-)


4
Câu trả lời này có tính di động cao hơn các giải pháp sed, dựa trên gnu sed, v.v. (ví dụ: sed trong OS-X hút!)
Jay Taylor

1
Điều này thực sự dễ hiểu hơn, nhưng đối với tôi nó thêm một dòng thay vì thay thế nó; lệnh được sử dụng: awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json
Ngũ cốc Killer

tương tự ở đây, lệnh dễ hiểu nhất, nhưng nó thêm một dòng phía trên chuỗi tìm thấy thay vì thay thế nó
Flion

1
Trả lời khá dễ đọc. Đây là phiên bản của tôi thay thế chuỗi thay vì thêm một dòng mới. awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c
Orsiris de Jong

18

Khá nhiều bộ sưu tập các câu trả lời trên linuxtopia sed FAQ . Nó cũng nhấn mạnh rằng một số câu trả lời mà mọi người cung cấp sẽ không hoạt động với phiên bản sed không phải GNU, vd

sed '0,/RE/s//to_that/' file

trong phiên bản không phải GNU sẽ phải

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

Tuy nhiên, phiên bản này sẽ không hoạt động với gnu sed.

Đây là phiên bản hoạt động với cả hai:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

Ví dụ:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename

Thật vậy, đã thử nghiệm làm việc trên Ubuntu Linux v16 và FreeBSD v10.2. Cảm ơn bạn.
Sopalajo de Arrierez

12
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

Cách tập lệnh này hoạt động: Đối với các dòng giữa 1 và đầu tiên #include(sau dòng 1), nếu dòng bắt đầu bằng #include, thì hãy thêm vào dòng được chỉ định.

Tuy nhiên, nếu dòng đầu tiên #includenằm trong dòng 1, thì cả dòng 1 và dòng tiếp theo #includesẽ có dòng được chuẩn bị trước. Nếu bạn đang sử dụng GNU sed, nó có một phần mở rộng trong đó 0,/^#include/(thay vì 1,) sẽ làm điều đúng đắn.


11

Chỉ cần thêm số lần xuất hiện ở cuối:

sed s/#include/#include "newfile.h"\n#include/1

7
Thật không may, điều này không hoạt động. Nó thay thế lần xuất hiện đầu tiên trên mỗi dòng của tệp và không phải là lần xuất hiện đầu tiên trong tệp.
David Dibben

1
Ngoài ra, nó là một phần mở rộng sed GNU, không phải là một tính năng sed tiêu chuẩn.
Jonathan Leffler

10
Hmmm ... thời gian trôi qua. POSIX 2008/2013 để sedchỉ định lệnh thay thế bằng: [2addr]s/BRE/replacement/flagsvà lưu ý rằng "Giá trị của các cờ sẽ bằng 0 hoặc nhiều hơn: n Thay thế cho lần xuất hiện thứ n của BRE được tìm thấy trong không gian mẫu." Do đó, ít nhất là trong POSIX 2008, trailing 1không phải là sedphần mở rộng GNU . Thật vậy, ngay cả trong tiêu chuẩn SUS / POSIX 1997 , điều này đã được hỗ trợ, vì vậy tôi đã hết hàng trong năm 2008
Jonathan Leffler

7

Một giải pháp khả thi:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :a
    n
    ba

Giải trình:

  • đọc các dòng cho đến khi chúng tôi tìm thấy #incoide, in các dòng này sau đó bắt đầu chu kỳ mới
  • chèn dòng bao gồm mới
  • nhập một vòng lặp chỉ đọc các dòng (theo mặc định sed cũng sẽ in các dòng này), chúng tôi sẽ không quay lại phần đầu tiên của tập lệnh từ đây

sed: file me4.sed line 4: ":" lacks a label
rogerdpack

rõ ràng một cái gì đó đã thay đổi trong các phiên bản sed gần đây và nhãn trống không còn được phép. đã cập nhật câu trả lời
mitchnull

4

Tôi biết đây là một bài viết cũ nhưng tôi đã có một giải pháp mà tôi từng sử dụng:

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

Về cơ bản sử dụng grep để in lần xuất hiện đầu tiên và dừng ở đó. Ngoài ra in số dòng tức là 5:line. Đặt ống đó vào sed và loại bỏ: và bất cứ thứ gì sau đó để bạn chỉ còn lại một số dòng. Chuyển ống vào sed có thêm s /.*/ thay thế cho số cuối, kết quả là tập lệnh 1 dòng được dẫn vào sed cuối cùng để chạy dưới dạng tập lệnh trên tệp.

vì vậy, nếu regex = #includevà thay thế = blahvà lần xuất hiện đầu tiên grep tìm thấy nằm trên dòng 5 thì dữ liệu được dẫn đến sed cuối cùng sẽ là 5s/.*/blah/.

Hoạt động ngay cả khi lần đầu tiên xuất hiện trên dòng đầu tiên.


Tôi hoàn toàn ghét các kịch bản sed đa dòng hoặc các lệnh sed với bất cứ thứ gì ngoại trừ s và vải lanh nên tôi tham gia với phương pháp này. Đây là những gì tôi đã sử dụng cho usecase của tôi (hoạt động với bash): filepath = / etc / hosts; patt = '^ \ (127 \ .0 \ .0 \ .1. * \)'; thay thế = '\ 1 newhostalias'; sed $ (IFS =: linearray = ($ (grep -E -m 1 -n "$ patt" "$ filepath")) && echo $ {linearray [0]}) s / "$ patt" / "$ repl" / "$ filepath"
chẵn lẻ3

Nó hoạt động. Mặc dù chỉ với sed đủ thông minh để chấp nhận sed -f -điều mà một số người không biết, nhưng bạn có thể làm việc xung quanh nó :)
rogerdpack

3

Nếu bất cứ ai đến đây để thay thế một nhân vật cho lần xuất hiện đầu tiên trong tất cả các dòng (như bản thân tôi), hãy sử dụng điều này:

sed '/old/s/old/new/1' file

-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12

Ví dụ, bằng cách thay đổi 1 thành 2, bạn chỉ có thể thay thế tất cả các giây thứ hai.


2
Bạn không cần phải làm tất cả điều này, 's/a/b/'có nghĩa là match a, vàdo just first match for every matching line
Samveen

Tôi với Samveen. Ngoài ra điều này không trả lời câu hỏi được hỏi ở đây . Tôi sẽ khuyên nên xóa câu trả lời này.
Socowi

Câu hỏi là "lần đầu tiên xuất hiện trong một tập tin" chứ không phải "lần đầu tiên xuất hiện trong một dòng"
Manuel Romeiro

3

Với -ztùy chọn GNU sed, bạn có thể xử lý toàn bộ tệp như thể nó chỉ là một dòng. Bằng cách đó, a s/…/…/sẽ chỉ thay thế trận đấu đầu tiên trong toàn bộ tập tin. Hãy nhớ rằng: s/…/…/chỉ thay thế trận đấu đầu tiên trong mỗi dòng, nhưng với -ztùy chọn sedcoi toàn bộ tệp là một dòng duy nhất.

sed -z 's/#include/#include "newfile.h"\n#include'

Trong trường hợp chung, bạn phải viết lại biểu thức sed của mình vì không gian mẫu hiện giữ toàn bộ tệp thay vì chỉ một dòng. Vài ví dụ:

  • s/text.*//có thể được viết lại như s/text[^\n]*//. [^\n]phù hợp với tất cả mọi thứ trừ nhân vật dòng mới. [^\n]*sẽ khớp tất cả các biểu tượng sau textcho đến khi đạt được một dòng mới.
  • s/^text//có thể được viết lại như s/(^|\n)text//.
  • s/text$//có thể được viết lại như s/text(\n|$)//.

2

tôi sẽ làm điều này với một kịch bản awk:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

sau đó chạy nó với awk:

awk -f awkscript headerfile.h > headerfilenew.h

có thể cẩu thả, tôi mới biết điều này.


2

Là một gợi ý thay thế, bạn có thể muốn xem edlệnh.

man 1 ed

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   /# *include/i
   #include "newfile.h"
   .
   ,p
   q
EOF

2

Cuối cùng tôi đã có được điều này để làm việc trong một tập lệnh Bash được sử dụng để chèn dấu thời gian duy nhất vào mỗi mục trong nguồn cấp dữ liệu RSS:

        sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
            production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter

Nó chỉ thay đổi lần xuất hiện đầu tiên.

${nowms}là thời gian tính bằng mili giây được đặt bởi tập lệnh Perl, $counterlà bộ đếm được sử dụng để điều khiển vòng lặp trong tập lệnh, \cho phép tiếp tục lệnh trên dòng tiếp theo.

Tệp được đọc trong và thiết bị xuất chuẩn được chuyển hướng đến một tệp công việc.

Theo cách tôi hiểu, sẽ 1,/====RSSpermalink====/nói với sed khi nào nên dừng lại bằng cách đặt giới hạn phạm vi và sau đó s/====RSSpermalink====/${nowms}/là lệnh sed quen thuộc để thay thế chuỗi đầu tiên bằng chuỗi thứ hai.

Trong trường hợp của tôi, tôi đặt lệnh trong dấu ngoặc kép vì tôi đang sử dụng nó trong tập lệnh Bash với các biến.


2

Sử dụng FreeBSD ed và tránh lỗi ed"không khớp" trong trường hợp không có includecâu lệnh nào trong tệp cần xử lý:

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917 
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   ,g/# *include/u\
   u\
   i\
   #include "newfile.h"\
   .
   ,p
   q
EOF

Điều này khá giống với câu trả lời của timo nhưng đã được thêm vào hơn một năm sau đó.
Jonathan Leffler

2

Điều này có thể làm việc cho bạn (GNU sed):

sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....

hoặc nếu bộ nhớ không phải là vấn đề:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...

0

Lệnh sau sẽ loại bỏ sự xuất hiện đầu tiên của một chuỗi, trong một tệp. Nó loại bỏ các dòng trống quá. Nó được trình bày trên một tệp xml, nhưng nó sẽ hoạt động với bất kỳ tệp nào.

Hữu ích nếu bạn làm việc với các tệp xml và bạn muốn xóa thẻ. Trong ví dụ này, nó loại bỏ sự xuất hiện đầu tiên của thẻ "isTag".

Chỉ huy:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

Tệp nguồn (source.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <isTag>false</isTag>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

Tệp kết quả (output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ps: nó không hoạt động với tôi trên Solaris SunOS 5.10 (khá cũ), nhưng nó hoạt động trên Linux 2.6, phiên bản sed 4.1.5


Điều này có vẻ giống như ý tưởng cơ bản giống như một số câu trả lời trước đó, với cùng một cảnh báo rằng nó chỉ hoạt động với GNU sed(do đó nó không hoạt động với Solaris). Bạn nên xóa cái này, làm ơn - nó thực sự không cung cấp thông tin mới đặc biệt cho câu hỏi đã 4 năm tuổi khi bạn trả lời. Cấp, nó có một ví dụ hoạt động, nhưng đó là giá trị gây tranh cãi khi câu hỏi có nhiều câu trả lời như câu hỏi này.
Jonathan Leffler

0

Không có gì mới nhưng có lẽ câu trả lời cụ thể hơn một chút: sed -rn '0,/foo(bar).*/ s%%\1%p'

Ví dụ: xwininfo -name unity-launchertạo đầu ra như:

xwininfo: Window id: 0x2200003 "unity-launcher"

  Absolute upper-left X:  -2980
  Absolute upper-left Y:  -198
  Relative upper-left X:  0
  Relative upper-left Y:  0
  Width: 2880
  Height: 98
  Depth: 24
  Visual: 0x21
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x20 (installed)
  Bit Gravity State: ForgetGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +-2980+-198  -2980+-198  -2980-1900  +-2980-1900
  -geometry 2880x98+-2980+-198

Trích xuất ID cửa sổ với xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'sản xuất:

0x2200003

0

POSIXly (cũng hợp lệ trong sed), Chỉ một regex được sử dụng, chỉ cần bộ nhớ cho một dòng (như bình thường):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

Giải thích:

sed '
/\(#include\).*/!b          # Only one regex used. On lines not matching
                            # the text  `#include` **yet**,
                            # branch to end, cause the default print. Re-start.
//{                         # On first line matching previous regex.
    h                       # hold the line.
    s//\1 "newfile.h"/      # append ` "newfile.h"` to the `#include` matched.
    G                       # append a newline.
  }                         # end of replacement.
:1                          # Once **one** replacement got done (the first match)
n                           # Loop continually reading a line each time
b1                          # and printing it by default.
'                           # end of sed script.

0

Trường hợp sử dụng có thể là các lần xuất hiện của bạn được trải đều trong tệp của bạn, nhưng bạn biết mối quan tâm duy nhất của bạn là trong 10, 20 hoặc 100 dòng đầu tiên.

Sau đó, chỉ cần nhấn vào các dòng đó sẽ khắc phục vấn đề - ngay cả khi từ ngữ của OP chỉ liên quan đến đầu tiên.

sed '1,10s/#include/#include "newfile.h"\n#include/'

0

Một giải pháp khả thi ở đây có thể là yêu cầu trình biên dịch đưa vào tiêu đề mà không được đề cập trong các tệp nguồn. Ở GCC có các tùy chọn sau:

   -include file
       Process file as if "#include "file"" appeared as the first line of
       the primary source file.  However, the first directory searched for
       file is the preprocessor's working directory instead of the
       directory containing the main source file.  If not found there, it
       is searched for in the remainder of the "#include "..."" search
       chain as normal.

       If multiple -include options are given, the files are included in
       the order they appear on the command line.

   -imacros file
       Exactly like -include, except that any output produced by scanning
       file is thrown away.  Macros it defines remain defined.  This
       allows you to acquire all the macros from a header without also
       processing its declarations.

       All files specified by -imacros are processed before all files
       specified by -include.

Trình biên dịch của Microsoft có / FI tùy chọn (bắt buộc bao gồm).

Tính năng này có thể hữu ích cho một số tiêu đề phổ biến, như cấu hình nền tảng. Makefile của nhân Linux sử dụng -includecho việc này.


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.