k
単語の最初のインスタンスのみを置き換えたい。
これどうやってするの?
例えば。ファイルにfoo.txt
は、単語 'linux'の出現が100個含まれているとします。
最初の50件のみを置換する必要があります。
k
単語の最初のインスタンスのみを置き換えたい。
これどうやってするの?
例えば。ファイルにfoo.txt
は、単語 '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拡張old
とを\n
。
拡張正規表現機能\<
は、単語の先頭と単語\>
の末尾を一致させるために使用されます。これにより、完全な単語のみが一致することが保証されます。拡張正規表現には、-E
オプションが必要sed
です。
s/\<old\>/new/g
old
残りの最初の3つのオカレンスのみが、これらすべてをに置き換えますnew
。
s/\n/old/g
の4番目と残りのすべては、最初のステップでold
置き換えられまし\n
た。これにより、元の状態に戻ります。
GNUのsedの場合は使用できません、あなたは最初の3つの出現変更するold
には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
支援の単語境界記号、\<
および\>
。
ファイル全体を読み込んでから置換を実行するようにsedに指示できます。たとえばold
、BSDスタイルのsed を使用した最初の3つの出現を置き換えるには:
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文字列に入れることは不可能なので、これは通常安全な仮定です。
tr '\n' '|' < input_file | sed …
ます。しかし、もちろん、これは入力全体を1行に変換し、GNU以外のsedでは、任意の長い行を処理できません。(2)「…上記、引用された文字列'|'
は任意の文字または文字列でtr
置き換える必要があります...」と言いますが、文字を(長さ> 1の)文字列で置き換えることはできません。(3)最後の例では、あなたは言う-e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new
。これはのタイプミスのようです-e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
。
awkコマンドを使用して、単語の最初のN個の出現を置換に置き換えることができます。
コマンドは、単語が完全に一致する場合にのみ置き換えられます。
以下の例では、最初の27
出現を次のように置き換えold
ています。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
これらの行が偽の場合に行を処理しないため、速度が少し向上します。
結果
例えば
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回置換しないようにすることができます。たとえば、置き換えhe
てhey
も機能します。
私はこれを次のように行います:
s/1/\
&/g
次に、h
出現ごとに古いスペースに文字を追加することにより、置換をカウントしています。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"
そしてw / 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
シェルループと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で説明されているコマンドをループすることです -ファイル
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
レコードの置換語に設定し、残りのレコード区切り文字を保持する場合です
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
または
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file