Skip to content
Use Java for shell scripting
Java Shell Other
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.github/workflows [minor] fix nuget publish Jan 22, 2020
examples fix nuget Jan 22, 2020
gradle/wrapper add semver Dec 25, 2019
misc fix varius introspections found by idea Jan 17, 2020
src
.gitignore fix: ensure error code on testsuite fail Jan 2, 2020
LICENSE Create LICENSE Dec 26, 2019
build.gradle [patch] enable repeatable builds Jan 18, 2020
gradlew add gradle wrapper + github actions workflow Dec 24, 2019
gradlew.bat add gradle wrapper + github actions workflow Dec 24, 2019
readme.adoc [minor] jbang --init=<template> support Jan 22, 2020
settings.gradle formatting, fix merge of checksum fix, misc Dec 27, 2019

readme.adoc

j’bang - Having fun with Java scripting

Release Build Status

Intro

Want to learn or explore Java instantly without setup ?

Do you like Java but uses python, groovy, kotlin or similar languages for your scripts ?

Ever tried out Java 10+ support for running .java files directly in your shell but felt it was a bit too cumbersome ?

Then try jbang which lets you do this:

$ jbang --init=cli hello.java
$ jbang hello.java Max!
[jbang] Resolving dependencies...
[jbang]     Resolving info.picocli:picocli:4.1.4...Done
[jbang] Dependencies resolved
[jbang] Building jar...
Hello Max!
$ jbang hello.java -h
Usage: hello [-hV] <greeting>
hello made with jbang
      <greeting>   The greeting to print
  -h, --help       Show this help message and exit.
  -V, --version    Print version information and exit.

Instant cli app generated built using java and picocli as a dependency that was fetched as needed for the compilation and execution.

Features

  • .java Scripting for Java 8 and upwards

  • .jsh via JShell from Java 9 and upwards

  • Works on Windows, OSX and Linux

  • Dependency declarations using //DEPS <gav> for automatic dependency resolution

  • Control compile and runtime options with //JAVAC_OPTIONS <flags> and //JAVA_OPTIONS <flags>

  • Compiled jar and Dependency resolution caching

  • Launch with debug enabled for instant debugging from your favorite IDE

  • Init templates to get started easily (jbang --init=cli hello.java)

  • Generate gradle and IDE config with dependencies for easy editing in your favorite IDE (jbang --edit myfile.java)

  • Installable with HomeBrew or SDKman on OSX and Linux.

  • (WAITING FOR APPROVAL) Installable with Chocolatey on Windows

  • (PLANNED) Lookup dependencies with a short-hand name, i.e. // DEPS log4j:1.2+,picocli for quick getting started.

To use it simply install jbang and run jbang yourscript.java

Requirements

Java 8+

Tested and verified to use on OSX, Linux and Windows.

Installation

To use jbang you as a minimum need to have Java 8 available.

To install both java and jbang we recommend sdkman

curl -s "https://get.sdkman.io" | bash # (1)
source ~/.bash_profile # (2)

sdk install java # (3)

Once Java is installed and ready, you install jbang with

sdk install jbang

To test your installation run:

jbang --help

This should print out usage information.

To update run:

sdk update jbang

Homebrew

On OSX you can install jbang with Homebrew using maxandersen/tap.

brew install maxandersen/tap/jbang

To upgrade to latest version:

brew upgrade maxandersen/tap/jbang

Manual and Windows (for now) install

Unzip the latest binary release, put the jbang-<version>/bin folder in to your $PATH and you are set.

Script input

A script is just a single .java file with a classic static main method or a .jsh file which will be passed to jshell.

Below is an (almost) minimal example you can save in helloworld.java:

//usr/bin/env jbang "$0" "$@" ; exit $? // (1)

class helloworld { // (2)

