完整的键值对

完整的键值对

背景:

我想编写一个 bash 脚本(MacOS 10.9.5)来插入与某些字符串关联的值。在脚本中,我将定义每个可能的关联值。

例如,我可以将与链接文本关联的文本定义yahoowww.yahoo.com变量

XX_yahoo="www.yahoo.com"

添加前缀XX_是为了避免与现有变量发生名称冲突。然后我的脚本是替换所有出现的

\MakeLink[yahoo]{}

\MakeLink[yahoo]{www.yahoo.com}

通过将宏方括号内的链接文本\MakeLink与现有变量进行匹配。如果未提供某些文本的变量,则我们使用链接文本的标题大小写。因此,

\MakeLink[foo bar]{}

应该成为

\MakeLink[foo bar]{Foo Bar}

下面的脚本处理以下情况

  • 链接文本确实不是其中有一个空格并且
  • 链接文本变量尚未定义

问题:

由于链接文本的可能值的数量可能有数千个,并且其中可能有空格,我的问题是:

  1. 这是最好的方法吗?使用数组作为变量会更好吗?
  2. 我应该如何处理链接文本有空格的情况。例如,我希望能够拥有

    \MakeLink[the google]{}
    

    被替换为

    \MakeLink[the google]{www.google.com}.
    

笔记

  • 可以假设会有仅有的\MakeLink每行出现一次。
  • MakeTitleCase宏需要得到增强,以拥有一个单词列表,其中的大小写不会改变(就像在标题中一样),但我可以稍后解决这个问题。

现有解决方案的已知问题:

  • 我的匹配方式存在问题\MakeLink,因为即使省略前导反斜杠,匹配仍然会发生。请参阅测试用例第一段的最后一行。
  • 如果我的?文件中有一个,那么似乎sed有问题。
  • 不知道如何处理链接文本包含空格的情况。

脚本

#!/bin/bash

## Can't have a backslash in the values of these variables, which is ok for my purposes.
XX_yahoo="www.yahoo.com"
XX_google="www.google.com"

function MakeTitleCase {
    echo $(echo "$1" | awk '{for(j=1;j<=NF;j++){ $j=toupper(substr($j,1,1)) substr($j,2) }}1')
}


while read -d $'\n' LINE; do
    ## Extract target which is the text within the square brackets of "\MakeLink[target]{}"
    TARGET=$(echo ${LINE} | sed -e 's?\]{}.*??' -e 's?\MakeLink\[??')
    TEMP=XX_${TARGET}
    if [ -z "${!TEMP}" ]; then
        REPLACEMENT=$(MakeTitleCase "${TARGET}")
    else
        REPLACEMENT=${!TEMP}
    fi

    ## Incorrect handling of leading backslash for the match.
    echo "${LINE}" | sed "s?\MakeLink\[${TARGET}\]{}?\\\MakeLink\[${TARGET}\]{${REPLACEMENT}}?";
done 

exit 0

输入文件示例:

A very popular site on the internet was
\MakeLink[yahoo]{} but was surpassed by
\MakeLink[google]{} due to its  
MakeLink[search engine]{}.

Due to its dominance
\MakeLink[the google]{} has had to deal with
\MakeLink[antitrust issues]{}.

电流输出:

A very popular site on the internet was
\MakeLink[yahoo]{www.yahoo.com} but was surpassed by
\MakeLink[google]{www.google.com} due to its
\MakeLink[search engine]{Search Engine}.

Due to its dominance
\MakeLink[the google]{The Google} has had to deal with
\MakeLink[antitrust issues]{Antitrust Issues}.

期望的输出:

与上面唯一的变化是 的相关文本the google,并且MakeLink[search engine]{}应该不是由于缺少前导反斜杠而被更改。

A very popular site on the internet was
\MakeLink[yahoo]{www.yahoo.com} but was surpassed by
\MakeLink[google]{www.google.com} due to its
MakeLink[search engine]{}.

Due to its dominance
\MakeLink[the google]{www.google.com} has had to deal with
\MakeLink[antitrust issues]{Antitrust Issues}.

答案1

Perl 来救援:

