我见过 grep 出现在很多答案中,但我从未认真思考过它。
现在,我正在尝试从互联网获取本地保存页面的 HTML 标签内的内容,但我遇到了困难。我可以让 grep 来识别我想要的输出,但根本不可能将其定界为可用。
这是我的 test.sh 文件内容:
a=$(awk '/<div class="power-bar-text">/,/<\/div>/' 'Acid Fast.html')
b=$(echo $a | grep -PzTo [0-9\.]+)
echo $a
echo $b
结果是这个终端输出:
test.sh: line 4: warning: command substitution: ignored null byte in input
<div class="power-bar-text"> 9 </div> <div class="power-bar-text"> 8 </div> <div class="power-bar-text"> 11.25 </div> <div class="power-bar-text"> 10 </div> <div class="power-bar-text"> 6 </div> <div class="power-bar-text"> 5 </div> <div class="power-bar-text"> 2 (1s) </div> <div class="power-bar-text"> 3 </div> <div class="power-bar-text"> 2.50 </div>
9811.2510652132.50
这是之前的迭代,其人类可读性稍好一些:
$ awk '/<div class="power-bar-text">/,/<\/div>/' 'Acid Fast.html' | grep -Pzn -C1 [0-9\.]+ -
1: <div class="power-bar-text">
9
</div>
<div class="power-bar-text">
8
</div>
<div class="power-bar-text">
11.25
</div>
<div class="power-bar-text">
10
</div>
<div class="power-bar-text">
6
</div>
<div class="power-bar-text">
5
</div>
<div class="power-bar-text">
2 (1s)
</div>
<div class="power-bar-text">
3
</div>
<div class="power-bar-text">
2.50
</div>
我不知道如何在上面的代码框中设置颜色,但终端确实使用默认匹配的字体颜色红色为每个数字和句点进行编码。
(如果类名碰巧是“power.bar.text”,这可能不起作用,因为句点在那里匹配......因此,对于确保任何句点都是数字有什么帮助吗?我想这可能[0-9]+\.?[0-9]*
适用于正则表达式。 )
但是回到使用 bash 中的代码的第一个代码块,它提供的最终输出是9811.2510652132.50
。但我想要类似的东西9,8,11.25,10,6,5,2,1,3,2.50
如果我编写了 grep 代码,我可以选择-d,
将分隔符设置为输出中的命令。不幸的是,当我尝试时,这个想法并没有奏效。
我的一个可能可行的可怕想法是使用 -m 参数输出来重复处理它,增加允许的匹配数,然后找到每个输出之间的新内容。再说一遍,这听起来很糟糕。 (例如,我期望 -m1 会得到 9,然后 -m2 会得到 98,而 -m3 会得到 9811.25,我会从 m1 的输出中“减去”m2 的输出,得到 8;从 m2 的输出中“减去”m3 的输出,得到11.25。)
实际上刚刚尝试过,它不起作用,因为我猜 awk 使它成为一行,所以无论我限制它有多少个匹配,都会9811.2510652132.50
输出完整的字符串,因为它的整体是第一个也是唯一的匹配。
当然有更好的方法吗?
答案1
正如评论中提到的,grep
(从非结构化文本文档中提取行的实用程序)不是您想要用来解析 HTML 或一般结构化文档的工具。理想情况下,您希望使用能够对文档应用结构化查询的工具,以其他方式提取、修改或处理数据。对于 XML 文档,这样的命令行工具之一是xmlstarlet
,您可以使用它来应用XPath 查询到 XML 文档。
div
假设您的 HTML 文档是正确的 XHTML,我们可以提取具有class
值为 的属性的节点的内容power-bar-text
,同时修剪掉两侧的空格:
xmlstarlet select --template \
--match '//div[@class="power-bar-text"]' \
--value-of 'normalize-space()' -nl file.xml
这首先匹配div
我们感兴趣的节点,然后提取normalize-space()
应用于这些匹配节点的函数的结果。最后,-nl
用换行符分隔每个输出。
或者,使用简短的选项,
xmlstarlet sel -t \
-m '//div[@class="power-bar-text"]' \
-v 'normalize-space()' -n file.xml
鉴于您显示的文档片段,这可能会输出如下所示的内容:
9
8
11.25
10
6
5
2 (1s)
3
2.50
可以通过将其传递到以逗号作为分隔符的单行中
paste -d , -s -
...就像这样:
$ xmlstarlet sel -t -m '//div[@class="power-bar-text"]' -v 'normalize-space()' -n file.xml | paste -d , -s -
9,8,11.25,10,6,5,2 (1s),3,2.50
如果您只想在命令的每行输出的第一个空格之前发生任何内容,请添加一些额外的处理xmlstarlet
:
$ xmlstarlet sel -t -m '//div[@class="power-bar-text"]' -v 'normalize-space()' -n file.xml | sed 's/ .*//' | paste -d , -s -
9,8,11.25,10,6,5,2,3,2.50
如果您的文件不是 XHTML,您可能可以使用以下命令将其转换为可用的内容
xmlstarlet format --recover --html file.html >file.xml
答案2
添加到 Kusalananda 的回答,如果你有更通用的 HTML,你可能想要使用 BeautifulSoup 而不是希望转换为 XML(并不是说它本身不使用不同的 XML 解析器,它处理解析的方式可能只是更优雅)您的用例)。
你会写一个脚本——但不是一个bash
脚本,而是一个Python脚本(这是我直接从头开始写的,只是进行了表面测试)
#! /usr/bin/env python3
from bs4 import BeautifulSoup
import sys
if not len(sys.argv) == 2:
print(
f"expected one argument, got {len(sys.argv) - 1}:\n {' '.join(sys.argv)}",
file=sys.stderr,
)
sys.exit(-1)
with open(sys.argv[1]) as inputfile:
soup = BeautifulSoup(inputfile)
hits = soup.find_all("div", class_="power-bar-text")
for hit in hits:
content = hit.contents[0].strip()
print(f"found value {content}")
保存到某个文件,例如myparser.py
,使其可执行 ( chmod 755 myparser.py
),并使用 HTML 文件名作为参数运行 ( /path/to/myparser.py /path/to/input.html
)。
漂亮的、不言自明的代码就到此为止了。如果您觉得需要在 shell 中执行此操作,您可以将其压缩为一行。 (我建议你不要这样做;你可以将上面完整的易读、生成合理错误的 python 代码嵌入到你的 bash 脚本中的多行字符串/HEREDOC 中):
infile="foo.html"
python3 -c "from bs4 import BeautifulSoup as BS;soup=BS(open('${infile}'));print('\n'.join(tag.contents[0].strip() for tag in soup.find_all('div', class_='power-bar-text')))"