I observe a strange behavior of wildcard expansion behavior for Java7 on Windows.
For centuries there was a clean difference between "*" versus *.
Seems this it not longer true for Java7 (at least on Windows7).
I noticed the problem when using a wildcard classpath.
In despite of quoting the wildcard-classpath it gets expanded.
Thus it seems not possible any more to pass a wildcard to the java application.
So using java -cp "somewhere/*" will fail (as does "somewhere\*").
A workaround seems to be: java -cp "somewhere/*;" which inhibits the expansion.
To verify the behavior I wrote a small Echo.java class.
I found that using java 1.6.0 quoted "*" and plain * works like expected,
whereas on Java7 I always got the expanded wildcard.
Until now this was observed on Windows7, don't know what happens on XP.
The problem arises, since wildcards on Windows are never ever expanded by dark age CMD.EXE (like any shell does on UNIX). Instead each executable has to perform this explicitly using setargv.obj.
I found two related issues which seem to describe a similar problem:
Multiple command line wildcard expansion confuses Windows users
setargv.obj wildcard handling broken
Is this observed by someone else?
Or are there some obscure Windows or batch-file settings to control this?
Dieter.
Yes, I noticed the same issue.
It's explained as a 'known issue' in the release notes for Java7 update 4.
Here is the bug report. The fix will be delivered in Java7 update 8 (current release is update 6).
Note that there isn't a shell-options workaround, because in Windows, the shell doesn't handle wildcard expansion. (Whereas in Unixes, the shell performs the expansion).
Not a direct solution to the broken /* issue but I hope you could use the following script to ease your situation.
libDir2Scan4jars="../test";cp=""; for j in `ls ${libDir2Scan4jars}/*.jar`; do if [ "$j" != "" ]; then cp=$cp:$j; fi; done; echo $cp| cut -c2-${#cp} > .tmpCP.tmp; export tmpCLASSPATH=`cat .tmpCP.tmp`; if [ "$tmpCLASSPATH" != "" ]; then echo .; echo "classpath set, you can now use ~> java -cp \$tmpCLASSPATH"; echo .; else echo .; echo "Error please check libDir2Scan4jars path"; echo .; fi;
Scripted for Linux, could have a similar one for windows too. If proper directory is provided as input to the "libDir2Scan4jars"; the script will scan all the jars and create a classpath string and export it to a env variable "tmpCLASSPATH".
I found a generic workaround for the Windows-Java-launcher-argument-wildcard-expansion problem:
Arguments from an #argfile do not undergo wildcard expansion. E.g.
java foo.Echo '*.txt' => 1.txt 2.txt
echo "foo.Echo *.txt" >argfile
java #argfile => *.txt
So if you have a wrapper BASH script, you can do:
argfile=c:/tmp/argfile$$;
echo foo.Echo "$#" >$argfile;
java #$argfile;
, and your command line args will not be wildcard-expanded by the launcher.
Tested with:
adopt_openjdk_15+36_swm_cert_v3
adopt_openjdk-11.0.11.9-hotspot
adopt_openjdk-14.0.2_12-hotspot
adopt_openjdk-16.0.1.9-hotspot
jdk-13.0.2+8
jdk-17.0.1+12
Related
I want to use an environment variable as a JVM option when executing java -jar.
The command I want to execute it:
java -XX:onOutOfMemory='echo test' -jar foo.jar
When I run the above command as is, the jar will run.
(If you don't have the foo.jar, you will get an Error: Unable to access jarfile foo.jar error. But this still means that the option gets used correctly).
But when I create an environment variable containing the JVM option, and run the command using that variable.
OOM="-XX:onOutOfMemory='echo test'"
java $OOM -jar foo.jar
Than I get the following error:
Error: Could not find or load main class test'
It seems like the java command is ignoring the quotes around 'echo test'.
After looking for similar questions on SO and on other websites, I tried various variations of using quotes:
OOM="-XX:OnOutOfMemoryError=\"echo test\""
OOM='-XX:OnOutOfMemoryError="echo test"'
OOM=-XX:OnOutOfMemoryError="echo test"
But they all result in the same error.
An article from Oracle concerning JVM options, mentions using a semicolon:
-XX:OnOutOfMemoryError="<cmd args>; <cmd args>"
But the purpose of the semicolon is to separate multiple commands, not command and arguments. So this does not fix my problem.
Does anybody know how I can correctly move the -XX:onOutOfMemory='echo test' option into an environment variable?
When running java, you should quote $OOM
Example:
java "$OOM" -jar foo.jar
See Why does my shell script choke on whitespace or other special characters? on Unix stackexchange for why this is needed.
I am trying to use a class into a jar using a Nashorn Shebang script with the -cp option (java version "1.8.0_31"). However it does not works. I have perform some test. The following shebang line works:
#!/usr/bin/jjs -scripting
#!/usr/bin/jjs -fv (returns nashorn full version 1.8.0_31-b13)
while the following not:
#!/usr/bin/jjs -cp ./some/lib/lib.jar will return the following error message: "-cp ./some/lib/lib.jar" is not a recognized option.
#!/usr/bin/jjs -scripting -fv will return the error message: "-scripting -fv" is not a recognized option. Use "-h" or "-help" to see a list of all supported options"
All options are theoretically valid. The classpath option should also works as seen on http://www.adam-bien.com/roller/abien/entry/setting_the_classpath_for_nashorn .
More info about nashorn and Shebang: http://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/shell.html#CHDEGHJJ
You ran into an issue which doesn’t have anything to do with Nashorn nor Java. According to this answer the command line argument handling with shebang never was clearly specified and it seems to be a common behavior to treat everything encountered after the first white-space as one single argument.
So one solution would be to write a shell script containing an invocation of jjs with the actual arguments and use that shell script as the interpreter in the shebang line of your Nashorn script.
You can use -Dnashorn.args=-cp in a shebang script. See also https://bugs.openjdk.java.net/browse/JDK-8072138
As it appears you want a way to automatically add JARs to your classpath, I'll highlight a little wrapper I've written which allows you to define Maven co-ordinate dependencies (including transitives) to be added to the classpath of your script, so you can write a script using the "# dep" lines:
#!/usr/bin/env jjs-with-deps
#
# The line below is parsed by the jjs-with-deps script to build a new
# classloader in which the script is really executed, including logback
# and its transitive dependencies.
#
# dep:ch.qos.logback:logback-classic:1.1.2
var log = org.slf4j.LoggerFactory.getLogger("com.example.app.Logger");
log.info("Hello World!");
It does require Maven to be installed somewhere on your PATH, and it does slightly increase startup time (but then again, you're already starting up a JVM ;). First invocation of a given script will be much slower while any dependencies are downloaded to the local M2 repository.
Link is https://github.com/stevestorey/jjs-with-deps
I am using Jenkins CI to run java from execute shell build step :
java -jar -Dadmin.hostname=$hostname -Dschema_name=$schema myapp.jar
I have noticed that although both parameters hostname and schema are defined well and has non empty values on runtime, Jenkins parses it the following way:
java -jar -Dadmin.hostname= -Dschema_name=MYDB myapp.jar
meaning admin.hostname value doesn't get parsed.
I have tracked the problem and noticed that when i dont use . in the parameter name everything get parsed okay.
I am wondering if this is a limitation of Java command line or a bug in Jenkins perhaps. (I am using RHEL64)
Thanks
EDIT:
I think this is bug with Parametrized build jenkins plugin as using . in other builds works fine.
Perhaps the problem is that environment variables on UNIX are case sensitive
$ echo $hostname
$ echo $HOSTNAME
myhostname
There shouldn't be any problem using . because may built in properties uses them.
BTW: Just because a property is set to blank doesn't mean its not set.
System.out.println("not.set=" + System.getProperty("not.set"));
System.out.println("admin.hostname='" + System.getProperty("admin.hostname")+"'");
when run with -Dadmin.hostname= prints
not.set=null
admin.hostname=''
I'm trying to compile a Java project under Cygwin using a native Win32 Java.
The Java binaries are correctly found under /cygdrive/c/jdk/bin on my machine.
The following command works fine:
javac -d . ./gnu/kawa/util/PreProcess.java
The PreProcess.class file is generated in ./gnu/kawa/util/. Trying to invoke Java on this fails however:
CLASSPATH=.:$CLASSPATH java gnu.kawa.util.PreProcess \
%java6 +use:com.sun.net.httpserver +enable:XML \
`sed -e 's|\([^ ]*\)|./\1|' < ./patch-source-list`
Error: Could not find or load main class gnu.kawa.util.PreProcess
...
This command was invoked by make, that's where the $CLASSPATH variable is set dynamically. patch-source-list is just a list of class names. The : in the classpath looks suspicious, but I'm not sure how to test ; while not annoying sh.
My only other suspicion is that the native Java is trying gnu\kawa\util\PreProcess, but I think cygwin can transparently handle that.
Any ideas? Thanks for your time.
Another option would be to build your path using the ':' and then fix the results using cygpath. This is probably overkill in your specific situation, but in a general case where you may have references to multiple directories, some of which may be referenced as absolute rather than relative paths, or if you are working with cygwin symlinks, it is much more useful.
$ ln -s /cygdrive/c/development/libraries/ ../libs
$ cygpath -pw /cygdrive/c/development/:.:../libs
C:\development\;.;C:\development\libraries\
so then you'd build your CLASSPATH variable as before, and in the final stage run
CLASSPATH="`cygpath -pw "$CLASSPATH"`" java (whatever)
Remember, the JVM has no idea that you are using the cygwin bash shell.
Two things:
for the classpath locations, use the windows path names. Thus, no "/cygdrive/c/somepath", but "c:\somepath\" ("/" and "\" can be used interchangeably however)
use ';' instead of ':' in the classpath list
This should work:
export CLASSPATH="./gnu/kawa/util/PreProcess.class"
CLASSPATH=".;$CLASSPATH" java gnu.kawa.util.PreProcess
The : in the classpath looks suspicious, but I'm not sure how to test ; while not annoying sh.
You're exactly right: you need to use ; instead of :. As for how to use it — as Mat alludes to above, you need to "quote" the semicolon. Any of these will work:
CLASSPATH=.\;$CLASSPATH java Foo
CLASSPATH=.';'$CLASSPATH java Foo
CLASSPATH='.;'$CLASSPATH java Foo
CLASSPATH=".;$CLASSPATH" java Foo
You can use whichever one you like best. (The first uses a backslash, which quotes a single following character. The second and third use single-quotes, which quote a sequence of zero or more characters. The fourth uses double-quotes, which are like single-quotes except that they still allow the variable $CLASSPATH to be expanded. For that matter, you could also write something like
CLASSPATH=".;"$CLASSPATH java Foo
if you want. See the above link for lots more information about quoting in Bash.)
This question already has answers here:
How do I auto load a database jar in Groovy without using the -cp switch?
(5 answers)
Closed 6 years ago.
I have a groovy script that needs a library in a jar. How do I add that to the classpath? I want the script to be executable so I'm using #!/usr/bin/env groovy at the top of my script.
Starting a groovy script with #!/usr/bin/env groovy has a very important limitation - No additional arguments can be added. No classpath can be configured, no running groovy with defines or in debug. This is not a groovy issue, but a limitation in how the shebang (#!) works - all additional arguments are treated as single argument so #!/usr/bin/env groovy -d is telling /usr/bin/env to run the command groovy -d rathen then groovy with an argument of d.
There is a workaround for the issue, which involves bootstrapping groovy with bash in the groovy script.
#!/bin/bash
//usr/bin/env groovy -cp extra.jar:spring.jar:etc.jar -d -Dlog4j.configuration=file:/etc/myapp/log4j.xml "$0" $#; exit $?
import org.springframework.class.from.jar
//other groovy code
println 'Hello'
All the magic happens in the first two lines. The first line tells us that this is a bash script. bash starts running and sees the first line. In bash # is for comments and // is collapsed to / which is the root directory. So bash will run /usr/bin/env groovy -cp extra.jar:spring.jar:etc.jar -d -Dlog4j.configuration=file:/etc/myapp/log4j.xml "$0" $# which starts groovy with all our desired arguments. The "$0" is the path to our script, and $# are the arguments. Now groovy runs and it ignores the first two lines and sees our groovy script and then exits back to bash. bash then exits (exit $?1) with status code from groovy.
If you really have to you can also load a JAR at runtime with:
this.getClass().classLoader.rootLoader.addURL(new File("file.jar").toURL())
My Favorite way to do this is with Groovy Grapes. These access the Maven Central Repository, download the referenced jar, and then put it on the classpath. Then you can use the library like any other library. The syntax is really simple:
#Grab(group='com.google.collections', module='google-collections', version='1.0')
You can read more details here. One major advantage here is that you don't need to distribute your dependencies when you distribute your script. The only drawback to this method is that the Jar has to be in the Maven repository.
You can add the jars to $HOME/.groovy/lib
You can also try out Groovy Grape. It lets you use annotations to modify the classpath. Its experimental right now, but pretty cool. See docs.groovy-lang.org/.../grape
The same as you would in Java.
This is an example of running a MySQL status monitoring script. mysql.jar contains the MySQL connector that I call from script status.groovy.
groovy -cp mysql.jar status.groovy ct1
Below is a combination of Patrick's solution, Maarteen Boekhold's solution, and foozbar's comment that works with both Linux and Cygwin:
#!/bin/bash
// 2>/dev/null; SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
// 2>/dev/null; OPTS="-cp $SCRIPT_DIR/lib/extra.jar:$SCRIPT_DIR/lib/spring.jar"
// 2>/dev/null; OPTS="$OPTS -d"
// 2>/dev/null; OPTS="$OPTS -Dlog4j.configuration=file:/etc/myapp/log4j.xml"
// 2>/dev/null; exec groovy $OPTS "$0" "$#"; exit $?
import org.springframework.class.from.jar
//other groovy code
println 'Hello'
How it works:
// is a valid groovy comment, so all of the bash commands are ignored by Groovy.
// will return an error, but the error output is redirected to /dev/null and is therefore not displayed.
bash executes commands following a semicolon even though the previous command failed.
exec replaces the current program in the current process without forking a new process. Thus, groovy runs within the original script process (ps shows the process as the script rather than the groovy executable)
The exit $? statement following exec groovy prevents bash from trying to interpret the rest of the script as a bash script, and also preserves the return code from the groovy script.
The above bash trick is more convenient in some cases than the RootLoader trick because you can use regular import statements within the script. Using the RootLoader trick forces you to load all of the classes using reflection. This is fine in some situations (such as when you need to load a JDBC driver), but inconvenient in others.
If you know your script will never be executed on Cygwin, then using Patrick's or Maarteen's solution will likely result in slightly better performance because they avoid the overhead of generating and throwing away an error.
Adding to #Patrick his answer, which helped me a lot, I recently discovered another trick.
If you add lots of jars to the classpath all on one line, things can become quite unreadable. But you can do the following!
#!/bin/bash
//bin/true && OPTS="-cp blah.jar -Dmyopt=value"
//bin/true && OPTS="$OPTS -Dmoreopts=value2"
//usr/bin/env groovy $OPTS "$0" $#; exit $?
println "inside my groovy script"
Let your imagination run wild on how complex a command line you can break down this way into manageable pieces
Maarten
If you want to use it right away before the import declarations it is possible like this :) :
// printEmployees.groovy
this.class.classLoader.rootLoader.addURL(
new URL("file:///C:/app/Dustin/product/11.1.0/db_1/jdbc/lib/ojdbc6.jar"))
import groovy.sql.Sql
sql = Sql.newInstance("jdbc:oracle:thin:#localhost:1521:orcl", "hr", "hr",
"oracle.jdbc.pool.OracleDataSource")
sql.eachRow("SELECT employee_id, last_name, first_name FROM employees")
{
println "The employee's name is ${it.first_name} ${it.last_name}."
}
Taken from this javaworld.com article.