背景:
我想编写一个 bash 脚本(MacOS 10.9.5)来插入与某些字符串关联的值。在脚本中,我将定义每个可能的关联值。
例如,我可以将与链接文本关联的文本定义yahoo
为www.yahoo.com
变量
XX_yahoo="www.yahoo.com"
添加前缀XX_
是为了避免与现有变量发生名称冲突。然后我的脚本是替换所有出现的
\MakeLink[yahoo]{}
和
\MakeLink[yahoo]{www.yahoo.com}
通过将宏方括号内的链接文本\MakeLink
与现有变量进行匹配。如果未提供某些文本的变量,则我们使用链接文本的标题大小写。因此,
\MakeLink[foo bar]{}
应该成为
\MakeLink[foo bar]{Foo Bar}
下面的脚本处理以下情况
- 链接文本确实不是其中有一个空格并且
- 链接文本变量尚未定义
问题:
由于链接文本的可能值的数量可能有数千个,并且其中可能有空格,我的问题是:
- 这是最好的方法吗?使用数组作为变量会更好吗?
我应该如何处理链接文本有空格的情况。例如,我希望能够拥有
\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