Argumentos de la línea de órdenes en Kotlin: args, opciones y códigos de salida

Un programa de terminal recibe sus datos como argumentos: el archivo que tiene que leer, cuántas líneas mostrar, si debe dar detalle. En Kotlin llegan en el parámetro args de main. Aquí verás cómo leerlos sin que el programa reviente, cómo interpretar opciones como --limite 5 y cómo terminar con el código de salida correcto.

args: lo que escribiste después del programa

En Kotlin, main puede recibir un parámetro args: Array<String>. Ahí llega cada palabra que escribiste después del nombre del programa, ya separada por la terminal, sin el nombre del programa (al revés que en C o en Python, donde el primero es el propio programa):

fun main(args: Array<String>) {
    println("Recibí ${args.size} argumentos")
    for ((i, arg) in args.withIndex()) {
        println("  args[$i] = $arg")
    }
}

Para probarlo hay que compilar a un .jar con el runtime de Kotlin dentro y ejecutarlo con java:

kotlinc basico.kt -include-runtime -d basico.jar
salida$ java -jar basico.jar ventas.csv --limite 5
Recibí 3 argumentos
  args[0] = ventas.csv
  args[1] = --limite
  args[2] = 5
$ java -jar basico.jar "mi archivo.csv" -v
Recibí 2 argumentos
  args[0] = mi archivo.csv
  args[1] = -v
$ java -jar basico.jar
Recibí 0 argumentos

Fíjate en dos cosas. Las comillas las quita la terminal: "mi archivo.csv" llega como un solo argumento, con su espacio. Y --limite no tiene nada de especial: para Kotlin es un texto más. Interpretar las opciones es trabajo tuyo, o de una biblioteca.

Si tu programa no usa argumentos, escribe fun main() sin parámetros: las dos formas son válidas.

El error de siempre: pedir un argumento que no llegó

fun main(args: Array<String>) {
    val archivo = args[0]
    println("Procesando $archivo")
}
salidaException in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
    at PrimeroKt.main(primero.kt:2)

args es un Array de Java por debajo, y leer una posición que no existe lanza ArrayIndexOutOfBoundsException, con una traza que al usuario no le dice nada. La forma idiomática es firstOrNull() (o getOrNull(1) para el segundo), que devuelve null si no hay, y el operador Elvis ?: para decidir qué hacer en ese caso:

import kotlin.system.exitProcess

fun main(args: Array<String>) {
    val archivo = args.firstOrNull() ?: run {
        System.err.println("Uso: resumen ARCHIVO")
        exitProcess(2)
    }
    println("Procesando $archivo")
}
salidaUso: resumen ARCHIVO
(código de salida: 2)

exitProcess(2), de kotlin.system, termina el programa con ese código de salida. La convención de las herramientas de Unix es 0 si todo fue bien, 1 para fallos generales y 2 para un uso incorrecto; los scripts de shell y las tareas de CI lo leen con $?. El mensaje de error va a System.err, no a la salida normal, para que no se mezcle con los datos si alguien redirige la salida a un archivo.

Opciones con nombre, a mano

Este es un programa completo con el estilo habitual de las herramientas de Unix: opciones cortas y largas (-n y --limite), una que lleva valor, una ayuda y mensajes claros cuando algo falla. Muestra las primeras líneas de un archivo:

import java.io.File
import kotlin.system.exitProcess

data class Opciones(val archivo: String, val limite: Int = 10, val detalle: Boolean = false)

const val AYUDA = """Uso: resumen [opciones] ARCHIVO

Muestra las primeras líneas de ARCHIVO.

Opciones:
  -n, --limite N   cuántas líneas mostrar (10 por omisión)
  -v, --detalle    numera las líneas
  -h, --ayuda      muestra esta ayuda"""

fun fallar(mensaje: String): Nothing {
    System.err.println("error: $mensaje")
    System.err.println("Prueba con --ayuda.")
    exitProcess(2)
}

