FFM menjembatani Java dengan kode native di dalam proses yang sama. Jalur interop kedua sama sekali berbeda: meluncurkan program eksternal — git, ffmpeg, atau program Java lain — sebagai proses sistem operasi terpisah dengan aliran masukan dan keluarannya sendiri. Java menyediakan ProcessBuilder untuk merakit konfigurasi semacam itu: perintah, direktori kerja, environment, dan pengalihan berkas. Kelas ini bersifat immutable builder: directory() atau environment() mengubah salinan konfigurasi di dalam objek, dan satu builder bisa dipakai berulang untuk meluncurkan banyak proses identik. Chapter ini berfokus pada sisi konfigurasi; membaca aliran, menunggu, dan menghentikan proses dibahas pada chapter berikutnya.
Merakit Perintah #
Perintah dinyatakan sebagai daftar string — elemen pertama program, sisanya argumen — dan daftar ini dikirim ke sistem operasi apa adanya tanpa perantara shell. Format daftar menghilangkan satu kelas kesalahan klasik: argumen yang mengandung spasi atau karakter khusus tidak akan terpecah karena tidak ada parsing yang terjadi. Sebaliknya, API lawas Runtime.exec(String) memecah satu string besar dengan aturan spasi sendiri sehingga argumen bertanda kutip mudah salah potong. Jika shell sungguhan diperlukan (piping, globbing), shell harus diminta eksplisit: new ProcessBuilder("/bin/sh", "-c", "git log | head") — dan tanggung jawab atas isinya menjadi milik pemanggil.
Direktori Kerja dan Environment #
Anak proses mewarisi dua hal dari parent secara default: direktori kerja dan environment. Keduanya bisa diatur sebelum peluncuran. directory() memindahkan titik jalannya anak, sedangkan environment() mengembalikan Map yang bisa dimodifikasi — tambah variabel baru, hapus variabel sensitif, atau ganti nilai warisan. Program berikut meluncurkan sh dengan environment yang sudah disesuaikan dan direktori kerja yang dibuat lebih dulu:
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
public class KonfigurasiProses {
public static void main(String[] args) throws Exception {
// Direktori kerja anak dibuat lebih dulu.
Path proyek = Path.of("proyek");
Files.createDirectories(proyek);
ProcessBuilder pb = new ProcessBuilder(
"/bin/sh", "-c",
"echo PENGGUNA=$PENGGUNA; echo WARNA=$WARNA; touch penanda.txt");
pb.directory(proyek.toFile());
// Environment anak: salinan dari parent, lalu kita sesuaikan.
Map<String, String> env = pb.environment();
env.put("PENGGUNA", "Budi");
env.remove("WARNA"); // variabel dari parent tidak diteruskan
env.putIfAbsent("WARNA", "netral");
Process anak = pb.start();
String keluaran = new String(anak.getInputStream().readAllBytes(),
StandardCharsets.UTF_8);
anak.waitFor();
System.out.print(keluaran);
System.out.println("penanda dibuat di direktori kerja anak: "
+ Files.exists(proyek.resolve("penanda.txt")));
}
}Output yang Dihasilkan:
PENGGUNA=Budi
WARNA=netral
penanda dibuat di direktori kerja anak: trueMari kita telaah baris-baris kuncinya:
- Baris
new ProcessBuilder("/bin/sh", "-c", "..."): tiga elemen daftar dikirim apa adanya —shmenerima-cdan seluruh skrip sebagai satu argumen utuh, tanpa pemenggalan spasi. - Baris
pb.directory(proyek.toFile()):touch penanda.txtdijalankan dari dalamproyek/, dibuktikan baris terakhir yang memeriksaproyek/penanda.txtada. - Baris
env.put("PENGGUNA", "Budi"): variabel baru hanya terlihat oleh anak — environment parent tidak berubah. - Baris
env.remove("WARNA")laluputIfAbsent: menghapus dulu memastikan nilai warisan dari parent tidak lolos; hasilnyaWARNA=netralapa pun kondisi parent.
Pengalihan Aliran ke Berkas #
Secara default aliran anak tersambung ke parent lewat pipa (Redirect.PIPE). Untuk keluaran besar atau yang memang ditujukan ke berkas, pengalihan langsung lebih efisien daripada membaca lalu menulis ulang:
Bentuk Redirect | Efek |
|---|---|
Redirect.PIPE (default) | parent membaca lewat anak.getInputStream() |
Redirect.INHERIT | anak berbagi stdin/stdout/stderr parent |
Redirect.to(berkas) | keluaran menimpa isi berkas |
Redirect.appendTo(berkas) | keluaran ditambahkan di akhir berkas |
Redirect.from(berkas) | stdin anak dibaca dari berkas |
Program berikut memakai tiga pola sekaligus — tulis ke berkas, tambahkan, dan gabungkan stderr anak ke stdout-nya:
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class PengalihanProses {
public static void main(String[] args) throws Exception {
Path berkas = Path.of("laporan.txt");
// 1) Output anak ditulis ke berkas (mengganti isi).
ProcessBuilder tulis = new ProcessBuilder("/bin/echo", "baris pertama");
tulis.redirectOutput(berkas.toFile());
tulis.start().waitFor();
System.out.println("isi berkas : " + Files.readString(berkas).trim());
// 2) Mode append menambah tanpa mengganti.
ProcessBuilder tambah = new ProcessBuilder("/bin/echo", "baris kedua");
tambah.redirectOutput(ProcessBuilder.Redirect.appendTo(berkas.toFile()));
tambah.start().waitFor();
System.out.println("jumlah baris: " + Files.readAllLines(berkas).size());
// 3) stderr anak digabung ke stdout-nya.
ProcessBuilder gabung = new ProcessBuilder("/bin/sh", "-c",
"echo ke-stdout; echo ke-stderr >&2");
gabung.redirectErrorStream(true);
Process anak = gabung.start();
String tampil = new String(anak.getInputStream().readAllBytes(),
StandardCharsets.UTF_8);
anak.waitFor();
System.out.println("tergabung : " + tampil.replace("\n", " ").trim());
}
}Output yang Dihasilkan:
isi berkas : baris pertama
jumlah baris: 2
tergabung : ke-stdout ke-stderrBedah singkatnya: baris redirectOutput(berkas.toFile()) membuat sistem operasi menulis keluaran langsung ke berkas sehingga parent tidak perlu memediasi satu byte pun; Redirect.appendTo memakai mode tulis-tambah sehingga baris kedua menempel di bawah baris pertama; redirectErrorStream(true) menyatukan dua pipa menjadi satu sehingga anak.getInputStream() memuat kedua aliran dalam urutan penulisan anak.
Konfigurasi kini lengkap: perintah, direktori, environment, dan pengalihan. Chapter berikutnya mengambil alih sisi operasionalnya — menjalankan proses, membaca aliran tanpa kehabisan buffer, menunggu dengan batas waktu, dan menafsirkan exit code.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
KonfigurasiProses.java
Unduh berkasimport java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
public class KonfigurasiProses {
public static void main(String[] args) throws Exception {
// Direktori kerja anak dibuat lebih dulu.
Path proyek = Path.of("proyek");
Files.createDirectories(proyek);
ProcessBuilder pb = new ProcessBuilder(
"/bin/sh", "-c",
"echo PENGGUNA=$PENGGUNA; echo WARNA=$WARNA; touch penanda.txt");
pb.directory(proyek.toFile());
// Environment anak: salinan dari parent, lalu kita sesuaikan.
Map<String, String> env = pb.environment();
env.put("PENGGUNA", "Budi");
env.remove("WARNA"); // variabel dari parent tidak diteruskan
env.putIfAbsent("WARNA", "netral");
Process anak = pb.start();
String keluaran = new String(anak.getInputStream().readAllBytes(),
StandardCharsets.UTF_8);
anak.waitFor();
System.out.print(keluaran);
System.out.println("penanda dibuat di direktori kerja anak: "
+ Files.exists(proyek.resolve("penanda.txt")));
}
}
PengalihanProses.java
Unduh berkasimport java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class PengalihanProses {
public static void main(String[] args) throws Exception {
Path berkas = Path.of("laporan.txt");
// 1) Output anak ditulis ke berkas (mengganti isi).
ProcessBuilder tulis = new ProcessBuilder("/bin/echo", "baris pertama");
tulis.redirectOutput(berkas.toFile());
tulis.start().waitFor();
System.out.println("isi berkas : " + Files.readString(berkas).trim());
// 2) Mode append menambah tanpa mengganti.
ProcessBuilder tambah = new ProcessBuilder("/bin/echo", "baris kedua");
tambah.redirectOutput(ProcessBuilder.Redirect.appendTo(berkas.toFile()));
tambah.start().waitFor();
System.out.println("jumlah baris: " + Files.readAllLines(berkas).size());
// 3) stderr anak digabung ke stdout-nya.
ProcessBuilder gabung = new ProcessBuilder("/bin/sh", "-c",
"echo ke-stdout; echo ke-stderr >&2");
gabung.redirectErrorStream(true);
Process anak = gabung.start();
String tampil = new String(anak.getInputStream().readAllBytes(),
StandardCharsets.UTF_8);
anak.waitFor();
System.out.println("tergabung : " + tampil.replace("\n", " ").trim());
}
}