Una herramienta de línea de órdenes completa en Java

Esto es Java usado como lenguaje de guiones: un archivo, sin proyecto ni Maven, ejecutable con java Contar.java. Cuenta líneas, palabras y caracteres, como wc, y de paso saca las palabras más repetidas.

Lo que hace

salida$ java Contar.java texto.txt
       3      13      62  texto.txt

$ java Contar.java texto.txt otro.txt
       3      13      62  texto.txt
       1       3      15  otro.txt

$ java Contar.java -l texto.txt
       3  texto.txt

$ cat texto.txt | java Contar.java
       3      13      62  (entrada estándar)
salida$ java Contar.java -t texto.txt
  texto.txt
    duerme         2
    gato           2
    casa           1
    corre          1
    grande         1

La forma del programa

void main(String[] args) {
    try { System.exit(ejecutar(args)); }
    catch (Exception e) { System.err.println("contar: " + e.getMessage()); System.exit(1); }
}

int ejecutar(String[] args) throws IOException { ... }

Toda la lógica está en un método que devuelve el código de salida y deja subir las excepciones; main es lo único que llama a System.exit. Esto importa por dos razones: System.exit en mitad del programa se salta los finally y no cierra nada, y un método que devuelve un int se puede probar desde una prueba automática, cosa que un main que llama a exit no.

Las opciones

for (int i = 0; i < args.length; i++) {
    switch (args[i]) {
        case "-l" -> lineas = true;
        case "-p" -> palabras = true;
        case "-c" -> caracteres = true;
        case "-t" -> top = true;
        case "-h", "--ayuda" -> { System.out.println(USO); return 0; }
        default -> {
            if (args[i].startsWith("-")) { System.err.println("contar: opción desconocida " + args[i]); return 2; }
            rutas.add(args[i]);
        }
    }
}
if (!lineas && !palabras && !caracteres && !top) { lineas = palabras = caracteres = true; }

Esa última línea es el detalle que hace que la herramienta se sienta bien: sin opciones, muestra las tres cuentas. Un programa que no hace nada cuando lo llamas sin argumentos es un programa que no has terminado.

static final String USO = """
        uso: contar [-l] [-p] [-c] [-t] [archivo...]
          -l  solo líneas      -c  solo caracteres
          -p  solo palabras    -t  las 5 palabras más repetidas
        sin archivos lee de la entrada estándar; sin opciones muestra las tres cuentas""";

Archivos o entrada estándar

if (rutas.isEmpty()) {                      // sin archivos: la entrada estándar
    try (var r = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
        informar("(entrada estándar)", r.lines().toList(), ...);
    }
    return 0;
}
int fallos = 0;
for (String ruta : rutas) {
    Path p = Path.of(ruta);
    if (!Files.isReadable(p)) { System.err.println("contar: no puedo leer " + ruta); fallos++; continue; }
    informar(ruta, Files.readAllLines(p, StandardCharsets.UTF_8), ...);
}
return fallos == 0 ? 0 : 1;

Dos costumbres de Unix que conviene respetar: si no hay archivos, lee de la entrada estándar —eso es lo que permite encadenar con tuberías—, y si un archivo falla, sigue con los demás y devuelve error al final, en vez de abandonar en el primero.

Los mensajes de error van a System.err y los resultados a System.out. Así java Contar.java *.txt > cuentas.txt deja el informe limpio en el archivo y los errores siguen viéndose en la pantalla.

Los códigos de salida

echo $?-h              -> 0
archivo bueno   -> 0
archivo ausente -> 1
opción mala     -> 2

Cero cuando todo va bien —incluida la ayuda, que no es un fallo—, 2 para «me has llamado mal» y 1 para «lo intenté y no pude». Es lo que espera cualquier guion que compruebe el resultado.

Usarla a diario

# 1. tal cual, sin compilar nada
java Contar.java -t informe.txt

# 2. como orden del sistema, con la línea mágica al principio del archivo
#!/usr/bin/env java
chmod +x contar && ./contar -t informe.txt

# 3. compilada y empaquetada, para que arranque más rápido
javac -d clases Contar.java
jar --create --file contar.jar --main-class Contar -C clases .
java -jar contar.jar -t informe.txt

La primera forma recompila en memoria cada vez: para un archivo pequeño eso es una fracción de segundo, y a cambio no deja nada. La segunda, con #!/usr/bin/env java como primera línea, convierte el .java en un ejecutable más del sistema. La tercera es la de siempre, y es la que conviene si la herramienta se llama mil veces desde otro guion.

Y si algún día esto crece hasta tener subórdenes y veinte opciones, lo que toca es picocli y un proyecto de verdad, como el de la última lección del curso.

Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.