Kotlin - Scripting

Overview

Kotlin scripting allows you to use Kotlin for quick automation tasks, build scripts, and command-line utilities. This tutorial covers .kts files, scripting APIs, integration with build tools, and practical automation examples.

๐ŸŽฏ Learning Objectives:
  • Understand Kotlin scripting fundamentals and .kts files
  • Learn command-line scripting with kotlinc
  • Master build script creation with Gradle Kotlin DSL
  • Apply scripting for automation and DevOps tasks
  • Create custom scripting solutions and integrations

Getting Started with Kotlin Scripts

What is Kotlin Scripting?

Kotlin scripting allows you to execute Kotlin code directly without the traditional compile-then-run cycle. Scripts use the .kts extension and can be run immediately.

Basic Script Structure

#!/usr/bin/env kotlin

// hello.kts
println("Hello, Kotlin Scripting!")

// Variables and functions work normally
val name = "World"
fun greet(name: String) = "Hello, $name!"

println(greet(name))

// Access command line arguments
if (args.isNotEmpty()) {
    println("Arguments: ${args.joinToString()}")
}

Running Kotlin Scripts

# Using kotlinc
kotlinc -script hello.kts

# With arguments
kotlinc -script hello.kts arg1 arg2 arg3

# Using kotlin command (if available)
kotlin hello.kts

# Make script executable (Unix/Linux/Mac)
chmod +x hello.kts
./hello.kts

Command-Line Scripting

File Processing Script

#!/usr/bin/env kotlin

// file_processor.kts
import java.io.File

fun processFile(filename: String) {
    val file = File(filename)
    
    if (!file.exists()) {
        println("File not found: $filename")
        return
    }
    
    val lines = file.readLines()
    val wordCount = lines.sumOf { it.split("\\s+".toRegex()).size }
    val charCount = lines.sumOf { it.length }
    
    println("File: $filename")
    println("Lines: ${lines.size}")
    println("Words: $wordCount")
    println("Characters: $charCount")
    println("Average words per line: ${wordCount.toDouble() / lines.size}")
}

// Process command line arguments
if (args.isEmpty()) {
    println("Usage: kotlin file_processor.kts ")
} else {
    args.forEach { processFile(it) }
}

System Information Script

#!/usr/bin/env kotlin

// sysinfo.kts
import java.lang.management.ManagementFactory
import java.text.SimpleDateFormat
import java.util.*

fun getSystemInfo() {
    val runtime = Runtime.getRuntime()
    val mb = 1024 * 1024
    val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    
    println("=== System Information ===")
    println("Current time: ${dateFormat.format(Date())}")
    println("OS: ${System.getProperty("os.name")} ${System.getProperty("os.version")}")
    println("Architecture: ${System.getProperty("os.arch")}")
    println("Java version: ${System.getProperty("java.version")}")
    println("User: ${System.getProperty("user.name")}")
    println("Working directory: ${System.getProperty("user.dir")}")
    println()
    
    println("=== Memory Information ===")
    println("Total memory: ${runtime.totalMemory() / mb} MB")
    println("Free memory: ${runtime.freeMemory() / mb} MB")
    println("Used memory: ${(runtime.totalMemory() - runtime.freeMemory()) / mb} MB")
    println("Max memory: ${runtime.maxMemory() / mb} MB")
    println()
    
    println("=== CPU Information ===")
    println("Available processors: ${runtime.availableProcessors()}")
    
    val threadBean = ManagementFactory.getThreadMXBean()
    println("Thread count: ${threadBean.threadCount}")
    println("Peak thread count: ${threadBean.peakThreadCount}")
}

getSystemInfo()

Dependency Management in Scripts

#!/usr/bin/env kotlin

// Using @file:DependsOn for external dependencies
@file:DependsOn("com.squareup.okhttp3:okhttp:4.11.0")
@file:DependsOn("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")

import okhttp3.*
import com.fasterxml.jackson.module.kotlin.*
import java.io.IOException

// weather.kts - Fetch weather data
val client = OkHttpClient()
val mapper = jacksonObjectMapper()

data class WeatherData(
    val name: String,
    val main: Map,
    val weather: List>
)

