ファイルからのテキストがあるとします。
(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が最適なソリューションではない場合、他に何を使用できますか?
これを試してください(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の詳細な使用方法を読むために読むことができます。
正規表現の置換を提供するほぼすべてのツールとは異なり、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" }'
_
(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