web-dev-qa-db-ja.com

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

Wordの最初のkインスタンスのみを置き換えたい。

これどうやってするの?

例えば。ファイルを言うfoo.txtには、Word 'linux'の100個のインスタンスが含まれています。

最初の50個のオカレンスのみを置き換える必要があります。

24

以下の最初のセクションでは、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ソリューション

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文字列に含めることは不可能であるため、これは通常安全な仮定です。

33
John1024

Awkの使用

Awkコマンドを使用して、Wordの最初のN個のオカレンスを置換で置き換えることができます。
コマンドは、Wordが完全に一致する場合にのみ置き換えられます。

以下の例では、oldの最初の27newに置き換えています。

サブを使用

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
9
user78605

文字列の最初の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置換文字列に置換文字列が含まれています。たとえば、heheyに置き換えても機能します。

私はこれを次のようにします:

s/1/\
&/g

次に、出現ごとにholdスペースに文字を追加して置換をカウントしています。 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
7
mikeserv

Perlの短い代替:

Perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file

`$ n $の値を好きなように変更します。

仕組み:

  • すべての行で、newolds/old/new/)に置き換えようとし続け、可能な場合は常に変数$i++$i)をインクリメントします。
  • 合計で1 while ...未満の置換を行い、その行で少なくとも1つの置換を行うことができる限り、ライン($n)で動作し続けます。
4
Joseph R.

シェルループと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
4
Wildcard

単純ですが、それほど高速ではない解決策は、 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に対してのみ機能し、newwordoldword。GNU以外のsedについては、ファイルの最初のパターンのみを置き換える方法 こちら を参照してください。

3
jofel

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
2
iruvar