I/O Teks

Membaca dan menulis berkas teks baris demi baris dengan fgetc, fgets, fputs, fprintf, dan fscanf.

Daftar isi

Berkas teks menyimpan data sebagai deretan karakter yang masih dapat dibaca manusia, dan stdio.h menyediakan keluarga fungsi yang bekerja pada tingkat karakter, baris, serta data terformat. Topik ini membahas penulisan dengan fputs dan fprintf, pembacaan karakter dengan fgetc, pembacaan baris demi baris dengan fgets, dan pembacaan data terformat dengan fscanf. Setelah membacanya, isi berkas teks dapat ditulis dan dibaca dengan pola yang aman, termasuk mengetahui kapan pembacaan berakhir.

Fungsi fputs menulis satu teks ke stream apa adanya, tanpa penambahan pindah baris dan tanpa pemformatan. Bila isi yang ditulis perlu disusun dari beberapa nilai, fungsi fprintf bekerja persis seperti printf. Bedanya, argumen pertama fprintf adalah stream tujuan, sehingga hasilnya mengalir ke berkas alih-alih ke layar. Kedua fungsi mengembalikan nilai yang dapat diperiksa, yaitu bilangan tak negatif saat berhasil untuk fputs dan jumlah karakter tercetak untuk fprintf. Program berikut menyusun berkas rekap.txt dengan pemformatan kolom:

c tulis-rekap.c
#include <stdio.h>

int main(void) {
    FILE *berkas = fopen("rekap.txt", "w");
    if (berkas == NULL) {
        perror("fopen rekap.txt");
        return 1;
    }

    fprintf(berkas, "Jadwal piket kelas\n");
    fprintf(berkas, "------------------\n");
    fprintf(berkas, "%-8s %s\n", "Senin",  "Ali");
    fprintf(berkas, "%-8s %s\n", "Selasa", "Budi");
    fprintf(berkas, "%-8s %s\n", "Rabu",   "Citra");
    fclose(berkas);

    printf("rekap.txt selesai ditulis.\n");
    return 0;
}
text Keluaran
rekap.txt selesai ditulis.
bash
# kompilasi dan jalankan program penulis
gcc tulis-rekap.c -o tulis-rekap && ./tulis-rekap
# tampilkan isi berkas yang baru ditulis
cat rekap.txt
text Keluaran
rekap.txt selesai ditulis.
Jadwal piket kelas
------------------
Senin    Ali
Selasa   Budi
Rabu     Citra

Penentu %-8s meratakan nama hari ke kiri dalam kolom selebar delapan karakter, sehingga berkasnya tampak rapi ketika dibuka di editor mana pun. Semua penentu format yang berlaku pada printf berlaku pula di sini karena fprintf menggunakan mesin pemformatan yang sama dengan printf. Pindah baris \n dituliskan secara eksplisit pada setiap akhir baris, karena tidak ada fungsi penulis berkas yang menambahkannya secara otomatis.

Bila pemformatan tidak diperlukan, fputs menjadi pilihan yang lebih ringkas karena langsung menerima teks jadi. Nilai kembali kedua fungsi ini tetap layak diperiksa pada kode kritis, misalnya saat berkas yang ditulis tidak boleh berhenti setengah jalan. Disk penuh dapat menghentikan penulisan tanpa pesan apa pun.

Membaca satu karakter dengan fgetc#

Isi berkas belanja.txt berikut menjadi bahan bacaan untuk dua contoh selanjutnya:

text belanja.txt
Beras 5 kg
Minyak 2 L
Gula 1 kg

Fungsi fgetc membaca satu karakter berikutnya dari stream dan mengembalikan kodenya bertipe int. Nilai kembali sengaja bukan char agar EOF tidak pernah tertukar dengan kode karakter. EOF adalah penanda habisnya data yang bernilai negatif, sedangkan kode karakter semuanya bernilai nol ke atas. Pola baca yang paling umum adalah perulangan yang kondisinya membandingkan hasil fgetc dengan EOF:

c baca-belanja.c
#include <stdio.h>

int main(void) {
    FILE *berkas = fopen("belanja.txt", "r");
    if (berkas == NULL) {
        perror("fopen belanja.txt");
        return 1;
    }

    int c;
    int jumlah = 0;
    while ((c = fgetc(berkas)) != EOF) {
        putchar(c);
        jumlah++;
    }
    fclose(berkas);

    printf("Total karakter: %d\n", jumlah);
    return 0;
}
text Keluaran
Beras 5 kg
Minyak 2 L
Gula 1 kg
Total karakter: 32

Keluaran program sama persis dengan isi berkasnya, karena setiap karakter termasuk pindah baris ikut terbaca dan dicetak kembali oleh putchar. Total 32 karakter mencakup tiga pindah baris, sehingga penghitungan semacam ini juga berguna untuk statistik ukuran teks. Pembacaan per karakter lambat bagi berkas besar, tetapi pola ini menjadi dasar bagi fungsi pembaca baris berikutnya.

Perhatikan urutan operasi dalam kondisi perulangan tersebut, yaitu pembacaan dilakukan terlebih dahulu, hasilnya disimpan pada c, baru dibandingkan dengan EOF. Urutan ini menjamin setiap karakter terproses tepat satu kali dan perulangan berhenti pada bacaan yang gagal. Pola yang sama berlaku saat pembacaan diarahkan ke keyboard, karena getchar yang dibahas pada modul Dasar Bahasa pada dasarnya adalah fgetc untuk stream masukan standar.

Membaca baris dengan fgets#

