如何从 shell 执行 XPath 单行程序?

Posted

技术标签:

【中文标题】如何从 shell 执行 XPath 单行程序?【英文标题】:How to execute XPath one-liners from shell? 【发布时间】:2013-03-05 21:34:27 【问题描述】:

是否有适用于 Ubuntu 和/或 CentOS 的软件包,它有一个命令行工具,可以执行像 foo //element@attribute filename.xmlfoo //element@attribute < filename.xml 这样的 XPath 单行程序并逐行返回结果?

我正在寻找可以让我只使用 apt-get install fooyum install foo 的东西,然后就可以开箱即用,不需要包装器或其他调整。

以下是一些接近的例子:

Nokogiri。如果我编写这个包装器,我可以按上述方式调用包装器:

#!/usr/bin/ruby

require 'nokogiri'

Nokogiri::XML(STDIN).xpath(ARGV[0]).each do |row|
  puts row
end

XML::XPath。可以使用这个包装器:

#!/usr/bin/perl

use strict;
use warnings;
use XML::XPath;

my $root = XML::XPath->new(ioref => 'STDIN');
for my $node ($root->find($ARGV[0])->get_nodelist) 
  print($node->getData, "\n");

来自 XML::XPath 的xpath 返回太多噪音,-- NODE --attribute = "value"

来自 XML::Twig 的xml_grep 无法处理不返回元素的表达式,因此不能用于提取属性值而无需进一步处理。

编辑:

echo cat //element/@attribute | xmllint --shell filename.xml 返回类似于xpath 的噪声。

xmllint --xpath //element/@attribute filename.xml 返回attribute = "value"

xmllint --xpath 'string(//element/@attribute)' filename.xml 返回我想要的,但仅限于第一场比赛。

对于另一个几乎可以满足该问题的解决方案,这里有一个 XSLT,可用于评估任意 XPath 表达式(需要 XSLT 处理器中的 dyn:evaluate 支持):

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:dyn="http://exslt.org/dynamic" extension-element-prefixes="dyn">
  <xsl:output omit-xml-declaration="yes" indent="no" method="text"/>
  <xsl:template match="/">
    <xsl:for-each select="dyn:evaluate($pattern)">
      <xsl:value-of select="dyn:evaluate($value)"/>
      <xsl:value-of select="'&#10;'"/>
    </xsl:for-each> 
  </xsl:template>
</xsl:stylesheet>

使用xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml 运行。

【问题讨论】:

+1 提出好问题并集思广益,寻找一种简单可靠的方法在换行符上打印多个结果 请注意,来自xpath 的“噪音”是在 STDERR 上,而不是在 STDOUT 上。 @miken32 不。我只想要输出的值。 hastebin.com/ekarexumeg.bash 【参考方案1】:

你应该试试这些工具:

xmlstarlet : 可以编辑、选择、转换...默认没有安装,xpath1 xmllint:通常默认安装libxml2-utils,xpath1(检查我的wrapper,让--xpath打开非常旧的版本和换行符分隔输出(v xpath : 通过 perl 的模块安装 XML::XPath, xpath1 xml_grep : 通过 perl 的模块安装 XML::Twig, xpath1 (有限的 xpath 使用) xidel: xpath3 saxon-lint :我自己的项目,包装在 @Michael Kay 的 Saxon-HE Java 库,xpath3

xmllint 附带libxml2-utils(可通过--shell 开关用作交互式shell)

xmlstarletxmlstarlet

xpath自带perl的模块XML::Xpath

xml_grep自带perl的模块XML::Twig

xidelxidel

saxon-lint 使用 SaxonHE 9.6 ,XPath 3.x(+复古兼容性)

例如:

xmllint --xpath '//element/@attribute' file.xml
xmlstarlet sel -t -v "//element/@attribute" file.xml
xpath -q -e '//element/@attribute' file.xml
xidel -se '//element/@attribute' file.xml
saxon-lint --xpath '//element/@attribute' file.xml
xmlstarlet page man xmllint xpath page xml_grep xidel saxon-lint

.

【讨论】:

太棒了! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xml 正是我想要的! 注意:据传xmlstarlet已被废弃,但现在又在积极开发中。 注意:一些旧版本的xmllint 不支持命令行参数--xpath,但大多数似乎支持--shell。输出稍微脏一点,但在绑定中仍然有用。 我似乎仍然无法查询节点内容,而不是属性。任何人都可以为此提供一个例子吗?出于某种原因,我仍然发现 xmlstarlet 难以弄清楚并在匹配、值、根目录之间正确查看文档结构等。即使使用此页面中的第一个 sel -t -m ... -v ... 示例:arstechnica.com/information-technology/2005/11/linux-20051115/2,匹配所有但是最后一个节点并将该节点保存为像我的用例一样的值表达式,我似乎仍然无法得到它,我只是得到空白输出.. 在 xpath 版本上不错的一个 - 我只是遇到了其他优秀 xmllint 的这个限制【参考方案2】:

你也可以试试我的Xidel。它不在存储库中的包中,但您可以从网页下载它(它没有依赖项)。

这个任务的语法很简单:

xidel filename.xml -e '//element/@attribute' 

它是支持 XPath 2 的少数工具之一。

【讨论】:

Xidel 看起来很酷,尽管您可能应该提到您也是您推荐的这个工具的作者。 Saxon 和 saxon-lint 使用 xpath3 ;) Xidel (0..8.win32.zip) 在 Virustotal 上显示为恶意软件。所以请自担风险virustotal.com/#/file/… 太棒了 - 我将把 xidel 添加到我的个人扳手工具箱中 不错!我必须对具有与给定 xpath 查询匹配的节点的 XML 文件运行递归搜索。使用 xidel 与 find 类似:find . -name "*.xml" -printf '%p : ' -exec xidel -s -e 'expr' \;【参考方案3】:

一个很可能已经安装在系统上的软件包是python-lxml。如果是这样,则无需安装任何额外的软件包即可:

python -c "from lxml.etree import parse; from sys import stdin; print('\n'.join(parse(stdin).xpath('//element/@attribute')))"

【讨论】:

如何传递文件名? 这适用于stdin。这消除了在已经相当长的单行中包含open()close() 的需要。要解析文件,只需运行 python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" &lt; my_file.xml 并让您的 shell 处理文件查找、打开和关闭。【参考方案4】:

在搜索 maven pom.xml 文件时,我遇到了这个问题。但是我有以下限制:

必须跨平台运行。 必须存在于所有主要的 Linux 发行版上,无需安装任何额外的模块 必须处理复杂的 xml 文件,例如 maven pom.xml 文件 语法简单

我已经尝试了上述许多方法,但都没有成功:

python lxml.etree 不是标准 python 发行版的一部分 xml.etree 是但不能很好地处理复杂的 maven pom.xml 文件,还没有深入挖掘 python xml.etree 不处理 maven pom.xml 文件,原因不明 xmllint 也不起作用,核心转储经常在 ubuntu 12.04 "xmllint: using libxml version 20708"

我遇到的稳定、简短、可在许多平台上工作且成熟的解决方案是 ruby​​ 中内置的 rexml lib:

ruby -r rexml/document -e 'include REXML; 
     puts XPath.first(Document.new($stdin), "/project/version/text()")' < pom.xml

启发我找到这篇文章的是以下文章:

Ruby/XML, XSLT and XPath Tutorial IBM: Ruby on Rails and XML

【讨论】:

这比问题的标准更窄,所以它绝对适合作为答案。我相信很多遇到你情况的人都会从你的研究中得到帮助。我将xmlstarlet 保留为可接受的答案,因为它符合我更广泛的标准,而且它真的很整洁。但我可能会不时使用您的解决方案。 我会添加这一点以避免在结果周围使用引号,在 Ruby 命令中使用 puts 而不是 p【参考方案5】:

Saxon 不仅会为 XPath 2.0 执行此操作,还会为 XQuery 1.0 和(在商业版本中)3.0 执行此操作。它不是作为 Linux 包提供的,而是作为 jar 文件提供的。语法(您可以轻松地将其封装在一个简单的脚本中)是

java net.sf.saxon.Query -s:source.xml -qs://element/attribute

2020 年更新

Saxon 10.0 包含 Gizmo 工具,可以从命令行交互或批量使用。例如

java net.sf.saxon.Gizmo -s:source.xml
/>show //element/@attribute
/>quit

【讨论】:

SaxonB 在 Ubuntu 中,包libsaxonb-java,但如果我运行saxonb-xquery -qs://element/@attribute -s:filename.xml,我会得到SENR0001: Cannot serialize a free-standing attribute node,与例如相同的问题。 xml_grep. 如果您想查看此查询选择的属性节点的完整详细信息,请在命令行上使用 -wrap 选项。如果您只想要属性的字符串值,请将 /string() 添加到查询中。 谢谢。添加 /string() 更接近。但它会输出一个 XML 标头并将所有结果放在一行中,所以仍然没有雪茄。 如果您不需要 XML 标头,请添加选项 !method=text。 要使用命名空间,请将其添加到-qs,如下所示:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'【参考方案6】:

您可能还对xsh 感兴趣。它具有交互模式,您可以在其中对文档进行任何您喜欢的操作:

open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;

【讨论】:

它似乎没有作为一个包提供,至少在 Ubuntu 中没有。 @clacke:不是,但可以通过cpan XML::XSH2从CPAN安装。 @choroba,我在 OS X 上试过了,但安装失败,出现某种 makefile 错误。 @cnst: 你有安装 XML::LibXML 吗? @choroba,我不知道;但我的意思是,cpan XML::XSH2 无法安装任何东西。【参考方案7】:

clacke’s answer 很棒,但我认为只有当您的源代码是格式良好的 XML 而不是普通的 html 时才有效。

因此,对普通的 Web 内容做同样的事情 - HTML 文档不一定是格式良好的 XML:

echo "<p>foo<div>bar</div><p>baz" | python -c "from sys import stdin; \
from lxml import html; \
print '\n'.join(html.tostring(node) for node in html.parse(stdin).xpath('//p'))"

改为使用 html5lib(以确保您获得与 Web 浏览器相同的解析行为——因为与浏览器解析器一样,html5lib 符合 HTML 规范中的解析要求)。

echo "<p>foo<div>bar</div><p>baz" | python -c "from sys import stdin; \
import html5lib; from lxml import html; \
doc = html5lib.parse(stdin, treebuilder='lxml', namespaceHTMLElements=False); \
print '\n'.join(html.tostring(node) for node in doc.xpath('//p'))

【讨论】:

是的,我相信我自己在问题中的假设,即 XPath 意味着 XML。这个答案是对这里其他答案的一个很好的补充,感谢您让我了解 html5lib!【参考方案8】:

类似于 Mike 和 clacke 的答案,这里是 python 单线(使用 python >= 2.5)从 pom.xml 文件中获取构建版本,它绕过了 pom.xml 文件通常没有的事实一个 dtd 或默认命名空间,所以在 libxml 中看起来格式不正确:

python -c "import xml.etree.ElementTree as ET; \
  print(ET.parse(open('pom.xml')).getroot().find('\
  http://maven.apache.org/POM/4.0.0version').text)"

在 Mac 和 Linux 上测试,不需要安装任何额外的软件包。

【讨论】:

我今天用过这个!我们的构建服务器既没有lxml,也没有xmllint,甚至没有Ruby。本着 my own answer 格式的精神,我在 bash 中将其写为 python3 -c "from xml.etree.ElementTree import parse; from sys import stdin; print(parse(stdin).find('.//element[subelement=\"value\"]/othersubelement').text)" &lt;&lt;&lt; "$variable_containing_xml".getroot() 似乎没有必要。【参考方案9】:

除了XML::XSH 和XML::XSH2 之外,还有一些类似grep 的实用程序不如App::xml_grep2XML::Twig(包括xml_grep 而不是xml_grep2)。在为快速单行或Makefile 目标处理大型或大量 XML 文件时,这些可能非常有用。当您想要比 $SHELLxmllint xstlproc 提供更多处理时,XML::Twig 特别适合用于 perl 脚本方法。

应用程序名称中的编号方案表明“2”版本是本质上相同工具的更新/更新版本,可能需要其他模块(或perl本身)的更新版本。

【讨论】:

xml_grep2 -t //element@attribute filename.xml 工作并按照我的预期工作(xml_grep --root //element@attribute --text_only filename.xml 仍然没有,返回“无法识别的表达式”错误)。太好了! xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml 怎么样?在这种情况下,不确定那里发生了什么或 XPath 对 [] 的评价,但用方括号括起来的 @attribute 适用于 xml_grepxml_grep2 我的意思是//element/@attribute,而不是//element@attribute。显然无法对其进行编辑,但将其留在那里而不是删除+替换,以免混淆此讨论的历史。 //element[@attribute] 选择类型为 element 且具有属性 attribute 的元素。我不想要元素,只想要属性。 &lt;element attribute='foo'/&gt; 应该给我foo,而不是完整的&lt;element attribute='foo'/&gt;【参考方案10】:

值得一提的是,nokogiri 本身附带了一个命令行工具,应该使用gem install nokogiri 安装。

您可能会找到this blog post useful。

【讨论】:

【参考方案11】:

我已经尝试了几个命令行 XPath 实用程序,当我意识到我花了太多时间在谷歌上搜索并弄清楚它们是如何工作的,所以我用 Python 编写了最简单的 XPath 解析器,它可以满足我的需要。

如果 XPath 表达式计算结果为字符串,则以下脚本显示字符串值,如果结果是节点,则显示整个 XML 子节点:

#!/usr/bin/env python
import sys
from lxml import etree

tree = etree.parse(sys.argv[1])
xpath = sys.argv[2]

for e in tree.xpath(xpath):

    if isinstance(e, str):
        print(e)
    else:
        print((e.text and e.text.strip()) or etree.tostring(e))

它使用lxml — 一个用 C 语言编写的快速 XML 解析器,它不包含在标准 python 库中。使用pip install lxml 安装它。在 Linux/OSX 上可能需要前缀 sudo

用法:

python xmlcat.py file.xml "//mynode"

lxml 也可以接受 URL 作为输入:

python xmlcat.py http://example.com/file.xml "//mynode" 

提取附件节点下的url属性,即&lt;enclosure url="http:...""..&gt;)

python xmlcat.py xmlcat.py file.xml "//enclosure/@url"

谷歌浏览器中的 Xpath

作为一个不相关的附注:如果您想针对网页的标记运行 XPath 表达式,那么您可以直接从 Chrome 开发工具中执行此操作:右键单击 Chrome 中的页面 > 选择检查,然后在 DevTools 控制台中将 XPath 表达式粘贴为 $x("//spam/eggs")

获取此页面上的所有作者:

$x("//*[@class='user-details']/a/text()")

【讨论】:

不是单行的,lxml 已经在 two 其他 answers 中提到过。【参考方案12】:

这是一个 xmlstarlet 用例,用于从嵌套元素 elem1、elem2 中提取数据到此类 XML 中的一行文本(还展示了如何处理命名空间):

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<mydoctype xmlns="http://xml-namespace-uri" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xml-namespace-uri http://xsd-uri" format="20171221A" date="2018-05-15">

  <elem1 time="0.586" length="10.586">
      <elem2 value="cue-in" type="outro" />
  </elem1>

</mydoctype>

输出将是

0.586 10.586 cue-in outro

在这个 sn-p 中,-m 匹配嵌套的 elem2,-v 输出属性值(带有表达式和相对寻址),-o 文字文本,-n 添加换行符:

xml sel -N ns="http://xml-namespace-uri" -t -m '//ns:elem1/ns:elem2' \
 -v ../@time -o " " -v '../@time + ../@length' -o " " -v @value -o " " -v @type -n file.xml

如果需要来自 elem1 的更多属性,可以这样做(也显示 concat() 函数):

xml sel -N ns="http://xml-namespace-uri" -t -m '//ns:elem1/ns:elem2/..' \
 -v 'concat(@time, " ", @time + @length, " ", ns:elem2/@value, " ", ns:elem2/@type)' -n file.xml

注意命名空间(ns,用 -N 声明)的(IMO 不必要的)复杂性,这让我几乎放弃了 xpath 和 xmlstarlet,并编写了一个快速的临时转换器。

【讨论】:

xmlstarlet 很棒,但接受的主要排名答案已经提到了它。如果有的话,有关如何处理名称空间的信息可能与评论相关。任何遇到命名空间和 xmlstarlet 问题的人都可以找到出色的 discussion in the documentation 当然,@clacke,xmlstarlet 已被多次提及,但也很难掌握,而且文档不足。我猜测了一个小时如何从嵌套元素中获取信息。我希望我有这个例子,这就是为什么我在这里发布它以避免其他人浪费时间(而且这个例子太长了,无法评论)。【参考方案13】:

我的 Python 脚本 xgrep.py 正是这样做的。为了在文件filename.xml ... 中搜索元素element 的所有属性attribute,您可以按如下方式运行它:

xgrep.py "//element/@attribute" filename.xml ...

有多种控制输出的开关,例如-c 用于计数匹配,-i 用于缩进匹配部分,-l 用于仅输出文件名。

该脚本不能作为 Debian 或 Ubuntu 软件包提供,但它的所有依赖项都是。

【讨论】:

你在 sourcehut 上托管!不错!【参考方案14】:

安装BaseX数据库,然后像这样使用"standalone command-line mode":

basex -i - //element@attribute &lt; filename.xml

basex -i filename.xml //element@attribute

查询语言实际上是 XQuery (3.0),而不是 XPath,但由于 XQuery 是 XPath 的超集,您可以在不注意的情况下使用 XPath 查询。

【讨论】:

【参考方案15】:

由于这个项目显然是相当新的,请查看 https://github.com/jeffbr13/xq ,似乎是 lxml 的包装,但这就是您真正需要的(并在其他答案中使用 lxml 发布了临时解决方案)

【讨论】:

【参考方案16】:

我对用于 HTML XPath 查询的 Python 单行代码不满意,所以我自己编写了代码。假设您安装了python-lxml 包或运行了pip install --user lxml

function htmlxpath()  python -c 'for x in __import__("lxml.html").html.fromstring(__import__("sys").stdin.read()).xpath(__import__("sys").argv[1]): print(x)' $1 

一旦你有了它,你就可以像下面这个例子一样使用它:

> curl -s https://slashdot.org | htmlxpath '//title/text()'
Slashdot: News for nerds, stuff that matters

【讨论】:

【参考方案17】:

很抱歉成为争吵中的另一个声音。我尝试了这个线程中的所有工具,发现它们都不能满足我的需求,所以我自己写了。你可以在这里找到它:https://github.com/charmparticle/xpe

它已经上传到pypi,所以你可以像这样用pip3轻松安装它:

sudo pip3 install xpe

安装后,您可以使用它来针对各种输入运行 xpath 表达式,其灵活性与在 selenium 或 javascript 中使用 xpath 所获得的灵活性相同。是的,你可以使用 xpaths 来处理 HTML。

【讨论】:

【参考方案18】:

即使名称空间声明位于顶部也有效的解决方案:

如果 xml 在顶部声明了命名空间,则答案中提出的大多数命令都不能开箱即用。考虑一下:

输入xml:

<elem1 xmlns="urn:x" xmlns:prefix="urn:y">
    <elem2 attr1="false" attr2="value2">
        elem2 value
    </elem2>
    <elem2 attr1="true" attr2="value2.1">
        elem2.1 value
    </elem2>    
    <prefix:elem3>
        elem3 value
    </prefix:elem3>        
</elem1>

不起作用:

xmlstarlet sel -t -v "/elem1" input.xml
# nothing printed
xmllint -xpath "/elem1" input.xml
# XPath set is empty

解决方案:

# Requires >=java11 to run like below

# Prints the contents and self of "elem1"
java ExtractXpath.java "/elem1" input.xml

# Prints the value of the attribute: "value2.1"
java ExtractXpath.java "/elem1/elem2/@attr2" input.xml

# Prints the values inside elemt3: "elem3 value"
java ExtractXpath.java "/elem1/elem3/text()" input.xml

ExtractXpath.java:

import java.io.File;
import java.io.FileInputStream;
import java.io.StringWriter;
import java.util.Iterator;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ExtractXpath 

    public static void main(String[] args) throws Exception 
        assertThat(args.length==2, "Wrong number of args");
        String xpath = args[0];
        File file = new File(args[1]);
                
        assertThat(file.isFile(), file.getAbsolutePath()+" is not a file.");
        FileInputStream fileIS = new FileInputStream(file);
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = builderFactory.newDocumentBuilder();
        Document xmlDocument = builder.parse(fileIS);
        XPath xPath = XPathFactory.newInstance().newXPath();
        String expression = xpath;
        NodeList nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);
        
        for (int i = 0; i < nodeList.getLength(); i++) 
            System.out.println("==MATCH_START==\n" + convertNodeToString(nodeList.item(i)) + "\n==MATCH_END==");
                
    

    private static String convertNodeToString(Node node) throws TransformerConfigurationException, TransformerException 
            short nType = node.getNodeType();
        switch (nType) 
            case Node.ATTRIBUTE_NODE , Node.TEXT_NODE -> 
                return node.getNodeValue();
            
            case Node.ELEMENT_NODE -> 
                StringWriter writer = new StringWriter();
                Transformer trans = TransformerFactory.newInstance().newTransformer();
                trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
                trans.setOutputProperty(OutputKeys.INDENT, "yes");
                trans.transform(new DOMSource(node), new StreamResult(writer));
                return writer.toString();
            
            default -> 
                System.err.println("WARNING: FIXME: Node type:"+nType+" could possibly be handled in a better way.");
                return node.getNodeValue();
            
                
        
    

    
    private static void assertThat(boolean b, String msg) 
        if(!b)
            System.err.println(msg+"\n\nUSAGE: program xpath xmlFile");
            System.exit(-1);
        
    


@SuppressWarnings("unchecked")
class NamespaceResolver implements NamespaceContext 
    //Store the source document to search the namespaces
    private final Document sourceDocument;
    public NamespaceResolver(Document document) 
        sourceDocument = document;
    

    //The lookup for the namespace uris is delegated to the stored document.
    @Override
    public String getNamespaceURI(String prefix) 
        if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) 
            return sourceDocument.lookupNamespaceURI(null);
         else 
            return sourceDocument.lookupNamespaceURI(prefix);
        
    

    @Override
    public String getPrefix(String namespaceURI) 
        return sourceDocument.lookupPrefix(namespaceURI);
    

    @SuppressWarnings("rawtypes")
    @Override
    public Iterator getPrefixes(String namespaceURI) 
        return null;
    


为了简单起见:

xpath-extract 命令:

#!/bin/bash
java ExtractXpath.java "$1" "$2"

【讨论】:

以上是关于如何从 shell 执行 XPath 单行程序?的主要内容,如果未能解决你的问题,请参考以下文章

shell-脚本的执行

多行shell脚本如何执行?

如何在单行中执行多个FTP命令?

如何在 Perl/CGI 中为 Web 应用程序创建持久的 shell 命令会话?

shell基础

shell执行流控制语句实例详解