Chcę zastąpić tylko pierwsze k
wystąpienia słowa.
W jaki sposób mogę to zrobić?
Na przykład. Powiedz plik foo.txt
zawiera 100 wystąpień słowa „linux”.
Muszę wymienić tylko pierwsze 50 wystąpień.
Chcę zastąpić tylko pierwsze k
wystąpienia słowa.
W jaki sposób mogę to zrobić?
Na przykład. Powiedz plik foo.txt
zawiera 100 wystąpień słowa „linux”.
Muszę wymienić tylko pierwsze 50 wystąpień.
Odpowiedzi:
Pierwsza sekcja poniżej opisuje użycie sed
do zmiany pierwszych k-wystąpień na linii. Druga sekcja rozszerza to podejście, aby zmienić tylko pierwsze k-wystąpienia w pliku, niezależnie od tego, w której linii się pojawiają.
W przypadku standardowego sed istnieje polecenie zastąpienia k-tego wystąpienia słowa w wierszu. Jeśli k
wynosi 3, na przykład:
sed 's/old/new/3'
Lub można zastąpić wszystkie wystąpienia:
sed 's/old/new/g'
Żadne z nich nie jest tym, czego chcesz.
GNU sed
oferuje rozszerzenie, które zmieni k-te wystąpienie, a potem. Jeśli k wynosi 3, na przykład:
sed 's/old/new/g3'
Można je łączyć, aby robić, co chcesz. Aby zmienić pierwsze 3 wystąpienia:
$ 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
gdzie \n
jest to przydatne, ponieważ możemy być pewni, że nigdy nie występuje na linii.
Używamy trzech sed
poleceń podstawienia:
s/\<old\>/\n/g4
To rozszerzenie GNU zastąpić czwarty i wszystkie kolejne wystąpienia old
z \n
.
Rozszerzona funkcja wyrażenia regularnego \<
służy do dopasowania początku słowa i \>
dopasowania do końca słowa. Zapewnia to, że dopasowywane są tylko pełne słowa. Rozszerzone wyrażenie regularne wymaga -E
opcji sed
.
s/\<old\>/new/g
Pozostały tylko trzy pierwsze wystąpienia, old
co zastępuje je wszystkie new
.
s/\n/old/g
Czwarte i wszystkie pozostałe wystąpienia old
zostały zastąpione \n
w pierwszym kroku. To przywraca ich pierwotny stan.
Jeśli GNU sed nie jest dostępny i chcesz zmienić pierwsze 3 wystąpienia old
na new
, użyj trzech s
poleceń:
$ 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
Działa to dobrze, gdy k
jest mała, ale skaluje się słabo do dużej k
.
Ponieważ niektóre sedy inne niż GNU nie obsługują łączenia poleceń ze średnikami, każde polecenie tutaj jest wprowadzane z własną -e
opcją. Może być również konieczne sprawdzenie, czy sed
obsługujesz symbole granic słów, \<
oraz \>
.
Możemy nakazać sedowi odczytanie całego pliku, a następnie wykonanie podstawień. Na przykład, aby zastąpić pierwsze trzy wystąpienia old
użycia sed w stylu BSD:
sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
Polecenia sed H;1h;$!d;x
odczytują cały plik.
Ponieważ powyższe nie używa żadnego rozszerzenia GNU, powinno działać na sedku BSD (OSX). Należy pamiętać, że takie podejście wymaga sed
obsługi długich linii. GNU sed
powinno być w porządku. Osoby używające wersji innej niż GNU sed
powinny przetestować swoją zdolność do obsługi długich linii.
W przypadku GNU sed możemy dalej wykorzystać g
lewę opisaną powyżej, ale z \n
zastąpioną przez \x00
, aby zastąpić pierwsze trzy wystąpienia:
sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'
To podejście dobrze się skaluje i k
staje się duże. Zakłada się jednak, że \x00
nie ma go w oryginalnym ciągu. Ponieważ niemożliwe jest umieszczenie znaku \x00
w ciągu bash, jest to zazwyczaj bezpieczne założenie.
tr '\n' '|' < input_file | sed …
. Ale, oczywiście, to przekształca cały sygnał wejściowy w jedną linię, a niektóre sedy inne niż GNU nie mogą obsługiwać dowolnie długich linii. (2) Mówisz: „… powyżej cytowany ciąg '|'
powinien zostać zastąpiony dowolnym znakiem lub ciągiem znaków,…” Ale nie możesz użyć, tr
aby zastąpić znak ciągiem (o długości> 1). (3) W swoim ostatnim przykładzie mówisz -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new
. Wydaje się, że to literówka -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
.
Poleceń awk można użyć do zastąpienia pierwszych N wystąpień słowa zamiennikiem.
Polecenia zostaną zastąpione tylko wtedy, gdy słowo jest w pełni zgodne.
W poniższych przykładach, jestem zastępując pierwsze 27
wystąpienia old
znew
Korzystanie z sub
awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file
To polecenie zapętla każde pole, aż się dopasuje
old
, sprawdza, czy licznik jest poniżej 27, zwiększa i zastępuje pierwsze dopasowanie w linii. Następnie przechodzi do następnego pola / linii i powtarza się.
Wymiana pola ręcznie
awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Podobnie jak wcześniej polecenie, ale ponieważ ma już znacznik, na którym polu ma zamiar
($i)
, po prostu zmienia wartość pola zold
nanew
.
Przeprowadzanie kontroli wcześniej
awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Sprawdzanie, czy linia zawiera stary i czy licznik jest poniżej 27,
SHOULD
zapewnia niewielkie zwiększenie prędkości, ponieważ nie będzie przetwarzać linii, gdy są one fałszywe.
WYNIKI
Na przykład
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
do
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
Powiedz, że chcesz zastąpić tylko trzy pierwsze wystąpienia ciągu ...
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
uwaga: powyższe prawdopodobnie nie będzie działać z osadzonymi komentarzami
... lub w moim przykładzie przypadku „1” ...
22
211
211
311
Tam używam dwóch znaczących technik. Przede wszystkim każde wystąpienie 1
na linii jest zastępowane przez \n1
. W ten sposób, wykonując następnie zamiany rekurencyjne, mogę być pewien, że nie zastąpię wystąpienia dwukrotnie, jeśli mój ciąg zastępujący zawiera mój ciąg zastępujący. Na przykład, jeśli mogę wymienić he
z hey
nim będzie nadal działać.
Robię to tak:
s/1/\
&/g
Po drugie, liczę zamienniki, dodając znak do h
starego miejsca dla każdego wystąpienia. Gdy osiągnę trzy, nie będzie już więcej. Jeśli zastosujesz to do swoich danych i zmienisz \{3\}
całkowitą liczbę żądanych zamienników oraz /\n1/
adresy na cokolwiek, co chcesz zastąpić, powinieneś wymienić tylko tyle, ile chcesz.
Zrobiłem wszystkie te -e
rzeczy dla czytelności. POSIXly Można to napisać w ten sposób:
nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"
I w / GNU sed
:
sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'
Pamiętaj też, że sed
jest on zorientowany liniowo - nie czyta całego pliku, a następnie próbuje zapętlić go z powrotem, jak to często bywa w innych edytorach. sed
jest prosty i wydajny. To powiedziawszy, często wygodnie jest zrobić coś takiego:
Oto mała funkcja powłoki, która łączy ją w prosto wykonane polecenie:
firstn() { sed "s/$2/\
&/g;:t
/\n/{x
/.\{$(($1))"',\}/!{
s/$/./; x; s/\n'"$2/$3"'/
b t
};x
};s/\n//g'; }
Dzięki temu mogę zrobić:
seq 11 100 311 | firstn 7 1 5
...i dostać...
55
555
255
311
...lub...
seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'
... żeby dostać ...
10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25
... lub, aby dopasować swój przykład (o mniejszym rzędzie wielkości) :
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
Krótka alternatywa w Perlu:
perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file
Zmień wartość „$ n $ na swoje upodobania.
Jak to działa:
new
przez old
( s/old/new/
) i gdy to możliwe, zwiększa ona zmienną $i
( ++$i
).1 while ...
), o ile $n
w sumie dokonał mniej niż podstawień i może dokonać co najmniej jednego podstawienia w tym wierszu.Użyj pętli powłoki i ex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
Tak, to trochę głupie.
;)
Uwaga: Może się to nie powieść, jeśli old
w pliku jest mniej niż 50 wystąpień . (Nie przetestowałem tego.) Jeśli tak, plik pozostanie niezmodyfikowany.
Jeszcze lepiej, użyj Vima.
vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x
Wyjaśnienie:
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
Prostym, ale niezbyt szybkim rozwiązaniem jest zapętlenie poleceń opisanych w /programming/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a -plik
for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done
Ta konkretna komenda sed prawdopodobnie działa tylko dla GNU sed i jeśli newword nie jest częścią oldword . W przypadku wersji innych niż GNU zobacz tutaj, jak zastąpić tylko pierwszy wzorzec w pliku.
Za pomocą GNU awk
możesz ustawić separator rekordów RS
na słowo, które ma być zastąpione ograniczeniem przez granice słów. Jest to przypadek ustawienia separatora rekordów na wyjściu na słowo zastępcze dla pierwszych k
rekordów, przy zachowaniu oryginalnego separatora rekordów dla reszty
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
LUB
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file