使用 Shell - 将 HTML 表单转换为复杂的 Shell 变量
我知道,有数百万个 shell 脚本等待编写,以帮助管理您的计算机、运行您的服务器和微调您的后端,但我痴迷于与在线数据交互的脚本,所以这就是我关注的重点。我的上一篇文章标志着我们的 Twitterbot 的结束,这是一个简单的脚本,可以监听并响应 Twitter 查询。您可以尝试从您的 Twitter 帐户向 @davesbot 发送“@”消息。
本月,考虑到本期以娱乐为主题,我认为深入研究与 Web 交互的 shell 脚本的另一个方面会很有趣,即研究如何模拟复杂表单。我们要模拟的表单?雅虎电影高级搜索。
首先查看图 1(它显示了表单)。您也可以访问 movies.yahoo.com/mv/advsearch 在线查看。
我们可以打开 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 表单标签作为输入并生成 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
这意味着表单中有几个元素我们将在脚本中忽略,包括电影发行的年代和一些更晦涩的条件参数。不过,这足以让我们忙碌。
我之前谈到过 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”这个词。
不过,代码的当前状态存在一个问题,等待着我们。问题是,如果用户在关键字值字段中指定了两个单词,或者更糟的是,在标题字段中也这样做(记住,最后一个或几个单词是标题模式,雅虎电影系统的核心搜索)怎么办?
答案是我们需要将空格转换为 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 上找到他。