我想在文本文件中执行一组(非递归)替换。 我想在ascii文件中定义规则" table.txt"其中包含空行空格行列表字符串:
aaa 3
aa 2
a 1
我试图用awk脚本解决它" substitute.awk":
BEGIN { while (getline < file) { subs[$1]=$2; } }
{ line=$0; for(i in subs)
{ gsub(i,subs[i],line); }
print line;
}
当我调用脚本给它时,字符串&#34; aaa&#34;:
echo aaa | awk -v file="table.txt" -f substitute.awk
我得到了
21
代替所需的&#34; 3&#34;。置换&#34; table.txt&#34;中的行。没有帮助。谁能解释这里的问题,以及如何规避它? (这是我实际任务的简化版本。我有一个包含ascii编码的拼音符号的大文件,我想将其转换为Latex代码。符号的ascii编码包含{$,&amp;, - ,%,[az ],[0-9],......))。
任何意见和建议!
PS:
当然在这个替换table.txt的应用程序中:
aa ab
a 1
原始字符串:&#34; aa&#34;应转换成&#34; ab&#34;而不是&#34; 1b&#34;。这意味着通过应用规则产生的字符串必须保持不变。
如何解释?
答案 0 :(得分:3)
默认情况下,循环for (i in subs)
的顺序未定义。
在较新版本的awk
中,您可以使用PROCINFO["sorted_in"]
来控制排序顺序。有关详细信息,请参阅12.2.1 Controlling Array Traversal
部分和(链接的)部分8.1.6 Using Predefined Array Scanning Orders
。
或者,如果您不能或不想这样做,您可以将替换存储在subs
中的数字索引条目中,并按顺序遍历数组。
要做到这一点,您需要将模式和替换存储在数组的值中,这需要一些小心的组合。您可以考虑使用SUBSEP
或任何其他不能出现在模式或替换中的字符,然后使用split
值来获取循环中的模式和替换。
另请注意http://awk.info/?tip/getline上列出getline
的警告/等等,并考虑不使用NR==1{...}
,而是使用table.txt
并仅列出awk
作为第一个文件参数gsub
。
编辑:实际上,对于手动循环版本,您还可以保留两个数组,一个映射输入文件行号到要匹配的模式,另一个映射模式到替换。然后循环遍历行号数组将获得模式,并且可以在第二个数组中使用模式来获取替换(对于{{1}})。
答案 1 :(得分:2)
不是将替换存储在关联数组中,而是将它们放在两个由整数索引的数组中(一个数组用于替换字符串,一个用于替换)并按顺序迭代数组:
BEGIN {i=0; while (getline < file) { subs[i]=$1; repl[i++]=$2}
n = i}
{ for(i=0;i<n;i++) { gsub(subs[i],repl[i]); }
print tolower($0);
}
答案 2 :(得分:1)
似乎perl的零宽度字边界是你想要的。这是awk非常直接的转换:
#!/usr/bin/env perl
use strict;
use warnings;
my %subs;
BEGIN{
open my $f, '<', 'table.txt' or die "table.txt:$!";
while(<$f>) {
my ($k,$v) = split;
$subs{$k}=$v;
}
}
while(<>) {
while(my($k, $v) = each %subs) {
s/\b$k\b/$v/g;
}
print;
}
答案 3 :(得分:1)
以下是来自另一个StackExchange网站的答案,来自一个非常相似的问题:Replace multiple strings in a single pass。
它略有不同,因为它按目标字符串的长度(即最长的目标首先)按相反顺序进行替换,但这是字面字符串的目标的唯一合理顺序,如图所示这个问题也是如此。
如果您安装了tcc
,则可以使用以下shell函数,该函数将替换文件处理到lex
生成的扫描程序中,然后使用tcc编译它编译并运行 - 并且运行选项。
# Call this as: substitute replacements.txt < text_to_be_substituted.txt
# Requires GNU sed because I was too lazy to write a BRE
substitute () {
tcc -run <(
{
printf %s\\n "%option 8bit noyywrap nounput" "%%"
sed -r 's/((\\\\)*)(\\?)$/\1\3\3/;
s/((\\\\)*)\\?"/\1\\"/g;
s/^((\\.|[^[:space:]])+)[[:space:]]*(.*)/"\1" {fputs("\3",yyout);}/' \
"$1"
printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
} | lex -t)
}
使用gcc
或clang
,您可以使用类似的东西从替换列表中编译替换程序,然后在给定文本上执行该程序。 Posix标准版c99
不允许来自stdin
的输入,但gcc
和clang
很乐意这样做,前提是您明确告诉他们这是一个C程序({{ 1}})。为了避免过多的编辑,我们使用-x c
(需要make
,Gnu make)。
以下要求替换列表位于扩展名为gmake
的文件中;缓存的已编译可执行文件将具有相同的名称,并具有.txt
扩展名。如果makefile位于名为.exe
的当前目录中,则可以将其作为Makefile
调用(其中make repl
是没有文本扩展名的替换文件的名称),但是因为#39;不太可能出现这种情况,我们将使用shell函数来实际调用make。
请注意,在以下文件中,每行开头的空格以制表符开头:
repl
.SECONDARY:
%: %.exe
@$(<D)/$(<F)
%.exe: %.txt
@{ printf %s\\n "%option 8bit noyywrap nounput" "%%"; \
sed -r \
's/((\\\\)*)(\\?)$$/\1\3\3/; #\
s/((\\\\)*)\\?"/\1\\"/g; #\
s/^((\\.|[^[:space:]])+)[[:space:]]*(.*)/"\1" {fputs("\3",yyout);}/' \
"$<"; \
printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"; \
} | lex -t | c99 -D_POSIX_C_SOURCE=200809L -O2 -x c -o "$@" -
您可以使用以下命令调用上述命令:
substitute() {
gmake -f/path/to/substitute.mak "${1%.txt}"
}
其中substitute file
是替换文件的名称。 (文件名必须以file
结尾,但您不必输入文件扩展名。)
输入文件的格式是由目标字符串和替换字符串组成的一系列行。这两个字符串由空格分隔。您可以在字符串中使用任何有效的C转义序列;你也可以 \ - 设置空格字符以将其包含在目标中。如果你想要包含一个文字 \ ,你需要加倍它。
如果你不想要C转义序列并且希望反斜杠不是元字符,你可以用更简单的程序替换.txt
程序:
sed
(由于sed -r 's/([\\"])/\\\1/g' "$<"; \
的工作方式,; \
是必要的。)
答案 4 :(得分:1)
a)除非您有非常具体的需求并完全理解所有警告,否则不要使用getline,请参阅http://awk.info/?tip/getline
b)当你想要字符串时不要使用正则表达式(是的,这意味着你不能使用sed)。
c)while循环需要不断移动到你已经改变过的那一行之外,否则你可能会陷入无限循环。
你需要这样的东西:
$ cat substitute.awk
NR==FNR {
if (NF==2) {
strings[++numStrings] = $1
old2new[$1] = $2
}
next
}
{
for (stringNr=1; stringNr<=numStrings; stringNr++) {
old = strings[stringNr]
new = old2new[old]
slength = length(old)
tail = $0
$0 = ""
while ( sstart = index(tail,old) ) {
$0 = $0 substr(tail,1,sstart-1) new
tail = substr(tail,sstart+slength)
}
$0 = $0 tail
}
print
}
$ echo aaa | awk -f substitute.awk table.txt -
3
$ echo aaaa | awk -f substitute.awk table.txt -
31
并向table.txt添加一些RE元字符,以显示它们与其他每个字符一样处理,并显示当目标文本存储在文件中而不是通过管道传输时如何运行它:
$ cat table.txt
aaa 3
aa 2
a 1
. 7
\ 4
* 9
$ cat foo
a.a\aa*a
$ awk -f substitute.awk table.txt foo
1714291
您的新要求需要这样的解决方案:
$ cat substitute.awk
NR==FNR {
if (NF==2) {
strings[++numStrings] = $1
old2new[$1] = $2
}
next
}
{
delete news
for (stringNr=1; stringNr<=numStrings; stringNr++) {
old = strings[stringNr]
new = old2new[old]
slength = length(old)
tail = $0
$0 = ""
charPos = 0
while ( sstart = index(tail,old) ) {
charPos += sstart
news[charPos] = new
$0 = $0 substr(tail,1,sstart-1) RS
tail = substr(tail,sstart+slength)
}
$0 = $0 tail
}
numChars = split($0, olds, "")
$0 = ""
for (charPos=1; charPos <= numChars; charPos++) {
$0 = $0 (charPos in news ? news[charPos] : olds[charPos])
}
print
}
$ cat table.txt
1 a
2 b
$ echo "121212" | awk -f substitute.awk table.txt -
ababab