web-dev-qa-db-ja.com

文字列の置換に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を追加したい場合は、各行に"を続けます。

(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が最適なソリューションではない場合、他に何を使用できますか?

13
Tim

これを試してください(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つの数値がどのように異なるのかはわかりません。 1つ目は印刷されたページ番号で、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

testwithnew例:

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の詳細な使用方法を読むために読むことができます。

12
Kent

正規表現の置換を提供するほぼすべてのツールとは異なり、awkは、置換テキストで\1などの後方参照を許可しません。 GNU match関数 を使用する場合、Awkは一致したグループへのアクセスを提供しますが、~または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はそれを行うことができますが、直接参照することはできません。
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 }'
_

gensubと配列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" }'
_
5
Peter.O

(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
3
Dubu