Path file bukan sekadar string—path memiliki struktur (direktori induk + nama), batasan panjang, dan konvensi yang berbeda antar sistem operasi. Pada Windows, pemisah direktori adalah backslash \, sedangkan pada POSIX adalah slash /. Path absolut (/tmp/x) berbeda dari path relatif (./x atau ../y). Pada chapter ini kita akan belajar membangun path lintas-platform, memisahkan dirname dan basename, serta memeriksa keberadaan dan tipe file dengan stat. Pengetahuan ini krusial untuk kode yang berjalan di banyak platform.
Path Separator: Tanda Pemisah Direktori #
Setiap sistem operasi memiliki konvensi pemisah direktori:
- POSIX (Linux, macOS):
/(slash). Contoh:/tmp/data/config.ini - Windows:
\(backslash). Contoh:C:\Users\Budi\data\config.ini
Untuk menulis kode lintas-platform, kita pakai preprocessor directive untuk memilih pemisah yang benar. Pada Windows, fungsi file I/O Windows API juga menerima / sebagai pemisah (sebagai kompatibilitas), sehingga banyak kode POSIX bisa langsung berjalan.
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#define PATH_SEP '\\'
#else
#define PATH_SEP '/'
#endif
int main(void) {
printf("=== Bangun path lintas platform ===\n");
const char *folder = "/tmp";
const char *subdir = "myapp";
const char *file = "config.ini";
char path[256];
snprintf(path, sizeof(path), "%s%c%s%c%s",
folder, PATH_SEP, subdir, PATH_SEP, file);
printf(" Path: %s\n", path);
printf(" Panjang: %zu\n", strlen(path));
return 0;
}Output yang Dihasilkan:
=== Bangun path lintas platform ===
Path: /tmp/myapp/config.ini
Panjang: 21Pada output di atas, path dibangun dengan pemisah / karena kita berjalan di Linux. Bila kode yang sama di-compile di Windows (_WIN32 terdefinisi), pemisah akan menjadi \ dan path menjadi \tmp\myapp\config.ini (atau C:\tmp\myapp\config.ini bila folder diawali dengan drive).
Mari kita telaah baris-baris kode di atas:
- Baris 4-7 (
#ifdef _WIN32 ... #define PATH_SEP '\\' ... #else ... #define PATH_SEP '/'): makro preprocessor memilih pemisah yang sesuai platform._WIN32terdefinisi pada target Windows (baik 32-bit maupun 64-bit). - Baris 16-17 (
snprintf(path, sizeof(path), "%s%c%s%c%s", ...)): membangun path dengan aman.%cmenyisipkan karakter pemisah.snprintfmembatasi penulisan kesizeof(path)byte untuk mencegah buffer overflow. - Baris 19 (
printf(" Panjang: %zu\n", strlen(path));): mencetak panjang path.%zuadalah specifier untuksize_t.
Memisahkan dirname dan basename #
POSIX menyediakan dua fungsi di <libgen.h> untuk memisahkan path:
dirname(path)— mengembalikan direktori induk. Bila path tidak ada/, mengembalikan".".basename(path)— mengembalikan nama file/komponen terakhir.
Kedua fungsi ini bisa memodifikasi string yang dilewatkan—karena itu selalu salin path ke buffer lokal sebelum memanggil.
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <string.h>
#include <libgen.h>
int main(void) {
printf("=== Pisahkan dirname dan basename ===\n");
const char *paths[] = {
"/tmp/data/config.ini",
"config.ini",
"/tmp/",
"/",
NULL
};
for (int i = 0; paths[i]; i++) {
/* dirname dan basename bisa memodifikasi string, salin dulu */
char buf1[256], buf2[256];
strncpy(buf1, paths[i], sizeof(buf1)-1);
buf1[sizeof(buf1)-1] = 0;
strncpy(buf2, paths[i], sizeof(buf2)-1);
buf2[sizeof(buf2)-1] = 0;
char *dir = dirname(buf1);
char *base = basename(buf2);
printf(" %-30s -> dir=\"%s\" base=\"%s\"\n",
paths[i], dir, base);
}
return 0;
}Output yang Dihasilkan:
=== Pisahkan dirname dan basename ===
/tmp/data/config.ini -> dir="/tmp/data" base="config.ini"
config.ini -> dir="." base="config.ini"
/tmp/ -> dir="/" base="tmp"
/ -> dir="/" base="/"Perhatikan hasil untuk "config.ini" (path relatif tanpa pemisah): dirname mengembalikan "." (direktori saat ini), bukan string kosong. Untuk "/tmp/" (path direktori dengan trailing slash): dirname mengembalikan "/" (akar) dan basename mengembalikan "tmp" (komponen terakhir sebelum slash). Untuk "/" (akar): keduanya mengembalikan "/"—ini kasus khusus.
Memeriksa Eksistensi dan Tipe File dengan stat #
Untuk mengetahui apakah path tertentu ada (dan apa jenisnya: file biasa, direktori, device, dll.), kita pakai stat(2) dari <sys/stat.h> (POSIX).
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <sys/stat.h>
int main(void) {
printf("=== Cek eksistensi file dengan stat ===\n");
const char *targets[] = {
"/tmp/test_path_dir.txt",
"/tmp/nonexistent_file_xyz",
"/tmp",
NULL
};
/* Buat dummy file dulu */
FILE *f = fopen("/tmp/test_path_dir.txt", "w");
if (f) { fputs("hello", f); fclose(f); }
for (int i = 0; targets[i]; i++) {
struct stat st;
if (stat(targets[i], &st) == 0) {
const char *tipe = S_ISDIR(st.st_mode) ? "direktori" :
S_ISREG(st.st_mode) ? "file biasa" :
"lainnya";
printf(" %-30s -> ADA (%s, %lld byte)\n",
targets[i], tipe, (long long)st.st_size);
} else {
printf(" %-30s -> TIDAK ADA\n", targets[i]);
}
}
return 0;
}Output yang Dihasilkan:
=== Cek eksistensi file dengan stat ===
/tmp/test_path_dir.txt -> ADA (file biasa, 5 byte)
/tmp/nonexistent_file_xyz -> TIDAK ADA
/tmp -> ADA (direktori, 24576 byte)Perhatikan output di atas: stat mengembalikan 0 bila path ada (berhasil), -1 bila tidak (dengan errno di-set ke ENOENT). Macro S_ISREG dan S_ISDIR memeriksa tipe file dari field st_mode struct stat. Untuk file biasa, st_size adalah ukuran dalam byte—untuk direktori, st_size biasanya ukuran inode atau directory entry (24576 byte pada /tmp di sistem kami).
Batasan Panjang Path #
Setiap sistem memiliki batasan panjang path dan nama file:
PATH_MAX— panjang maksimum path absolut. Linux: 4096 byte. Windows: 260 (tanpa prefix\\?\), 32767 dengan prefix.NAME_MAX— panjang maksimum nama file (satu komponen). Linux: 255 byte. Windows: 255 karakter.
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <limits.h>
int main(void) {
printf("=== PATH_MAX dan batasan panjang ===\n");
printf(" PATH_MAX = %d\n", PATH_MAX);
printf(" NAME_MAX = %d\n", NAME_MAX);
return 0;
}Output yang Dihasilkan:
=== PATH_MAX dan batasan panjang ===
PATH_MAX = 4096
NAME_MAX = 255Selalu alokasikan buffer path minimal PATH_MAX byte. Untuk path yang lebih panjang (jarang, tetapi mungkin di sistem deeply nested), gunakan pathconf("/", _PC_PATH_MAX) untuk mendapatkan batasan aktual.
Membuat Direktori dengan mkdir #
POSIX menyediakan mkdir(2) untuk membuat direktori:
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(void) {
printf("=== Membuat direktori dengan mkdir ===\n");
/* mkdir hanya membuat satu level, bukan recursive */
if (mkdir("/tmp/test_mkdir_demo", 0755) == 0) {
printf(" /tmp/test_mkdir_demo berhasil dibuat\n");
} else {
perror("mkdir pertama");
}
/* mkdir kedua gagal: sudah ada */
if (mkdir("/tmp/test_mkdir_demo", 0755) == 0) {
printf(" Tidak seharusnya berhasil\n");
} else {
perror("mkdir kedua (sudah ada)");
}
/* mkdir pada path nested gagal: parent belum ada */
if (mkdir("/tmp/test_mkdir_demo/sub/deep", 0755) == 0) {
printf(" Tidak seharusnya berhasil\n");
} else {
perror("mkdir nested (parent belum ada)");
}
return 0;
}Output yang Dihasilkan:
=== Membuat direktori dengan mkdir ===
/tmp/test_mkdir_demo berhasil dibuat
mkdir kedua (sudah ada): File exists
mkdir nested (parent belum ada): No such file or directoryBaris-baris mkdir kedua ...: File exists dan mkdir nested ...: No such file or directory dicetak oleh perror ke stderr. Karena stderr tidak di-buffer (sedangkan stdout di-buffer), output stderr sering muncul terlebih dahulu bila keduanya dipipa ke terminal yang sama. Pada contoh di atas, urutan visual dapat sedikit berbeda tergantung shell dan redirection.
Perhatikan tiga skenario di atas: panggilan pertama mkdir("/tmp/test_mkdir_demo", 0755) berhasil; panggilan kedua gagal dengan EEXIST (file exists); panggilan ketiga gagal dengan ENOENT karena direktori induk sub belum ada. mkdir(2) POSIX tidak rekursif—untuk membuat /a/b/c, kita harus memanggil mkdir("/a"), mkdir("/a/b"), mkdir("/a/b/c") secara berurutan. Beberapa sistem menyediakan mkdir -p rekursif via nftw atau implementasi manual loop.
Path Relatif vs Absolut #
Path absolut dimulai dari akar (/ di POSIX, C:\ di Windows). Path relatif diinterpretasikan relatif terhadap current working directory (CWD) proses. CWD bisa diubah dengan chdir(2) dan dibaca dengan getcwd(3).
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
int main(void) {
char cwd[PATH_MAX];
if (getcwd(cwd, sizeof(cwd)) != NULL) {
printf("CWD saat ini: %s\n", cwd);
} else {
perror("getcwd");
}
/* Ubah CWD */
if (chdir("/tmp") == 0) {
printf("Setelah chdir(/tmp):\n");
if (getcwd(cwd, sizeof(cwd)) != NULL) {
printf(" CWD baru: %s\n", cwd);
}
/* Sekarang path relatif "test_data.bin" merujuk /tmp/test_data.bin */
FILE *f = fopen("test_path_dir.txt", "r");
if (f) {
printf(" test_path_dir.txt bisa dibuka (relatif)\n");
fclose(f);
}
} else {
perror("chdir");
}
return 0;
}Output yang Dihasilkan:
CWD saat ini: /home/z/my-project
Setelah chdir(/tmp):
CWD baru: /tmp
test_path_dir.txt bisa dibuka (relatif)Setelah chdir("/tmp"), CWD berubah menjadi /tmp. Path relatif "test_path_dir.txt" sekarang merujuk ke /tmp/test_path_dir.txt—yang memang ada karena kita membuatnya di demo sebelumnya.