18

Question

I am willing to pass arguments to a jshell script. For instance, I would have liked something like this:

jshell myscript.jsh "some text"

and then to have the string "some text" available in some variable inside the script.

However, jshell only expects a list of files, therefore the answer is:

File 'some text' for 'jshell' is not found.

Is there any way to properly pass arguments to a jshell script?

Workaround so far

My only solution so far is to use an environment variable when calling the script:

ARG="some test" jshell myscript.jsh

And then I can access it in the script with:

System.getenv().get("ARG")
Naman
  • 23,555
  • 22
  • 173
  • 290
Gwendal
  • 461
  • 2
  • 11

3 Answers3

13

And what about option -R

> jshell -v -R-Da=b ./file.jsh

for script

{
  String value = System.getProperty("a");
  System.out.println("a="+value);
}
/exit

will give you

> jshell -v -R-Da=b ./file.jsh
a=b

Another way, would be following:

{
  class A {
    public void main(String args[])
    {
        for(String arg : args) {
          System.out.println(arg);
        }
    }
  }

  new A().main(System.getProperty("args").split(" "));
}

and execution

> jshell -R-Dargs="aaa bbb ccc" ./file_2.jsh

Update

Previous solution will fail with more complex args. E.g. 'This is my arg'.

But we can benefit from ant and it's CommandLine class

import org.apache.tools.ant.types.Commandline;
{
  class A {
    public void main(String args[])
    {
      for(String arg : args) {
        System.out.println(arg);
      }
    }
  }

  new A().main(Commandline.translateCommandline(System.getProperty("args")));
}

and then, we can call it like this:

jshell --class-path ./ant.jar -R-Dargs="aaa 'Some args with spaces' bbb ccc" ./file_2.jsh
aaa
Some args with spaces
bbb
ccc

Of course, ant.jar must be in the path that is passed via --class-path

Oo.oO
  • 9,723
  • 3
  • 19
  • 40
  • Interesting! I has tried to use `-R` but I had not understood its syntax correctly. This is still far from perfect, but avoids binding environment variables for nothing. Thanks! – Gwendal Oct 15 '17 at 19:38
  • -R passes arguments to Runtime Environment. So, what you see here i passing -Dkey=val to JVM. As a result you have key with value inside properties. – Oo.oO Oct 15 '17 at 19:51
  • I have a doubt though, why not [`-J` instead of `-R`](https://stackoverflow.com/questions/46399679/what-is-the-exact-meaning-purpose-of-the-j-and-r-flags-in-jshell)? – Naman Oct 15 '17 at 20:05
  • @nullpointer `-R' is for the execution engine and is the correct option as explained in the accepted answer to that question. (I also verified that it works with R and not with J) – Oleg Oct 15 '17 at 21:20
  • @Oleg I agree it doesn't work with `-J`, but the TLDR part of the answer specifies that the *Local execution (--execution="local") `-J` passes option to the only present JVM* while `-R` does nothing. Isn't the execution local now? – Naman Oct 15 '17 at 21:23
  • @nullpointer No, by default it's not local, 2 processes are created. As I said Oracle really screwed this up. – Oleg Oct 15 '17 at 21:24
  • @Oleg Could you better explain or add some link to proof about *Oracle really screwed this up*...not able to get what do you mean there? – Naman Oct 15 '17 at 21:27
  • @nullpointer Not much to explain, this is just my personal opinion. Not giving good option to pass arguments to the shell and starting 2 processes by default(causing confusion between `J` and `R`) to me means screwing it up. If you think that they did a good job then that's fine. – Oleg Oct 15 '17 at 21:31
  • @Oleg And could you detail *No, by default it's not local, 2 processes are created.* as well please. – Naman Oct 15 '17 at 21:39
  • @nullpointer It's in the question you linked to... https://stackoverflow.com/a/46408157/1398418 – Oleg Oct 15 '17 at 21:42
  • @Oleg I believe you are misunderstanding that. It never mentioned about *2 processes*, but *2 options left*. – Naman Oct 15 '17 at 21:58
  • @nullpointer `use **two**(why it's not bold inside backticks???) JVMs - one for JShell client (locally) and another for the execution engine` from the TL;DR `Remote execution (**default** case, execution over JDI)` I also verified it myself. By default 2 processes are created `JShellToolProvider` and `RemoteExecutionControl` when you pass the `--execution="local"` local option only `JShellToolProvider` is created. – Oleg Oct 15 '17 at 22:15
2

Oracle really screwed this up, there is no good way to do this. In addition to @mko's answer and if you use Linux(probably will work on Mac too) you can use process substitution.

jshell <(echo 'String arg="some text"') myscript.jsh

And then you can just use arg in myscript.jsh for example:

System.out.println(arg) // will print "some text"

You can simplify it with some bash function and probably write a batch file that will write to a temp file and do the same on windows.

Oleg
  • 5,726
  • 2
  • 20
  • 38
  • isn't this platform dependent even when it might work. Also not sure what your myscript.jsh contents are/ – Naman Oct 15 '17 at 21:18
  • @nullpointer `myscript.jsh` is from OP's question doesn't really matter what are the contents but if you're curious you can ask OP about it. Your first sentence is already addressed in the answer. – Oleg Oct 15 '17 at 21:22
  • What the solution suggested by you does is, edit the script altogether. instead of passing a value to a variable as mentioned in the question. – Naman Oct 15 '17 at 21:26
  • 1
    @nullpointer No it passes the value, `arg` can be used in `myscript.jsh` this is what OP wanted. It should be before it though, fixed, so thanks. – Oleg Oct 15 '17 at 21:29
1

It's completely beyond me how Oracle could ignore this. 8-() But anyway: if your system uses bash as shell, you can combine this approach replacing the shebang with the idea to (ab-)use system properties to transport the whole command line into a variable:

//usr/bin/env jshell --execution local "-J-Da=$*" "$0"; exit $?
String commandline = System.getProperty("a");
System.out.println(commandline);
/exit

This way, you can call the script on the commandline simply adding the arguments: thisscript.jsh arg1 arg2 would print arg1 arg2.

Please note that this joins all parameters into one String, separated by one space. You can split it again with commandline.split("\s"), but please be aware that this isn't exact: there is no difference between two parameters a b and one parameter "a b".

If you have a fixed number of arguments, you can also pass all of these into separate system properties with "-J-Darg1=$1" "-J-Darg2=$1" "-J-Darg3=$1" etc. Please observe that you have to use -R-D... if you are not using --execution local

Another variant is generating the script on the fly with bash's process substitution. You can use such a script also simply as thisscript.jsh arg1 arg2 also on Unix-like systems having a bash.

#!/usr/bin/env bash
jshell <(
cat <<EOF
System.out.println("$1");
System.out.println("$2");
/exit
EOF
)

This allows to access individual parameters, though it will break when there are double quotes or other special characters in a parameter. Expanding on that idea: here's a way to put all parameters into an Java String array, quoting some of those characters:

#!/usr/bin/env bash
set -- "${@//\\/\\\\}"
set -- "${@//\"/\\\"}"
set -- "${@/#/\"}"
set -- "${@/%/\",}"
jshell <(
cat <<EOF
String[] args = new String[]{$@};
System.out.println(Arrays.asList(args));
/exit
EOF
)

The set -- statements double backslashes, quote double quotes and prefix a " and append a ", to transform the arguments into a valid Java array.

Hans-Peter Störr
  • 22,852
  • 27
  • 96
  • 134