1. Що таке процеси в ОС і навіщо їх запускати з Java
Процеси: від JVM до bash і назад
Коли ви вмикаєте комп’ютер і відкриваєте браузер, месенджер або гру — усе це окремі процеси. Операційна система запускає для кожної програми власний «мінісвіт»: виділяє пам’ять, процесорний час, дозволяє працювати з файлами та мережею. Тож програми живуть поруч, але не заважають одна одній.
Java‑програма — не виняток. Коли ви запускаєте java MyApp, система створює для неї окремий процес із усіма необхідними ресурсами. Усередині нього вже працює ваша програма: рахує, малює, читає файли — усе, що їй потрібно.
Але інколи однієї Java замало. Буває, потрібно попросити про допомогу іншу програму — запустити архіватор, щоб заархівувати файли, викликати ffmpeg, щоб обробити відео, або просто дізнатися, яка версія Java встановлена на комп’ютері. Це і є запуск зовнішнього процесу: Java каже системі «виконай ось цю утиліту», а потім отримує результат її роботи.
По суті це спосіб зробити програму гнучкішою: об’єднати різні інструменти, автоматизувати рутину або вбудувати власну логіку в уже наявні системні процеси. Іноді простіше попросити зовнішню команду виконати частину роботи, ніж винаходити велосипед у коді.
JVM проти зовнішнього процесу
JVM‑процес — ваша програма, що виконується у віртуальній машині Java.
Зовнішній процес — будь-яка інша програма: калькулятор, Python‑скрипт, командний рядок, навіть інший екземпляр Java.
2. Клас ProcessBuilder
У «давні» часи Java запускала процеси через метод Runtime.getRuntime().exec(). Це був не найзручніший і не надто безпечний спосіб — як спроба забити цвях мікроскопом. Тому, починаючи з Java 5, з’явився клас ProcessBuilder, який дає змогу створювати, налаштовувати та запускати зовнішні процеси більш гнучко і зрозуміло.
ProcessBuilder — це своєрідний «конструктор», що дає змогу заздалегідь задати всі параметри майбутнього процесу: команду, аргументи, робочу директорію, змінні середовища тощо.
Синтаксис: створення процесу
ProcessBuilder pb = new ProcessBuilder("команда", "аргумент1", "аргумент2", ...);
- Перший аргумент — назва команди (наприклад, "ls", "dir", "ping", "java").
- Решта — параметри для команди.
Приклад: запуск команди ls (Linux/macOS) або dir (Windows)
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
До речі, у Windows команди на кшталт dir, copy тощо — це не окремі виконувані файли, а «вбудовані» в командний рядок (cmd.exe) команди. Тому їх потрібно запускати через cmd.exe /c ....
Приклад: запуск простого процесу
ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");
3. Налаштування середовища процесу
Передавання аргументів. Аргументи команди передаються окремими рядками:
ProcessBuilder pb = new ProcessBuilder("ping", "google.com");
Вказання робочої директорії. За замовчуванням процес запускається в тій самій папці, що й ваша програма. Але можна явно вказати іншу директорію:
pb.directory(new java.io.File("/tmp")); // Для Linux/macOS
pb.directory(new java.io.File("C:\\Temp")); // Для Windows
Зміна змінних середовища. Кожен процес має власний набір змінних середовища (environment variables). Їх можна додати або змінити:
pb.environment().put("MY_VAR", "HelloFromJava");
Це може бути корисно, якщо зовнішній процес очікує певні змінні.
4. Запуск процесу
Метод start(). Коли ви все налаштували, час запускати процес:
Process process = pb.start();
Метод start() повертає об’єкт Process, що дає змогу керувати запущеною програмою: читати її вивід, писати у її ввід, завершувати її тощо.
Обробка винятків. start() може згенерувати IOException, якщо команду не знайдено, бракує прав або виникла інша помилка запуску.
Приклад:
try {
Process process = pb.start();
// Працюємо з процесом...
} catch (IOException e) {
System.out.println("Помилка запуску процесу: " + e.getMessage());
}
5. Практика: запуск простих команд
Приклад 1: вивести список файлів у теці
import java.io.*;
public class ProcessDemo {
public static void main(String[] args) {
// Визначаємо команду залежно від ОС
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
try {
Process process = pb.start();
// Читаємо вивід процесу (stdout)
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// Чекаємо завершення процесу
int exitCode = process.waitFor();
System.out.println("Процес завершено з кодом: " + exitCode);
} catch (IOException | InterruptedException e) {
System.out.println("Помилка: " + e.getMessage());
}
}
}
Що тут відбувається?
- Визначаємо, яка ОС — щоб обрати правильну команду.
- Створюємо ProcessBuilder з потрібною командою.
- Запускаємо процес через start().
- Читаємо рядки зі stdout процесу та виводимо їх на екран.
- Чекаємо завершення процесу (waitFor()).
- Виводимо код повернення (0 — успіх, інше — помилка).
Приклад 2: запуск java -version
ProcessBuilder pb = new ProcessBuilder("java", "-version");
try {
Process process = pb.start();
// java -version виводить у stderr, тому читаємо getErrorStream()
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
Важливий нюанс: Деякі команди (наприклад, java -version) виводять інформацію не в стандартний вивід (stdout), а в потік помилок (stderr). Тому інколи потрібно читати process.getErrorStream().
6. Кросплатформність: відмінності між Windows і Linux/Mac
- Команди та їх параметри можуть відрізнятися.
- Шляхи до файлів записуються по‑різному (C:\Temp vs /tmp).
- Деякі команди (наприклад, ls, cat) є лише в Unix‑подібних системах, а у Windows їх замінюють аналоги (dir, type).
- У Windows вбудовані команди запускаються лише через cmd.exe /c команда.
Приклад перевірки ОС:
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows
} else if (os.contains("mac")) {
// macOS
} else if (os.contains("nix") || os.contains("nux")) {
// Linux
}
Порада: Завжди тестуйте ваші програми на цільових ОС, якщо розраховуєте на кросплатформеність.
7. Таблиця: основні методи та можливості ProcessBuilder
| Метод/поле | Призначення | Приклад використання |
|---|---|---|
|
Створити процес з командою та аргументами | |
|
Вказати робочу директорію | |
|
Отримати/змінити змінні середовища | |
|
Запустити процес | |
|
Отримати stdout процесу | |
|
Отримати stderr процесу | |
|
Отримати stdin процесу | |
|
Очікувати на завершення процесу | |
|
Отримати код повернення процесу | |
8. Типові помилки під час запуску зовнішніх процесів
Помилка № 1: Команду не знайдено. Якщо ви помилилися в назві команди або її немає в системі, отримаєте IOException: Cannot run program .... Наприклад, спроба запустити ls у Windows.
Помилка № 2: Некоректна передача аргументів. Не потрібно об’єднувати всю команду в один рядок! Правильно: new ProcessBuilder("ping", "google.com"). Неправильно: new ProcessBuilder("ping google.com").
Помилка № 3: Не врахували відмінності ОС. Команда, що чудово працює на Linux, може не існувати в Windows, і навпаки. Завжди перевіряйте ОС і адаптуйте команду.
Помилка № 4: Не обробили вивід процесу. Якщо не читати вивід процесу, він може «зависнути» через переповнення буфера. Навіть якщо ви не збираєтеся використовувати вивід — читайте його й, наприклад, просто ігноруйте.
Помилка № 5: Не закрили потоки. Потоки процесу потрібно закривати після використання, щоб не було витоків ресурсів.
Помилка № 6: Не обробили винятки. Запуск зовнішнього процесу — ризикована справа. Завжди використовуйте try-catch та інформуйте користувача про помилки.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