使用 Shell - 将 HTML 表单转换为复杂的 Shell 变量

作者:Dave Taylor

我知道,有数百万个 shell 脚本等待编写,以帮助管理您的计算机、运行您的服务器和微调您的后端,但我痴迷于与在线数据交互的脚本,所以这就是我关注的重点。我的上一篇文章标志着我们的 Twitterbot 的结束,这是一个简单的脚本,可以监听并响应 Twitter 查询。您可以尝试从您的 Twitter 帐户向 @davesbot 发送“@”消息。

本月,考虑到本期以娱乐为主题,我认为深入研究与 Web 交互的 shell 脚本的另一个方面会很有趣,即研究如何模拟复杂表单。我们要模拟的表单?雅虎电影高级搜索。

首先查看图 1(它显示了表单)。您也可以访问 movies.yahoo.com/mv/advsearch 在线查看。

Work the Shell - Converting HTML Forms into Complex Shell Variables

图 1. 雅虎电影高级标题搜索

我们可以打开 HTML 并阅读源代码,但我认为对其进行逆向工程更有趣,因为像大多数搜索表单一样,这个表单使用 GET 方法,因此,将其所有参数暴露在结果页面的 URL 中。例如,搜索标题“Strangelove”,不进行任何其他调整,将生成以下 URL。通常,此 URL 将全部在一行上,但我已将 URL 和参数分隔到多行上,以便更易于查看

http://movies.yahoo.com/mv/search
      ?p=strangelove
      &yr=all
      &gen=all
      &syn=
      &syn_match=all
      &type=feature
      &adv=y

搜索引擎本身位于上面列表第一行中显示的 URL。其余行是发送到搜索引擎的参数。您可以看到搜索词是“p”(“p=strangelove”)。您可以通过查看表单来推断其他参数:yr = 发行年代,gen = 类型,syn = 剧情简介关键字等等。

但是,由于可能的值太多,我们最终还是必须查看源代码。例如,那些类型?以下是雅虎电影的分类方式

  • act = 动作/冒险

  • ada = 改编

  • ani = 动画

  • ... (为了节省空间,跳过了很多条目)

  • tee = 青少年

  • thr = 惊悚

  • war = 战争

  • wes = 西部

真是一个很长的列表!

问题是,我们能否将这种性质的表单转换为一个简单的交互式 shell 脚本,让用户指定搜索的约束条件,并使用结果搜索弹出一个 Web 浏览器?当然可以!

将 HTML 转换为脚本

将问题标准化并提出通用解决方案,某种解析器,它将 HTML 表单标签作为输入并生成 shell 脚本片段作为输出,这将很酷。嗯,不用了。

相反,通过在 vi 中的一些技巧(是的,我不使用 Emacs),我有以下内容,作为 usage() 函数的一部分

usage()
{
cat << EOF
USAGE: findmovie -g genre -k keywords -nrst title
Where
   -n   only match those that have news or features
   -r   only match those with reviews
   -s   only match those that have showtimes
   -t   only match those that have trailers

and genre can be one of:
  act (Action/Adventure), ada (Adaptation), ani (Animation),
  ...
  tee (Teen), thr (Thriller), war (War) or wes (Western).
EOF

}

这使得生活变得轻松,并将记住类型的三字母缩写的小技巧推给了用户。狡猾,是吧?现在,公平地说,良好的界面设计会让我编写一个更复杂的脚本,让用户输入各种缩写(或完整单词)并将它们转换为雅虎批准的正确缩写,但这实际上是工作,所以我们也会跳过它,好吗?

现在,请注意我创建的实际用法

USAGE: findmovie -g genre -k keywords -nrst title

这意味着表单中有几个元素我们将在脚本中忽略,包括电影发行的年代和一些更晦涩的条件参数。不过,这足以让我们忙碌。

使用 getopts 解析参数

我之前谈到过 shell 脚本中出色的 getopts,没有它,解析六个参数(其中两个有参数,四个没有)将是一个巨大的麻烦。相反,这很简单。以下是前几行,给你一个概念