fun fetchWeather(city: String, apiKey: String) {
    val url = "https://api.openweathermap.org/data/2.5/weather?q=$city&appid=$apiKey&units=metric"
    
    val request = Request.Builder()
        .url(url)
        .build()
    
    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) {
            println("Failed to fetch weather: ${response.code}")
            return
        }
        
        val json = response.body?.string() ?: return
        val weather = mapper.readValue(json)
        
        println("Weather in ${weather.name}:")
        println("Temperature: ${weather.main["temp"]}ยฐC")
        println("Feels like: ${weather.main["feels_like"]}ยฐC")
        println("Humidity: ${weather.main["humidity"]}%")
        println("Description: ${weather.weather[0]["description"]}")
    }
}

// Usage
if (args.size < 2) {
    println("Usage: kotlin weather.kts  ")
} else {
    fetchWeather(args[0], args[1])
}

Build Script Automation

Gradle Kotlin DSL Scripts

// build.gradle.kts
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.9.10"
    application
    id("com.github.johnrengelman.shadow") version "8.1.1"
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    testImplementation(kotlin("test"))
}

application {
    mainClass.set("com.example.MainKt")
}

// Custom task
tasks.register("generateBuildInfo") {
    description = "Generates build information"
    group = "build"
    
    val outputFile = file("$buildDir/resources/main/build.properties")
    outputs.file(outputFile)
    
    doLast {
        outputFile.parentFile.mkdirs()
        outputFile.writeText("""
            version=${project.version}
            buildTime=${System.currentTimeMillis()}
            buildBy=${System.getProperty("user.name")}
        """.trimIndent())
    }
}

tasks.processResources {
    dependsOn("generateBuildInfo")
}

tasks.withType {
    kotlinOptions {
        jvmTarget = "17"
        freeCompilerArgs = listOf("-Xjsr305=strict")
    }
}

tasks.test {
    useJUnitPlatform()
    testLogging {
        events("passed", "skipped", "failed")
        exceptionFormat = TestExceptionFormat.FULL
    }
}

Custom Gradle Plugin Script

// buildSrc/src/main/kotlin/project-conventions.gradle.kts
plugins {
    kotlin("jvm")
}

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))
    testImplementation(kotlin("test"))
}

tasks.withType {
    kotlinOptions {
        jvmTarget = "17"
        freeCompilerArgs = listOf("-Xjsr305=strict")
    }
}

tasks.test {
    useJUnitPlatform()
}

// Custom extension
open class ProjectExtension {
    var customProperty: String = "default"
    
    fun customConfiguration(action: Action) {
        val config = CustomConfig()
        action.execute(config)
        println("Custom configuration applied: ${config.value}")
    }
}

class CustomConfig {
    var value: String = ""
}

DevOps and Automation Scripts

Deployment Script

#!/usr/bin/env kotlin

// deploy.kts
import java.io.File
import java.util.concurrent.TimeUnit

data class DeployConfig(
    val environment: String,
    val serverHost: String,
    val deployPath: String,
    val serviceName: String
)

class DeploymentScript {
    private val configs = mapOf(
        "staging" to DeployConfig("staging", "staging.example.com", "/opt/myapp", "myapp-staging"),
        "production" to DeployConfig("production", "prod.example.com", "/opt/myapp", "myapp-prod")
    )
    
    fun deploy(environment: String) {
        val config = configs[environment] ?: throw IllegalArgumentException("Unknown environment: $environment")
        
        println("Starting deployment to $environment...")
        
        // Build the application
        executeCommand("./gradlew clean build")
        
        // Create deployment package
        val jarFile = File("build/libs").listFiles()?.find { it.name.endsWith(".jar") }
            ?: throw RuntimeException("JAR file not found")
        
        // Copy to server
        executeCommand("scp ${jarFile.absolutePath} ${config.serverHost}:${config.deployPath}/")
        
        // Restart service
        executeCommand("ssh ${config.serverHost} 'sudo systemctl restart ${config.serviceName}'")
        
        // Verify deployment
        Thread.sleep(5000) // Wait for service to start
        val status = executeCommand("ssh ${config.serverHost} 'curl -f http://localhost:8080/health'")
        
        if (status == 0) {
            println("โœ… Deployment successful!")
        } else {
            println("โŒ Deployment failed - health check failed")
        }
    }
    
