Sed - Thay thế k trường hợp đầu tiên của một từ trong tệp


24

Tôi muốn chỉ thay thế các ktrường hợp đầu tiên của một từ.

Tôi có thể làm cái này như thế nào?

Ví dụ. Nói tập tin foo.txtchứa 100 lần xuất hiện của từ 'linux'.

Tôi chỉ cần thay thế 50 lần xuất hiện đầu tiên.


1
Bạn có thể tham khảo điều này: unix.stackexchange.com/questions/21178/ Kẻ
cuonglm

Bạn có cần sed cụ thể, hoặc các công cụ khác được chấp nhận? Bạn có cần phải làm việc trên dòng lệnh, hoặc một trình soạn thảo văn bản có thể chấp nhận được không?
evilsoup

Bất cứ điều gì hoạt động trên dòng lệnh đều được chấp nhận.
narendra-choudhary

Câu trả lời:


31

Phần đầu tiên mô tả việc sử dụng sedđể thay đổi lần xuất hiện k đầu tiên trên một dòng. Phần thứ hai mở rộng cách tiếp cận này để chỉ thay đổi các lần xuất hiện k đầu tiên trong một tệp, bất kể chúng xuất hiện trên dòng nào.

Giải pháp định hướng theo dòng

Với sed tiêu chuẩn, có một lệnh để thay thế sự xuất hiện thứ k của một từ trên một dòng. Nếu klà 3, ví dụ:

sed 's/old/new/3'

Hoặc, người ta có thể thay thế tất cả các lần xuất hiện bằng:

sed 's/old/new/g'

Đây không phải là những gì bạn muốn.

GNU sedcung cấp một phần mở rộng sẽ thay đổi lần xuất hiện thứ k và tất cả sau đó. Nếu k là 3, ví dụ:

sed 's/old/new/g3'

Đây có thể được kết hợp để làm những gì bạn muốn. Để thay đổi 3 lần xuất hiện đầu tiên:

$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old

nơi \nlà hữu ích ở đây bởi vì chúng ta có thể chắc chắn rằng nó không bao giờ xảy ra trên một đường thẳng.

Giải trình:

Chúng tôi sử dụng ba sedlệnh thay thế:

  • s/\<old\>/\n/g4

    Mở rộng GNU này để thay thế thứ tư và tất cả các lần xuất hiện tiếp theo của oldvới \n.

    Tính năng regex mở rộng \<được sử dụng để khớp với đầu của một từ và \>để khớp với cuối của một từ. Điều này đảm bảo rằng chỉ những từ hoàn chỉnh được khớp. Regex mở rộng yêu cầu -Etùy chọn để sed.

  • s/\<old\>/new/g

    Chỉ có ba lần xuất hiện đầu tiên oldcòn lại và điều này thay thế tất cả chúng bằng new.

  • s/\n/old/g

    Lần thứ tư và tất cả các lần xuất hiện còn lại oldđã được thay thế bằng \nbước đầu tiên. Điều này đưa họ trở lại trạng thái ban đầu của họ.

Giải pháp không GNU

Nếu GNU sed không có sẵn và bạn muốn thay đổi 3 lần xuất hiện đầu tiên oldthành new, thì hãy sử dụng ba slệnh:

$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old

Điều này hoạt động tốt khi klà một số nhỏ nhưng quy mô kém đến lớn k.

Vì một số sed không phải GNU không hỗ trợ kết hợp các lệnh với dấu chấm phẩy, mỗi lệnh ở đây được giới thiệu với -etùy chọn riêng . Cũng có thể cần phải xác minh rằng bạn sedhỗ trợ các ký hiệu ranh giới từ \<\>.

Giải pháp hướng tệp

Chúng ta có thể bảo sed đọc toàn bộ tập tin và sau đó thực hiện thay thế. Ví dụ: để thay thế ba lần đầu tiên oldsử dụng sed kiểu BSD:

sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'

Các lệnh sed H;1h;$!d;xđọc toàn bộ tập tin trong.

Bởi vì phần trên không sử dụng bất kỳ phần mở rộng GNU nào, nên nó sẽ hoạt động trên sed BSD (OSX). Lưu ý, suy nghĩ, rằng phương pháp này đòi hỏi một cách sedcó thể xử lý các dòng dài. GNU sedsẽ ổn thôi. Những người sử dụng phiên bản không phải GNU sednên kiểm tra khả năng xử lý các dòng dài của nó.

