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:

java HitungKata.java
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:

text
baris=2 kata=5 karakter=25

Mari kita telaah keputusan-keputusan pentingnya:

  1. Baris args.length == 1 && args[0].equals("--help"): bantuan ditangani paling awal dan keluar dengan exit code 0 — meminta bantuan bukan kegagalan. Bantuan ditulis ke stdout agar bisa dialirkan ke less atau grep.
  2. Baris System.exit(2): argumen salah adalah kesalahan pemakai, dan konvensi GNU memakai 2 khusus untuk galat pemakaian — berbeda dari 1 yang berarti galat saat bekerja.
  3. Method bukaSumber: satu titik keputusan masukan — argumen berkas atau stdin — selebihnya kode membaca dengan cara yang sama persis.
  4. Blok catch (IOException e): kegagalan I/O ditulis ke System.err lengkap dengan nama berkas dari pesan exception, lalu System.exit(1) memastikan skrip pemanggil tahu utilitas gagal.
  5. 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:

text
$ java HitungKata.java berkas.txt
baris=3 kata=6 karakter=35

Permintaan bantuan mencetak petunjuk lengkap dan tetap exit 0:

text
$ 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:

text
$ 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 codeArti konvensional
0sukses, keluaran sah
1galat saat bekerja (mis. berkas tidak ada)
2galat pemakaian (argumen salah)
126-127program 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 berkas
java
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=...");
    }
}