Przejdź do treści

Odczyt argumentów wiersza poleceń

  • Przydatne narzędzia
  • poziom: średni
  • aktualizacja: 16 sierpnia 2026

Program uruchamiany z konsoli dostaje jedną tablicę String[] args — i tyle, reszta należy do Ciebie. Odczytywanie jej po indeksach, args[0] jako plik, args[1] jako tryb, wywraca się przy pierwszej przestawionej opcji, a własny łańcuch if-ów po startsWith("-") gubi się przy pierwszej sklejonej flagce. Ta klasa układa tablicę w trzy przewidywalne rzeczy: mapę opcji z wartościami, zbiór flag i listę argumentów pozycyjnych. Rozumie obie formy zapisu wartości, sklejone flagi krótkie oraz POSIX-owy separator --, po którym każdy kolejny argument jest już zwykłą nazwą pliku — także taką zaczynającą się od myślnika, którą bez separatora każdy parser rozłoży na flagi.

JAVA ArgumentyPolecen.java 130 linii Pobierz .java
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Parser argumentów wiersza poleceń — skryptyJAVA.pl
 * Zapisz jako: ArgumentyPolecen.java
 */
public class ArgumentyPolecen {

    private final Map<String, String> wartosci = new LinkedHashMap<>();
    private final Set<String> flagi = new LinkedHashSet<>();
    private final List<String> pozycyjne = new ArrayList<>();

    /** W zWartoscia podaj nazwy opcji bez myślników (np. "wyjscie", "o"), które biorą wartość. */
    public ArgumentyPolecen(String[] args, String... zWartoscia) {
        Set<String> zWartosciami = Set.of(zWartoscia);
        boolean koniecOpcji = false;
        for (int i = 0; i < args.length; i++) {
            String argument = args[i];
            if (!koniecOpcji && argument.equals("--")) {
                koniecOpcji = true;                  // od tego miejsca wszystko jest pozycyjne
            } else if (koniecOpcji || argument.length() < 2 || !argument.startsWith("-")) {
                pozycyjne.add(argument);             // samotny "-" to zwykle strumień, nie opcja
            } else if (argument.startsWith("--")) {
                i = czytajDluga(argument.substring(2), args, i, zWartosciami);
            } else {
                i = czytajKrotkie(argument.substring(1), args, i, zWartosciami);
            }
        }
    }

    /** Obsługuje --nazwa=wartosc i --nazwa wartosc; zwraca indeks ostatniego zużytego argumentu. */
    private int czytajDluga(String tresc, String[] args, int i, Set<String> zWartosciami) {
        int rownosc = tresc.indexOf('=');
        if (rownosc >= 0) {
            wartosci.put(tresc.substring(0, rownosc), tresc.substring(rownosc + 1));
        } else if (!zWartosciami.contains(tresc)) {
            flagi.add(tresc);
        } else if (i + 1 < args.length && !args[i + 1].equals("--")) {
            wartosci.put(tresc, args[i + 1]);
            i++;                                     // wartość zabrana z kolejnego argumentu
        } else {
            throw new IllegalArgumentException("Opcja --" + tresc + " wymaga wartości.");
        }
        return i;
    }

    /** Obsługuje -v, sklejone flagi -vf oraz krótką opcję z wartością: -o plik i -oplik. */
    private int czytajKrotkie(String litery, String[] args, int i, Set<String> zWartosciami) {
        for (int k = 0; k < litery.length(); k++) {
            String nazwa = String.valueOf(litery.charAt(k));
            if (!zWartosciami.contains(nazwa)) {
                flagi.add(nazwa);
            } else if (k + 1 < litery.length()) {
                wartosci.put(nazwa, litery.substring(k + 1));   // reszta sklejki jest wartością
                return i;
            } else if (i + 1 < args.length && !args[i + 1].equals("--")) {
                wartosci.put(nazwa, args[i + 1]);
                return i + 1;
            } else {
                throw new IllegalArgumentException("Opcja -" + nazwa + " wymaga wartości.");
            }
        }
        return i;
    }

    // Odczyt: tekst() i liczba() mają wartość domyślną, wymagany() przerywa działanie programu.
    public boolean flaga(String nazwa) { return flagi.contains(nazwa); }

    public String tekst(String nazwa, String domyslna) { return wartosci.getOrDefault(nazwa, domyslna); }

    public List<String> pozycyjne() { return List.copyOf(pozycyjne); }

    public int liczba(String nazwa, int domyslna) {
        String wartosc = wartosci.get(nazwa);
        if (wartosc == null) { return domyslna; }
        try {
            return Integer.parseInt(wartosc.trim());
        } catch (NumberFormatException blad) {   // literówka ma dać komunikat, a nie ciche zero
            throw new IllegalArgumentException("Opcja --" + nazwa + " oczekuje liczby: " + wartosc);
        }
    }

    public String wymagany(String nazwa) {
        String wartosc = wartosci.get(nazwa);
        if (wartosc == null || wartosc.isBlank()) {
            throw new IllegalArgumentException("Brak wymaganej opcji --" + nazwa + ". Zobacz --pomoc.");
        }
        return wartosc;
    }

    public static String pomoc() {
        return """
                Użycie: java ArgumentyPolecen [opcje] [--] plik...
                  --wyjscie=PLIK | --wyjscie PLIK   plik wynikowy (wymagany)
                  --szerokosc N                     szerokość kolumny (domyślnie 72)
                  -o PLIK | -oPLIK                  krótka opcja z wartością
                  --tryb-cichy, -v, -f              flagi logiczne, można łączyć: -vf
                  --pomoc                           ten opis
                Wszystko po samotnym -- jest nazwą pliku, nawet gdy zaczyna się od myślnika.""";
    }