Fungsi fgets membaca satu baris ke dalam buffer yang disediakan pemanggil. Argumen keduanya menentukan ukuran buffer, dan fgets tidak pernah menulis melebihi itu karena paling banyak n - 1 karakter yang dibaca, menyisakan satu slot untuk penanda akhir \0. Pembacaan berhenti setelah pindah baris, dan pindah baris itu ikut disimpan di dalam buffer. Nilai kembanya adalah pointer buffer saat berhasil dan NULL saat data sudah habis atau terjadi error:

c baca-baris.c
#include <stdio.h>

int main(void) {
    FILE *berkas = fopen("belanja.txt", "r");
    if (berkas == NULL) {
        perror("fopen belanja.txt");
        return 1;
    }

    char baris[100];
    int nomor = 1;
    while (fgets(baris, sizeof baris, berkas) != NULL) {
        printf("%d. %s", nomor, baris);
        nomor++;
    }
    fclose(berkas);
    return 0;
}
text Keluaran
1. Beras 5 kg
2. Minyak 2 L
3. Gula 1 kg

Perhatikan bahwa format "%d. %s" tidak diakhiri \n, karena pindah baris sudah terbawa di dalam buffer. Apabila baris perlu diproses tanpa pindah barisnya, misalnya untuk dibandingkan dengan teks lain, karakter itu dihapus terlebih dahulu. Kombinasi fungsi strcspn dari string.h dengan penanda \0 menjadi idiom yang ringkas untuk keperluan itu:

c Menghapus pindah baris sisa fgets
// tidak-dijalankan
baris[strcspn(baris, "\n")] = '\0';

Ekspresi tersebut mencari posisi pertama pindah baris dalam baris, lalu menempatkan penanda akhir tepat di sana. Bila tidak ada pindah baris, misalnya karena barisnya lebih panjang dari buffer, strcspn mengembalikan panjang teks dan penanda akhir ditempatkan di ujungnya. Dengan begitu, idiom ini aman dalam kedua keadaan.

Membaca data terformat dengan fscanf#

Data bernilai seperti daftar nama dan angka lebih praktis dibaca dengan fscanf. Fungsi ini bekerja seperti scanf, tetapi argumen pertamanya berupa stream sumber. Nilai kembanya berupa jumlah data yang berhasil dibaca serta diubah. Justru nilai kembali itulah yang menjadi kondisi perulangan, karena pembacaan berhenti tepat ketika data tidak lagi lengkap. Berkas nilai.txt berikut memuat tiga pasang nama dan angka:

text nilai.txt
Ali 90
Budi 75
Citra 88
c baca-nilai.c
#include <stdio.h>

int main(void) {
    FILE *berkas = fopen("nilai.txt", "r");
    if (berkas == NULL) {
        perror("fopen nilai.txt");
        return 1;
    }

    char nama[32];
    int nilai = 0;
    int jumlah = 0;
    int total = 0;

    while (fscanf(berkas, "%31s %d", nama, &nilai) == 2) {
        printf("%s: %d\n", nama, nilai);
        total += nilai;
        jumlah++;
    }
    fclose(berkas);

    if (jumlah > 0) {
        printf("Rata-rata: %.1f\n", (double)total / jumlah);
    }
    return 0;
}
text Keluaran
Ali: 90
Budi: 75
Citra: 88
Rata-rata: 84.3

Penentu %31s membaca satu kata paling banyak 31 karakter, menyisakan satu slot untuk \0 pada buffer 32 karakter. Selama pasangan kata dan angka lengkap, nilai kembali fscanf adalah 2 dan perulangan berlanjut. Saat berkas habis, nilainya bukan 2, sehingga perulangan berhenti dengan sendirinya. Perhitungan rata-rata membagi total dengan jumlah data, dan hasilnya ditampilkan dengan satu angka di belakang koma.

Kesalahan umum#

Kesalahan pertama adalah menampung hasil fgetc dalam variabel char. Bila tipe char pada suatu platform tidak mampu menampung nilai negatif, perbandingan dengan EOF tidak pernah benar dan perulangannya tidak pernah berhenti. Deklarasikan penampungnya sebagai int, karena justru tipe itulah yang dikembalikan fgetc.

Kesalahan kedua adalah lupa bahwa fgets menyimpan pindah baris di ujung buffer. Perbandingan strcmp(baris, "Beras 5 kg") menghasilkan nilai tidak sama karena teks yang tersimpan masih bersambung dengan \n, dan panjang baris yang dihitung selalu lebih satu. Hapus pindah barisnya dengan idiom strcspn di atas, atau pertimbangkan keberadaannya dalam perhitungan.

Kesalahan ketiga adalah mengabaikan nilai kembali fscanf dan langsung menggunakan variabel penampungnya. Saat berkas habis atau datanya tidak cocok format, variabel lama tetap terpakai sehingga data terakhir tercetak dua kali tanpa disadari. Kesalahan sejenis berlaku pada fgets dan fgetc juga, karena semua fungsi baca menuntut nilai kembanya diperiksa sebelum hasilnya digunakan. Kondisikan perulangan pada nilai kembali fungsi bacanya, dan periksa habis atau error secara tuntas pada topik Penanganan Error Berkas.

Ringkasan#

  • fputs menulis teks apa adanya ke stream, sedangkan fprintf menulis dengan pemformatan lengkap milik printf.
  • fgetc membaca satu karakter dan mengembalikan int, dengan EOF sebagai penanda habisnya data.
  • fgets membaca satu baris ke buffer berukuran tetap, menyertakan pindah barisnya, dan mengembalikan NULL saat berakhir.
  • fscanf mengubah teks berkas menjadi nilai terformat, dan nilai kembanya menjadi kondisi alami perulangan baca.

Referensi#