
Die meisten Projekte enthalten Aufgaben, die zu klein für eine eigene Software sind, aber häufig genug ausgeführt werden, um ein kleines Programm zu rechtfertigen. Üblicherweise werden Bash-Skripte gewählt, weil keine Installation erforderlich ist und die meisten absehbaren Probleme gelöst werden können.
Die Verwendung alternativer Programmiersprachen für diese Aufgaben war bisher zu umständlich. Java wäre ideal, da Programme auf vielen Systemen laufen und die mitgelieferten Bibliotheken umfangreiche Funktionalität bieten, um Probleme in wenigen Zeilen zu lösen. Allerdings stellten das Aufsetzen einer Entwicklungsumgebung und das Erstellen ausführbarer Programme Hürden dar. Was in wenigen Zeilen Bash gelöst werden konnte, wurde zu einem vollständigen Projekt.
Das jbang-Projekt ermöglicht es, Java anstelle von Bash-Skripten zu verwenden und dabei den Fokus auf Problemlösung und IDE-Komfort beizubehalten.
Lokale Installation
Um jbang zu nutzen, installiert man es lokal im Projektverzeichnis. Skripte aus dem Internet sollten mit Vorsicht heruntergeladen und erst nach Prüfung des Quellcodes ausgeführt werden.
Installation:
curl -Ls https://sh.jbang.dev | bash -s - wrapper install
Bestehende Installationen aktualisieren:
curl -Ls https://sh.jbang.dev | bash -s - wrapper install --force
Erste Schritte
Ein jbang-Skript erstellen:
./jbang init --template=cli fun.java
Die Ausgabe bestätigt die Initialisierung:
[jbang] File initialized. You can now run it with 'jbang fun.java' or edit it using 'jbang edit --open=[editor] fun.java'
In IntelliJ Idea öffnen:
./jbang edit --open=idea fun.java
Das Template enthält picocli für die Verarbeitung von Kommandozeilen-Argumenten:
///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 = "fun", mixinStandardHelpOptions = true, version = "fun 0.1",
description = "fun made with jbang")
class fun 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 fun()).execute(args);
System.exit(exitCode);
}
@Override
public Integer call() throws Exception { // your business logic goes here...
System.out.println("Hello " + greeting);
return 0;
}
}
Für lokale Installationen muss die erste Zeile angepasst werden:
///usr/bin/env ./jbang "$0" "$@" ; exit $?
Ausführen mit:
./fun.sh
Wo ist die aktuellste Datei?
Ein praktisches Beispiel: die neueste Datei in einem Verzeichnis finden:
./jbang init --template=cli newest.java
./jbang edit --open=idea newest.java
Die vollständige Implementierung:
///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.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.Optional;
import java.util.concurrent.Callable;
@Command(name = "newest", mixinStandardHelpOptions = true, version = "newest 0.1",
description = "find the newest file inside of a directory")
class newest implements Callable<Integer> {
@CommandLine.Spec
CommandLine.Model.CommandSpec spec;
private File directory;
@Parameters(index = "0", description = "directory")
public void setDirectory(File directory) {
checkArgument(directory.exists(),directory+" does not exist");
checkArgument(directory.isDirectory(),directory+" is not a directory");
this.directory=directory;
}
private void checkArgument(boolean condition, String errorMessage) {
if (!condition) throw new CommandLine.ParameterException(spec.commandLine(), errorMessage);
}
public static void main(String... args) {
int exitCode = new CommandLine(new newest()).execute(args);
System.exit(exitCode);
}
@Override
public Integer call() throws Exception {
NewestFileContainer newestFileContainer=new NewestFileContainer();
Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
newestFileContainer.inspect(file, attrs.lastModifiedTime());
return FileVisitResult.CONTINUE;
}
});
newestFileContainer.newestPath().ifPresent(it -> System.out.println("newest file "+it));
return 0;
}
private static class NewestFileContainer {
private PathWithModificationDate newest = null;
public void inspect(Path file, FileTime lastModifiedTime) {
if (newest==null) {
newest = new PathWithModificationDate(file, lastModifiedTime);
} else {
newest = PathWithModificationDate.newerInstance(newest,
new PathWithModificationDate(file, lastModifiedTime));
}
}
public Optional<Path> newestPath() {
return Optional.ofNullable(newest).map(it -> it.file);
}
}
private static class PathWithModificationDate {
final Path file;
final FileTime lastModifiedTime;
public PathWithModificationDate(Path file, FileTime lastModifiedTime) {
this.file = file;
this.lastModifiedTime = lastModifiedTime;
}
public static PathWithModificationDate newerInstance(PathWithModificationDate a, PathWithModificationDate b) {
return a.lastModifiedTime.compareTo(b.lastModifiedTime) > 0 ? a : b;
}
}
}
Ausführung ohne Argumente:
Missing required parameter: '<directory>'
Usage: newest [-hV] <directory>
find the newest file inside of a directory
<directory> directory
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
Mit einem Verzeichnis als Argument:
./newest.sh .
Fazit
jbang bietet umfangreiche Möglichkeiten. Code in Java zu schreiben mit starker IDE-Unterstützung reduziert Fehler im Vergleich zu Bash-Skripten. Kompilierung und Ausführung verursachen nur vernachlässigbaren Zeitaufwand. Die IDE-Unterstützung bietet erhebliche Vorteile.
Über den Ersatz von Bash-Skripten hinaus könnte gemeinsamer Code als Abhängigkeiten Duplikation über Skripte hinweg eliminieren. Unit-Tests werden möglich, wo Bash-Skripte typischerweise keine Abdeckung haben.

