Linux shell的音译脚本

7
我有多个包含字母文本的 .txt 文件;我想要将这些文本翻译成另一种字母表;其中字母表1的某些字符与字母表2的字符1:1对应(即 a 变为 e),而其他字符则是1:2对应(即 x 变为 ch)。
我希望使用一个简单的 Linux shell 脚本来完成这个任务。
使用 trsed 我可以转换1:1对应的字符。
sed -f y/abcdefghijklmnopqrstuvwxyz/nopqrstuvwxyzabcdefghijklm/

a将变成nb将变成o等等(我认为这是凯撒密码)

但是如何处理1:2的字符?

4个回答

5

使用 Awk:

#!/usr/bin/awk -f
BEGIN {
    FS = OFS = ""
    table["a"] = "e"
    table["x"] = "ch"
    # and so on...
}
{
    for (i = 1; i <= NF; ++i) {
        if ($i in table) {
            $i = table[$i]
        }
    }
}
1

使用方法:

awk -f script.awk file

测试:

# echo "the quick brown fox jumps over the lazy dog" | awk -f script.awk
the quick brown foch jumps over the lezy dog

1
完美!非常感谢! - user3946687
1
+1 但是为了避免一些冗余的编码,不要显式地填充表格,可以使用以下代码:split("a e x ch ...",t,/ /); for (i=1; i in t; i+=2) table[t[i]] = t[i+1] - Ed Morton
@EdMorton:谢谢,但我无法使其工作;然而,我实际上很喜欢显式填充表的想法(请参见我对@TomFenech的评论)。 - user3946687
@mus_siluanus 如果您告诉我们无法使其工作的方式,我们可以帮助您。即使现在您不使用它,这也是用于使用初始值填充数组的常见awk习语,因此您可能希望在某个时候执行此操作。如果您愿意,您可以有两个填充的数组,一个关于另一个。我将添加一个答案,以便可以向您展示它的格式。 - Ed Morton

5

这不是答案,只是展示@konsolebox答案中讨论相关评论的一种更简明、习惯用语的方法来填充table[]数组:

BEGIN {
    split("a  e b", old)
    split("x ch o", new)
    for (i in old)
        table[old[i]] = new[i]
    FS = OFS = ""
}

因此,旧字符到新字符的映射在第一个split()中的字符被映射到其下方的字符(s)中清晰地显示出来。对于您想要的任何其他映射,您只需要更改split()中的字符串,而不是更改26个左右的table[]显式赋值。

您甚至可以创建一个通用脚本来进行映射,并将旧字符串和新字符串作为变量传递:

BEGIN {
    split(o, old)
    split(n, new)
    for (i in old)
        table[old[i]] = new[i]
    FS = OFS = ""
}

那么在shell中可以这样做:

old="a  e b"
new="x ch o"
awk -v o="$old" -v b="$new" -f script.awk file

你可以保护自己免受因填充字符串而产生的错误,例如:

BEGIN {
    numOld = split(o, old)
    numNew = split(n, new)

    if (numOld != numNew) {
        printf "ERROR: #old vals (%d) != #new vals (%d)\n", numOld, numNew | "cat>&1"
        exit 1
    }

    for (i=1; i <= numOld; i++) {
        if (old[i] in table) {
            printf "ERROR: \"%s\" duplicated at position %d in old string\n", old[i], i | "cat>&2"
            exit 1
        }
        if (newvals[new[i]]++) {
            printf "WARNING: \"%s\" duplicated at position %d in new string\n", new[i], i | "cat>&2"
        }
        table[old[i]] = new[i]
    }
}

如果您写下 b 映射到 x ,然后过一会儿又误将 b 映射到 y ,那不是很糟糕吗?上述方法确实是解决这个问题的最佳方式,但当然还要看您的意愿。

以下是评论中讨论的一种完整方案。

