How to execute a java script with jshell?
Asked Answered
D

5

32

Given that Java 9 is upon us and we can finally have a java REPL with jshell I was hoping there was a way to add a shebang to a script and have jshell interpret it.

I tried creating test.jsh:

#!/usr/bin/env jshell -s
System.out.println("Hello World")
/exit

However that gives:

⚡ ./test.jsh
|  Error:
|  illegal character: '#'
|  #!/usr/bin/env jshell -s
|  ^
|  Error:
|  illegal start of expression
|  #!/usr/bin/env jshell -s
|    ^
Hello World

It turns out there is an enhancement request for this in OpenJDK https://bugs.openjdk.java.net/browse/JDK-8167440.

Is there any other way to do this?

Deckhouse answered 5/7, 2017 at 3:36 Comment(0)
B
42

Use

//usr/bin/env jshell --show-version --execution local "$0" "$@"; exit $?

as the first line of test.jsh. The test.jsh script could look like:

//usr/bin/env jshell --show-version "$0" "$@"; exit $?
System.out.println("Hello World")
/exit

The command line option --show-version is optional, of course, but gives immediate feedback that the tool is running.

The extra command line option --execution local prevents jshell to spawn another VM. This speeds up launch time and if an exception is thrown by your script code, the local VM will exit.

Consult the output of jshell --help and jshell --help-extra for more options.

Update

Also take a look at https://github.com/jbangdev/jbang Having fun with Java scripting, which offers a neat wrapper around running .java files from the command line.

Bereave answered 5/7, 2017 at 11:51 Comment(8)
How does this even work? I wonder how portable this is. Granted that #! isn't standard POSIX anyway but surely as far as magic numbers go it would be the most widely adopted.Deckhouse
At least bash supports the inital // line. See this thread for details mail.openjdk.java.net/pipermail/kulla-dev/2016-October/… ... with Brian Goetz stating that making a .jsh file executable _ is trying to turn jshell into something it wasn't designed for._Bereave
> How does this even work? See the description of the "hack" at section "Mimic the shebang via Bash" at golangcookbook.com/chapters/running/shebangBereave
Just got to try this with ZSH and it also works, although I had to use usr/bin/env jshell instead and quote the variable expansions. Much nicer output than my answer.Deckhouse
I added the ZSH example without testing it. Is it: //usr/bin/env jshell --show-version "$0" "$@"; exit $?Bereave
Yeah although that should be the same for BASH too? The changes are more likely because I don't have JAVA_HOME set and the quotes are just to handle spaces correctly.Deckhouse
Seems like a bit of a cop out by Brian Goetz. I think it should have been designed for that. Groovy and Scala both support this.Deckhouse
Let us continue this discussion in chat.Bereave
D
6

It turns out that with a bit of trickery there is a way, although I haven't fully managed to suppress the interpreted commands but pretty close to what I want.

Change test.jsh to:

#!/usr/bin/env sh
tail -n +4 "$0" | jshell -s "$@"
exit $?
System.out.println("Hello World")
/exit

Which gives us:

⚡ ./test.jsh
-> System.out.println("Hello World")
Hello World
-> /exit
Deckhouse answered 5/7, 2017 at 3:36 Comment(0)
O
4

The below works too; put it into a someScript.jsh file and run it with ./someScript.jsh. All arguments received by someScript.jsh will go to String[] args.

#!/home/gigi/.sdkman/candidates/java/current/bin/java --source 11

import java.util.Arrays;
import ro.go.adrhc.*; // example of using your classes, e.g. App below

public class X {
    public static void main(String[] args) {
        // do whatever you want here, e.g.:
        // System.out.println("Hello World");
        // or
        // use a class:
        // App.main(args);
        // e.g. from ro.go.adrhc package, by running:
        // CLASSPATH="/path-to-ro.go.adrhc-classes" ./someScript.jsh 
    }
}

The usage of the wrapping class, here X, is a mandatory trick for this to work. Use the Java version you have by changing /home/gigi/.sdkman/candidates/java/current/bin/java.
Inspired by https://blog.codefx.org/java/scripting-java-shebang/.

Obnoxious answered 30/4, 2020 at 7:37 Comment(0)
P
2

Inspired by steiny answer, I came up with a more generic solution

https://gist.github.com/ffissore/012d7e32a096fde5266f49038c93dcaf

In essence: jshell-wrapper will strip the first line of the script (which is supposed to be the shebang) and will add a /exit at the end of the script

Photomultiplier answered 17/11, 2017 at 13:9 Comment(5)
nice script but would work 2 of it started in parallel?Obnoxious
they would be 2 different processes, so why notPhotomultiplier
I'm thinking about the usage of TMP='mktemp' which seems to be a file in the end used by many threads each one possible with different jsh files.Obnoxious
mktemp creates a new file each time it's calledPhotomultiplier
you are right; now I notice it’s not a name but the execution of mktempObnoxious
L
0
#!/usr/bin/awk NR>1{print|"jshell -"}
System.out.println("Hello world!");
Lefler answered 18/4, 2024 at 9:39 Comment(1)
Although this code might answer the question, I recommend that you also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes.Eulau

© 2022 - 2025 — McMap. All rights reserved.