Jak zamienić tylko N wystąpienie wzorca w pliku?

10

Jak zastąpić trzecie wystąpienie ciągu w pliku za pomocą sedpolecenia.

Przykład:

Zmień tylko trzecie wystąpienie isna usw pliku.

Mój plik wejściowy zawiera:

hai this is linux.
hai this is unix.
hai this is mac.
hai this is unchanged.

Oczekuję, że wynikiem będzie:

hai this is linux.
hai thus is unix.
hai this is mac.
hai this is unchanged.
Sureshkumar
źródło
3
Wejście i wyjście są takie same.
Hauke ​​Laging
4
sednie jest odpowiednim narzędziem do pracy.
choroba
@don_crissti Naprawiłem to. OP nie używał narzędzi do formatowania (swoją drogą, Sureshkumar, patrz tutaj, aby uzyskać pomoc na temat edytowania pytań), a kolejni redaktorzy źle zrozumieli, co jest potrzebne.
terdon

Odpowiedzi:

11

Łatwiej jest to zrobić perl.

Aby zmienić 3 rd wystąpienie:

perl -pe 's{is}{++$n == 3 ? "us" : $&}ge'

Aby zmienić co 3 rd wystąpienie:

perl -pe 's{is}{++$n % 3 ? $& : "us"}ge'
Stéphane Chazelas
źródło
3

Gdy zastępujący ciąg występuje tylko raz w linii, możesz łączyć różne narzędzia.
Gdy dane wejściowe znajdują się w pliku „input” i zastępujesz „is” przez „nas”, możesz użyć

LINENR=$(cat input | grep -n " is " | head -3 | tail -1 | cut -d: -f1)
cat input | sed ${LINENR}' s/ is / us /'
Walter A.
źródło
W przykładzie w pytaniu jest więcej niż jeden isna linię.
terdon
Myślałem, że szukasz „jest” ze spacjami. Mógłbym edytować swoją odpowiedź za pomocą polecenia tr takiego jak @jimmij, ale moje rozwiązanie stałoby się znacznie gorsze od jego.
Walter A
Nie jestem pytającym :). Myślałem o tym samym i dlatego głosowałem za odpowiedzią, ale jeśli spojrzysz na oryginalną wersję pytania (kliknij link „Edytowano X minut temu”), zobaczysz, że OP oczekiwał, że jest w tym zostać zmienionym w ten sposób . Nawiasem mówiąc, nie ma tam potrzeby kota .
terdon
2

Poniższy skrypt (wykorzystujący składnię GNU sed ) nadaje się do edycji w miejscu, a nie do wydruku, ponieważ zatrzymuje pożądane wiersze po pożądanym podstawieniu:

sed -i '/is/{: 1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; q}' text.file

Jeśli podoba Ci się taka decyzja dotycząca choroby , możesz zmodyfikować powyżej

sed '/is/{:1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; :2 ; n ; $!b2}' text.file

który wypisuje wszystkie linie

Lub musisz umieścić wszystkie linie w przestrzeni wzorów (w pamięci, więc uważaj na ograniczenie rozmiaru) i dokonaj podstawienia

sed ': 1 ; N ; $!b1 ; s/is/us/3 ' text.file
Costas
źródło
2

Możesz tego użyć sed, jeśli wcześniej nowe znaki zostały zastąpione innymi znakami, np .:

tr '\n' '\000' | sed 's/is/us/3' | tr '\000' '\n'

Podobnie jest z czystym (GNU) sed:

sed ':a;N;$!ba;s/\n/\x0/g;s/is/us/3;s/\x0/\n/g'

( sedzamiennik nowej linii bezwstydnie skradziony z https://stackoverflow.com/a/1252191/4488514 )

jimmij
źródło
Jeśli zamierzasz użyć sedskładni specyficznej dla GNU , równie dobrze możesz użyć sed -z 's/is/us/3'.
Stéphane Chazelas
@ StéphaneChazelas -zmusi być nową funkcją, GNU sed version 4.2.1nic nie wiem o tej opcji.
jimmij
1
Dodano w 4.2.2 (2012). W drugim rozwiązaniu nie potrzebujesz konwersji do \x0kroku.
Stéphane Chazelas
Przepraszam za edycję. Nie widziałem oryginalnej wersji pytania, a ktoś źle ją zrozumiał i zredagował niewłaściwą linię. Wróciłem do poprzedniej wersji.
terdon
1
p='[:punct:]' s='[:space:]'
sed -Ee'1!{/\n/!b' -e\}            \
     -e's/(\n*)(.*)/ \2 \1/'       \
     -e"s/is[$p]?[$s]/\n&/g"       \
     -e"s/([^$s])\n/\1/g;1G"       \
-e:c -e"s/\ni(.* )\n{3}/u\1/"      \
     -e"/\n$/!s/\n//g;/\ni/G"      \
     -e's//i/;//tc'                \
     -e's/^ (.*) /\1/;P;$d;N;D'

Ta część sedpo prostu przenosi liczbę iswystąpień z jednej linii do drugiej. Powinien niezawodnie obsłużyć tyle isesów w linii, ile w niego rzucisz, i nie musi buforować starych linii, podczas gdy to robi - zachowuje tylko jeden znak nowej linii dla każdego isnapotkanego, który nie jest częścią innego słowa.

Rezultatem jest to, że zmodyfikuje tylko trzecie wystąpienie w pliku - i będzie przenosił liczbę na linię. Więc jeśli plik wygląda jak:

1. is is isis
2. is does

... wydrukuje ...

1. is is isis
2. us does

Najpierw obsługuje skrzynki na krawędziach, wstawiając odstęp na czubku i na końcu każdej linii. To sprawia, że ​​granice słów są nieco łatwiejsze do ustalenia.

Następnie szuka poprawnych znaków, iswstawiając \newline, zanim wszystkie jego wystąpienia ispoprzedzą zero lub jeden znak interpunkcyjny, a po nim spację. Robi kolejne przejście i usuwa wszystkie \nowline, które są bezpośrednio poprzedzone znakiem spacji. Te pozostawione znaczniki będą pasować is., a isjednak nie thislub ?is.

Następnie zbiera każdy znacznik na ogonie sznurka - za każde \nidopasowanie w linii dołącza \newlinię do ogona sznurka i zastępuje go jednym ilub u. Jeśli są 3 \newline w rzędzie zebrane na końcu sznurka, wówczas używa u - w przeciwnym razie i. Pierwsze użycie au jest również ostatnie - zamiennik uruchamia nieskończoną pętlę, która sprowadza się do get line, print line, get line, print line,i tak dalej.

Pod koniec każdego cyklu pętli próbnej czyści wstawione spacje, drukuje tylko do pierwszego występującego nowego wiersza w przestrzeni wzorów i wraca.

Dodam lpolecenie ook na początku pętli, takie jak:

l; s/\ni(.* )\n{9}/u\1/...

... i zobacz, jak to działa, gdy działa z tymi danymi wejściowymi:

hai this is linux.
hai this is unix.


hai this is mac.
hai this is unchanged is.

... więc oto, co robi:

 hai this \nis linux. \n$        #behind the scenes
hai this is linux.               #actually printed
 hai this \nis unix. \n\n$       #it builds the marker string
hai this is unix.
  \n\n\n$                        #only for lines matching the

  \n\n\n$                        #pattern - and not otherwise.

 hai this \nis mac. \n\n\n$      #here's the match - 3 ises so far in file.
hai this us mac.                 #printed
hai this is unchanged is.        #no look here - this line is never evaled

Ma to większy sens, może z większą liczbą ises na linię:

nthword()(  p='[:punct:]' s='[:space:]'         
    sed -e '1!{/\n/!b' -e\}             \
        -e 's/\(\n*\)\(.*\)/ \2 \1/'    \
        -e "s/$1[$p]\{0,1\}[$s]/\n&/g"  \
        -e "s/\([^$s]\)\n/\1/g;1G;:c"   \
        -e "${dbg+l;}s/\n$1\(.* \)\n\{$3\}/$2\1/" \
        -e '/\n$/!s/\n//g;/\n'"$1/G"    \
        -e "s//$1/;//tc" -e 's/^ \(.*\) /\1/'     \
        -e 'P;$d;N;D'
)        

To praktycznie to samo, ale napisane w / POSIX BRE i podstawowa obsługa argumentów.

 printf 'is is. is? this is%.0s\n' {1..4}  | nthword is us 12

... dostaje ...

is is. is? this is
is is. is? this is
is is. is? this us
is is. is? this is

... a jeśli włączę ${dbg}:

printf 'is is. is? this is%.0s\n' {1..4}  | 
dbg=1 nthword is us 12

... możemy obserwować iterację ...

 \nis \nis. \nis? this \nis \n$
 is \nis. \nis? this \nis \n\n$
 is is. \nis? this \nis \n\n\n$
 is is. is? this \nis \n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n\n\n\n\n$
is is. is? this us
is is. is? this is
mikeserv
źródło
Czy zdałeś sobie sprawę, że twój przykład mówi „isis”?
flarn2006
@ flarn2006 - jestem pewien, że mówi, że jest.
mikeserv
0

Oto logiczne rozwiązanie, które używa sedi trale muszą być napisane w skrypcie do jego pracy. Poniższy kod zastępuje co 3 wystąpienie słowa określonego w sedpoleceniu. Wymień i=3się i=ndo tej pracy dla każdego n.

Kod:

# replace new lines with '^' character to get everything onto a single line
tr '\n' '^' < input.txt > output.txt

# count number of occurrences of the word to be replaced
num=`grep -o "apple" "output.txt" | wc -l`

# in successive iterations, replace the i + (n-1)th occurrence
n=3
i=3
while [ $i -le $num ]
do
    sed -i '' "s/apple/lemon/${i}" 'output.txt'
    i=$(( i + (n-1) ))
done

# replace the '^' back to new line character
tr '^' '\n' < output.txt > tmp && mv tmp output.txt


Dlaczego to działa:

Załóżmy, że plik tekstowy to a b b b b a c a d a b b b a b e b z b s b a b.

  • Gdy n = 2: chcemy zastąpić co drugie wystąpienie b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
    • Najpierw zastępujemy 2. wystąpienie, następnie 3. wystąpienie, następnie 4., 5. itd. Policz w pokazanej powyżej sekwencji, aby zobaczyć to na własne oczy.
  • Gdy n = 3: chcemy zastąpić co trzecie wystąpienie b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
    • Najpierw zastępujemy 3. wystąpienie, następnie 5., następnie 7., 9., 11. itd.
  • Gdy n = 4: chcemy zastąpić co trzecie wystąpienie b.

    • Najpierw zastępujemy 4. wystąpienie, następnie 7., następnie 10., 13. itd.
agdhruv
źródło