Setiap program C otomatis memiliki tiga stream standar yang dibuka oleh sistem operasi saat program dimulai: stdin untuk input, stdout untuk output normal, dan stderr untuk pesan kesalahan. Memisahkan stdout dan stderr tampak seperti detail yang sepele, tetapi pemisahan inilah yang memungkinkan shell memredirect hasil program ke berkas sambil tetap menampilkan pesan error di terminal. Pada chapter ini kita akan memahami perbedaan ketiga stream, kapan harus memakai fprintf(stderr, ...) alih-alih printf, dan bagaimana exit code memberitahu shell apakah program sukses atau gagal.
Tiga Stream Standar #
Ketiga stream standar didefinisikan di header <stdio.h> sebagai pointer FILE *. Fungsi printf dan scanf sebenarnya adalah shortcut untuk fprintf(stdout, ...) dan fscanf(stdin, ...). Berikut ringkasan tiga stream:
| Stream | Tujuan | Default perangkat | Buffering default |
|---|---|---|---|
stdin | Input standar | Keyboard (terminal) | Line-buffered |
stdout | Output normal | Layar terminal | Line-buffered (terminal) / Full-buffered (file) |
stderr | Output pesan kesalahan | Layar terminal | Unbuffered |
#include <stdio.h>
int main(void) {
/* Pesan normal ke stdout */
printf("Ini pesan info ke stdout.\n");
fprintf(stdout, "Pesan info memakai fprintf ke stdout.\n");
/* Pesan error ke stderr */
fprintf(stderr, "Ini pesan error ke stderr.\n");
/* stdin dibaca lewat getchar */
printf("Masukkan satu karakter: ");
int ch = fgetc(stdin);
fprintf(stdout, "Karakter: %c\n", ch);
return 0;
}Output yang Dihasilkan (input: A):
Ini pesan info ke stdout.
Pesan info memakai fprintf ke stdout.
Ini pesan error ke stderr.
Masukkan satu karakter: Karakter: AMari kita telaah baris-baris kode di atas:
- Baris 5 (
printf("Ini pesan info ke stdout.\n");):printfselalu menulis kestdout. Tidak ada cara langsung untuk membuatprintfmenulis kestderr. - Baris 6 (
fprintf(stdout, "...")):fprintfadalah versi umum dariprintfyang menerima stream tujuan sebagai argumen pertama. Melewatkanstdoutmenghasilkan perilaku yang sama persis denganprintf. - Baris 9 (
fprintf(stderr, "Ini pesan error ke stderr.\n");): pesan ini ditulis kestderr, bukanstdout. Saat program dijalankan langsung di terminal, keduanya tampak sama di layar. Perbedaan baru terlihat saat kita melakukan redirect. - Baris 12 (
int ch = fgetc(stdin);):fgetcmembaca satu karakter dari stream yang diberikan. Melewatkanstdinsama dengan memanggilgetchar(). - Baris 13 (
fprintf(stdout, "Karakter: %c\n", ch);): menulis hasil kembali kestdout. Boleh juga memakaiputchar(ch)atauprintf(...)— keduanya ekuivalen untuk kasus ini.
Redirect stdout dan stderr di Shell #
Untuk melihat perbedaan praktis antara stdout dan stderr, kita bisa memredirect hanya stdout ke berkas dan membiarkan stderr tetap mengalir ke terminal. Pola redirect yang umum: > untuk stdout, 2> untuk stderr, dan 2>&1 untuk menggabungkan keduanya ke stdout.
$ ./test_streams1 > /tmp/only_stdout.txt
$ cat /tmp/only_stdout.txtOutput yang Dihasilkan (setelah redirect stdout ke berkas):
Ini pesan info ke stdout.
Pesan info memakai fprintf ke stdout.
Masukkan satu karakter: Karakter: ASementara itu, di terminal tempat perintah dijalankan, pesan Ini pesan error ke stderr. tetap muncul karena stderr tidak ikut ter-redirect. Inilah inti pemisahan kedua stream: pengguna bisa menyimpan output program ke berkas tanpa kehilangan pesan error yang penting untuk debugging.
Memakai stderr dan exit code dengan Benar #
Saat program menemui kesalahan, kita biasanya ingin melakukan dua hal: mencetak pesan yang menjelaskan masalah ke stderr, dan mengembalikan exit code bukan-nol ke shell. Konstanta EXIT_FAILURE (nilai 1) dan EXIT_SUCCESS (nilai 0) dari <stdlib.h> membuat kode lebih jelas dibanding angka literal.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
/* Argumen kurang: tulis pesan error ke stderr lalu exit 1 */
fprintf(stderr, "Error: argumen tidak cukup.\n");
fprintf(stderr, "Penggunaan: %s <nama_file>\n", argv[0]);
return EXIT_FAILURE;
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
/* File tidak ada: tulis pesan error ke stderr */
fprintf(stderr, "Error: tidak bisa membuka file '%s'\n", argv[1]);
return EXIT_FAILURE;
}
/* Sukses: tulis ke stdout */
printf("Berhasil membuka file: %s\n", argv[1]);
int ch;
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
fclose(fp);
return EXIT_SUCCESS;
}Output yang Dihasilkan untuk berbagai skenario:
$ ./program
Error: argumen tidak cukup.
Penggunaan: ./program <nama_file>
$ echo $?
1
$ ./program /tmp/data.txt
Berhasil membuka file: /tmp/data.txt
isi berkas
$ echo $?
0
$ ./program /tmp/tidak_ada.txt
Error: tidak bisa membuka file '/tmp/tidak_ada.txt'
$ echo $?
1Mari kita telaah baris-baris kode di atas:
- Baris 4 (
int main(int argc, char *argv[])): signaturemaindengan parameterargc(jumlah argumen) danargv(array string argumen).argv[0]adalah nama program itu sendiri. - Baris 6 (
if (argc < 2)): jika pengguna tidak memberikan argumen (hanya menjalankan./programtanpa nama berkas),argcbernilai1. Kondisi ini terpenuhi. - Baris 8-9 (
fprintf(stderr, ...)): menulis dua baris pesan error kestderr. Pesan pertama menjelaskan masalah, pesan kedua menunjukkan format penggunaan yang benar. - Baris 10 (
return EXIT_FAILURE;): mengembalikan exit code1ke shell.EXIT_FAILURElebih jelas daripada angka literal1. - Baris 13 (
FILE *fp = fopen(argv[1], "r");): membuka berkas yang namanya diberikan sebagai argumen pertama. Mode"r"berarti read-only. - Baris 14 (
if (fp == NULL)): jikafopengagal (misalnya berkas tidak ada), fungsi mengembalikanNULL. Pemeriksaan ini wajib sebelum memakaifp. - Baris 17 (
return EXIT_FAILURE;): kembalikan exit code gagal, dengan pesan error yang menjelaskan berkas mana yang gagal dibuka. - Baris 22 (
while ((ch = fgetc(fp)) != EOF)): membaca karakter satu per satu sampai akhir berkas. Pola ini adalah idiom standar untuk membaca seluruh isi berkas. - Baris 23 (
putchar(ch);):putcharmenulis satu karakter kestdout— ekuivalen denganfputc(ch, stdout). - Baris 25 (
return EXIT_SUCCESS;): mengembalikan exit code0(sukses).
Menggabungkan stdin, stdout, dan stderr #
Pada contoh terakhir, kita akan mempraktikkan ketiga stream dalam satu program yang membaca nama dari stdin, menulis sapaan ke stdout, dan menulis peringatan ke stderr jika input kosong atau berakhir lebih awal.
#include <stdio.h>
#include <string.h>
int main(void) {
char nama[64];
/* Baca dari stdin, tulis hasil ke stdout */
fprintf(stdout, "Siapa nama Anda? ");
if (fgets(nama, sizeof(nama), stdin) != NULL) {
nama[strcspn(nama, "\n")] = '\0';
if (strlen(nama) == 0) {
fprintf(stderr, "Peringatan: nama kosong!\n");
fprintf(stdout, "Halo, orang tanpa nama!\n");
} else {
fprintf(stdout, "Halo, %s!\n", nama);
}
} else {
/* stdin berakhir sebelum ada input */
fprintf(stderr, "Error: input berakhir (EOF).\n");
return 1;
}
return 0;
}Output yang Dihasilkan untuk berbagai input:
$ echo "Budi" | ./program
Siapa nama Anda? Halo, Budi!
$ echo "" | ./program
Siapa nama Anda? Halo, orang tanpa nama!
(perintah ini juga mencetak "Peringatan: nama kosong!" ke stderr)
$ printf "" | ./program
Siapa nama Anda? Error: input berakhir (EOF).
$ echo $?
1Mari kita telaah baris-baris kode di atas:
- Baris 6 (
fprintf(stdout, "Siapa nama Anda? ");): prompt tanpa\nagar kursor tetap di baris yang sama. Pemakaianfprintf(stdout, ...)sengaja ditulis eksplisit untuk menekankan bahwa prompt ini kestdout. - Baris 7 (
if (fgets(nama, sizeof(nama), stdin) != NULL)): membaca satu baris daristdin. JikafgetsmengembalikanNULL, berartistdinsudah mencapai EOF tanpa ada karakter yang bisa dibaca. - Baris 8 (
nama[strcspn(nama, "\n")] = '\0';): idiom penghapusan newline yang sudah kita pelajari di chapterfgets. - Baris 10 (
if (strlen(nama) == 0)): jika setelah newline dihapus string menjadi kosong, berarti pengguna hanya menekan Enter tanpa mengetik apa-apa. Kasus ini dilaporkan sebagai peringatan kestderr. - Baris 11 (
fprintf(stderr, "Peringatan: nama kosong!\n");): pesan peringatan kestderr. Walaupun program tidak benar-benar gagal, peringatan ini berguna untuk debugging karena tidak akan ikut ter-redirect saat pengguna memredirectstdout. - Baris 12 (
fprintf(stdout, "Halo, orang tanpa nama!\n");): tetap memberi respons kestdoutagar program tetap ramah. Output ini bisa ter-redirect ke berkas tanpa kontaminasi pesan peringatan. - Baris 16 (
fprintf(stderr, "Error: input berakhir (EOF).\n");): jikafgetsmengembalikanNULL, kita melaporkan kestderrdan mengembalikan exit code1.