Utilitas command line yang baik terasa “tidak terlihat”: dipanggil, bekerja, melaporkan hasil lewat satu baris keluaran, lalu pergi tanpa membuat keributan. Rasanya seperti itu bukan kebetulan — melainkan hasil mengikuti konvensi yang sudah matang puluhan tahun di dunia Unix: data keluar lewat stdout, keluhan keluar lewat stderr, keberhasilan dilaporkan lewat exit code 0, dan --help selalu ada untuk manusia. Sejak JEP 330, berkas .java tunggal bisa dijalankan langsung dengan java NamaProgram.java tanpa langkah kompilasi manual, sehingga menulis utilitas kecil dalam Java menjadi semakin layak. Chapter ini merakit satu utilitas lengkap — penghitung baris, kata, dan karakter bergaya wc — sambil menerapkan setiap konvensi itu.
Anatomi Utilitas yang Ramah Skrip #
Tiga aturan menjadi pondasi. Pertama, sumber masukan fleksibel: utilitas menerima nama berkas sebagai argumen, tetapi bila tidak ada argumen, utilitas membaca standard input — pola ini membuatnya bisa berdiri di tengah pipeline (cat log.txt | java HitungKata.java). Kedua, keluaran satu format: hasil selalu satu baris dengan pasangan kunci=nilai sehingga skrip bisa membidik nilainya dengan grep atau cut. Ketiga, kegagalan terlapor: berkas yang tidak ada dan argumen berlebih masing-masing punya pesan di stderr dan exit code yang berbeda. Berikut utilitasnya secara utuh; contoh pertama menjalankannya tanpa argumen sehingga masukan dibaca dari standard input:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class HitungKata {
public static void main(String[] args) {
if (args.length == 1 && args[0].equals("--help")) {
tampilkanBantuan();
return;
}
if (args.length > 1) {
System.err.println("Galat: terlalu banyak argumen.");
System.err.println("Jalankan dengan --help untuk petunjuk.");
System.exit(2);
}
try (BufferedReader pembaca = bukaSumber(args)) {
long baris = 0, kata = 0, karakter = 0;
String barisSekarang;
while ((barisSekarang = pembaca.readLine()) != null) {
baris++;
karakter += barisSekarang.length();
kata += hitungKata(barisSekarang);
}
System.out.printf("baris=%d kata=%d karakter=%d%n",
baris, kata, karakter);
} catch (IOException e) {
System.err.println("Galat: " + e.getMessage());
System.exit(1);
}
}
private static BufferedReader bukaSumber(String[] args) throws IOException {
if (args.length == 1) {
return Files.newBufferedReader(Path.of(args[0]), StandardCharsets.UTF_8);
}
return new BufferedReader(
new InputStreamReader(System.in, StandardCharsets.UTF_8));
}
private static int hitungKata(String baris) {
String rapat = baris.trim();
return rapat.isEmpty() ? 0 : rapat.split("\\s+").length;
}
private static void tampilkanBantuan() {
System.out.println("Nama:");
System.out.println(" HitungKata - menghitung baris, kata, dan karakter");
System.out.println();
System.out.println("Penggunaan:");
System.out.println(" java HitungKata.java [BERKAS]");
System.out.println(" java HitungKata.java < MASUKAN.txt");
System.out.println();
System.out.println("Opsi:");
System.out.println(" --help tampilkan bantuan ini");
System.out.println();
System.out.println("Tanpa argumen, masukan dibaca dari standard input.");
System.out.println("Keluaran selalu satu baris berformat baris=... kata=... karakter=...");
}
}Output yang Dihasilkan:
baris=2 kata=5 karakter=25Mari kita telaah keputusan-keputusan pentingnya:
- Baris
args.length == 1 && args[0].equals("--help"): bantuan ditangani paling awal dan keluar dengan exit code0— meminta bantuan bukan kegagalan. Bantuan ditulis ke stdout agar bisa dialirkan kelessataugrep. - Baris
System.exit(2): argumen salah adalah kesalahan pemakai, dan konvensi GNU memakai2khusus untuk galat pemakaian — berbeda dari1yang berarti galat saat bekerja. - Method
bukaSumber: satu titik keputusan masukan — argumen berkas atau stdin — selebihnya kode membaca dengan cara yang sama persis. - Blok
catch (IOException e): kegagalan I/O ditulis keSystem.errlengkap dengan nama berkas dari pesan exception, laluSystem.exit(1)memastikan skrip pemanggil tahu utilitas gagal. - Baris
System.out.printf: satu-satunya tulisan ke stdout adalah hasil — tidak ada sapaan, tidak ada progres; skrip di hilir menerima data bersih.
Menjalankan Utilitas pada Berkas #
Dengan argumen nama berkas, sumber berganti dari stdin ke berkas; format keluaran tidak berubah sedikit pun. Berkas contoh berkas.txt berisi tiga baris nama buah tersedia di bundle chapter ini:
$ java HitungKata.java berkas.txt
baris=3 kata=6 karakter=35Permintaan bantuan mencetak petunjuk lengkap dan tetap exit 0:
$ java HitungKata.java --help
Nama:
HitungKata - menghitung baris, kata, dan karakter
Penggunaan:
java HitungKata.java [BERKAS]
java HitungKata.java < MASUKAN.txt
Opsi:
--help tampilkan bantuan ini
Tanpa argumen, masukan dibaca dari standard input.
Keluaran selalu satu baris berformat baris=... kata=... karakter=...Dua bentuk kegagalan melaporkan diri dengan jujur — pesan ke stderr, exit code berbeda:
$ java HitungKata.java berkas-tidak-ada.txt
Galat: berkas-tidak-ada.txt
$ echo $?
1
$ java HitungKata.java a b
Galat: terlalu banyak argumen.
Jalankan dengan --help untuk petunjuk.
$ echo $?
2| Exit code | Arti konvensional |
|---|---|
| 0 | sukses, keluaran sah |
| 1 | galat saat bekerja (mis. berkas tidak ada) |
| 2 | galat pemakaian (argumen salah) |
| 126-127 | program tidak dapat dijalankan / tidak ditemukan (konvensi shell) |
Utilitas kita sejauh ini hanya mengenali satu opsi (--help) yang diperiksa secara manual. Begitu opsi bertambah — flag tanpa nilai, opsi bernilai, -- penanda akhir opsi — penguraian manual cepat menjadi rumit. Chapter berikutnya membedah pola parsing argumen yang tertib.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
HitungKata.java
Unduh berkasimport java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class HitungKata {
public static void main(String[] args) {
if (args.length == 1 && args[0].equals("--help")) {
tampilkanBantuan();
return;
}
if (args.length > 1) {
System.err.println("Galat: terlalu banyak argumen.");
System.err.println("Jalankan dengan --help untuk petunjuk.");
System.exit(2);
}
try (BufferedReader pembaca = bukaSumber(args)) {
long baris = 0, kata = 0, karakter = 0;
String barisSekarang;
while ((barisSekarang = pembaca.readLine()) != null) {
baris++;
karakter += barisSekarang.length();
kata += hitungKata(barisSekarang);
}
System.out.printf("baris=%d kata=%d karakter=%d%n",
baris, kata, karakter);
} catch (IOException e) {
System.err.println("Galat: " + e.getMessage());
System.exit(1);
}
}
private static BufferedReader bukaSumber(String[] args) throws IOException {
if (args.length == 1) {
return Files.newBufferedReader(Path.of(args[0]), StandardCharsets.UTF_8);
}
return new BufferedReader(
new InputStreamReader(System.in, StandardCharsets.UTF_8));
}
private static int hitungKata(String baris) {
String rapat = baris.trim();
return rapat.isEmpty() ? 0 : rapat.split("\\s+").length;
}
private static void tampilkanBantuan() {
System.out.println("Nama:");
System.out.println(" HitungKata - menghitung baris, kata, dan karakter");
System.out.println();
System.out.println("Penggunaan:");
System.out.println(" java HitungKata.java [BERKAS]");
System.out.println(" java HitungKata.java < MASUKAN.txt");
System.out.println();
System.out.println("Opsi:");
System.out.println(" --help tampilkan bantuan ini");
System.out.println();
System.out.println("Tanpa argumen, masukan dibaca dari standard input.");
System.out.println("Keluaran selalu satu baris berformat baris=... kata=... karakter=...");
}
}