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


24

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

これどうやってするの?

例えば。ファイルにfoo.txtは、単語 'linux'の出現が100個含まれているとします。

最初の50件のみを置換する必要があります。


1
:あなたはこれを参照することができますunix.stackexchange.com/questions/21178/...
cuonglm

sedが特に必要ですか、または他のツールは受け入れられますか?コマンドラインで作業する必要がありますか、それともテキストエディタは受け入れられますか?
evilsoup 14

コマンドラインで機能するものはすべて受け入れられます。
ナレンドラ-choudhary 14

回答:


31

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

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


5
これは、ラインのみで動作し、すべての行の最初の4つの出現箇所を変更します

1
@mikeserv素晴らしいアイデア!回答が更新されました。
ジョン1024 14

(1)GNUおよび非GNU sedに言及し、提案し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'
Gマンは「Reinstate Monica」と言います14

@ G-Manありがとうございます!答えを更新しました。
John1024 14

これはとてもいです
ルイマドックス

8

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これらの行が偽の場合に行を処理しないため、速度が少し向上します。

結果

例えば

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

文字列「old」が* word oldの前にある場合、最初のもの(subを使用)は間違った動作をします。例えば、「老人にいくつかの金を与える。」→「老人にいくつかのgnewを与える。」
G-マンは「元に戻すモニカ言う

@ G-Manええ、私は$iビットを忘れました、編集されました、ありがとう:)

7

文字列の最初の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回置換しないようにすることができます。たとえば、置き換え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"

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

4

Perlの短い代替手段:

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

`$ n $の値を好みに合わせて変更します。

使い方:

  • すべての行についてnewolds/old/new/)の代用を試み続け、可能な限り変数をインクリメントします$i++$i)。
  • 合計1 while ...$n置換が少なくなり、その行で少なくとも1つの置換を行うことができる限り、行()で動作し続けます。

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

空の正規表現は最後に使用された検索を再利用するため、:s // new <CR>も同様に機能するはずです
eike

3

単純だがそれほど高速ではない解決策は、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で、newwordoldwordの一部でない場合にのみ機能します。非GNUについて見sedはこちらをファイルにのみ第一のパターンを交換する方法について説明します。


「古い」を「太字」に置き換えると問題が発生する可能性があることを識別するための+1。
Gマンは「Reinstate Monica」と言います

2

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.