BEGIN {
    numOld = split("a  e b", old)
    numNew = split("x ch o", new)

    if (numOld != numNew) {
        printf "ERROR: #old vals (%d) != #new vals (%d)\n", numOld, numNew | "cat>&1"
        exit 1
    }

    for (i=1; i <= numOld; i++) {
        if (old[i] in table) {
            printf "ERROR: \"%s\" duplicated at position %d in old string\n", old[i], i | "cat>&2"
            exit 1
        }
        if (newvals[new[i]]++) {
            printf "WARNING: \"%s\" duplicated at position %d in new string\n", new[i], i | "cat>&2"
        }
        map[old[i]] = new[i]
    }

    FS = OFS = ""
}
{
    for (i = 1; i <= NF; ++i) {
        if ($i in map) {
            $i = map[$i]
        }
    }
    print
}

我将table数组重命名为map,因为我认为这更好地代表了数组的目的。

将上述内容保存在一个名为script.awk的文件中,并使用awk -f script.awk inputfile来运行它。


我再次尝试了您的代码,但是它们没有输出;也许我漏掉了什么?我的操作:将代码复制到一个名为script.awk的新文件中;按照建议运行脚本。我既没有错误提示,也没有输出。 - user3946687
我刚刚展示了如何以不同的方式填充映射表,但你仍然需要@konsolebox发布的脚本的其余部分来实际处理该映射。稍等片刻,我将更新它并提供完整的解决方案。 - Ed Morton
现在它输出与输入相同的文本。我将您的新代码复制到一个新文件中,然后在shell中执行了以下操作:echo“ae”| awk-f script.awk。输出为:ae。 - user3946687
我在组合完整的解决方案时忘记添加FS和OFS的设置,现已更新。 - Ed Morton
1
现在它可以工作了!非常感谢;我喜欢它搜索错误的能力。 - user3946687

2
这可以使用 Perl 的一行命令非常简洁地完成:
perl -pe '%h=(a=>"xy",c=>"z"); s/(.)/defined $h{$1} ? $h{$1} : $1/eg'

或等价地(感谢Jaypal):
perl -pe '%h=(a=>"xy",c=>"z"); s|(.)|$h{$1}//=$1|eg'

%h 是一个哈希表,包含字符(键)及其替换值(值)。s 是替换命令(如 sed 中的)。g 修饰符表示替换应该全局进行,e 表示替换部分应该作为表达式来求值。它逐个捕获每个字符,并在哈希中查找其值,如果存在则进行替换,否则保留原始值。 -p 开关意味着自动打印输入中的每一行。

测试一下:

$ perl -pe '%h=(a=>"xy",c=>"z"); s|(.)|$h{$1}//=$1|eg' <<<"abc"
xybz

非常感谢!我喜欢使用一行代码的想法。但是对于长列表的替换(例如音译),我更喜欢@konsolebox的脚本,因为他的方法可以提供更清晰的视图,展示我将要做的事情...就像一个美丽的嵌入式字符映射... - user3946687
@glenn 感谢您的编辑 - 我猜测 a=">xy" 中间的双引号是一个打字错误?在第一次使用时似乎可以工作,我想这只是使用单行代码的症状。 - Tom Fenech
在这两个点上都是准确的。使用 use strict,会看到 Bareword "z" not allowed while "strict subs" in use 的错误提示。 - glenn jackman
1
@TomFenech 可以简化为 perl -pe'%h=(a=>"xy",b=>"z");s|(.)|$h{$1}//=$1|eg' <<<"abc"//= 在 5.8 之后引入,因此应该可以正常工作,除非使用古老的 perl - jaypal singh

1

使用sed

编写一个文件transliterate.sed,其中包含:

s/a/e/g
s/x/ch/g

然后在命令行中运行以下命令,从input.txt获取音译的output.txt

sed -f transliterate.sed input.txt > output.txt

如果您需要更频繁地使用此操作,请考虑在文件的第一行添加#!/bin/sed -f并使用chmod 744 transliterate.sed将其设置为可执行文件,如维基百科上的sed页面所述。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接