AWKで正規表現を使用して文字列を置換する方法は?


13

ファイルからテキストがあると仮定します。

(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 23" "#2")
("Exercises 31" "#30")
("Notes and References 42" "#34"))
)

私は各行に11を追加し、その後"に各行にa があれば、

(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 34" "#13")
("Exercises 42" "#41")
("Notes and References 53" "#45"))
)

GNU AWKと正規表現を使用した私のソリューションは次のとおりです。

awk -F'#' 'NF>1{gsub(/"(\d+)\""/, "\1+11\"")}'

つまり、私は交換したい(\d+)\"\1+10\"場所を、\1グループを代表しています(\d+)。しかし、それは機能しません。どうすればそれを機能させることができますか?

gawkが最適なソリューションではない場合、他に何を使用できますか?


複製についてすみません。しかし、私は最初にstackoverflowで尋ねましたが、満足のいく答えが得られなかったため、移行のフラグを立てました。しかし、それはしばらくの間起こらなかったので、私はそれが起こることを期待していなかったので、Unix.SEで尋ねました。
ティム

回答:


12

これを試してください(gawkが必要です)。

awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}' YourFile

例でテストします。

kent$  echo '(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 2" "#2")
("Exercises 30" "#30")
("Notes and References 34" "#34"))
)
'|awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}'   
(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 13" "#13")
("Exercises 41" "#41")
("Notes and References 45" "#45"))
)

このコマンドは、2つの数字(例:1 "と"#1 ")が異なる場合、またはこのパターンと同じ行にさらに数がある場合(例:23" ... 32 "..."#)は機能しないことに注意してください。 123 ")1行。


更新

@Tim(OP)は、"同じ行に続く数字が異なる可能性があると言ったため、以前のソリューションにいくつか変更を加え、新しい例で機能するようにしました。

ところで、この例から、それは目次構造のテーブルである可能性があると思うので、2つの数字がどのように異なるのかわかりません。最初は印刷されたページ番号で、2番目の#はページインデックスです。私は正しいですか?

とにかく、要件を最もよく知っています。今でも新しいソリューションで、まだgawkを使用しています(読みやすくするためにコマンドを行に分割しています)。

awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;}
        a=gensub(/.* ([0-9]+)$/,"\\1","g",$1);
        b=gensub(/([0-9]+)\"/,"\\1","g",$2); 
        gsub(/[0-9]+$/,a+11,$1);
        gsub(/^[0-9]+/,b+11,$2);
        print $1,$2
}' yourFile

新しい例を使用してテストします。

kent$  echo '(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 23" "#2")
("Exercises 31" "#30")
("Notes and References 42" "#34"))
)
'|awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;}
        a=gensub(/.* ([0-9]+)$/,"\\1","g",$1);
        b=gensub(/([0-9]+)\"/,"\\1","g",$2); 
        gsub(/[0-9]+$/,a+11,$1);
        gsub(/^[0-9]+/,b+11,$2);
        print $1,$2
}'                        
(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 34" "#13")
("Exercises 42" "#41")
("Notes and References 53" "#45"))
)


EDIT2 @Timさんのコメントに基づいて

(1)FS = OFS = "\" \ "#"は、入力と出力の両方のフィールドの区切り文字が二重引用符、スペース、二重引用符、および#であることを意味しますか?二重引用符を2回指定する理由

入力部と出力部の両方でセパレーターに適しています。セパレータを次のように定義しました:

" "#

(入力例に基づいて)必要な2つの数値を簡単にキャッチできるため、2つの二重引用符があります。

(2)/.*([0-9] +)$ /では、$は文字列の終わりを意味しますか?

丁度!

(3)gensub()の3番目の引数で、「g」と「G」の違いは何ですか?Gとgの間に違いはありません。これをチェックしてください:

gensub(regexp, replacement, how [, target]) #
    Search the target string target for matches of the regular expression regexp. 
    If "how" is a string beginning with g or G (short for global”), then 
        replace all matches of regexp with replacement.

これはhttp://www.gnu.org/s/gawk/manual/html_node/String-Functions.htmlからのものです。gensubの詳細な使用法を読むことができます。


ありがとう!2つの数値、たとえば1 "と"#1 "が異なる場合、どのように機能させるのでしょうか?
ティム

この回答は、現在の要件/例で機能します。要件が変更された場合は、質問を編集して、より良い例を挙げることができます。そして、あなたのコードから、あなたawk -F'#'は「#」の後の部分でのみ変更をしたいようです?
ケント

ご提案ありがとうございます。2つの数値が同じではないように例を変更しました。
ティム

@Timは、新しい例について、更新された回答を参照してください。
ケント

ありがとう!いくつかの質問:(1)FS=OFS="\" \"#"入力と出力の両方のフィールドの区切り文字は、二重引用符、スペース、二重引用符、#?二重引用符を2回指定する理由 (2)で/.* ([0-9]+)$/$文字列の終わりを意味しますか?gensubの第三引数((3))の違いは何である"g"とは"G"
ティム

7

