完成内容润色器

作者:Dave Taylor

您可能还记得在我的上一篇文章中,我分享了一个冗长而复杂的解释,说明为什么垃圾邮件会引起我的注意并让我着迷,甚至可能超出了应有的程度。部分原因是,我一直从事电子邮件相关的工作——我甚至在过去编写过最流行的老式电子邮件程序之一。但是,这里也有一个谜题因素,即处理数百万条记录的大量数据集,并尝试大规模生成“个性化”消息。

这种简单的方法是拥有像 ${firstname} 这样的命名数据字段,这样您就可以用“亲爱的 ${firstname},我听说您去了 ${college}?我也是!”等等来打开您的电子邮件。

但是,我更感兴趣的是“润色”方面——产生具有内置同义词的散文,例如


The {idea|concept|inspiration} is that each time you'd use a
{word|phrase} you instead list a set of {similar words|synonyms|
alternative words} and the software automatically picks one
{randomly|at random} and is done.

我知道,您可能正在摇头并想“戴夫到底怎么了?”,但请包容我,让我们一起探索这个文本处理难题。

在我 2016 年 6 月的专栏中,我介绍了文章润色器的核心构建块,这是一个脚本,可以识别 {} 包围的选择,隔离它们,计算有多少个选项,并将其作为调试输出显示给用户。

因此,上面内容将显示为


$ sh spinner.sh spinme.txt
The
3 options, spinning --- idea|concept|inspiration
is that each time you'd use a
2 options, spinning --- word|phrase
you instead list a set of
3 options, spinning --- similar words|synonyms|alternative words
and the software automatically picks one
2 options, spinning --- randomly|at random
and is done.

这是一个好的开始,但这一次,让我们完成这项工作,并在每次都从选项集中随机选择,仅输出选定的选项,并重新排列文本以使其看起来更好。

抽一张牌,随便哪张

在 Bash 中使用随机数的基本方法是使用特殊的 $RANDOM 变量。每次引用它时,它都会返回一个介于 1 和 MAXINT (32767) 之间的随机选择的数字。我通过使用模数函数将其约束在特定范围内,因此这将生成一个介于 0 和 MAXVALUE 之间的随机数


randomnum=$(( $RANDOM % $MAXVALUE ))

双括号表示法触发数学求值,但您已经知道了,对吧?

为了使底部的值为 1 而不是零,我只需在等式中添加更多数学运算


randomnum=$(( $RANDOM % $MAXVALUE + 1 ))

该脚本已经可以识别特定集群中有多少个选项(例如,“{one|two|three}”),现在我们有一个简单的一行代码来帮助随机选择一个值。当然,挑战在于选择实际的字符串值,而不仅仅是显示一个数字!

我知道,我知道——工作,工作,工作。

spinline() 函数(我将在稍后完整展示)的中途,$choices 存储集群中选项的数量,而 $source 是选项集,减去左右花括号。

这是我第一次尝试随机提取单词


pick=$(( $RANDOM % $choices ))
wordpick=$( echo $source | cut -d\| -f$pick )

但是,这在运行时会生成错误消息。这不是因为拼写错误,而是因为使用 cut 并将管道符号指定为字段分隔符是合法的——但这是因为我没有补偿随机数生成器的 0..n 选择:从 cut 请求字段 -f0,它会报错,因为,嗯,没有字段零。

既然我已经理解了问题,这很容易修复,所以这是第二个版本


pick=$(( $RANDOM % $choices + 1 ))
wordpick=$( echo $source | cut -d\| -f$pick )

请记住,模数运算为其值返回 0..(n-1),因此当有三个选项时,例如,$RANDOM % 3 返回 0、1 或 2。每个都加一,它又回到值 1、2 和 3 的轨道上。

通过一些有用的调试行,这是函数的完整版本


function spinline()
{
  source="$*"
  choices=$(grep -o '|' <<< "$*" | wc -l)
  choices=$(( $choices + 1 ))
  echo $choices options, spinning --- $source
  pick=$(( $RANDOM % $choices + 1 ))
  wordpick=$( echo $source | cut -d\| -f$pick )
  echo I pick choice $pick which is $wordpick
}

是的,代码。让我们看看当我使用测试句子作为输入运行时会发生什么


$ sh spinner.sh spinme.txt
The
3 options, spinning --- idea|concept|inspiration
I pick choice 2 which is concept
is that each time you'd use a
2 options, spinning --- word|phrase
I pick choice 1 which is word
you instead list a set of
3 options, spinning --- similar words|synonyms|alternative words
I pick choice 2 which is synonyms
and the software automatically picks one
2 options, spinning --- randomly|at random
I pick choice 2 which is at random
and is done.

实际上,它很接近——非常接近!

事实上,让我们摆脱那些多余的调试 echo 语句(实际上,我总是只是通过在每行前面加上 # 来注释掉它们,这样如果我进一步开发脚本,并且事情开始出错,我可以简单地取消注释这些行并找出问题所在)。

这是结果


$ sh spinner.sh spinme.txt
The
idea
is that each time you'd use a
word
you instead list a set of
synonyms
and the software automatically picks one
at random
and is done.

当整个输出通过方便的 fmt 命令进行管道传输,将所有拼图碎片放回一行时,魔力真正显现出来


$ sh spinner.sh spinme.txt | fmt
The idea is that each time you'd use a word you instead list a set of
synonyms and the software automatically picks one randomly and is done.

再次运行它,讨论的是相同的概念,但具体的词语选择是不同的


$ sh spinner.sh spinme.txt | fmt
The idea is that each time you'd use a phrase you instead list a set of
alternative words and the software automatically picks one randomly and
is done.

所以这就是程序——任务完成。

别烦我,伙计!

事实证明,脚本中存在一个错误;但是,这是一个微妙的错误,但仍然很难解决:如果润色文本包含一个单词集群,紧随其后是标点符号,则标点符号最终会被破坏。

例如,考虑一下如果我稍微修改一下 spinme 文本,像这样


The {idea|concept|inspiration} is that each time you'd
use a {word|phrase}, you instead list a
set of {similar words|synonyms|alternative words} and the
software automatically picks one
{randomly|at random} and is done.

看到第二行单词集群之后立即添加的标点符号了吗?这是如果我通过润色器脚本运行它会发生什么


The inspiration is that each time you'd use a phrase , you instead list
a set of similar words and the software automatically picks one randomly
and is done.

看到问题了吗?逗号前不应该有空格。这很容易用 sed 语句修复,但这是一个更大问题的实例,因此与其使用 sed 's/ ,/,/g',我将把它留给您,亲爱的读者,尝试提出一个更通用的解决方案,考虑到所有标点符号,包括诸如


({cat|dog})

这样它们将在最终输出中正确格式化。

这就是本文的结尾。对于我的下一篇文章,我将看看,嗯,其他的东西。也许是时候开始另一个游戏脚本了?

Dave Taylor 长期以来一直在 UNIX 和 Linux 系统上破解 shell 脚本。他是 Learning Unix for Mac OS XWicked Cool Shell Scripts 的作者。您可以在 Twitter 上找到他 @DaveTaylor,您可以通过他的技术问答网站联系他:Ask Dave Taylor

加载 Disqus 评论