Search code examples
javabashcygwinglob

How to pass * to a java program without wildcard expansion?


Consider following simple Java program:

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        System.out.println(Arrays.asList(args));
    }
}

Glob expansion is typically done by shell, not by JVM. Example, in Cygwin:

$ echo *
Main.class

Here Cygwin expanded * to Main.class (file in the directory)

This behavior can be turned off:

$ set -f
$ echo *
*

Now * has not been expanded.

However when passing * to the Java program the wildcard somehow gets expanded:

$ set -f
$ java Main *
[Main.class]

Quoting or escaping doesn't help either:

$ java Main '*'
[Main.class]
$ java Main \*
[Main.class]

Who's the culprit here, Shell or Java? It seems JVM, because a python program works fine:

Python file a.py:

import sys
print  sys.argv[1:]

Run python program with wildcard:

$ set -f; python a.py *
['*']

No expansion.

Why is JVM expanding the wildcard? That is supposed to be a function of Shell, not JVM. How can this be turned off?


Solution

  • On Unix, glob expansion is handled by the shell, and not by the program.

    On Windows, glob expansion is handled by the program, and not by the shell.

    This means that when you run a Windows program from a Unix shell, you risk having two passes of glob expansion.

    Here's the Windows OpenJDK source code responsible for this:

    /*
     * At this point we have the arguments to the application, and we need to
     * check with original stdargs in order to compare which of these truly
     * needs expansion. cmdtoargs will specify this if it finds a bare
     * (unquoted) argument containing a glob character(s) ie. * or ?
     */
    jobjectArray
    CreateApplicationArgs(JNIEnv *env, char **strv, int argc)
    {
       // (***snip***)
        NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                                                    "expandArgs",
                                                    "([Ljava/lang/String;)[Ljava/lang/String;"));
    
        // expand the arguments that require expansion, the java method will strip
        // out the indicator character.
        NULL_CHECK0(inArray = NewPlatformStringArray(env, nargv, argc));
        outArray = (*env)->CallStaticObjectMethod(env, cls, mid, inArray);
    

    And here's is the expandArgs that it calls into:

    static String[] expandArgs(List<StdArg> argList) {
        ArrayList<String> out = new ArrayList<>();
          // (***snip***)
                try (DirectoryStream<Path> dstream =
                        Files.newDirectoryStream(parent.toPath(), glob)) {
                    int entries = 0;
                    for (Path p : dstream) {
                        out.add(p.normalize().toString());
                        entries++;
                    }
    

    I don't know if this behavior can be disabled. Consider passing data in files, or use Windows Subsystem for Linux which simulates a Unix environment more accurately than CygWin.