Với một sed GNU, chúng ta có thể sử dụng thêm gthủ thuật được mô tả ở trên, nhưng \nthay thế bằng \x00, để thay thế ba lần xuất hiện đầu tiên:

sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'

Cách tiếp cận này quy mô tốt như ktrở nên lớn. Điều này giả định, mặc dù, đó \x00không phải là trong chuỗi ban đầu của bạn. Vì không thể đặt ký tự \x00trong chuỗi bash, đây thường là một giả định an toàn.


5
Điều này chỉ hoạt động cho các dòng và sẽ thay đổi 4 lần xuất hiện đầu tiên trong mỗi dòng

1
@mikeerv Ý tưởng tuyệt vời! Trả lời cập nhật.
John1024

(1) Bạn đề cập đến GNU và non-GNU sed, và đề xuất tr '\n' '|' < input_file | sed …. Nhưng, tất nhiên, điều đó chuyển đổi toàn bộ đầu vào thành một dòng và một số sed không phải GNU không thể xử lý các dòng dài tùy ý. (2) Bạn nói, “... trên, chuỗi trích dẫn '|'nên được thay thế bằng bất kỳ ký tự, hoặc chuỗi ký tự, ...” Nhưng bạn không thể sử dụng trđể thay thế một nhân vật với một chuỗi (chiều dài> 1). (3) Trong ví dụ cuối cùng của bạn, bạn nói -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new. Đây dường như là một lỗi đánh máy cho -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'.
G-Man nói 'Tái lập Monica'

@ G-Man Cảm ơn nhiều! Tôi đã cập nhật câu trả lời.
John1024

điều này thật xấu xí
Louis Maddox

8

Sử dụng Awk

Các lệnh awk có thể được sử dụng để thay thế N lần xuất hiện đầu tiên của từ bằng từ thay thế.
Các lệnh sẽ chỉ thay thế nếu từ là một kết hợp hoàn chỉnh.

Trong ví dụ dưới đây, tôi thay thế đầu tiên 27xuất hiện của oldvớinew

Sử dụng phụ

awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file

Lệnh này lặp qua từng trường cho đến khi khớp old, nó kiểm tra bộ đếm dưới 27, tăng và thay thế khớp đầu tiên trên dòng. Sau đó di chuyển lên trường / dòng tiếp theo và lặp lại.

Thay thế trường bằng tay

awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file

Tương tự như lệnh trước nhưng vì nó đã có một điểm đánh dấu trên trường nào ($i), nó chỉ đơn giản là thay đổi giá trị của trường từ oldsang new.

Thực hiện kiểm tra trước

awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file

Kiểm tra xem dòng chứa cũ và bộ đếm dưới 27 SHOULDcó tăng tốc độ nhỏ không vì nó sẽ không xử lý các dòng khi chúng sai.

CÁC KẾT QUẢ

Ví dụ

old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old

đến

new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old

Cái đầu tiên (sử dụng phụ) thực hiện sai nếu chuỗi cũ cũ trước chữ * cũ; vd: Trao tặng một số vàng cho ông già. Nghi → → Tặng một ít gặm nhấm cho ông già. Tiết
G-Man nói 'Tái lập lại Monica'

@ G-Man Vâng tôi đã quên $ibit, nó đã được chỉnh sửa, cảm ơn :)

7

Giả sử bạn chỉ muốn thay thế ba trường hợp đầu tiên của chuỗi ...

