Java CLI!

Problem:

When writing devops scripts I lose a lot of time when writing the logic in bash. Bash has a lot of powerful commands with functions. However, the orchestration of all these commands, returns checks, functions, debug, … is extremely hard using pure shell script.

 

Solution: JDK 11!

Since JDK 11 It is possible to run Java code without any compilation. That opens the possibility to use java files as CLI scripts. It is a similar solution to using javascript with nashorn in order to use the full power of java.

So my proposal is to orchestrate the script using java while executing bash commands.

This approach has 2 issues to solve.

  1. Be able to easily pass run parameters: CREST from Tomitribe!
  2. Execute shell commands and get the output.

Crest, as defined by tomitribe is a “Command-line API styled after JAX-RS”. For example, the declaration of a “ls” command would look like:

 @Command("ls")
  public void ls(@Option("a") boolean all, @Option("h") boolean human, @Option("l") boolean list, URI path) {
   //implementation
  }

Crest has a lot of options, so I suggest you take a look at his page.

Now, we are able to create a java command easily. The implementation could be java based or we could want to call some other bash commands.
That can be done using plain java as follows:

public CompletableFuture<Process> executeCommand(String command, Consumer<String> stdOutConsumer, Consumer<String> errConsumer) {

    Process p;

    try {
      p = Runtime.getRuntime().exec(command);
      run(p::getInputStream, stdOutConsumer);
      run(p::getErrorStream, errConsumer);
      return p.onExit();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

private void run(Supplier<InputStream> streamSupplier, Consumer<String> streamConsumer) {
    try (InputStream stream = streamSupplier.get()) {
      StringBuilder currentLine = new StringBuilder();
      int nextChar;
      while ((nextChar = stream.read()) != -1) {
        if (nextChar == '\n') {
          streamConsumer.accept(currentLine.toString());
          currentLine.setLength(0);
          continue;
        }
        currentLine.append((char) nextChar);
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

Here, I am passing the standard output text to the Consumers<> line per line.

To run everything together without the need to compile you must add the CREST dependencies into the classpath:


java -cp /path/to/tomitribe-crest-api-0.10.jar:/path/to/tomitribe-crest-0.10.jar:/path/to/tomitribe-util-1.0.0.jar \

/path/to/blogs-posts-code/cli/src/main/java/tech/lacambra/blog/cli/Cli.java ls -la .

One useful use case is small DB backups scripts. I use java to check, create and delete folders and create propers logs while using native mysqldump command to execute the DB backup

you can get the full code on github

Leave a Reply

Your email address will not be published.