正規表現の置換を提供するほぼすべてのツールとは異なり、awkは\1置換テキストなどの後方参照を許可しません。GNU Awkは、match関数を使用する場合に一致したグループへのアクセスを提供しますが、~またはsubまたはでは使用しませんgsub

また、\1サポートされていても、スニペットは+11数値計算を実行せずに文字列を追加することに注意してください。また、正規表現は正しく"42""ありません"#42"

これがawkソリューションです(警告、未テスト)。1行につき1回の置換のみを実行します。

awk '
  match($0, /"#[0-9]+"/) {
    n = substr($0, RSTART+2, RLENGTH-3) + 11;
    $0 = substr($0, 1, RSTART+1) n substr($0, RSTART+RLENGTH-1)
  }
  1 {print}'

Perlではよりシンプルになります。

perl -pe 's/(?<="#)[0-9]+(?=")/$1+11/e'

あなたの答えの最初の文はまさに私が探していたものです。ただし、「...置換テキスト」と言ったという事実は、次の質問を提起します:awkは正規表現パターン自体の後方参照を許可しますか?
ワイルドカード

1
@Wildcardいいえ、awkはグループを追跡しません(言及したGNU拡張機能を除く)。
ジル「SO-悪であるのをやめる」

5

awkそれはできますが、逆参照を使用しても直接ではありません。
GNU awkは、(部分的な)後方参照をgensubの形式で持っています。

インスタンス123"を一時的に包まれている \x01\x02のための(無修正としてそれらをマークするsub()。コ

または、ループを通過して候補を変更するだけでもかまいません。その場合、後方参照と「括弧」は必要ありません。ただし、文字インデックスを追跡する必要があります。

awk '{$0=gensub(/([0-9]+)\"/, "\x01\\1\"\x02", "g", $0 )
      while ( match($0, /\x01[0-9]+\"\x02/) ) {
        temp=substr( $0, RSTART, RLENGTH )
        numb=substr( temp, 2, RLENGTH-3 ) + 11
        sub( /\x01[0-9]+\"\x02/, numb "\"" ) 
      } print }'

ここに別の方法があります。and gensubarray split\x01フィールド区切り文字(splitの場合)として使用 します。\ x02は、算術加算の候補として配列要素をマークします。

awk 'BEGIN{ ORS="" } {
     $0=gensub(/([0-9]+)\"/, "\x01\x02\\1\x01\"", "g", $0 )
     split( $0, a, "\x01" )
     for (i=0; i<length(a); i++) { 
       if( substr(a[i],1,1)=="\x02" ) { a[i]=substr(a[i],2) + 11 }
       print a[i]
     } print "\n" }'

ありがとう!最初のコードでは、(1)どういう"\x01\\1\"\x02"意味ですか?私はまだ理解していない\x01\x02。(2)$0による戻りgensub$0最後の引数としての違いはgensub
ティム

@ティム。16進値\x01\x02は、置換マーカーとして使用されます。これらの値は非常にいずれかの可能性は低いです通常の ..彼らはただ一時的なラベルです...彼らは(すなわち、既存のものとの衝突が発生していない)を使用しても同様に「非常に」安全であるので、再度テキストファイル$0=gensub(... $0)。これを参照してくださいリンクString-Manipulation Functions、ただし要約:It(gensub)は、関数の結果として変更された文字列を返し、元のターゲット文字列は変更されません。... $0=単純に元のターゲットを変更...
Peter.O

2

(g)awkのソリューションは非常に複雑になるようであるため、Perlに代替ソリューションを追加したかったのです。

perl -wpe 's/\d+(?=")/$&+11/eg' < in.txt > out.txt

説明:

  • オプションは-w、警告を有効にします(望ましくない影響の可能性について警告します)。
  • オプション-pは、sedまたはawkと同様に機能するコードの周りのループを意味し、入力の各行をデフォルト変数に自動的に保存します$_
  • オプション-eは、プログラムコードがスクリプトファイルではなくコマンドラインで実行されていることをperlに伝えます。
  • コードはの正規表現置換(s/.../.../)であり$_、数字のシーケンスが後に続く場合、数字のシーケンスに"置き換えられ、加算での数値に11を加えたものとして解釈されます。
  • ゼロ幅の正の先読みアサーション (?=pattern)を探し"、私たちは、交換でそれを繰り返す必要はありませんので、試合中にそれを取らずに。$&置換のMATCH変数には、数字のみが含まれます。
  • /e正規表現の修飾子perlは、文字列としてではなくコードとして置換を「実行」するように指示します。
  • /g修飾子は、ライン内のすべての試合でそれを繰り返し、「グローバル」の交換になります。

MATCH変数$&は、残念ながら5.20より前のPerlバージョンのコードパフォーマンスに悪影響を及ぼします。より高速な(それほど複雑ではない)ソリューションでは、$1代わりにグループ化と後方参照を使用します。

perl -wpe 's/(\d+)?="/$1+11/eg' < in.txt > out.txt

また、先読みのアサーションがわかりにくい場合は、引用符を明示的に置き換えることもできます。

perl -wpe 's/(\d+)"/$1+11 . q{"}/eg' < in.txt > out.txt
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.