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は、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
ここで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にファイル全体を読み、交換を実行するように指示できます。たとえば、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
長い行を処理する能力をテストする必要があります。
g
GNU 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
元の文字列にはないとします。\x00
bash文字列に文字を入れることは不可能なので、これは通常安全な仮定です。
答え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つの注目すべき技術を使用しました。まず、1
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"
そして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