search:

jbang

28 Aug 2021

jbang을 이용하면 Java 언어를 마치 shell script인 것처럼 사용할 수 있다. jshell을 이용해도 비슷한 걸 할 수 있지만 script처럼 사용하기에는 jbang이 더 좋아보인다.

설치하기

mac의 경우 brew를 이용하면 쉽게 설치할 수 있다. 그 외 환경은 install 문서를 참고하자.

$ brew install jbangdev/tap/jbang
Updating Homebrew...
==> Auto-updated Homebrew!
Updated Homebrew from 6da8630f4 to 7e7bdca67.
Updated 2 taps (homebrew/core and homebrew/cask).
==> New Formulae
vue-cli
==> Updated Formulae
Updated 75 formulae.
==> New Casks
sigmaos
==> Updated Casks
Updated 21 casks.

==> Installing jbang from jbangdev/tap
==> Downloading https://github.com/jbangdev/jbang/releases/download/v0.78.0/jbang-0.
Already downloaded: /Users/user/Library/Caches/Homebrew/downloads/9c213ca1805dffaf132ad48ff2475626ea565798f8c427cf44275b10f930d557--jbang-0.78.0.zip
🍺  /usr/local/Cellar/jbang/0.78.0: 7 files, 5.9MB, built in 6 seconds

Hello, Jbang

첫 번째 script를 만들어보자.

아래의 명령을 복붙하여 hello.java를 만들자.

$ cat <<EOM > hello.java
class hello {
  public static void main(String[] args) {
    System.out.println("Hello, Jbang");
  }
}
EOM

실행 방법 및 결과는 다음과 같다.

$ jbang hello.java
Downloading JBang...
Installing JBang...
[jbang] Building jar...
Hello, Jbang

“Downloading”과 “Installing”은 jbang을 최초 실행 시에만 출력된다. "Building"은 소스 코드에 변경이 있을 때마다 실행된다.

마지막 line을 보면 알겠지만 "Hello, Jbang" 잘 출력되었다.

JVM 기반이므로 실행이 약간 굼뜬 것은 어쩔 수 없다. 그리고 javac 후에 java로 실행하는 것보다도 더 느리다.

본인 환경에서는 위의 간단한 실행을 하는데도 1.4초가 소요된다.

$ time jbang hello.java
Hello, Jbang

real    0m1.426s
user    0m1.495s
sys    0m0.240s

실행 파일 만들기

매번 실행할 때마다 jbang hello.java 입력하기 번거로울 것이다. 일반 스크립트처럼 ./hello.java만 입력해도 실행할 수 있도록 만들어보자.

hello.java 제일 위 줄을 다음과 같이 만들면 된다.

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

class hello {
  public static void main(String[] args) {
    System.out.println("Hello, Jbang");
  }
}

실행 방법 및 결과는 다음과 같다.

$ ./hello.java
Hello, Jbang

bash에 ///usr/bin/env jbang "$0" "$@" ; exit $? 와 같은 표현이 있는지 모르고 있었다.

어떤 의미인지 궁금하여 검색해보니 Stackexchange에 관련된 글이 있었다.

shell에서 첫 line이 //로 시작하는 하는 경우 그 뒤에 나오는 문자들을 실행하고, 이후 내용은 무시하라는 것을 의미했다. (호환성을 위해서 /// 처럼 slash 3개를 사용하라고 하는데 정확한 의미는 모르겠다)

따라서 ./hello.java라고 실행을 하는 경우 실제 실행되는 명령어는 다음과 같다.

$ /usr/bin/env jbang ./hello.java; exit $?

참고로 $0는 쉘에 입력한 명령을 의미하고 $@는 argument 전체를 의미하고 $?는 앞서 실행한 명령의 exit code를 의미한다.

maven 의존성 추가하기

jshell 대비 jbang의 강력한 기능이다. maven 의존성을 쉽게 추가할 수 있다.

아래의 프로그램은 j-text-utils를 이용한 cli 프로그램이다.

j-text-utils 관련된 코드는 이 문서에 있는 코드를 그대로 사용했다.

다음과 같이 print-peole.java라는 파일을 만들자

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS com.massisframework:j-text-utils:0.3.4

import dnl.utils.text.table.TextTable;

class Printer {
  public static void main(String[] args) {
    String[] columnNames = {
        "First Name",
        "Last Name",
        "Sport",
        "# of Years",
        "Vegetarian"
    };

    Object[][] data = {
        {"Kathy", "Smith", "Snowboarding", new Integer(5), new Boolean(false)},
        {"John", "Doe", "Rowing", new Integer(3), new Boolean(true)},
        {"Sue", "Black", "Knitting", new Integer(2), new Boolean(false)},
        {"Jane", "White", "Speed reading", new Integer(20), new Boolean(true)},
        {"Joe", "Brown", "Pool", new Integer(10), new Boolean(false)}
    };

    TextTable tt = new TextTable(columnNames, data);

    // this adds the numbering on the left
    tt.setAddRowNumbering(true);

    // sort by the first column
    tt.setSort(0);
    tt.printTable();
  }
}

두 번째 line에 있는 //DEPS com.massisframework:j-text-utils:0.3.4가 의존성을 추가한 것이다.

실행 결과는 다음과 같다.

$ ./print-person.java
[jbang] Resolving dependencies...
[jbang]     Resolving com.massisframework:j-text-utils:0.3.4...Done
[jbang] Dependencies resolved
[jbang] Building jar...

   ______________________________________________________________
   | First Name| Last Name| Sport        | # of Years| Vegetarian|
   |=============================================================|
1. | Jane      | White    | Speed reading| 20        | true      |
2. | Joe       | Brown    | Pool         | 10        | false     |
3. | John      | Doe      | Rowing       | 3         | true      |
4. | Kathy     | Smith    | Snowboarding | 5         | false     |
5. | Sue       | Black    | Knitting     | 2         | false     |

"Resolving dependencies..."가 출력되면서 의존성 패키지를 다운로드하는 걸 볼 수 있다.

template

template 기반으로 java file을 생성할 수도 있다.

이에 관한 도움말은 templates문서에서 볼 수 있다.

예를 들어 cli 프로그램을 개발한다고 할 때 다음의 명령을 입력하면 기본 구조를 만들어준다.

template 명령 실행

$ jbang init --template=cli helloworld.java

helloworld.java 내용

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.5.0

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

import java.util.concurrent.Callable;

@Command(name = "helloworld", mixinStandardHelpOptions = true, version = "helloworld 0.1",
        description = "helloworld made with jbang")
class helloworld implements Callable<Integer> {

    @Parameters(index = "0", description = "The greeting to print", defaultValue = "World!")
    private String greeting;

    public static void main(String... args) {
        int exitCode = new CommandLine(new helloworld()).execute(args);
        System.exit(exitCode);
    }

    @Override
    public Integer call() throws Exception { // your business logic goes here...
        System.out.println("Hello " + greeting);
        return 0;
    }
}

사용 가능한 template은 다음과 같이 조회할 수 있다.

$ jbang template list
agent = Agent template
cli = CLI template
hello = Basic Hello World template
hello.kt = Basic kotlin Hello World template
qcli = Quarkus CLI template
qmetrics = Quarkus Metrics template
qrest = Quarkus REST template

아직 개수는 많지 않다.

또한 자기 자신의 template도 등록할 수 있다. 본인은 테스트해보지 않았다.