Wordの最初のk
インスタンスのみを置き換えたい。
これどうやってするの?
例えば。ファイルを言うfoo.txt
には、Word 'linux'の100個のインスタンスが含まれています。
最初の50個のオカレンスのみを置き換える必要があります。
以下の最初のセクションでは、sed
を使用して行の最初のk出現を変更する方法について説明します。 2番目のセクションでは、このアプローチを拡張して、どの行に表示されるかに関係なく、ファイル内の最初のk出現のみを変更します。
標準のsedでは、行にある単語のk番目の出現を置き換えるコマンドがあります。たとえば、k
が3の場合:
sed 's/old/new/3'
または、すべての出現箇所を次のように置き換えることができます。
sed 's/old/new/g'
これらのどちらもあなたが望むものではありません。
GNU sed
は、k番目以降の出現を変更する拡張機能を提供します。たとえば、kが3の場合:
sed 's/old/new/g3'
これらを組み合わせて、好きなことを行うことができます。最初の3つのオカレンスを変更するには:
$ 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
は、行で発生しないことが確実であるため、ここで役立ちます。
3つのsed
置換コマンドを使用します。
s/\<old\>/\n/g4
これは、GNU拡張機能で、4番目以降のold
を\n
に置き換えます。
拡張正規表現機能\<
を使用して単語の先頭を照合し、\>
を使用して単語の末尾を照合します。これにより、完全な単語のみが一致することが保証されます。拡張正規表現では、sed
に-E
オプションが必要です。
s/\<old\>/new/g
old
の最初の3つのオカレンスのみが残り、これによりすべてがnew
に置き換えられます。
s/\n/old/g
old
の4番目以降の出現箇所は、最初のステップで\n
に置き換えられました。これにより、元の状態に戻ります。
GNU sedが利用できず、old
の最初の3つの出現をnew
に変更したい場合は、3つのs
コマンドを使用します。
$ 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
これは、k
が小さい場合にうまく機能しますが、大きいk
へのスケーリングが不十分です。
一部の非GNU sedはセミコロンとのコマンドの組み合わせをサポートしていないため、ここの各コマンドは独自の-e
オプションで導入されています。 sed
がWord境界記号\<
および\>
をサポートしていることを確認する必要がある場合もあります。
ファイル全体を読み取ってから置換を実行するようにsedに指示できます。たとえば、BSDスタイルのsedを使用して、最初の3つのold
を置き換えるには:
sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
SedコマンドH;1h;$!d;x
はファイル全体を読み込みます。
上記はGNU拡張子を使用していないため、BSD(OSX)sedで動作するはずです。このアプローチには、長い行を処理できるsed
が必要であることに注意してください。 GNU sed
は問題ありません。GNU以外のバージョンのsed
を使用している場合は、長い行を処理する機能をテストする必要があります。
GNU sedを使用すると、上記のg
トリックをさらに使用できますが、\n
を\x00
に置き換えて、最初の3つのオカレンスを置き換えます:
sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'
このアプローチは、k
が大きくなるにつれて拡張します。ただし、これは\x00
が元の文字列に含まれていないことを前提としています。文字\x00
をbash文字列に含めることは不可能であるため、これは通常安全な仮定です。
Awkコマンドを使用して、Wordの最初のN個のオカレンスを置換で置き換えることができます。
コマンドは、Wordが完全に一致する場合にのみ置き換えられます。
以下の例では、old
の最初の27
をnew
に置き換えています。
サブを使用
awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file
このコマンドは、
old
に一致するまで各フィールドをループし、カウンタが27未満であることを確認し、増分して、行の最初の一致を置き換えます。次に、次のフィールド/行に移動して繰り返します。
フィールドを手動で置き換える
awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
以前のコマンドと似ていますが、
($i)
までのマーカーが既にあるため、フィールドの値をold
からnew
に変更するだけです。
前にチェックを実行
awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
行が古いことを確認し、カウンターが27
SHOULD
未満であることを確認すると、これらがfalseの場合に行が処理されないため、速度が少し向上します。
[〜#〜]結果[〜#〜]
例えば
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
に
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
文字列の最初の3つのインスタンスのみを置換したいとします...
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
注:上記は埋め込みコメントでは機能しない可能性があります
...または私の例では、「1」の...
22
211
211
311
そこでは2つの注目すべきテクニックを使用しています。そもそも、行のすべての1
は\n1
に置き換えられます。このようにして、次に再帰的な置換を行うときに、オカレンスを2回置換しないようにすることができますif置換文字列に置換文字列が含まれています。たとえば、he
をhey
に置き換えても機能します。
私はこれを次のようにします:
s/1/\
&/g
次に、出現ごとにh
oldスペースに文字を追加して置換をカウントしています。 3つに達すると、もう発生しません。これをデータに適用し、\{3\}
を希望する合計置換数に変更し、/\n1/
アドレスを置換するものに変更する場合は、必要な数だけ置換する必要があります。
私は読みやすさのためにすべての-e
を行いました。 POSIXlyこれは次のように書くことができます:
nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"
そしてGNU sed
:
sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'
sed
は行指向であることも忘れないでください。ファイル全体を読み取ってから、他のエディターでよくあるように、ループバックを試みます。 sed
はシンプルで効率的です。そうは言っても、以下のようなことをするのがしばしば便利です:
これは、単純に実行されるコマンドにバンドルする小さなシェル関数です。
firstn() { sed "s/$2/\
&/g;:t
/\n/{x
/.\{$(($1))"',\}/!{
s/$/./; x; s/\n'"$2/$3"'/
b t
};x
};s/\n//g'; }
だから私はできる:
seq 11 100 311 | firstn 7 1 5
...そして...
55
555
255
311
...または...
seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'
...取得するため...
10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25
...または、例に一致させるには((小さい方の桁):
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
Perlの短い代替:
Perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file
`$ n $の値を好きなように変更します。
仕組み:
new
をold
(s/old/new/
)に置き換えようとし続け、可能な場合は常に変数$i
(++$i
)をインクリメントします。1 while ...
未満の置換を行い、その行で少なくとも1つの置換を行うことができる限り、ライン($n
)で動作し続けます。シェルループとex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
はい、それは少し間抜けです。
;)
注:ファイル内のold
のインスタンスが50個未満の場合、これは失敗する可能性があります。 (私はテストしていません。)テストした場合、ファイルは変更されません。
Vimを使用してください。
vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x
説明:
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
単純ですが、それほど高速ではない解決策は、 https://stackoverflow.com/questions/148451/how-to-use-sed-to-replace-only-the-first-occurrenceで説明されているコマンドをループすることです-in-a-file
for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done
この特定のsedコマンドはおそらくGNU sedに対してのみ機能し、newwordがoldword。GNU以外のsedについては、ファイルの最初のパターンのみを置き換える方法 こちら を参照してください。
GNU awk
を使用すると、レコード区切り記号RS
を置き換えられるワードワード境界で区切って設定できます。最初のk
レコードの出力のレコードセパレーターを置換Wordに設定し、残りのレコードセパレーターは保持する場合
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
OR
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file