    private fun executeCommand(command: String): Int {
        println("Executing: $command")
        
        val process = ProcessBuilder()
            .command(command.split(" "))
            .inheritIO()
            .start()
        
        return if (process.waitFor(60, TimeUnit.SECONDS)) {
            process.exitValue()
        } else {
            process.destroyForcibly()
            -1
        }
    }
}

// Main execution
if (args.isEmpty()) {
    println("Usage: kotlin deploy.kts ")
    println("Available environments: staging, production")
} else {
    try {
        DeploymentScript().deploy(args[0])
    } catch (e: Exception) {
        println("Deployment failed: ${e.message}")
        kotlin.system.exitProcess(1)
    }
}

Database Migration Script

#!/usr/bin/env kotlin

@file:DependsOn("mysql:mysql-connector-java:8.0.33")

// migrate.kts
import java.sql.DriverManager
import java.io.File

data class Migration(
    val version: Int,
    val description: String,
    val sql: String
)

class DatabaseMigrator(
    private val jdbcUrl: String,
    private val username: String,
    private val password: String
) {
    fun migrate() {
        val connection = DriverManager.getConnection(jdbcUrl, username, password)
        
        // Create migrations table if it doesn't exist
        connection.createStatement().execute("""
            CREATE TABLE IF NOT EXISTS schema_migrations (
                version INT PRIMARY KEY,
                description VARCHAR(255),
                applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        """)
        
        // Get applied migrations
        val appliedVersions = mutableSetOf()
        val result = connection.createStatement().executeQuery("SELECT version FROM schema_migrations")
        while (result.next()) {
            appliedVersions.add(result.getInt("version"))
        }
        
        // Load and apply pending migrations
        val migrations = loadMigrations()
        val pendingMigrations = migrations.filter { it.version !in appliedVersions }
            .sortedBy { it.version }
        
        if (pendingMigrations.isEmpty()) {
            println("No pending migrations")
            return
        }
        
        connection.autoCommit = false
        
        try {
            pendingMigrations.forEach { migration ->
                println("Applying migration ${migration.version}: ${migration.description}")
                
                // Execute migration SQL
                connection.createStatement().execute(migration.sql)
                
                // Record migration
                val stmt = connection.prepareStatement(
                    "INSERT INTO schema_migrations (version, description) VALUES (?, ?)"
                )
                stmt.setInt(1, migration.version)
                stmt.setString(2, migration.description)
                stmt.executeUpdate()
            }
            
            connection.commit()
            println("โœ… Applied ${pendingMigrations.size} migrations successfully")
            
        } catch (e: Exception) {
            connection.rollback()
            println("โŒ Migration failed: ${e.message}")
            throw e
        } finally {
            connection.close()
        }
    }
    
    private fun loadMigrations(): List {
        val migrationsDir = File("migrations")
        if (!migrationsDir.exists()) {
            println("Migrations directory not found")
            return emptyList()
        }
        
        return migrationsDir.listFiles { file -> file.name.endsWith(".sql") }
            ?.mapNotNull { file ->
                val parts = file.nameWithoutExtension.split("_", limit = 2)
                if (parts.size >= 2) {
                    val version = parts[0].toIntOrNull()
                    val description = parts[1].replace("_", " ")
                    
                    if (version != null) {
                        Migration(version, description, file.readText())
                    } else null
                } else null
            }
            ?.sortedBy { it.version }
            ?: emptyList()
    }
}

// Usage
if (args.size < 3) {
    println("Usage: kotlin migrate.kts   ")
    println("Example: kotlin migrate.kts 'jdbc:mysql://localhost:3306/mydb' user pass")
} else {
    DatabaseMigrator(args[0], args[1], args[2]).migrate()
}

Testing and Monitoring Scripts

Load Testing Script

#!/usr/bin/env kotlin

@file:DependsOn("com.squareup.okhttp3:okhttp:4.11.0")

import okhttp3.*
import kotlinx.coroutines.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import kotlin.system.measureTimeMillis

