Shell 脚本 - 数字命理学,或数字 23

作者:Dave Taylor

我承认,我看了很多电影。在我活着的几十年里(一位绅士不会透露他的年龄!),我已经看了数万部电影,平均每周大约看 6-8 部电影。说实话,我更喜欢 40 年代和 50 年代的经典电影,但我的口味范围很广,从俗气的恐怖片到最新的前卫外国电影。

当我意识到这篇专栏的截止日期临近时,我做了任何一个有自尊的极客都会做的事情:我被其他事情分心了。在这种情况下,分心的事情是出人意料地细致入微且有趣的 数字 23,由金·凯瑞主演,乔·舒马赫执导。

在电影中,凯瑞痴迷于数字命理学,以及他生活中如此多的事情是如何加起来等于数字 23 的。他“被这个数字困扰”,最终“被这个数字攻击”,随着电影情节的曲折发展。

我发现有趣的是他发现 23 是如此普遍的数字的方法,从角色的生日(2 月 3 日)到时钟上的时间(如果你看模拟时钟表面,2:15 是 2/3)。数字命理学完全是关于字母的序数值,其中 A 是 1,B 是 2,依此类推。电影的大部分内容也关于单词和名字如何加起来等于 23。

啊,我想,我能写一个 shell 脚本来做基本的数字命理学吗?难道这本杂志本身也充满了那个邪恶的数字吗?让我们来看看!

将单词分解为字符

编写基本数字命理学脚本的第一步是学习如何将单词或短语分解为组成部分,去除所有标点符号和空格。我们还希望将所有大写字母转换为小写字母,反之亦然,因为 A 和 a 应该具有相同的数值 (1)。

这可以通过脚本中的单行代码完成,这要归功于功能强大的 tr 命令

tr '[A-Z]' '[a-z]' | tr -Cd '[:alnum:]' 

第一个 tr 调用按要求将大写字母转换为小写字母(尽管为了完全可移植,我真的应该将其写成'[:upper:]' '[:lower:]',但我想在这里展示这两个常见的惯用法,供您阅读欣赏)。

第二个 tr 调用有点棘手:-d 选项指示程序删除输入流中与指定集合匹配的字符,而 -C 反转匹配的逻辑。通过使用 '[:alnum:]',我只提取字母和数字,去除其他所有内容。

让我们看看这个代码片段的工作情况

$ echo "This Is A - 12,3 - Test" | \

tr '[A-Z]' '[a-z]' | tr -Cd '[:alnum:]'

thisisa123test 

而且,这干净利落地轻松完成了。现在,更困难的部分——如何在 shell 脚本中逐个字母地遍历一个单词?这正是 cut 命令的工作!

我也将使用一个步进整数变量来简化操作,称为 ptr(这里是一个示例,说明我非常怀念 Perl 或 C 语言的 for 循环及其所有功能)

ptr=1

while [ some condition ] ; do

letter="$( echo $cleanword | cut -c $ptr )"

ptr="$(( $ptr + 1 ))"

done 

问题是我们应该测试什么条件,才能使其获取字符串中的每个字符,但不多也不少?根据 cut 手册页,程序在失败时会产生非零返回码,而且在我看来,像这样的调用

echo 123 | cut -c4 

应该是一个错误,因为没有第四个字符,但实验表明并非如此。以下是我测试它的方法

#!/bin/sh 

echo 123 | cut -c4 

if [ $? -ne 0 ] ; then

echo error condition

else

echo no error condition

fi 

唉,结果是“没有错误条件”。从积极的方面来看,cut 确实正确返回了一个空字符串,因此我们可以对此进行测试。但是,因为我们正在进行最大程度的偏执编码,所以拥有单词或短语的长度也很有用。毕竟,如果它是 23 个字符长怎么办?

鉴于长度已经计算出来(通过快速调用wc -c),条件可以简单地是测试 ptr 与字符串长度,在字符串清理后计算。换句话说,while [ $ptr -lt $basislength ].

计算字母值

这个脚本最难的部分无疑是将字母映射到数值。Perl、C、Awk 和几乎每种脚本语言都有解决方案,但在 shell 本身中呢?我想象不出任何无需付出巨大努力的解决方案。

幸运的是,有一个 15 个字符的 Perl 解决方案,使我们能够编写一个适用于放入命令管道的命令

perl -e '$a=getc(); print ord($a)-96' 

因此,我们有了一个工具来计算序数值,而没有太多困难,现在我们知道如何提取单个字母

ordvalue="$(echo $letter | \

  perl -e '$a=getc(); print ord($a)-96' )" 

让我们把它们放在一起,看看我们现在在哪里

#!/bin/sh 

# Given a word or phrase, figure out its numeric equivalents 

ptr=1 

if [ -z "$1" ] ; then

  echo -n "Word or phrase: "

  read basis

else

  basis="$@"

fi 

basis="$( echo $basis | \

   tr '[A-Z]' '[a-z]' | \

   tr -Cd '[:alnum:]' )" 

basislength="$( echo $basis | wc -c )" 

echo "(Working with $basis which has \

   $basislength characters)" 

while [ $ptr -lt $basislength ] ; do

  letter="$( echo $basis | cut -c $ptr )"

  ordvalue="$(echo $letter | \

     perl -e '$a=getc(); print ord($a)-96' )"

  echo "... letter $letter has value $ordvalue"

  ptr="$(( $ptr + 1 ))"

done 

exit 0 

顶部的条件使此脚本具有最大的灵活性。如果您在调用脚本时指定单词或短语,它将使用该单词或短语。如果您忘记了,它会提示您输入单词或短语。无论哪种方式,最终都会成为 basis,然后对其进行连续清理以删除不需要的字母。basislength 是结果字符串的长度,在 while 循环中逐个字母地遍历该长度。

快速测试

$ sh numerology.sh

Word or phrase: linux

(Working with linux which has 6 characters)

... letter l has value 12

... letter i has value 9

... letter n has value 14

... letter u has value 21

... letter x has value 24 

太棒了。我们有了一个数字命理学计算器的基础,所有困难的工作都已完成。剩下的就是做一些汇总值并尝试可能的组合,看看我们是否可以确定那个讨厌的 23 是否真的无处不在!

致谢

感谢 Dave Sifry 在简洁的 Perl 代码片段方面提供的帮助。

Dave Taylor 是一位拥有 26 年 UNIX 经验的资深人士,Elm Mail System 的创建者,以及最近畅销书 Wicked Cool Shell ScriptsTeach Yourself Unix in 24 Hours 的作者,以及他的 16 本技术书籍。他的主要网站是 www.intuitive.com,他还通过 AskDaveTaylor.com 提供技术支持。

加载 Disqus 评论