    private static void pokaz(String opis, String... args) {
        System.out.println("== " + opis);
        try {
            ArgumentyPolecen opcje = new ArgumentyPolecen(args, "wyjscie", "szerokosc", "o");
            if (opcje.flaga("pomoc")) { System.out.println(pomoc()); return; }
            System.out.println("  wyjście=" + opcje.wymagany("wyjscie") + ", szerokość="
                    + opcje.liczba("szerokosc", 72) + ", -o=" + opcje.tekst("o", "brak"));
            System.out.println("  cichy/v/f=" + opcje.flaga("tryb-cichy") + "/" + opcje.flaga("v")
                    + "/" + opcje.flaga("f") + ", pliki=" + opcje.pozycyjne());
        } catch (IllegalArgumentException blad) {
            System.out.println("  Błąd: " + blad.getMessage());   // tak reaguje program konsolowy
        }
    }

    // ── PRZYKŁADY ───────────────────────────────────────────
    public static void main(String[] args) {
        if (args.length > 0) { pokaz("argumenty z konsoli", args); return; }
        pokaz("pełne wywołanie", "--wyjscie=raport.txt", "--szerokosc", "100", "-owynik.txt",
                "--tryb-cichy", "-vf", "dane.csv", "--", "--plik-z-myslnikiem.csv");
        pokaz("pusta tablica args");
        pokaz("zła liczba", "-o", "wynik.txt", "--wyjscie", "raport.txt", "--szerokosc=sto");
        pokaz("prośba o pomoc", "--pomoc");
    }
}

Jak użyć tego skryptu

  1. Wklej kod do pliku ArgumentyPolecen.java (nazwa pliku musi odpowiadać klasie publicznej) i wywołaj java ArgumentyPolecen.java. Bez podania czegokolwiek zobaczysz cztery wywołania przygotowane w kodzie: pełne, z pustą tablicą, z błędną liczbą i z --pomoc. Możesz też dopisać własne argumenty za nazwą pliku — trafią prosto do parsera.
  2. W swoim programie utwórz obiekt w pierwszej linii metody main, np. new ArgumentyPolecen(args, "wyjscie", "szerokosc"). Nazwy wymienione po tablicy to opcje, po których parser sięgnie po wartość do kolejnego argumentu; niezadeklarowany argument z myślnikiem ląduje wśród flag, chyba że sam niesie znak równości — wtedy i tak trafia do mapy wartości.
  3. Wartości odczytuj przez tekst(nazwa, domyslna), liczba(nazwa, domyslna) i flaga(nazwa), a to, bez czego program nie ruszy — przez wymagany(nazwa). Nazwy plików odbierzesz z metody pozycyjne() jako List<String>, w kolejności podania.
  4. Zanim zaczniesz liczyć cokolwiek, sprawdź flaga("pomoc") i wypisz pomoc(), a wyjątek IllegalArgumentException złap w main i pokaż sam komunikat — użytkownik konsoli ma zobaczyć jedno zdanie, nie stos wywołań. Separator -- nie może być wartością w formie z odstępem; dosłowną wartość „--” podaj jako --wyjscie=-- albo -o--. Gdy ta sama opcja pojawi się dwa razy, obowiązuje ostatnia wartość.

Warto wiedzieć

  • Wszystko, co przychodzi w args, jest danymi wejściowymi od użytkownika — parser wyłącznie je rozdziela, nie sprawdza sensu. Zanim użyjesz wartości, zweryfikuj ją tak samo jak pole formularza: przypilnuj zakresu liczby, sprawdź przez Path.normalize(), czy ścieżka nie ucieka poza katalog roboczy, i nie doklejaj tekstu z argumentu do zapytania SQL ani do polecenia powłoki.
  • Nie przekazuj w argumentach haseł, tokenów ani kluczy API. Wiersz polecenia uruchomionego procesu widzi każdy użytkownik maszyny — w Menedżerze zadań wystarczy włączyć kolumnę „Wiersz polecenia", w systemach uniksowych pokaże go ps -ef — a powłoka dopisze całe wywołanie do swojej historii i do logów serwera CI. Sekret podaj zmienną środowiskową albo plikiem z ograniczonymi prawami dostępu.
  • Parser sam nie zgadnie, które opcje biorą wartość. Jeśli nie wymienisz nazwy w konstruktorze, wywołanie --wyjscie raport.txt zapisze flagę „wyjscie", a raport.txt wyląduje wśród argumentów pozycyjnych — i program po cichu policzy coś innego, niż chciałeś. Forma --wyjscie=raport.txt jest odporna na to przeoczenie, bo znak równości rozstrzyga sprawę na miejscu.
  • Ten kod celowo kończy się tam, gdzie zaczyna się biblioteka: nie zna podkomend ani typów innych niż tekst i liczba całkowita, a treść metody pomoc() piszesz ręcznie — dopisanie opcji do konstruktora nie zmieni w niej ani znaku, więc opis rozjedzie się z kodem przy pierwszym pośpiechu. Przy narzędziu z kilkunastoma opcjami sięgnij po picocli albo JCommander — opcje deklarujesz adnotacjami przy polach, a konwersję typów, komunikaty o błędach i --help dostajesz gotowe.
#parsowanie #walidacja #kolekcje #map #bezpieczeństwo

Podobne skrypty