// loadtest.kts
class LoadTester(
    private val baseUrl: String,
    private val concurrency: Int = 10,
    private val requests: Int = 100
) {
    private val client = OkHttpClient()
    private val successCount = AtomicInteger(0)
    private val errorCount = AtomicInteger(0)
    private val totalResponseTime = AtomicLong(0)
    
    suspend fun runTest() = coroutineScope {
        println("Starting load test...")
        println("URL: $baseUrl")
        println("Concurrency: $concurrency")
        println("Total requests: $requests")
        println("=" * 50)
        
        val startTime = System.currentTimeMillis()
        
        // Create semaphore to limit concurrency
        val semaphore = kotlinx.coroutines.sync.Semaphore(concurrency)
        
        // Launch all requests
        val jobs = (1..requests).map { requestId ->
            async {
                semaphore.withPermit {
                    makeRequest(requestId)
                }
            }
        }
        
        // Wait for all requests to complete
        jobs.awaitAll()
        
        val endTime = System.currentTimeMillis()
        val totalTime = endTime - startTime
        
        printResults(totalTime)
    }
    
    private suspend fun makeRequest(requestId: Int) {
        val request = Request.Builder()
            .url(baseUrl)
            .build()
        
        val responseTime = measureTimeMillis {
            try {
                client.newCall(request).execute().use { response ->
                    if (response.isSuccessful) {
                        successCount.incrementAndGet()
                    } else {
                        errorCount.incrementAndGet()
                        println("Request $requestId failed: ${response.code}")
                    }
                }
            } catch (e: Exception) {
                errorCount.incrementAndGet()
                println("Request $requestId error: ${e.message}")
            }
        }
        
        totalResponseTime.addAndGet(responseTime)
    }
    
    private fun printResults(totalTime: Long) {
        val successRate = (successCount.get().toDouble() / requests) * 100
        val avgResponseTime = totalResponseTime.get().toDouble() / requests
        val requestsPerSecond = (requests.toDouble() / totalTime) * 1000
        
        println("\n" + "=" * 50)
        println("RESULTS")
        println("=" * 50)
        println("Total time: ${totalTime}ms")
        println("Total requests: $requests")
        println("Successful requests: ${successCount.get()}")
        println("Failed requests: ${errorCount.get()}")
        println("Success rate: ${"%.2f".format(successRate)}%")
        println("Average response time: ${"%.2f".format(avgResponseTime)}ms")
        println("Requests per second: ${"%.2f".format(requestsPerSecond)}")
    }
}

// Main execution
runBlocking {
    if (args.isEmpty()) {
        println("Usage: kotlin loadtest.kts  [concurrency] [requests]")
        println("Example: kotlin loadtest.kts http://localhost:8080/health 5 50")
    } else {
        val url = args[0]
        val concurrency = args.getOrNull(1)?.toIntOrNull() ?: 10
        val requests = args.getOrNull(2)?.toIntOrNull() ?: 100
        
        LoadTester(url, concurrency, requests).runTest()
    }
}

Log Analysis Script

#!/usr/bin/env kotlin

// loganalyzer.kts
import java.io.File
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.regex.Pattern

data class LogEntry(
    val timestamp: LocalDateTime,
    val level: String,
    val message: String,
    val source: String?
)

class LogAnalyzer {
    private val logPattern = Pattern.compile(
        "^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3})\\s+(\\w+)\\s+(.+)$"
    )
    private val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
    
    fun analyzeLogFile(filename: String) {
        val file = File(filename)
        if (!file.exists()) {
            println("Log file not found: $filename")
            return
        }
        
        val entries = file.readLines().mapNotNull { parseLogEntry(it) }
        
        if (entries.isEmpty()) {
            println("No valid log entries found")
            return
        }
        
        println("=== Log Analysis for $filename ===")
        println("Total entries: ${entries.size}")
        println()
        
        // Analyze by level
        val levelCounts = entries.groupingBy { it.level }.eachCount()
        println("Entries by level:")
        levelCounts.toSortedMap().forEach { (level, count) ->
            val percentage = (count.toDouble() / entries.size) * 100
            println("  $level: $count (${"%.1f".format(percentage)}%)")
        }
        println()
        
        // Find time range
        val startTime = entries.minByOrNull { it.timestamp }?.timestamp
        val endTime = entries.maxByOrNull { it.timestamp }?.timestamp
        println("Time range: $startTime to $endTime")
        println()
        
        // Error analysis
        val errors = entries.filter { it.level == "ERROR" }
        if (errors.isNotEmpty()) {
            println("Error messages:")
            errors.take(10).forEach { error ->
                println("  [${error.timestamp}] ${error.message.take(100)}")
            }
            if (errors.size > 10) {
                println("  ... and ${errors.size - 10} more errors")
            }
            println()
        }
        
        // Activity by hour
        val hourlyActivity = entries.groupingBy { it.timestamp.hour }.eachCount()
        println("Activity by hour:")
        (0..23).forEach { hour ->
            val count = hourlyActivity[hour] ?: 0
            val bar = "โ–ˆ".repeat(count / 10)
            println("  %02d:00 %4d %s".format(hour, count, bar))
        }
    }
    