fun leerOpciones(args: Array<String>): Opciones {
    var archivo: String? = null
    var limite = 10
    var detalle = false
    var i = 0
    while (i < args.size) {
        when (val a = args[i]) {
            "-h", "--ayuda" -> {
                println(AYUDA)
                exitProcess(0)
            }
            "-v", "--detalle" -> detalle = true
            "-n", "--limite" -> {
                val valor = args.getOrNull(i + 1) ?: fallar("falta el número después de $a")
                limite = valor.toIntOrNull()?.takeIf { it > 0 }
                    ?: fallar("$a espera un número positivo, no «$valor»")
                i++
            }
            else -> when {
                a.startsWith("-") -> fallar("opción desconocida: $a")
                archivo == null -> archivo = a
                else -> fallar("sobra el argumento: $a")
            }
        }
        i++
    }
    return Opciones(archivo ?: fallar("falta el ARCHIVO"), limite, detalle)
}

fun main(args: Array<String>) {
    val op = leerOpciones(args)
    val archivo = File(op.archivo)
    if (!archivo.isFile) fallar("no existe el archivo ${op.archivo}")
    archivo.useLines { lineas ->
        lineas.take(op.limite).forEachIndexed { n, linea ->
            println(if (op.detalle) "${n + 1}: $linea" else linea)
        }
    }
}

Lo ejecutamos varias veces, con usos correctos e incorrectos, mirando en cada caso el código de salida:

salida$ java -jar opciones.jar -n 3 -v ventas.csv
1: fecha,producto,cantidad
2: 2026-11-02,café,3
3: 2026-11-02,taza,2
(código de salida: 0)

$ java -jar opciones.jar ventas.csv --limite 2
fecha,producto,cantidad
2026-11-02,café,3
(código de salida: 0)

$ java -jar opciones.jar --ayuda
Uso: resumen [opciones] ARCHIVO

Muestra las primeras líneas de ARCHIVO.

Opciones:
  -n, --limite N   cuántas líneas mostrar (10 por omisión)
  -v, --detalle    numera las líneas
  -h, --ayuda      muestra esta ayuda
(código de salida: 0)

$ java -jar opciones.jar --limite tres ventas.csv
error: --limite espera un número positivo, no «tres»
Prueba con --ayuda.
(código de salida: 2)

$ java -jar opciones.jar ventas.csv --color
error: opción desconocida: --color
Prueba con --ayuda.
(código de salida: 2)

$ java -jar opciones.jar
error: falta el ARCHIVO
Prueba con --ayuda.
(código de salida: 2)

$ java -jar opciones.jar otro.csv
error: no existe el archivo otro.csv
Prueba con --ayuda.
(código de salida: 2)

Tres detalles de Kotlin hacen corto el analizador:

  • fallar() devuelve Nothing, el tipo de las funciones que nunca terminan con normalidad. Por eso se puede escribir a la derecha de ?:: el compilador sabe que, si se llega ahí, el programa no sigue, y que valor ya no puede ser null.
  • when (val a = args[i]) declara la variable dentro del propio when, y una rama puede tener varios valores separados por comas.
  • toIntOrNull()?.takeIf { it > 0 } convierte y valida en una línea: devuelve null si el texto no es un número o si no es positivo.

Si vienes de Java: lo mismo, con menos ceremonia

En Java el parámetro es String[] args y la lógica sería igual, con Integer.parseInt dentro de un try y System.exit(2). Puedes compararlo con los argumentos de la línea de órdenes en Java.

¿Y una biblioteca?

Para una herramienta con subcomandos, opciones repetidas o autocompletado, el analizador a mano se queda corto. La biblioteca más usada en Kotlin es Clikt (com.github.ajalt.clikt:clikt, versión 5.1.0 en Maven Central al escribir esto), que genera la ayuda y los mensajes de error a partir de la declaración de las opciones. Para dos o tres opciones, lo de esta página basta y no añade dependencias.

Resumen

  • ParaUsa
  • Recibir los argumentosfun main(args: Array<String>)
  • El primero, si lo hayargs.firstOrNull() o args.getOrNull(0)
  • Un númerotexto.toIntOrNull() ?: fallar(...)
  • Salir con un códigoexitProcess(2), de kotlin.system
  • Mensajes de errorSystem.err.println(...)
  • Empaquetar y ejecutarkotlinc x.kt -include-runtime -d x.jar y java -jar x.jar ...

Todo el código de esta página se compiló con Kotlin 2.4.21 y se ejecutó en la JVM de Temurin 25.0.5 en un contenedor limpio y sin red antes de publicarla; las salidas y los mensajes del compilador están copiados de esa ejecución.