Cannot invoke shell commands on a path that includes one or more blanks


(Luca Arzeni) #1

In a build script I need to execute a command on a file that is created by the user. I cannot control the path of this file, and if the user creates a path that includes a blank, my build script crashes.

You can reproduce the problem in this way: 1) create a directory from the shell prompt, with a name that includes a blank (please note the blank between the word “test” and the word “dir”; I need to surrount this path with quotes to make the shell happy)

mkdir "/tmp/test dir"
  1. now try to execute a ls of that dir with the following task:
task testCommandTask << {
 String l_aPathThatIncludesABlank="/tmp/test dir"
 String l_command="ls -l ${l_aPathThatIncludesABlank}" // broken, error because path includes blanks.
               // ls complains: cannot access /tmp/test: No such file or directory
   Process l_process = l_command.execute()
   l_process.waitFor()
 String l_stdout = l_process.in.text
 String l_stderr = l_process.err.text
 assert (l_process.exitValue() == 0), "DETAILS: failure executing external command."
}
  1. I’ve also tried surrounding the path with double quotes, single quotes but found no way to solve this issue

String l_command=“ls -l “${l_aPathThatIncludesABlank}”” // broken, looks for file “/tmp/test and then looks from file dir” String l_command=“ls -l ‘${l_aPathThatIncludesABlank}’” // broken, looks for file ‘/tmp/test and then looks from file dir’

  1. I’ve also tried escaping the blank with a slash, but even this attempt failed

String l_aPathThatIncludesABlank="/tmp/test\ dir" String l_command=“ls -l ${l_aPathThatIncludesABlank}” // broken, looks for file ‘/tmp/test and then fro file dir’

Can you help me? Thanks, Luca

N.B.: It seems that the parser has some problem showing you the task code, so I didn’t use a code marker around it.


(Luke Daley) #2

basically, don’t use String.execute() because it has these kinds of problems and others.

Use an dsl:org.gradle.api.tasks.Exec task, or the dsl:org.gradle.api.Project:exec(groovy.lang.Closure) method.


(Luca Arzeni) #3

I need to invoke the external script from within buildscript closure. I’ve understood that I have no access to the project when I’m inside the buildscript closure; so I guess I cannot use the project API or the tasks.

So the real question could be rephrased this way:

What is the suggested way to run an external command from the code that is inside a buildscript?

Thanks for your help, Luca


(Peter Niederwieser) #4

What is the suggested way to run an external command from the code that is inside a buildscript?

Why do you need to do that? Anyway, you can always use ‘java.lang.ProcessBuilder’.


(Luca Arzeni) #5

Hi Peter, there are few reasons to do this.

The main one is that I have few jars that are needed from my build. They came from thirdy party libs, but are not found on maven repository (example: paypal.jars).

I use a flat dir repository (I don’t want to manage a local maven/ivy repository), so I need to put these jars in my dir repository, giving them an adequate name, (ex paypal-1.2.3.jar).

This way I can refer them in my build with a standard "compile dependency like “paypal:paypal:1.2.3”.

I want to have a compile dependency since I’m doing a multiproject, and I want that all projects that use the first one have the jar automatically referred.

By the way: I will use a symlink and not a copy of the files in the repository, since this way I will have a clue to see from which partner they came.

So my solution is to put in the buildscript of each project that make use of them, the creation of a link from the original jar (ex: /opt/paypal-1.2.3/libs/paypal.jar) to a name in my local repository (ex: /home/user/myrepository/paypal-123.jar)

If a project needs these jars, he will put in the buildscript a function like

buildscript{
importLib("/opt/paypal-1.2.3/libs/paypal.jar", myRepositoryPath, "paypal-123.jar")
}

This is a kind of pre-dependency just to avoid a compile files("/opt/paypal-1.2.3/libs/paypal.jar") which will ignore the jar in the following projects.

If you have any better idea it will be appreciated!

Thanks, Luca

Please Note: I defined the importLib function in an external class of a project (gradleTools) that I’m writing. I had to do this also to create an aspectJ plugin, an XDoclet Task and an antlr task, since the one’s that came with gradle are not adequate for my needs.

I can contribute them to gradle, if you want them.


(Peter Niederwieser) #6

If you don’t want to maintain a repository, I’d put these Jars into a lib directory that’s under source control. I don’t understand the need for symlinks, invoking an external command from the ‘buildscript’ block, etc.


(Luca Arzeni) #7

Hi Peter, I will explain my need detailing further the example:

Thirdy party vendors often don’t give you only the jars in the lib directory.

They often give you also tools, examples and many other things, that are placed (and often works only…) in their directory tree.

I have many releases of paypal and other tools, but, for this issue, let’s go with paypal. They are stored on our central server (which is the authoritative reference for our developments). The server (with its source tree) may also be versioned (I don’t know) under a scm tool, but I don’t manage that server.

That server shares these tools (including his jars) for all developers. Note that non all thirdy party releases are allowed to go to this server, but only the ones that have been certified by our internal q.a. team.

So I have a network share, with, let me say N version of paypal tools:

/share/paypal-1.0.0/bin
/share/paypal-1.0.0/lib
/share/paypal-1.0.0/scripts
  /share/paypal-2.0.0/bin
/share/paypal-2.0.0/lib
/share/paypal-2.0.0/scripts
  /share/paypal-3.1.0/bin
/share/paypal-3.1.0/lib
/share/paypal-3.1.0/scripts

and so on.

My app must be certified with all these releases of paypal tools (remember that we don’t care what happens for other releases, as, looking in the example, the 3.0.0 that is not certified).

Now I have a multi/multiproject build (that is a multi project build that is used by many other multiprojects build), with has, in the low level layers, a direct dependency from few of these external libs (they are wrapped to limit the impact on upper level layers of code)

We can indeed store in our development scm the libs (even if, to be useful, we should version the entire tree :slight_smile: ), but It gaves us no advantage to do so, since it’s already versioned by the guys who own the authoritative server.

What I really need is to switch my build from one release of the thirdy party tools to the other, with less effort possible, using for the build the tools that are given by the thirdy party (for example code generators ecc).

I reach this goal putting a variable that says: I want to build with paypal-1.0.0, eBay 5.6.7, Jboss 4.2.3.GA, and so on.

One thing that is driving me crazy, for example, is that I’m not allowed to use/test a “generic” release of (say) commons-httpclient.jar, but I have to be sure to test/use the one that comes with JBoss 5.1.2 which is certified by Rh AND has some few differences/patches against the httpclient that comes from apache.

I hope it’s more understandable now.

Thanks, Luca