文字列の置換に正規表現とAWKを使用する方法は?

文字列の置換に正規表現とAWKを使用する方法は?

ファイルにテキストがあるとします。

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

"各数字に11を追加し、ある場合は各行に1を追加したいと思います。

(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が最善の解決策でない場合は、何を使用できますか?

答え1

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

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") 行に注意してください。


修正する

@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"))
)


編集2@Timのコメントに基づいて

(1) FS=OFS="\" \"#" は、入力と出力のフィールド区切り文字が二重引用符、空白、二重引用符、および # であることを意味しますか。なぜ二重引用符を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

正規表現の置き換えを提供するほとんどすべてのツールとは異なり、awkは\1代替テキストなどの逆参照を許可しません。 GNU Awkを使用すると、一致するグループにアクセスできますmatch機能~ただし、orsubまたはと一緒に使用することはできませんgsub

また\1、サポートされていても、コードスニペットは+11数値計算を実行する代わりに文字列を追加します。また、正規表現が正しくありません。"42""and notなどの項目が一致しています"#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'

答え3

awk実行できますが、逆参照を使用しても直接的ではありません。
GNU awkフォームの(部分)逆参照があります。

のインスタンスは123"一時的にラップされ、変更されていないと \x01マーク\x02されます(sub().coの場合)。

あるいは、ループを段階的に実行して、いつでも候補を変更することができます。この場合、逆参照と「括弧」は必要ありませんが、文字インデックスは追跡する必要があります。

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 }'

gensub以下は、配列split\x01フィールド区切り文字を使用する別のアプローチです。分ける).. \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" }'

答え4

(g)awkのソリューションがかなり複雑になるので、Perlに代替ソリューションを追加したかったのです。

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

説明する:

  • 警告を有効にするオプション-w(これは副作用を引き起こす可能性があることを警告します)。
  • Optionは-pコードの周りを循環することを意味し、sedやawkのように動作し、各入力行をデフォルト変数に自動的に保存します$_
  • オプションは、-ePerlにスクリプトファイルではなくコマンドラインでコードをプログラムするように指示します。
  • コードは置換(s/.../.../)の正規表現です$_。数値シーケンスの後に続くと、"数値に11を加えたと解釈される対応するシーケンスに置き換えられます。
  • これ幅0の肯定的な予測アサーション (?=pattern)探してください。ただし、"一致としてインポートしないでください。これにより、交換時に繰り返す必要はありません。置換の MATCH 変数には$&数値のみが含まれます。
  • /e正規表現の修飾子は、置換をperl文字列ではなくコードとして「実行」するように指示します。
  • 修飾子は/g置換を「グローバル」にし、行のすべての項目でこれを繰り返します。

残念ながら、MATCH変数は$&Perl 5.20より前のバージョンのコードパフォーマンスを低下させます。より高速で複雑でない解決策は、グループ化と逆参照を使用することです$1

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

予測アサーションが混乱しすぎる場合は、引用符を明示的に置き換えることもできます。

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

関連情報