    public static void main(String[] args) {
        if(args.length==0) {
            System.out.println("Hello World!");
        } else {
            System.out.println("Hello " + args[0]);
        }
    }
}
  1. By using this // style instead of shebang #! you trick bash, zsh etc. to run this as a script while still being valid java code.

  2. A classname, can be anything when using jbang but to be valid java for most IDE’s you’ll want to name it the same as the source file.

Now to run this you can call it via jbang:

jbang helloworld.java

or mark it executable and just run it directly:

chmod +x helloworld.java
./helloworld jbang!

You can use http(s):/ and file:/ url’s for input too:

jbang https://raw.githubusercontent.com/maxandersen/jbang/master/examples/helloworld.java

Using .jsh for jshell

There are experimental support to run .jsh via jshell. The advantage of jshell is that you do not need to have a class or static main method.

Classic jshell does not support passing in arguments, jbang does.

In the case of .jsh files jbang injects a startup script that declares a String[] args which will contain any passed in arguments.

Example:

System.out.println("Hello " + (args.length>0?args[0]:"World")); // (1)
/exit // (2)
  1. Line where args are accessible without previous declaration.

  2. /exit is so the jshell app will exit. If you remove it jbang will launch into inter-active mode.

Getting started

To get started you can run jbang --init helloworld.java and a simple java class with a static main is generated.

Declare dependencies

If you want to write real scripts you will want to use some java libraries. To specify dependencies you use gradle-style locators. Below are examples for log4j.

Using //DEPS

//usr/bin/env jbang "$0" "$@" ; exit $?

//DEPS log4j:log4j:1.2.17 (1)

import static java.lang.System.out;

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;

import java.util.Arrays;

class classpath_example {

	static final Logger logger = Logger.getLogger(classpath_example.class);

	public static void main(String[] args) {
		BasicConfigurator.configure(); // (2)
		logger.info("Welcome to jbang");

		Arrays.asList(args).forEach(arg -> logger.warn("arg: " + arg));
		logger.info("Hello from Java!");
	}
}
  1. //DEPS has to be start of line and can be one or more space separated dependencies.

  2. Minimal logging setup - required by log4j.

Now when you run this the first time with no existing dependencies installed you should get an output like this:

$ ./classpath_example.java
[jbang] Resolving dependencies...
[jbang]     Resolving log4j:log4j:1.2.17...Done
[jbang] Dependencies resolved
0 [main] INFO classpath_example  - Welcome to jbang
1 [main] INFO classpath_example  - Hello from Java!

Using @Grab

There is also support for using Groovy lang style @Grab syntax.

//usr/bin/env jbang "$0" "$@" ; exit $?

import static java.lang.System.out;

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;

import java.util.Arrays;

import groovy.lang.Grab; // (1)
import groovy.lang.Grapes;

@Grapes({ // (2)
		@Grab(group="org.codehaus.groovy", module="groovy", version="2.5.8"), // (3)
		@Grab(module = "log4j", group = "log4j", version = "1.2.17")
})
class classpath_example {

	static final Logger logger = Logger.getLogger(classpath_example.class);

	public static void main(String[] args) {
		BasicConfigurator.configure();
		Arrays.asList(args).forEach(arg -> out.println(arg));
	}
}
  1. Import needed to make the compiler be okey with @Grab annotation.

  2. In Groovy you normally put @Grab on import statements. That is not allowed in Java thus when having multiple imports you need to put them in a @Grapes annotation first.

  3. jbang will grab any @Grab annotation and assume it is declaring dependencies.

Editing

You can edit your script in your IDE by using jbang --edit helloworld.java. This will generate a project in a temporary location with symbolic links to your script and output the generated folder name. The easiest way to use that is to use it in a call to your IDE:

code `jbang --edit helloworld.java`

If you add additional dependencies to your file just re-run the edit command and the relevant files will be regenerated with the updated dependencies.

Note
On Windows you might need elevated priviliges to create symbolic links. If you don’t have permissions then the --edit option will result in an error. To use it enable symbolic links for your user or run your shell/terminal as administrator to have this feature working.