#!/usr/bin/perl
use warnings;
use strict;

my %replace = ( yahoo              => 'www.yahoo.com',
                google             => 'www.google.com',
                'search engine'    => 'Search Engine',
                'the google'       => 'The Google',
                'antitrust issues' => 'Antitrust Issues',
              );

while (<>) {
    s/\\MakeLink\[(.*?)\]\{\}/\\MakeLink[$1]{$replace{$1}}/g;
    print;
}

您创建一个替换哈希表并在替换中使用它。您可以在最新的 bash 版本中创建哈希表,但不能直接在 sed 中使用它们,因此没有直接的 bash+sed 对应项。

答案2

与 choroba 的答案类似(我在没有看到你的情况下写了这个,我发誓!),但无需硬编码即可处理标题外壳:

#!/usr/bin/perl
use strict;
use warnings;

my %links = (
    yahoo => "www.yahoo.com",
    google => "www.google.com",
);
$links{"the $_"} = $links{$_} for keys %links;

while (<>) {
    s{\\MakeLink\[(.+?)\]\{\}}{
        sprintf "\\MakeLink[%s]{%s}", 
            $1, 
            exists $links{$1} ? $links{$1}
                              : join " ", map {ucfirst lc} split " ", $1;
    }eg;
    print;
}

运行它:

$ perl link.pl input
A very popular site on the internet was
\MakeLink[yahoo]{www.yahoo.com} but was surpassed by
\MakeLink[google]{www.google.com} due to its  
MakeLink[search engine]{}.

Due to its dominance
\MakeLink[the google]{www.google.com} has had to deal with
\MakeLink[antitrust issues]{Antitrust Issues}.

答案3

我还没有审阅您的脚本,但我看到您在几个地方遇到了引用问题(当您不希望它们出现时具有特殊含义的字符):

  • read -d $'\n' LINE(一种复杂的书写方式read LINE)解析反斜杠转义符,因此它有效地吃掉了反斜杠。做了read -r LINE。该命令还会删除前导空格和尾随空格;为了避免这种情况,请做到IFS= read -r LINE
  • 您将变量替换为 sed 脚本。这些变量的内容被解析为 sed 脚本,而不是您想要的搜索字符串或替换文本。这是?文件中的问题:当它出现在 中时$TARGET,sed 会看到一个?.要解决此问题,请在 sed 中的所有特殊字符之前添加反斜杠字符(并注意在正则表达式和替换文本中,您需要转义不同的字符!)。

实际上……不要做我上面写的事情。我只是在解释出了什么问题;但你应该完全重写你的脚本,因为你正在使用螺丝刀钉钉子。

您正在使用 bash,它具有关联数组。使用具有构造名称的变量是一种技巧,当没有更好的方法可用时,它很方便,但它比正确的数据结构更难使用。除非XX_yahoo变量实际上必须来自环境,否则请使用关联数组。

typeset -A targets
targets[yahoo]='www.yahoo.com'

虽然可以在 shell 中逐行解析文件while read …,但它并不适合大文件(速度很慢)或具有非平凡语法的文件(正如您所发现的,当您执行以下操作时很难正确解析内容)在 shell 和外部工具(例如 sed)之间来回切换。您的任务是 awk 脚本(或 perl,如其他答案中所示)的主要材料。

如果您无论如何都要使用 awk,那么您也可以直接在 awk 中定义关联数组。

未经测试的代码。

#!/bin/awk -f
BEGIN {
    targets[yahoo]="www.yahoo.com";
    targets[google]="www.google.com";
}
function MakeTitleCase(text) {
    split(text, words);
    text = "";
    for (w in words) {
        text = text toupper(substr(w,1,1)) substr(w,2)
    }
    return text;
}

/^ *\\MakeLink\[[^][{}]*\]{}/ {
    target_start = index($0, "[") + 1;
    target_end = index($0, "]") - 1;
    target = substr($0, target_start, target_end - target_start);
    if (target in targets) {
        replacement = targets[target];
    } else {
        replacement = MakeTitleCase(target);
    }
    $0 = substr($0, 1, target_start-1) replacement substr($0, target_end);
}

1

相关内容