Tôi muốn chỉ thay thế các k
trườ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.txt
chứ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.
Tôi muốn chỉ thay thế các k
trườ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.txt
chứ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.
Câu trả lời:
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.
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 k
là 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 sed
cung 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 \n
là 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.
Chúng tôi sử dụng ba sed
lệ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 old
vớ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 -E
tùy chọn để sed
.
s/\<old\>/new/g
Chỉ có ba lần xuất hiện đầu tiên old
cò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 \n
bước đầu tiên. Điều này đưa họ trở lại trạng thái ban đầu của họ.
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 old
thành new
, thì hãy sử dụng ba s
lệ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 k
là 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 -e
tùy chọn riêng . Cũng có thể cần phải xác minh rằng bạn sed
hỗ trợ các ký hiệu ranh giới từ \<
và \>
.
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 old
sử 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 sed
có thể xử lý các dòng dài. GNU sed
sẽ ổn thôi. Những người sử dụng phiên bản không phải GNU sed
nê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 g
thủ thuật được mô tả ở trên, nhưng \n
thay 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ư k
trở nên lớn. Điều này giả định, mặc dù, đó \x00
không phải là trong chuỗi ban đầu của bạn. Vì không thể đặt ký tự \x00
trong chuỗi bash, đây thường là một giả định an toàn.
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'
.
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 27
xuất hiện của old
vớ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ừold
sangnew
.
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
SHOULD
có 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
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' ...
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 1
mộ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ế he
bằng hey
nó 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 h
khô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 -e
thứ 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 đó sed
là đị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. sed
là đơ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
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:
new
cho old
( s/old/new/
) và bất cứ khi nào có thể, nó sẽ tăng biến $i
( ++$i
).1 while ...
) miễn là nó đã thực hiện ít hơn $n
tổng số thay thế và nó có thể thực hiện ít nhất một thay thế trên dòng đó.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 old
trong 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
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ừ cũ . Đố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.
Với GNU, awk
bạn có thể đặt dấu tách bản ghi RS
thà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 k
bả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