IDE support

The --edit feature generates a build.gradle to use with IDE’s that understands it directly + IDE specific config files to make it easy to run and debug the single file based scripts.

Currently launchers and project files are generated for Eclipse and vscode. Intellij just reads build.gradle for now thus to run/debug you will need to manually set it up.

Debugging

When running .java scripts with jbang you can pass the --debug-flag and the script will enable debug, suspend the execution and wait until you connect a debugger to port 4004.

jbang --debug helloworld.java
Listening for transport dt_socket at address: 4004

You can change the debug port by passing in a number to the debug argument, i.e. --debug=4321.

Note
Be sure to put a breakpoint in your IDE/debugger before you connect to make the debugger actually stop when you need it.

java and javac Options

If you want to tweak memory settings or enable preview features you can setup the necessary options using //JAVA_OPTS and //COMPILER_OPTS as in the following example using Java 14 experimental record feature:

//usr/bin/env jbang "$0" "$@" ; exit $?
//JAVAC_OPTIONS --enable-preview -source 14 (1)
//JAVA_OPTIONS --enable-preview // (2)

import static java.lang.System.*;

public class records {

    record Point(int x, int y) {}

    public static void main(String[] args) {
        var p = new Point(2,4);
        out.println(p);
    }
}

Since Java 9 JDK_JAVA_OPTIONS and JDK_JAVAC_OPTIONS are also picked up by the Java runtime and compiler automatically.

For Java 8 and if you want to set explicilty only for jbang you can also add flags by setting JBANG_JAVA_OPTIONS and JBANG_JAVAC_OPTIONS respectively.

JAVA_HOME vs PATH

If JAVA_HOME environment variable is set that will be used over what is in the path.

Allows you to expliclity control what JDK you are using for jbang, i.e.

JAVA_HOME=~/sdkman/candidates/java/14.ea.302-open/bin/java jbang examples/records.java

This will use Java 14 from sdkman no matter what your PATH or JAVA_HOME contains in the shell environment.

Bash/Zsh auto-completion

If you are using bash or zsh in your terminal you can get auto-completion by running the following:

source <(jbang --completion)

Caching

In previous versions of jbang Java 10+ direct launch of .java was used, but since v0.6 jbang works with Java 8 and thus it needs to do a separate compile step. Besides now working with Java 8 it also allow us to cache the compile step and thus launch faster on consecutive runs.

The caching goes to ~/.jbang by default, you can run jbang --clear-cache to remove all cache data from this folder.

FAQ

  1. Why the name j’bang?

    I was reading up on how to use the new shebang (#!) feature support in Java 10 and came up with the idea of port kscript to Java and needed a name. From there came j’bang which is a "bad" spelling of how shebang is pronounced in french.

  2. Why use of gradle resource locators rather than ?

    kscript used it and its nice as it is a one-liner and easily parsable.

  3. Why would I use Java to write scripts ? Java sucks for that…​ Use gradle, kotlin, scala, etc. instead!

    Well, does it really suck ? With Java 8 streams, static imports and greatly improved standard java libraries it is very close to how kscript and grape looks like. With the following advantages:

    • works with plain Java without installing additional compiler/build tools

    • all IDE’s support editing .java files very well, content assist etc.

    • great debugging

      And to be honest I built jbang just to see if I could and get my Java skills refreshed for the newer features in the language. Use it at your own risk :)

  4. Why not use normal shebang(\#!) in the header ?

    You can use normal shebang (\#!/usr/bin/env jbang) and Java 10+ will actually work with it from the command line. Not recommended though as many tools and especially IDE’s will start complaining about syntax errors as they don’t ignore the first line in this case.

    By using the // form it is treated as both a bash/shell file AND a valid java file and thus works everywhere a java file will work.

    Its worth noting that Go uses a similar approach which is also where I learned it from.

Thanks

jbang was heavily inspired by how kscript by Holger Brand works.

You can’t perform that action at this time.