在没有XML解析器的情况下为最近修改的文件过滤svn ls --xml

时间:2016-05-17 20:48:51

标签: xml linux bash svn fedora

我对Linux和Bash Scripting非常陌生,我正在努力开始使用它。我有一个XML元素列表,我想只选择其中一些。根据最新年份和最近一个月(最后一次更改)的元素,我想只按名称选择那些在过去4个月内更改的元素。基本上我想要一个过去4个月使用的元素名称列表。我正在使用 svn ls --xml 在xml中吐出数据,我正在尝试将其传输到 grep 来执行上述操作。我无法使用xml解析器,因为这需要我在脚本运行的每个系统中安装它。以下是两个这样的xml条目:

<entry
   kind="directory">
<name>foo</name>
<commit
   revision="69">
<author>myself</author>
<date>2016-05-13T00:21:59.396753Z</date>
</commit>
</entry>
<entry
   kind="directory">
<name>bar</name>
<commit
   revision="666">
<author>myself</author>
<date>2013-04-04T01:56:54.484359Z</date>
</commit>
</entry>
</list>
</lists>

1 个答案:

答案 0 :(得分:1)

你要求的可怕,不好,非常糟糕的答案

假设(这是一个假设绝对不能保证在将来的版本中保留),此输出的格式将在未来保持不变(超出XML提供的格式保证的方式)规范),并且您的文件名永远不会包含需要在XML中转义的字符:

date_re='^<date>(.*)</date>$'
name_re='^<name>(.*)</name>$'
end_re='^</entry>$'

limit=$(date -d 'now - 4 months' '+%Y-%m-%dT%H:%M:%S') || exit

date=; name=
while read -r line; do
  [[ $line =~ $date_re ]] && date=${BASH_REMATCH[1]}
  [[ $line =~ $name_re ]] && name=${BASH_REMATCH[1]}
  [[ $line =~ $end_re && $date && $name ]] && [[ $date > $limit ]] && {
    printf '%s\t%q\n' "$date" "$name"
    date=; name=
  }
done < <(svn ls --xml) | sort -r

此输出将是一个类似于(对于您的输入)的流:

2016-05-13T00:21:59.396753Z foo

请注意,如果您的文件名非常有趣,则此表现不佳。在您的输出中预计&gt;&amp;和类似内容,而实际文件名包含>&等。如果SVN的未来版本为这些XML标记添加属性,它们也将停止工作,而这些XML标记完全被允许这样做。不要这样做。

正确的事

...获取四个最新文件:

xmlstarlet sel -t -m '//entry' -v './commit/date' -o $'\t' -v './name' -n \
  | sort -r \
  | head -n 4

...现在,只有当我们假设Subversion无法存储带有文字换行符的文件名时,这才是明确的。幸运的是,这是它在实践中强制执行的规则;因此,通过此输出流中第一个制表符的所有内容都可以安全地解释为文件系统组件。

正确的事,可移植

上述xmlstarlet命令恰好等同于使用xsltproc来应用以下模板:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" version="1.0" extension-element-prefixes="exslt">
  <xsl:output omit-xml-declaration="yes" indent="no"/>
  <xsl:template match="/">
    <xsl:for-each select="//entry">
      <xsl:call-template name="value-of-template">
        <xsl:with-param name="select" select="./commit/date"/>
      </xsl:call-template>
      <xsl:text>        </xsl:text>
      <xsl:call-template name="value-of-template">
        <xsl:with-param name="select" select="./name"/>
      </xsl:call-template>
      <xsl:value-of select="'&#10;'"/>
    </xsl:for-each>
  </xsl:template>
  <xsl:template name="value-of-template">
    <xsl:param name="select"/>
    <xsl:value-of select="$select"/>
    <xsl:for-each select="exslt:node-set($select)[position()&gt;1]">
      <xsl:value-of select="'&#10;'"/>
      <xsl:value-of select="."/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

如果保存为names-and-dates.xslt,则:

xsltproc names-and-dates.xslt - < <(svn ls --xml) | sort -r | head

...将相应地应用它。

脚注:应用日期截止

如果您希望强制执行日期截止,而不是采用head的最后N方法,则将awk -v min_date=$(date -d 'now - 4 months' '+%Y-%m-%dT%H:%M:%S') '($1 < min_date) { exit } { print }'替换为head

如果您想相对于第一个条目需要四个月,而不是相对于当前日期,您可以通过以下方式管理结果:

{
   read -r date name
   min_date=$(date -d "$date - 4 months" '+%Y-%m-%dT%H:%M:%S')
   printf '%s\t%s\n' "$date" "$name"
   while read -r date name; do
     [[ $date > $min_date ]] || break
     printf '%s\t%s\n' "$date" "$name"
   done
}

请注意,这假定GNU日期;调整非GNU平台的可移植性是留给读者的练习。