while getopts "g:k:nrst" arg
do
  case "$arg" in
    g) params="${params:+$params&}gen=$OPTARG" ;;

这里有很多要说的,但我们之前已经介绍过 getopts,您也可以 <咳嗽> 查看手册页,对吧?简而言之,带有尾随冒号的字母表示它有一个必需的参数,所以 g 和 k 有参数 (g:k:),而 n、r、s 和 t 没有 (nrst)。

params 扩展也是一个巧妙的 shell 小技巧,值得特别提及。符号 ${params:+$params } 扩展为 $params 变量的值,加上尾随空格(如果变量已经有一个值)。否则,它是空字符串。重点是什么?避免在我们正在构建的 URL 中出现前导与号。

让我们快速看一下

$ findmovie.sh -g war -k peace -r
finished. params = gen=war&syn=peace&revs=1

正如我们所希望的那样,params 变量已被扩展以反映用户在命令行上指定的特定值——在本例中,是战争片,它们有评论并在剧情简介中包含“peace”这个词。

构建完整的 URL

不过,代码的当前状态存在一个问题,等待着我们。问题是,如果用户在关键字值字段中指定了两个单词,或者更糟的是,在标题字段中也这样做(记住,最后一个或几个单词是标题模式,雅虎电影系统的核心搜索)怎么办?

答案是我们需要将空格转换为 http 系统可接受的符号。幸运的是,这很容易做到

params="$(echo $params | sed 's/ /+/g')"

这不是最优雅的解决方案,但它肯定是功能性的!

这里更大的问题是雅虎要求某些参数实际存在才能进行搜索。在 Web 界面上选择一个类型并单击搜索,您将看到这不足以使其继续进行。

因此,我们搜索的基本 URL 将会更复杂一些

baseurl="http://movies.yahoo.com/mv/search"
baseurl="${baseurl}?yr=all&syn_match=all&"

试试看,你会发现它不起作用。为什么?因为雅虎在表单中加入了一些隐藏参数,这些参数是发送到搜索程序所必需的。没有它们,它就会停止。

实际上,这是我们需要的 baseurl 值

baseurl="http://movies.yahoo.com/mv/search"
baseurl="${baseurl}?yr=all&syn_match=all&adv=y&type=feature&"

现在,我们如何将所有这些组合在一起?这并不容易,因为我们仍然需要获取调用末尾的内容(标题模式),然后屏蔽空格

shift $(( $OPTIND - 1 ))

等一下,在继续之前让我解释一下这一行。OPTIND 包含脚本的位置参数的索引,指示第一个未被 getopts 处理吸收的参数。不幸的是,它的索引从 1 开始,而 options 数组的索引从零开始。结果?我们必须从该值中减去 1 才能使用 $* 符号获得实际值

params="$(echo $params | sed 's/ /+/g')"

pattern="$(echo $* | sed 's/ /+/g')"
echo URL: $baseurl${params}\&p=$pattern

现在,最后,有了这些,我们可以搜索包含“love”这个词并有评论的电影

$ findmovie.sh -r love

URL: ...BASEURL...revs=1&p=love

输入它,你会发现它工作正常,显示了 80 部标题中出现“love”并且雅虎电影知道这些电影的在线评论的电影。

大多数 Linux 和其他 UNIX 版本都有一种方法,您可以从命令行启动 Web 浏览器,并将指定的 URL 作为其主页。这就是我们要做的

echo $baseurl${params}\&p=$pattern
open -a safari "$baseurl${params}\&p=$pattern"

现在我们已经将雅虎高级搜索表单转换为 shell 脚本,我们还可以做其他事情,但我们将把这些留到下个月!

Dave Taylor 从事 shell 脚本编程已经很长时间了,30 年。他是流行的 Wicked Cool Shell Scripts 的作者,可以在 Twitter 上找到他 @DaveTaylor,更普遍地可以在 www.DaveTaylorOnline.com 上找到他。

加载 Disqus 评论