4

I have written a tiny Groovy script (just a few simple instructions) and named it a-b.groovy.
My colleague has trouble running it and said:

I believe '-' is illegal in Groovy scripts filenames

Question: What is the complete list of characters that are illegal in Groovy filenames?

(Note: Unlike Naming convention for groovy script files, my question is not about conventions)

SuperStormer
  • 4,997
  • 5
  • 25
  • 35
Nicolas Raoul
  • 58,567
  • 58
  • 222
  • 373

3 Answers3

6

The most accurate answer is to use the same standards for naming Groovy scripts as we use for naming Java classes. Groovy, similarly to Java supports Unicode characters encoding and you can most probably use any Unicode character as a script name (it doesn't mean you should do it).

Every Groovy script gets compiled to a Java class that extends groovy.lang.Script class. Its name is taken from a Groovy script file name. Take a look at this example:

someSimpleGroovyScript.groovy

println "test"

When we compile it with groovyc someSimpleGroovyScript.groovy we will see a class file someSimpleGroovyScript.class. Now if we take a look what does this class look like:

javap someSimpleGroovyScript

we will get something like this:

Compiled from "someSimpleGroovyScript.groovy"
public class someSimpleGroovyScript extends groovy.lang.Script {
  public static transient boolean __$stMC;
  public someSimpleGroovyScript();
  public someSimpleGroovyScript(groovy.lang.Binding);
  public static void main(java.lang.String...);
  public java.lang.Object run();
  protected groovy.lang.MetaClass $getStaticMetaClass();
}


Corner cases for Groovy script names

Even though Java class naming standards apply to Groovy script names, there are some corner cases that Groovy supports.

Using - character in the script name

You can name your script as a-b.groovy and run as a Groovy script:

groovy a-b.groovy

You can even compile it with groovyc to get following Java class:

Compiled from "a-b.groovy"
public class a-b extends groovy.lang.Script {
  public static transient boolean __$stMC;
  public a-b();
  public a-b(groovy.lang.Binding);
  public static void main(java.lang.String...);
  public java.lang.Object run();
  protected groovy.lang.MetaClass $getStaticMetaClass();
}

Even though this class name incorrect for Java compiler, you can still run it with Java (you need to add groovy-all to the classpath):

java -classpath ".:./groovy-all-2.4.12.jar" a-b

Using $ in the script name

In Groovy you can even call your script as 123$.groovy and you can simply run it as:

groovy 123\$.groovy

Groovy compiler however will replace $ with underscore when compiling this script to Java class:

javap 123_

Output:

Compiled from "123$.groovy"
public class 123_ extends groovy.lang.Script {
  public static transient boolean __$stMC;
  public 123_();
  public 123_(groovy.lang.Binding);
  public static void main(java.lang.String...);
  public java.lang.Object run();
  protected groovy.lang.MetaClass $getStaticMetaClass();
}

You can still run such compiled script with Java:

java -classpath ".:./groovy-all-2.4.12.jar" 123_

Using space in the script name

You can also use a space in your script name, e.g. this is script.groovy can be executed as:

groovy this\ is\ script.groovy

Groovy compiler however will replace every space with a underscore:

Compiled from "this is script.groovy"
public class this_is_script extends groovy.lang.Script {
  public static transient boolean __$stMC;
  public this_is_script();
  public this_is_script(groovy.lang.Binding);
  public static void main(java.lang.String...);
  public java.lang.Object run();
  protected groovy.lang.MetaClass $getStaticMetaClass();
}

You can still run such compiled script with Java:

java -classpath ".:./groovy-all-2.4.12.jar" this_is_script

Using class.groovy as a script name

It goes even further. You can create a script called class.groovy that will compile to a following Java class:

Compiled from "class.groovy"
public class class extends groovy.lang.Script {
  public static transient boolean __$stMC;
  public class();
  public class(groovy.lang.Binding);
  public static void main(java.lang.String...);
  public java.lang.Object run();
  protected groovy.lang.MetaClass $getStaticMetaClass();
}

javac compiler would never allow you compiling a class with such name, however java has no problems with running such bytecode:

java -classpath ".:./groovy-all-2.4.12.jar" class
Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
3

As the file name of the script is used to generate the corresponding class name, it would be restricted to valid java class names Valid characters in a Java class name

You can have almost any character, including most Unicode characters! The exact definition is in the Java Language Specification under section 3.8: Identifiers.

An identifier is an unlimited-length sequence of Java letters and Java digits, the first of which must be a Java letter. ...

Letters and digits may be drawn from the entire Unicode character set, ... This allows programmers to use identifiers in their programs that are written in their native languages.

An identifier cannot have the same spelling (Unicode character sequence) as a keyword (§3.9), boolean literal (§3.10.3), or the null literal (§3.10.7), or a compile-time error occurs.

Michael Rutherfurd
  • 13,815
  • 5
  • 29
  • 40
  • This link's highest-voted answer says "almost any character, including most Unicode characters", you can agree that "almost" is not an ideal answer to "*What is the complete list of characters that are illegal*" :-/ – Nicolas Raoul Jul 09 '18 at 07:38
  • I was mainly referring to the block in yellow copied from the Java Language Spec but either way the answer above is better – Michael Rutherfurd Jul 09 '18 at 09:10
  • Oh. That's one of the problems with posting links, it is difficult to know what is actually referred to. Would you mind copy/pasting the relevant parts, so that your answer remains valid even if that other question gets edited or answers change? Thanks a lot! – Nicolas Raoul Jul 09 '18 at 09:13
1

I encountered a weird issue with a script containing a dash in the filename and with a function having a Closure as parameter

Let's consider this exemple, test-script.groovy :

#!/usr/bin/env groovy

void foo(String msg, Closure clo)
{
    println msg
    clo()
}

foo 'bar', { -> println 'closure-bar'}

On my dev env with Groovy 2.4.16 it works like a charm, it prints :

bar
closure-bar

On the prod env with Groovy 2.4.5, I get this error :

Caught: java.lang.ClassFormatError: Illegal class name "test-script$foo" in class file test-script$foo
java.lang.ClassFormatError: Illegal class name "test-script$foo" in class file test-script$foo
        at test-script.run(test-script.groovy:9)

If I rename test-script.groovy to testScript.groovy, it works for both version 2.4.16 and 2.4.5

I tracked down the "bug fix" to Groovy 2.4.15. Prior to this version, it failed.

The changelog for this version does not indicate any relevant fix so maybe it has been enclosed in another bug fix.

Anyway, I've been stuck a few hours on this case so if it can help someone, good thing !

ToYonos
  • 16,469
  • 2
  • 54
  • 70