seq 11 100 311 | 
sed -e 's/1/\
&/g'              \ #s/match string/\nmatch string/globally 
-e :t             \ #define label t
-e '/\n/{ x'      \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{'   \ #if not 3 characters in hold space do
-e     's/$/./'   \ #add a new char to hold space
-e      x         \ #exchange hold/pattern spaces again
-e     's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e     'b t'      \ #branch back to label t
-e '};x'          \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g'      #end match function; remove all newline characters

lưu ý: ở trên có thể sẽ không hoạt động với các nhận xét được nhúng
... hoặc trong trường hợp ví dụ của tôi, về '1' ...

ĐẦU RA:

22
211
211
311

Ở đó tôi sử dụng hai kỹ thuật đáng chú ý. Ở nơi đầu tiên, mọi sự xuất hiện của 1một dòng được thay thế bằng \n1. Theo cách này, khi tôi thực hiện thay thế đệ quy tiếp theo, tôi có thể chắc chắn không thay thế lần xuất hiện hai lần nếu chuỗi thay thế của tôi chứa chuỗi thay thế. Ví dụ, nếu tôi thay thế hebằng heynó vẫn sẽ hoạt động.

Tôi làm điều này như sau:

s/1/\
&/g

Thứ hai, tôi đang đếm số lần thay thế bằng cách thêm một ký tự vào hkhông gian cũ cho mỗi lần xuất hiện. Một khi tôi đạt đến ba không xảy ra nữa. Nếu bạn áp dụng điều này cho dữ liệu của mình và thay đổi \{3\}tổng số thay thế mà bạn mong muốn và /\n1/địa chỉ thành bất cứ điều gì bạn muốn thay thế, bạn chỉ nên thay thế bao nhiêu tùy ý.

Tôi chỉ làm tất cả những -ethứ cho dễ đọc. POSIXly Nó có thể được viết như thế này:

nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"

Và w / GNU sed:

sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'

Cũng cần nhớ rằng đó sedlà định hướng theo dòng - nó không đọc trong toàn bộ tệp và sau đó cố gắng lặp lại nó như thường thấy trong các trình soạn thảo khác. sedlà đơn giản và hiệu quả. Điều đó nói rằng, thường là thuận tiện để làm một cái gì đó như sau:

Đây là một hàm shell nhỏ kết hợp nó thành một lệnh được thực hiện đơn giản:

firstn() { sed "s/$2/\
&/g;:t 
    /\n/{x
        /.\{$(($1))"',\}/!{
            s/$/./; x; s/\n'"$2/$3"'/
            b t
        };x
};s/\n//g'; }

Vì vậy, với điều đó tôi có thể làm:

seq 11 100 311 | firstn 7 1 5

...và lấy...

55
555
255
311

...hoặc là...

seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'

... để có được ...

10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25

... hoặc, để phù hợp với ví dụ của bạn (theo thứ tự cường độ nhỏ hơn) :

yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux

4

Một thay thế ngắn trong Perl:

perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file

Thay đổi giá trị của `$ n $ theo ý thích của bạn.

Làm thế nào nó hoạt động:

  • Đối với mỗi dòng, nó tiếp tục cố gắng thay thế newcho old( s/old/new/) và bất cứ khi nào có thể, nó sẽ tăng biến $i( ++$i).
  • Nó tiếp tục hoạt động trên dòng ( 1 while ...) miễn là nó đã thực hiện ít hơn $ntổng số thay thế và nó có thể thực hiện ít nhất một thay thế trên dòng đó.

4

Sử dụng một vòng lặp shell và ex!

{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt

Vâng, đó là một chút ngớ ngẩn.

;)

Lưu ý: Điều này có thể thất bại nếu có ít hơn 50 trường hợp oldtrong tệp. (Tôi chưa kiểm tra nó.) Nếu vậy, nó sẽ khiến tập tin không được sửa đổi.


Tốt hơn nữa, sử dụng Vim.

vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x

Giải trình:

q                                # Start recording macro
 q                               # Into register q
  gg                             # Go to start of file
    /old<CR>                     # Go to first instance of 'old'
            :s/old/new/<CR>      # Change it to 'new'
                           q     # Stop recording
                            49@q # Replay macro 49 times

:x  # Save and exit

: s // new <CR> cũng sẽ hoạt động vì một regex trống sử dụng lại tìm kiếm được sử dụng cuối cùng
eike

3

Một giải pháp đơn giản nhưng không nhanh chóng là lặp lại các lệnh được mô tả trong /programming/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a -tập tin

for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/"  file.txt  ; done

Lệnh sed đặc biệt này có lẽ chỉ hoạt động với GNU sed và nếu từ mới không phải là một phần của từ . Đối với sed không phải GNU, hãy xem ở đây cách thay thế mẫu đầu tiên trong một tệp.


+1 để xác định rằng thay thế "cũ" bằng "đậm" có thể gây ra sự cố.
G-Man nói 'Phục hồi Monica'

2

Với GNU, awkbạn có thể đặt dấu tách bản ghi RSthành từ được thay thế giới hạn bởi các ranh giới từ. Sau đó, đó là trường hợp đặt dấu tách bản ghi trên đầu ra thành từ thay thế cho các kbản ghi đầu tiên trong khi vẫn giữ phần tách bản ghi gốc cho phần còn lại

awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file

HOẶC LÀ

awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file
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.