Sed - ファイル内の単語の最初のkインスタンスを置き換えます。

Sed - ファイル内の単語の最初のkインスタンスを置き換えます。

k私は単語の最初のインスタンスだけを変更したいと思います。

どうすればいいですか?

例えば。ファイルにfoo.txt「linux」という単語が100回含まれているとします。

最初の50項目を交換するだけです。

答え1

sed以下の最初の部分では、行で最初のk回発生する項目を変更する方法について説明します。 2番目の部分は、表示される行に関係なく、ファイル内の最初のk個の項目のみを変更することによってこのアプローチを拡張します。

ライン中心ソリューション

標準sedには、1行目からk番目に表示される単語を置き換えるコマンドがあります。たとえば、3の場合k

sed 's/old/new/3'

または、すべての項目を次のように置き換えることができます。

sed 's/old/new/g'

これらのどれもあなたが望むものではありません。

GNUは、sedk番目の発生とすべての後続のケースを変更する拡張機能を提供します。たとえば、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

ここでwhere\nは一行には決して現れないので便利です。

説明する:

3つの代替コマンドを使用しますsed

  • s/\<old\>/\n/g4

    これは、4番目とそれ以降のすべての項目old\n

    \<単語の始まりと\>終わりを一致させるための拡張正規表現機能。これにより、完全な単語のみが一致します。拡張正規表現には-Eオプションが必要ですsed

  • s/\<old\>/new/g

    最初の3つの項目だけを保持すると、oldすべて置き換えられますnew

  • s/\n/old/g

    4番目と残りのすべての発生は最初のステップoldに置き換えられます。\nこれにより元の状態に復元されます。

非GNUソリューション

GNU sedを使用できず、の最初の3つの項目をに変更するには、次の3oldつのコマンドをnew使用します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にファイル全体を読み、交換を実行するように指示できます。たとえば、oldBSD スタイルを使用して 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長い行を処理する能力をテストする必要があります。

gGNU sedを使用すると、上記のトリックをさらに一歩進めることができますが、最初の3つの項目を置き換えることができます\n\x00

sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'

このアプローチはスケーラビリティが高く、k規模も大きくなります。しかし、\x00元の文字列にはないとします。\x00bash文字列に文字を入れることは不可能なので、これは通常安全な仮定です。

答え2

awkを使う

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これらの動作が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

文字列の最初の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つの注目すべき技術を使用しました。まず、11行のすべての項目がで置き換えられます\n1。これにより、次回の再帰的置換を実行するときに置換が2回発生しないようにすることができます。もし私の代替文字列には私の代替文字列が含まれています。たとえば、交換しても機能heheyます。

私はこれをこうする:

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"

そして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

答え4

シェルループを使用して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

関連情報