    private fun parseLogEntry(line: String): LogEntry? {
        val matcher = logPattern.matcher(line)
        return if (matcher.matches()) {
            try {
                LogEntry(
                    timestamp = LocalDateTime.parse(matcher.group(1), dateFormatter),
                    level = matcher.group(2),
                    message = matcher.group(3),
                    source = null
                )
            } catch (e: Exception) {
                null
            }
        } else null
    }
}

// Usage
if (args.isEmpty()) {
    println("Usage: kotlin loganalyzer.kts ")
} else {
    args.forEach { LogAnalyzer().analyzeLogFile(it) }
}

Advanced Scripting Techniques

Configuration Management

#!/usr/bin/env kotlin

// config.kts
import java.io.File
import java.util.Properties

class ConfigManager(private val configFile: String) {
    private val properties = Properties()
    
    init {
        loadConfig()
    }
    
    private fun loadConfig() {
        val file = File(configFile)
        if (file.exists()) {
            file.inputStream().use { properties.load(it) }
        }
    }
    
    fun get(key: String, default: String = ""): String {
        return properties.getProperty(key, default)
    }
    
    fun getInt(key: String, default: Int = 0): Int {
        return properties.getProperty(key)?.toIntOrNull() ?: default
    }
    
    fun getBoolean(key: String, default: Boolean = false): Boolean {
        return properties.getProperty(key)?.toBoolean() ?: default
    }
    
    fun set(key: String, value: String) {
        properties.setProperty(key, value)
        saveConfig()
    }
    
    private fun saveConfig() {
        File(configFile).outputStream().use { 
            properties.store(it, "Configuration file")
        }
    }
    
    fun listAll() {
        properties.forEach { key, value ->
            println("$key = $value")
        }
    }
}

// Usage
val config = ConfigManager("app.properties")

when (args.getOrNull(0)) {
    "get" -> {
        if (args.size > 1) {
            println(config.get(args[1], "Not found"))
        } else {
            println("Usage: kotlin config.kts get ")
        }
    }
    "set" -> {
        if (args.size > 2) {
            config.set(args[1], args[2])
            println("Set ${args[1]} = ${args[2]}")
        } else {
            println("Usage: kotlin config.kts set  ")
        }
    }
    "list" -> config.listAll()
    else -> {
        println("Usage: kotlin config.kts ")
        println("Commands: get , set  , list")
    }
}

Template Generation

#!/usr/bin/env kotlin

// codegen.kts
import java.io.File

data class ClassTemplate(
    val packageName: String,
    val className: String,
    val properties: List
)

data class Property(
    val name: String,
    val type: String,
    val nullable: Boolean = false
)

class CodeGenerator {
    fun generateDataClass(template: ClassTemplate): String {
        val props = template.properties.joinToString(",\n    ") { prop ->
            val type = if (prop.nullable) "${prop.type}?" else prop.type
            "val ${prop.name}: $type"
        }
        
        return """
package ${template.packageName}

/**
 * Generated data class: ${template.className}
 */
data class ${template.className}(
    $props
) {
    companion object {
        fun empty() = ${template.className}(
            ${template.properties.joinToString(",\n            ") { prop ->
                when (prop.type) {
                    "String" -> "${prop.name} = \"\""
                    "Int" -> "${prop.name} = 0"
                    "Boolean" -> "${prop.name} = false"
                    "Double" -> "${prop.name} = 0.0"
                    else -> "${prop.name} = null"
                }
            }}
        )
    }
}
        """.trimIndent()
    }
    
    fun generateRepository(className: String, entityName: String): String {
        return """
package com.example.repository

import com.example.entity.$entityName
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface ${className}Repository : JpaRepository<$entityName, Long> {
    fun findByName(name: String): List<$entityName>
    fun findByNameContaining(name: String): List<$entityName>
}
        """.trimIndent()
    }
}

