awk替换ascii表规则bash

时间:2014-12-26 12:43:22

标签: bash awk

我想在文本文件中执行一组(非递归)替换。 我想在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;。这意味着通过应用规则产生的字符串必须保持不变。

如何解释?

5 个答案:

答案 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)
}

使用gccclang,您可以使用类似的东西从替换列表中编译替换程序,然后在给定文本上执行该程序。 Posix标准版c99不允许来自stdin的输入,但gccclang很乐意这样做,前提是您明确告诉他们这是一个C程序({{ 1}})。为了避免过多的编辑,我们使用-x c(需要make,Gnu make)。

以下要求替换列表位于扩展名为gmake的文件中;缓存的已编译可执行文件将具有相同的名称,并具有.txt扩展名。如果makefile位于名为.exe的当前目录中,则可以将其作为Makefile调用(其中make repl是没有文本扩展名的替换文件的名称),但是因为#39;不太可能出现这种情况,我们将使用shell函数来实际调用make。

请注意,在以下文件中,每行开头的空格以制表符开头:

substitute.mak

repl

调用上面的Shell函数:

.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