【问题标题】:Ant: How to execute a command for each file in directory?Ant:如何对目录中的每个文件执行命令?
【发布时间】:2010-11-30 21:06:02
【问题描述】:

我想从 Ant 构建文件中为目录中的每个文件执行一个命令。
我正在寻找一个独立于平台的解决方案。

我该怎么做?

当然,我可以用某种脚本语言编写脚本, 但这会给项目增加更多的依赖。

【问题讨论】:

    标签: file-io ant build-process build-automation


    【解决方案1】:

    简答

    将<foreach> 与嵌套的<FileSet> 一起使用

    Foreach 需要 ant-contrib。

    最近 ant-contrib 的更新示例:

    <target name="foo">
      <foreach target="bar" param="theFile">
        <fileset dir="${server.src}" casesensitive="yes">
          <include name="**/*.java"/>
          <exclude name="**/*Test*"/>
        </fileset>
      </foreach>
    </target>
    
    <target name="bar">
      <echo message="${theFile}"/>
    </target>
    

    这将使用 ${theFile} 调用目标“bar”,从而生成当前文件。

    【讨论】:

    • 所以,我必须包括一些东西?还是我需要一些外部蚂蚁库?我收到“问题:无法创建任务或键入 foreach”。如果我理解正确,这意味着 foreach 是一个未知关键字。
    • 哦,蹩脚......这是一个 Ant-Contrib 任务。所以,你必须安装一些东西。见这里:ant-contrib.sourceforge.net
    • 该示例来自一个 foreach,然后它被包含在 ant-contrib 中。 ant.1045680.n5.nabble.com/Using-foreach-td1354624.html 有一个很好的示例,我将更新示例以使其正常工作。
    • 嵌套文件集元素已被弃用,请改用嵌套路径
    • @dude 使用
    【解决方案2】:

    ant-contrib 是邪恶的;编写一个自定义的 ant 任务。

    ant-contrib 是邪恶的,因为它试图将 ant 从声明式风格转换为命令式风格。但是 xml 是一种垃圾编程语言。

    相比之下,自定义 ant 任务允许您使用真正的 IDE 用真正的语言 (Java) 编写,您可以在其中编写单元测试以确保您拥有所需的行为,然后在您的构建关于你想要的行为的脚本。

    只有当你关心编写可维护的 ant 脚本时,这种咆哮才有意义。如果您不关心可维护性,请尽一切努力。 :)

    jtf

    【讨论】:

    • 你说得对,我应该用 Java 写一个自定义的 ant 任务;)
    • ant-contrib 真的很邪恶。现在,我正处于一个大型 ant 构建项目的中间,该项目大量使用 if/then/else 和 antcalls,它读起来真的很糟糕。整个事情看起来像一个转换后的批处理/shell脚本,并且ant所做的所有依赖项都被大量使用ant-contrib完全关闭。如果您想保持设置干净,请构建您自己的任务。 :-/
    • @cringe,我不同意。像任何事情一样,您必须知道何时使用 ant-contrib 最符合您的利益。远离 if 和 var 之类的东西,使用 ant-contrib 避免重新发明轮子。
    • 一开始就应该是一种脚本语言,如此命令性而非声明性。所以它是谁是邪恶的海事组织
    • 也许 ant-contrib 是先验的邪恶,但可以这么说,black3r 对它的使用似乎是合理的和 ant-ish。
    【解决方案3】:

    Tassilo Horn(original target is here)建议了一种没有 ant-contrib 的方法

    基本上,因为没有 <java> 的扩展名(还没有?)以相同的方式 <apply> 扩展了 <exec>,他建议使用 (当然也可以在命令中运行 java 程序行)

    这里有一些例子:

      <apply executable="java"> 
        <arg value="-cp"/> 
        <arg pathref="classpath"/> 
        <arg value="-f"/> 
        <srcfile/> 
        <arg line="-o ${output.dir}"/> 
    
        <fileset dir="${input.dir}" includes="*.txt"/> 
      </apply> 
    

    【讨论】:

    • 这不是非常独立于平台的,尽管在原始问题中明确要求...
    【解决方案4】:

    使用<apply> task。

    它为每个文件执行一次命令。通过文件集或任何其他资源指定文件。 是内置的;不需要额外的依赖;无需自定义任务实现。

    也可以只运行一次命令,一次性将所有文件作为参数附加。使用parallel属性切换行为。

    对不起,迟到了一年。

    【讨论】:

    • 嗯,我在 2011 年才发现这很有用,所以还是谢谢你!
    • 的问题是它只执行外部系统命令。它不会直接做任何事情。不清楚这是否是最初的问题。基本上你必须在两种不同的情况下使用 或 ......这完全是愚蠢的。
    【解决方案5】:

    这是使用 javascript 和 ant scriptdef 任务执行此操作的方法,由于 scriptdef 是核心 ant 任务,因此您不需要 ant-contrib 来运行此代码。

    <scriptdef name="bzip2-files" language="javascript">
    <element name="fileset" type="fileset"/>
    <![CDATA[
      importClass(java.io.File);
      filesets = elements.get("fileset");
    
      for (i = 0; i < filesets.size(); ++i) {
        fileset = filesets.get(i);
        scanner = fileset.getDirectoryScanner(project);
        scanner.scan();
        files = scanner.getIncludedFiles();
        for( j=0; j < files.length; j++) {
    
            var basedir  = fileset.getDir(project);
            var filename = files[j];
            var src = new File(basedir, filename);
            var dest= new File(basedir, filename + ".bz2");
    
            bzip2 = self.project.createTask("bzip2");        
            bzip2.setSrc( src);
            bzip2.setDestfile(dest ); 
            bzip2.execute();
        }
      }
    ]]>
    </scriptdef>
    
    <bzip2-files>
        <fileset id="test" dir="upstream/classpath/jars/development">
                <include name="**/*.jar" />
        </fileset>
    </bzip2-files>
    

    【讨论】:

    • 一个变量project 在上面的示例中被引用,但没有提供先前的定义。最好能说明或澄清这一点。编辑:n/m,发现project 是访问当前项目的预定义变量。我怀疑过,但不确定。
    • 对于那些在 JDK8 或更高版本中尝试此操作的人,请记住,如果 JRE 加载的 ScriptEngine 是 rhino(大多数 JDK 6 和 7 都是如此),则“importClass”有效,而在 Nashorn (从 8 开始)您可以使用向后兼容的“File = java.io.File”或更新但不向后兼容的 Java.type。正如我今天所经历的那样,在运行 scriptdef 时,Ant 似乎默默地失败了。
    【解决方案6】:

    您可以使用 ant-contrib 任务“for”来迭代由任何分隔符分隔的文件列表,默认分隔符是“,”。

    以下是显示此内容的示例文件:

    <project name="modify-files" default="main" basedir=".">
        <taskdef resource="net/sf/antcontrib/antlib.xml"/>
        <target name="main">
            <for list="FileA,FileB,FileC,FileD,FileE" param="file">
              <sequential>
                <echo>Updating file: @{file}</echo>
                <!-- Do something with file here -->
              </sequential>
            </for>                         
        </target>
    </project>
    

    【讨论】:

      【解决方案7】:

      我知道这篇文章已经很老了,但是现在已经过了一段时间和 ant 版本,有一种方法可以用基本的 ant 功能做到这一点,我想我应该分享它。

      这是通过调用嵌套任务的递归宏定义来完成的(甚至可以调用其他宏)。唯一的约定是使用固定的变量名(此处为元素)。

      <project name="iteration-test" default="execute" xmlns="antlib:org.apache.tools.ant" xmlns:if="ant:if" xmlns:unless="ant:unless">
      
          <macrodef name="iterate">
              <attribute name="list" />
              <element name="call" implicit="yes" />
              <sequential>
                  <local name="element" />
                  <local name="tail" />
                  <local name="hasMoreElements" />
                  <!-- unless to not get a error on empty lists -->
                  <loadresource property="element" unless:blank="@{list}" >
                      <concat>@{list}</concat>
                      <filterchain>
                          <replaceregex pattern="([^;]*).*" replace="\1" />
                      </filterchain>
                  </loadresource>
                  <!-- call the tasks that handle the element -->
                  <call />
      
                  <!-- recursion -->
                  <condition property="hasMoreElements">
                      <contains string="@{list}" substring=";" />
                  </condition>
      
                  <loadresource property="tail" if:true="${hasMoreElements}">
                      <concat>@{list}</concat>
                      <filterchain>
                          <replaceregex pattern="[^;]*;(.*)" replace="\1" />
                      </filterchain>
                  </loadresource>
      
                  <iterate list="${tail}" if:true="${hasMoreElements}">
                      <call />
                  </iterate>
              </sequential>
          </macrodef>
      
          <target name="execute">
              <fileset id="artifacts.fs" dir="build/lib">
                  <include name="*.jar" />
                  <include name="*.war" />
              </fileset>
      
              <pathconvert refid="artifacts.fs" property="artifacts.str" />
      
              <echo message="$${artifacts.str}: ${artifacts.str}" />
              <!-- unless is required for empty lists to not call the enclosed tasks -->
              <iterate list="${artifacts.str}" unless:blank="${artifacts.str}">
                  <echo message="I see:" />
                  <echo message="${element}" />
              </iterate>
              <!-- local variable is now empty -->
              <echo message="${element}" />
          </target>
      </project>
      

      需要的关键功能:

      我没有设法使分隔符变量,但这可能不是主要的缺点。

      【讨论】:

      • 不幸的是,这会耗尽内存并且很快就会爆炸。
      • 是的,它可能会炸毁您的堆栈,具体取决于您处理的文件数量。我用大约 66 个文件(没有增加内存选项)做得非常成功。但是,如果您无权访问提供更多功能(例如并行运行)的 ant-contrib,则它只是一个选项。
      • 这很有帮助。
      【解决方案8】:

      按照 blak3r 的建议做,并像这样定义你的目标类路径

      <taskdef resource="net/sf/antcontrib/antlib.xml">
          <classpath>
              <fileset dir="lib">
                <include name="**/*.jar"/>
              </fileset>
          </classpath>        
      </taskdef>
      

      lib 是你存储 jar 的地方

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-28
        • 1970-01-01
        • 2018-06-08
        • 2011-11-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多