web-dev-qa-db-ja.com

ファイル内のパターンのN番目の出現のみを置き換える方法は?

sedコマンドを使用して、ファイル内で3番目に出現する文字列を置き換える方法。

例:

ファイル内で3番目に出現するisのみをusに変更します。

私の入力ファイルには以下が含まれます:

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

私は出力が期待されています:

hai this is linux.
hai thus is unix.
hai this is mac.
hai this is unchanged.
10
Sureshkumar

Perlを使用すると、はるかに簡単です。

3を変更するにはrd 発生:

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

3つごとに交換するrd 発生:

Perl -pe 's{is}{++$n % 3 ? $& : "us"}ge'
11

置換文字列が1行に1回だけ出現する場合は、さまざまなユーティリティを組み合わせることができます。
入力がファイル「input」にあり、「is」を「us」に置き換えている場合、使用できます

LINENR=$(cat input | grep -n " is " | head -3 | tail -1 | cut -d: -f1)
cat input | sed ${LINENR}' s/ is / us /'
3
Walter A

以前に改行を他の文字に置き換えた場合は、sedを使用できます。例:

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

純粋な(GNU)sedの場合も同じです。

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

sed改行の置き換えは https://stackoverflow.com/a/1252191/4488514 から恥知らずに盗まれました)

2
jimmij

以下のスクリプト([〜#〜] gnu [〜#〜]sed構文を使用)は、出力ではなくインプレース編集に使用できますこれは、必要な置換の後に印刷行を停止するためです。

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

もしあなたが好きなら チョロバ 決定あなたは上記を修正することができます

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

すべてのラインを出力します

または、すべての行をパターン空間(メモリ内にあるのでサイズ制限に注意してください)に配置して置換を行う必要があります

sed ': 1 ; N ; $!b1 ; s/is/us/3 ' text.file
2
Costas
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'

そのsedのビットは、ある行から次の行へのis発生の集計を運ぶだけです。それはあなたがそれに投げるのと同じくらい多くのisesをラインごとに確実に処理するべきです、そしてそれはそれがしている間古いラインをバッファリングする必要はありません-それは別のWordの一部ではない、遭遇するすべてのisに対して単一の改行文字を保持するだけです。

その結果、ファイル内の3番目のオカレンスのみが変更され、1行あたりのカウントが保持されます。したがって、ファイルが次のようになっているとします。

1. is is isis
2. is does

...印刷されます...

1. is is isis
2. us does

まず、各行の先頭と末尾にスペースを挿入してEdgeケースを処理します。これにより、Wordの境界を確認しやすくなります。

次に、ゼロまたは1つの句読文字の直後にスペースが続くisのすべての出現箇所の前に\newlineを挿入して、有効なisesを探します。別のパスを実行し、スペース以外の文字が直前にあるすべての\newlinesを削除します。残されたこのマーカーは、is.およびisと一致しますが、thisまたは?isとは一致しません。

次に、各マーカーを文字列の末尾に収集します。行で\niが一致するたびに、文字列の末尾に\newlineが追加され、iまたはuに置き換えられます。文字列の末尾に収集された行に\newlinesが3つある場合は、uを使用します。それ以外の場合はiを使用します。 uが最初に使用されたときも最後です-置換により、無限ループが始まり、get line, print line, get line, print line,になります。

各tryループサイクルの最後に、挿入されたスペースをクリーンアップし、パターンスペースで最初に出現する改行までのみを印刷してから、再度実行します。

次のように、ループの先頭にlookコマンドを追加します。

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

...そして、この入力で動作するときに何が行われるかを確認します。

hai this is linux.
hai this is unix.


hai this is mac.
hai this is unchanged is.

...だからここにそれが何をするかです:

 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

1行あたりのisesが多ければ多いほど、理にかなっています。

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'
)        

これは実質的に同じことですが、POSIX BREと基本的な引数処理を使用して記述されています。

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

...取得...

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

...そして${dbg}を有効にした場合:

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

...それが繰り返されるのを見ることができます...

 \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
1
mikeserv

sedtrを使用する論理的なソリューションを次に示しますが、機能するためにはスクリプトで記述する必要があります。以下のコードは、sedコマンドで指定されたWordのすべての番目の出現を置き換えます。置換i=3i=nこれをすべてのnで機能させる。

コード:

# 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


これが機能する理由:

テキストファイルがa b b b b a c a d a b b b a b e b z b s b a b

  • N = 2の場合、bの2番目のオカレンスをすべて置き換えます。

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
    • 最初に2番目のオカレンス、3番目のオカレンス、4番目、5番目のように置き換えます。上に示した順序で数えて、これを実際に見てください。
  • N = 3の場合、bの3番目ごとの出現を置き換えます。

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
    • 最初に3番目、次に5番目、7番目、9番目、11番目というように置き換えます。
  • N = 4の場合、bの3番目ごとの出現を置き換えます。

    • 最初に4番目、次に7番目、10番目、13番目というように置き換えます。
0
agdhruv