101

I want to execute a command from an Ant buildfile, for each file in a directory.
I am looking for a platform-independent solution.

How do I do this?

Sure, I could write a script in some scripting language, but this would add further dependencies to the project.

8 Answers 8

89

Use the <apply> task.

It executes a command once for each file. Specify the files by means of filesets or any other resource. <apply> is built-in; no additional dependency needed; no custom task implementation needed.

It's also possible to run the command only once, appending all files as arguments in one go. Use the parallel attribute to switch the behaviour.

Sorry for being late a year.

Sign up to request clarification or add additional context in comments.

2 Comments

Well I just found this useful in 2011, so thanks for that anyway!
The problem with <apply> is that it only executes external system commands. It won't do any anty stuff directly. Not clear if that's what the original question was about or not. Basically you have to use <apply> or <foreach> for the two different situations... which is totally dumb.
62

Short Answer

Use <foreach> with a nested <FileSet>

Foreach requires ant-contrib.

Updated Example for recent 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>

This will antcall the target "bar" with the ${theFile} resulting in the current file.

6 Comments

So, I have to include something? Or do I need some external ant lib? I'm getting "Problem: failed to create task or type foreach". If I understand correctly, this means, foreach is an unknown keyword.
Ohhh lame... it's an Ant-Contrib Task. So, you have to install something. See here: ant-contrib.sourceforge.net
That example is from a foreach before it was included in ant-contrib. There's a good example at ant.1045680.n5.nabble.com/Using-foreach-td1354624.html I'm going to update the example to work.
The nested fileset element is deprectated, use a nested path instead
@dude Use <foreach><path><fileset ><include name="*.jar"/></fileset></path></foreach>
|
30

An approach without ant-contrib is suggested by Tassilo Horn (the original target is here)

Basicly, as there is no extension of <java> (yet?) in the same way that <apply> extends <exec>, he suggests to use <apply> (which can of course also run a java programm in a command line)

Here some examples:

  <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> 

2 Comments

This is not very platform independent although it was clearly required in the original question...
Here in 2024, I just spent 7 hours trying to get the files in my 'fileset' to be echoed. I CANNOT BELIEVE how hard it was to FINALLY discover this 100-percent solution! It was NOT an intuitively inspired journey that landed me here. Everyone, if you see this solution and do not think it is relevant, like I did... GUESS AGAIN! Just try it. Use the "ls" executable like this: <apply executable="ls"> <arg value="-l"/> <fileset refid="wmserver-${sarversion}-oltp-files" /> </apply>
19

Here is way to do this using javascript and the ant scriptdef task, you don't need ant-contrib for this code to work since scriptdef is a core ant task.

<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>

2 Comments

a variable project is referenced in the example above, but with no prior definition made available. Would be good to have that represented or clarified. EDIT: n/m, found that project is a predefined var to access the current project. I'd suspected that, but wasn't sure.
For those trying this in JDK8 or later, keep in mind that "importClass" works if the ScriptEngine loaded by the JRE is rhino (which was true for most JDK 6 and 7), while in Nashorn (from 8 onward) you can use the backward-compatible "File = java.io.File" or the newer but not backward-compatible Java.type. Ant seems to fail silently when having issues running scriptdef, as I got to experience today.
16

ant-contrib is evil; write a custom ant task.

ant-contrib is evil because it tries to convert ant from a declarative style to an imperative style. But xml makes a crap programming language.

By contrast a custom ant task allows you to write in a real language (Java), with a real IDE, where you can write unit tests to make sure you have the behavior you want, and then make a clean declaration in your build script about the behavior you want.

This rant only matters if you care about writing maintainable ant scripts. If you don't care about maintainability by all means do whatever works. :)

Jtf

5 Comments

Your're right, I should just write a custom ant task in Java ;)
ant-contrib really is evil. Right now I'm in the middle of a large ant build project that makes intense use of if/then/else and antcalls and it really reads horrible. The whole thing looks like a converted batch/shell script and all the dependency stuff that ant does is completly turned off by the heavy use of ant-contrib. If you want to keep your setup clean, build your own task. :-/
@cringe, I disagree. Like anything, you have to know when it is in your best interests to use ant-contrib. Stay away from things like if and var and use ant-contrib to avoid having to reinvent the wheel.
And should've been a scripting language, so imperative and not declarative, to begin with. So it's And who is evil IMO
Maybe ant-contrib is a priori evil but black3r's use of it seems reasonable and ant-ish, so to speak.
8

I know this post is realy old but now that some time and ant versions passed there is a way to do this with basic ant features and i thought i should share it.

It's done via a recursive macrodef that calls nested tasks (even other macros may be called). The only convention is to use a fixed variable name (element here).

<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>

The key features needed where:

I didnt manage to make the delimiter variabel, but this may not be a major downside.

3 Comments

Unfortunately this runs out of memory and blows up rather quickly.
Yes it may blow up your stack, depending on the number of files you process. I did it pretty successful with about 66 Files (without increased Memory options). However it s of cause only an option if you dont have access to ant-contrib wich offers more functionality (e.g. running parallel).
This was very helpful.
1

You can use the ant-contrib task "for" to iterate on the list of files separate by any delimeter, default delimeter is ",".

Following is the sample file which shows this:

<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>

Comments

0

Do what blak3r suggested and define your targets classpath like so

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

where lib is where you store your jar's

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.