How to execute Unix command on each file in a folder, output = file of same name in another folder

I’m a gradle newbie. I want to create a task in which I execute a Unix command (m4) on each file in sourceDir. ‘M4’ only writes output to stdout (it has no ‘-o output-file’ type option) so in essence I want to do something like:

command ‘sh’, -‘c’, ‘m4’, “$sourceDir/$myFile”, “>$targetDir/$myFile” // (but of course this won’t work)

on each file in $sourceDir. How would I do this, o Gracious Gradle Gurus?

Thanx in advance!

1 Like

Hi

Assuming your command line works for a single file, you only need to wrap it in a loop, to be called on each file of a given directory. This is more a Groovy than a Gradle question
I’d do something like

def sourceDir = '/path/to/src/dir'
def targetDir = '/path/to/target/dir'
file("$sourceDir").eachFile  { file ->
 exec {
   commandLine 'sh', '-c', 'm4', "$sourceDir/$file", "> $targetDir/$file"
   }
 }

So far so good except that now I get “Task has not declared any outputs”…

:smile:

What did you write exactly?

What you get is a debug output, saying that your task did not declare its output files. This tells Gradle to consider the task NOT up-to-date, and that it shall be executed. It’s not an error message at all. If you do not obtain the desired result, it’s because of something else. Maybe the commanLine arguments are not ok.

I did not check my code snippet, but you might try by replacing “$sourceDir/$file” by “$sourceDir/${file.name}” (and similar thing for targetDir)

task processZant(type: Exec) {
description = 'Uses m4 to process sources’
def sourceDir = zant.zantSource
def targetDir = antlr.antlrSource
file("$sourceDir").eachFile { aFile ->
exec {
commandLine ‘sh’, ‘-c’, ‘m4’, “$aFile”, “> $targetDir/$aFile”
}
}
}

There’s a couple of issues here.

@Francois_Guillot gave you some implementation code for your task that will call exec to run m4 for each input file. However, your processZant task should not be an Exec task itself. Remove the (type: Exec) part of the task declaration.

The closure that loops through each file with the exec call should be in a doLast { } block since that’s the action that should occur when you run the task.

You should also make sure that your targetDir exists before trying to write there. Based on the naming there, you might be writing into another source directory that’s guaranteed to be there instead of a build directory, but either way, adding a call to mkdirs() won’t hurt anything.

If you can’t run sh -c m4 filename from the command line successfully, it won’t work here either. I’ve always needed to quote the script portion of the sh -c command, even at the command line, regardless of what sh says it will do with extra arguments. I would do the same thing here.

Finally, you do need to use the name of the File anywhere where that you use the sourceDir or targetDir. Otherwise, you have an absolute file location appended to another absolute directory location.

With all of these changes, it looks like this:

task processZant {
    description = 'Uses m4 to process sources'
    def sourceDir = zant.zantSource
    def targetDir = antlr.antlrSource
    doLast {
        file(targetDir).mkdirs()
        file(sourceDir).eachFile { aFile ->
            exec {
                commandLine 'sh', '-c', "m4 $aFile > $targetDir/$aFile.name"
            }
        }
    }
}
1 Like