Kotlin cocok untuk skrip otomasi sistem file karena standard library-nya memberi akses penuh ke java.nio.file dan java.io.File. Tugas-tugas seperti rename massal, copy selektif, dan pengelompokan file berdasarkan tanggal modifikasi dapat ditulis dengan ringkas. Pada bab ini kita akan membangun tiga jenis otomasi: rename massal dengan prefix, copy selektif berdasarkan ekstensi, dan organize by date. Operasi dijalankan pada direktori sementara agar tidak mengganggu filesystem pengguna.
Tiga Operasi Umum #
Otomasi sistem file yang sering dibutuhkan terbagi tiga kategori. Rename massal mengubah nama banyak file sesuai pola (misalnya menambah prefix atau normalisasi huruf). Copy selektif membackup file dengan ekstensi tertentu ke folder backup. Organize by date memindahkan file ke subfolder berdasarkan tanggal modifikasi untuk arsip terstruktur.
| Operasi | API Kotlin/Java | Use case |
|---|---|---|
| Rename massal | File.renameTo() | Normalisasi nama, tambah prefix |
| Copy selektif | Files.copy() | Backup file .txt, .jpg |
| Organize by date | Files.move() + LocalDate | Arsip foto, log rotasi |
| Walk tree | File.walkTopDown() | Cari file, hitung ukuran |
API Penting java.nio.file #
Package java.nio.file menyediakan API modern yang lebih aman daripada java.io.File lama. Class Files berisi static method untuk copy, move, dan delete dengan opsi StandardCopyOption.REPLACE_EXISTING. Class Path menggantikan File dan mendukung operasi seperti resolve(), parent(), dan fileName.
import java.nio.file.Files
import java.nio.file.StandardCopyOption
val sumber = Path.of("laporan.txt")
val target = Path.of("backup/laporan.txt")
Files.copy(sumber, target, StandardCopyOption.REPLACE_EXISTING)StandardCopyOption.REPLACE_EXISTING menimpa file target bila sudah ada. Tanpa opsi ini, copy akan melempar FileAlreadyExistsException.
Demo Otomasi Lengkap #
Cuplikan berikut menjalankan ketiga operasi pada direktori sementara. File contoh dibuat dengan tanggal modifikasi yang berbeda untuk demonstrasi organize by date:
// Demo otomasi sistem file: rename massal, copy, organize by date.
// Operasi dilakukan pada direktori sementara yang dibuat saat runtime
// agar output deterministik dan tidak mengganggu filesystem pengguna.
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.time.LocalDate
import java.time.format.DateTimeFormatter
fun main() {
// 1. Buat direktori kerja sementara di dalam /tmp
val kerja = Files.createTempDirectory("fs-demo-")
println("=== File System Automation ===")
println("Direktori kerja: <tmp>/fs-demo-*")
println()
// 2. Buat file-file contoh dengan tanggal modifikasi yang berbeda
val contoh = listOf(
Triple("laporan-2026-01.txt", "Isi laporan Januari", LocalDate.of(2026, 1, 15)),
Triple("laporan-2026-02.txt", "Isi laporan Februari", LocalDate.of(2026, 2, 20)),
Triple("foto-liburan.jpg", "Data biner foto", LocalDate.of(2026, 3, 10)),
Triple("catatan-lama.txt", "Catatan lama", LocalDate.of(2025, 12, 5)),
Triple("draft-artikel.md", "Draft artikel Kotlin", LocalDate.of(2026, 1, 30))
)
contoh.forEach { (nama, isi, tanggal) ->
val file = kerja.resolve(nama).toFile()
file.writeText(isi)
file.setLastModified(tanggal.toEpochDay() * 24L * 60L * 60L * 1000L)
}
println("File awal di direktori:")
listFile(kerja)
println()
// 3. Rename massal: tambahkan prefix "PROSES_"
println("=== Operasi 1: Rename massal dengan prefix ===")
val fileSebelum = kerja.toFile().listFiles()?.toList() ?: emptyList()
fileSebelum.filter { it.isFile }.forEach { file ->
val baru = file.parentFile.resolve("PROSES_${file.name}")
file.renameTo(baru)
println(" ${file.name} -> PROSES_${file.name}")
}
println()
println("Hasil setelah rename:")
listFile(kerja)
println()
// 4. Copy file .txt ke subfolder backup
println("=== Operasi 2: Copy file .txt ke folder backup ===")
val backupDir = kerja.resolve("backup").toFile()
backupDir.mkdirs()
kerja.toFile().listFiles()?.filter { it.isFile && it.extension == "txt" }?.forEach { file ->
val target = backupDir.resolve(file.name).toPath()
Files.copy(file.toPath(), target, StandardCopyOption.REPLACE_EXISTING)
println(" copy ${file.name} -> backup/${file.name}")
}
println()
println("Isi folder backup:")
listFile(backupDir.toPath())
println()
// 5. Organize by date: kelompokkan berdasarkan tahun-bulan modifikasi
println("=== Operasi 3: Organize by date (tahun/bulan) ===")
val formatter = DateTimeFormatter.ofPattern("yyyy-MM")
kerja.toFile().listFiles()?.filter { it.isFile }?.forEach { file ->
val lastMod = LocalDate.ofEpochDay(file.lastModified() / (24L * 60L * 60L * 1000L))
val folder = formatter.format(lastMod)
val targetDir = kerja.resolve(folder).toFile()
targetDir.mkdirs()
val target = targetDir.resolve(file.name).toPath()
Files.move(file.toPath(), target, StandardCopyOption.REPLACE_EXISTING)
println(" ${file.name} -> $folder/${file.name}")
}
println()
println("Struktur akhir direktori:")
listTree(kerja, "")
// 6. Bersih-bersih
kerja.toFile().deleteRecursively()
println()
println("Direktori kerja dihapus.")
}
fun listFile(dir: Path) {
dir.toFile().listFiles()?.sortedBy { it.name }?.forEach {
val tipe = if (it.isDirectory) "[DIR]" else " "
println(" $tipe ${it.name}")
}
}
fun listTree(dir: Path, indent: String) {
val file = dir.toFile()
val anak = file.listFiles()?.sortedBy { it.name } ?: return
anak.forEach { item ->
val marker = if (item.isDirectory) "[DIR]" else " "
println("$indent$marker ${item.name}")
if (item.isDirectory) {
listTree(item.toPath(), "$indent ")
}
}
}Output yang Dihasilkan:
=== File System Automation ===
Direktori kerja: <tmp>/fs-demo-*
File awal di direktori:
catatan-lama.txt
draft-artikel.md
foto-liburan.jpg
laporan-2026-01.txt
laporan-2026-02.txt
=== Operasi 1: Rename massal dengan prefix ===
catatan-lama.txt -> PROSES_catatan-lama.txt
laporan-2026-01.txt -> PROSES_laporan-2026-01.txt
draft-artikel.md -> PROSES_draft-artikel.md
foto-liburan.jpg -> PROSES_foto-liburan.jpg
laporan-2026-02.txt -> PROSES_laporan-2026-02.txt
Hasil setelah rename:
PROSES_catatan-lama.txt
PROSES_draft-artikel.md
PROSES_foto-liburan.jpg
PROSES_laporan-2026-01.txt
PROSES_laporan-2026-02.txt
=== Operasi 2: Copy file .txt ke folder backup ===
copy PROSES_laporan-2026-02.txt -> backup/PROSES_laporan-2026-02.txt
copy PROSES_catatan-lama.txt -> backup/PROSES_catatan-lama.txt
copy PROSES_laporan-2026-01.txt -> backup/PROSES_laporan-2026-01.txt
Isi folder backup:
PROSES_catatan-lama.txt
PROSES_laporan-2026-01.txt
PROSES_laporan-2026-02.txt
=== Operasi 3: Organize by date (tahun/bulan) ===
PROSES_draft-artikel.md -> 2026-01/PROSES_draft-artikel.md
PROSES_laporan-2026-02.txt -> 2026-02/PROSES_laporan-2026-02.txt
PROSES_catatan-lama.txt -> 2025-12/PROSES_catatan-lama.txt
PROSES_foto-liburan.jpg -> 2026-03/PROSES_foto-liburan.jpg
PROSES_laporan-2026-01.txt -> 2026-01/PROSES_laporan-2026-01.txt
Struktur akhir direktori:
[DIR] 2025-12
PROSES_catatan-lama.txt
[DIR] 2026-01
PROSES_draft-artikel.md
PROSES_laporan-2026-01.txt
[DIR] 2026-02
PROSES_laporan-2026-02.txt
[DIR] 2026-03
PROSES_foto-liburan.jpg
[DIR] backup
PROSES_catatan-lama.txt
PROSES_laporan-2026-01.txt
PROSES_laporan-2026-02.txt
Direktori kerja dihapus.Mari kita bedah bagian-bagian kunci dari kode di atas:
Files.createTempDirectory: membuat direktori unik di/tmpdengan prefixfs-demo-. Direktori otomatis dihapus di akhir viadeleteRecursively()agar tidak meninggalkan jejak.setLastModifieddengan epoch day: tanggal modifikasi diatur manual agar demo organize by date menghasilkan output deterministik. KonversiLocalDate.of(2026, 1, 15)ke milidetik sejak epoch memakaitoEpochDay() * 24 * 60 * 60 * 1000.renameTosederhana: methodFile.renameTo()mengembalikanBoolean. Bila false, rename gagal (misalnya target sudah ada atau permission ditolak). Untuk skripsi production, lebih baik gunakanFiles.move()yang melempar exception spesifik.- Filter
.extension == "txt": propertyextensionKotlin padaFilemengembalikan string setelah tanda titik terakhir. Filter ini memilih hanya file teks untuk di-copy ke folder backup. - Organize by date: setiap file dipindahkan ke subfolder
yyyy-MMberdasarkan tanggal modifikasi.LocalDate.ofEpochDay()mengkonversi milidetik epoch kembali ke tanggal untuk diformat. listTreerekursif: function ini menelusuri direktori secara rekursif dan mencetak dengan indentasi yang bertambah. Pola ini mirip dengan outputfind .atautreedi Unix.
Walk Directory Tree #
Untuk operasi yang memerlukan penelusuran penuh direktori, File.walkTopDown() dan File.walkBottomUp() menyediakan Sequence<File> yang efisien. Sequence bersifat lazy sehingga cocok untuk direktori besar.
fun cariFileBesar(root: File, ukuranMin: Long): List<File> {
return root.walkTopDown()
.filter { it.isFile && it.length() > ukuranMin }
.toList()
}walkTopDown() mengunjungi direktori induk sebelum anak. walkBottomUp() melakukan sebaliknya, berguna untuk operasi delete yang harus menghapus anak sebelum induk.
Referensi Lanjutan #
- Kotlin Command Line
— panduan resmi
kotlinCLI untuk menjalankan skrip otomasi berbasisjava.nio.file
Selanjutnya #
Sekarang kita bisa mengotomatiskan operasi file tunggal. Bab berikutnya membahas batch processing dengan paralelisme coroutines dan progress reporting, untuk skenario yang melibatkan ribuan file. Materi tersebut ada di chapter Batch Processing.