我可以将Ant'replace'任务输出发送到新文件吗?

Sat*_*ish 6 ant replace

Ant replace任务在不创建新文件的情况下执行就地替换.

以下代码段使用"my.properties"文件中的相应值替换任何"*.xml"文件中的标记.

<replace dir="${projects.prj.dir}/config"
         replacefilterfile="${projects.prj.dir}/my.properties"
         includes="*.xml" summary="true" />
Run Code Online (Sandbox Code Playgroud)

我希望那些替换了其标记的文件以模式(例如)'*.xml.filtered'命名,并保留原始文件.

这有可能在Ant中实现一些聪明的任务组合吗?

mat*_*att 7

有两种方法可以接近您想要的内容而无需复制到临时目录并进行复制.

Filtersets

如果可以更改源文件,以便可以使用开始和结束标记分隔要替换的部分,如@date@(@是默认标记,但可以更改),那么您可以将copy任务与a globmapper和a一起使用filterset:

<copy todir="config">
  <fileset dir="config" includes="*.xml" />
  <globmapper from="*.xml" to="*.xml.filtered" />
  <filterset filtersfile="replace.properties" />
</copy>
Run Code Online (Sandbox Code Playgroud)

如果replace.properties包含FOO = bar,那么@FOO@源xml文件文件中的任何出现都将替换bar为目标.

请注意,源目录和目标目录是相同的,globmapper表示目标文件并以后缀命名.filtered.将文件复制到不同的目标目录是可能的(也是更常见的))

Filterchains

如果无法更改源文件以添加开始和结束标记,则可能的替代方法是使用filterchain带有一个或多个replacestring过滤器而不是filterset:

<copy todir="config">
  <fileset dir="config" includes="*.xml" />
  <globmapper from="*.xml" to="*.xml.filtered" />
  <filterchain>
    <tokenfilter>
      <replacestring from="foo" to="bar" />
      <!-- extra replacestring elements here as required -->
    </tokenfilter>
  </filterchain>
</copy>
Run Code Online (Sandbox Code Playgroud)

这将取代任何发生foobar在文件,它更像是行为在任何地方replace工作.不幸的是,这种方式意味着您需要在构建文件本身中包含所有替换项,您不能将它们包含在单独的属性文件中.

在这两种情况下,copy任务只会复制比目标文件更新的源文件,因此不会进行不必要的工作.

复制然后更换

第三种可能性(在编写其他两种情况时刚刚出现的情况)是首先执行复制到重命名的文件,然后运行replace指定重命名文件的任务:

<copy todir="config">
  <fileset dir="config" includes="*.xml" />
  <globmapper from="*.xml" to="*.xml.filtered" />
</copy>
<replace dir="config" replacefilterfile="replace.properties" summary="true" 
  includes="*.xml.filtered" />
Run Code Online (Sandbox Code Playgroud)

这可能是最接近原始要求的解决方案.缺点是replace每次重命名的文件都会运行任务.这可能是一些替换模式的问题(诚然,它们会像奇怪的那样foo=foofoo,但是前两种方法它们会没问题)并且当依赖关系没有改变时你将做不必要的工作.


mar*_*ton 1

replace任务不观察依赖关系,而是通过为每个输入文件写入临时文件来执行替换。如果临时文件与输入文件相同,则将其丢弃。与输入文件不同的临时文件被重命名以替换该输入。这意味着所有文件都会被处理,即使它们都不需要 - 因此它可能效率低下。

这个问题最初的解决办法是进行复制-替换-复制。不过,不需要第二个副本,因为可以在第一个副本中使用映射器。在副本中,依赖项可用于将处理限制为仅处理已更改的文件 - 通过显式文件集中的depend选择器:

<copy todir="${projects.prj.dir}">
  <fileset dir="${projects.prj.dir}">
    <include name="*.xml" />
    <depend targetdir="${projects.prj.dir}">
      <mapper type="glob" from="*.xml" to="*.xml.filtered" />
    </depend>
  </fileset>
  <mapper type="glob" from="*.xml" to="*.xml.filtered" />
</copy>
Run Code Online (Sandbox Code Playgroud)

这会将复制文件集限制为仅那些已更改的文件。映射器的另一种语法是:

<globmapper from="*.xml" to="*.xml.filtered" />
Run Code Online (Sandbox Code Playgroud)

最简单的替换是:

<replace dir="${projects.prj.dir}"
         replacefilterfile="my.properties"
         includes="*.xml.filtered" />
Run Code Online (Sandbox Code Playgroud)

即使没有一个文件需要进行替换,这仍然会处理所有文件。replace任务具有隐式文件集,并且可以对显式文件集进行操作,但与类似任务不同,隐式文件集不是可选的,因此要利用显式文件集中的选择器,您必须使隐式文件集“不执行任何操作” - 因此.dummy此处的文件:

<replace dir="${projects.prj.dir}"
         replacefilterfile="my.properties">
         includes=".dummy" />
  <fileset dir="${projects.prj.dir}" includes="*.xml.filtered">
    <not>
      <different targetdir="${projects.prj.dir}">
        <globmapper from="*.xml.filtered" to="*.xml" />
      </different>
    </not>
  </fileset>
</replace>
Run Code Online (Sandbox Code Playgroud)

这将防止replace任务不必要地处理先前已进行替换的文件。但是,它不会阻止处理未更改且不需要替换的文件。

除此之外,我不确定是否有一种方法可以“编码高尔夫”这个问题以将步骤数减少到一个。没有可在任务中使用的多字符串替换过滤器copy来实现与 相同的效果replace,这是一种耻辱,因为感觉这将是正确的解决方案。

另一种方法是为一系列replace string过滤器生成 xml,然后让 Ant 执行它。但这将比现有解决方案更复杂,并且容易出现替换字符串问题,如果将替换字符串粘贴到 xml 片段中,将导致无法解析的内容。

另一种方法是编写自定义任务或script任务来完成工作。如果有很多文件并且复制替换解决方案被认为太慢,那么这可能是正确的选择。但同样,这种方法不如现有解决方案简单。

如果要求是最小化处理过程中完成的工作,而不是提出最短的 Ant 解决方案,那么这种方法可能可行。

  • 创建一个包含已更改的输入列表的文件集。
  • 从该文件集中创建相应过滤文件的逗号分隔列表。
  • 对文件集执行复制。
  • 对逗号分隔的列表执行替换。

这里的一个问题是,如果没有文件发生更改,替换任务中的隐式文件集将回退到处理所有内容。为了克服这个问题,我们插入一个虚拟文件名。

<fileset id="changed" dir="${projects.prj.dir}" includes="*.xml">
  <depend targetdir="${projects.prj.dir}">
    <globmapper from="*.xml" to="*.xml.filtered" />
  </depend>
</fileset>

<pathconvert property="replace.includes" refid="changed">
  <map from=".xml" to=".xml.filtered" />
</pathconvert>

<copy todir="${projects.prj.dir}" preservelastmodified="true">
  <fileset refid="changed" />
 <globmapper from="*.xml" to="*.xml.filtered" />
</copy>

<replace dir="${projects.prj.dir}"
         replacefilterfile="my.properties"
         includes=".dummy,${replace.includes}" summary="true" />
Run Code Online (Sandbox Code Playgroud)