// Usage
if (args.size < 2) {
    println("Usage: kotlin codegen.kts   [properties...]")
    println("Types: dataclass, repository")
    println("Example: kotlin codegen.kts dataclass User name:String age:Int email:String?")
} else {
    val generator = CodeGenerator()
    
    when (args[0]) {
        "dataclass" -> {
            val className = args[1]
            val properties = args.drop(2).map { propStr ->
                val parts = propStr.split(":")
                if (parts.size >= 2) {
                    val name = parts[0]
                    val type = parts[1].removeSuffix("?")
                    val nullable = parts[1].endsWith("?")
                    Property(name, type, nullable)
                } else {
                    Property(propStr, "String")
                }
            }
            
            val template = ClassTemplate("com.example.model", className, properties)
            val code = generator.generateDataClass(template)
            
            val outputFile = File("${className}.kt")
            outputFile.writeText(code)
            println("Generated data class: ${outputFile.absolutePath}")
        }
        
        "repository" -> {
            val entityName = args[1]
            val code = generator.generateRepository(entityName, entityName)
            
            val outputFile = File("${entityName}Repository.kt")
            outputFile.writeText(code)
            println("Generated repository: ${outputFile.absolutePath}")
        }
        
        else -> println("Unknown type: ${args[0]}")
    }
}

Best Practices for Kotlin Scripting

Script Structure and Organization

  • Use shebang: Add #!/usr/bin/env kotlin for executable scripts
  • Organize dependencies: Use @file:DependsOn for external libraries
  • Handle arguments: Always validate command-line arguments
  • Error handling: Use try-catch blocks for robust scripts
  • Documentation: Add comments explaining script purpose and usage

Performance Considerations

#!/usr/bin/env kotlin

// Efficient scripting practices

// โœ… Use lazy initialization
val expensiveResource by lazy {
    println("Initializing expensive resource...")
    // Expensive computation here
    "Resource initialized"
}

// โœ… Use sequences for large data processing
fun processLargeDataset(data: List) {
    data.asSequence()
        .filter { it.isNotBlank() }
        .map { it.trim().uppercase() }
        .take(100)
        .toList()
}

// โœ… Cache results when possible
val cache = mutableMapOf()

fun cachedOperation(input: String): String {
    return cache.getOrPut(input) {
        // Expensive operation
        input.reversed()
    }
}

// โœ… Use appropriate data structures
val frequentLookups = setOf("item1", "item2", "item3")
val orderedData = linkedMapOf()

println("Script optimization examples loaded")

Integration with IDEs and Tools

IntelliJ IDEA Configuration

// .idea/kotlinScripting.xml configuration for custom script templates

// Custom script template
// File > Settings > Editor > File and Code Templates
// Create new template: Kotlin Script (.kts)

#!/usr/bin/env kotlin

/**
 * ${NAME}
 * 
 * Description: ${DESCRIPTION}
 * Author: ${USER}
 * Date: ${DATE}
 */

fun main() {
    println("Script: ${NAME}")
    // Your code here
}

if (args.isNotEmpty()) {
    main()
} else {
    println("Usage: kotlin ${NAME}.kts")
}

Key Takeaways

  • Kotlin scripting enables quick automation and tooling development
  • .kts files can be executed directly without compilation
  • External dependencies can be declared with @file:DependsOn
  • Scripts are perfect for DevOps, build automation, and system administration
  • Gradle Kotlin DSL uses scripting for type-safe build configuration
  • Proper error handling and argument validation make scripts robust

Practice Exercises

  1. Create a script that monitors system resources and sends alerts
  2. Build a deployment automation script with rollback capabilities
  3. Write a code generation script for REST API boilerplate
  4. Develop a log aggregation and analysis tool

Quiz

  1. What's the difference between .kt and .kts files?
  2. How do you add external dependencies to Kotlin scripts?
  3. When should you prefer scripting over regular Kotlin applications?
Show Answers
  1. .kt files are regular Kotlin source files that need compilation, while .kts files are Kotlin scripts that can be executed directly.
  2. Use @file:DependsOn("group:artifact:version") annotations at the top of the script file.
  3. Use scripting for automation tasks, build scripts, one-time utilities, quick prototypes, and when you need immediate execution without compilation.