假设文件中有一些文本:
(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 不是最好的解决方案,还可以使用什么?
答案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"))
)
请注意,如果两个数字(例如 1" 和 "#1")不同,或者该模式同一行中有更多数字(例如 23" ...32"..."# 123") 在一行中。
更新
由于@Tim(OP)说同一行中后面的数字"
可能不同,因此我对之前的解决方案做了一些更改,并使其适用于您的新示例。
顺便说一句,从这个例子中我觉得它可能是一个目录结构,所以我不明白这两个数字有什么不同。第一个是打印的页码,第二个带 # 的是页面索引。我对吗?
不管怎样,你最了解自己的需求。现在新的解决方案,仍然使用 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)/.*([0-9]+)$/中,$是否表示字符串结束?
确切地!
(3) gensub()的第三个参数中,“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
功能,但不能与~
或sub
或 一起使用gsub
。
另请注意,即使\1
支持,您的代码片段也会附加字符串+11
,而不是执行数值计算。另外,您的正则表达式不太正确,您正在匹配诸如"42""
和 not 之类的东西"#42"
。
这是一个 awk 解决方案(警告,未经测试)。它只对每行执行一次替换。
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
and\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,自动将每行输入保存在默认变量 中$_
。 - 选项
-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