Program command line yang baik dirancang untuk bekerja sama dengan program lain. Filosofi Unix mengajarkan setiap tool melakukan satu tugas dengan baik, lalu menyambungkan hasilnya lewat pipe — dan sambungan itu hanya rapi bila program memisahkan tiga aliran data standar: stdin untuk masukan, stdout untuk hasil kerja, dan stderr untuk pesan diagnostik. Pada chapter ini kita akan belajar menulis dan membaca ketiga stream tersebut dari skrip PHP, merangkai pipeline antar perintah, serta mendeteksi apakah skrip sedang berjalan di terminal interaktif atau dalam mode otomatis.

Tiga Stream Standar #

Setiap proses yang dimulai dari shell otomatis menerima tiga stream yang sudah terbuka. PHP CLI menyediakan konstanta STDIN, STDOUT, dan STDERR berupa resource stream untuk ketiganya, plus wrapper php://stdin, php://stdout, dan php://stderr untuk fungsi yang meminta nama stream:

StreamDeskriptor berkasKonstanta PHPKegunaan
stdin0STDINmasukan: argumen ketikan, isi pipe, isi berkas
stdout1STDOUThasil kerja skrip — data yang layak dipipakan
stderr2STDERRpesan galat, peringatan, dan progres

Pemisahan stdout dan stderr adalah kunci otomasi. Pengguna manusia melihat keduanya tercampur di layar, tetapi shell memperlakukan keduanya sebagai saluran berbeda — pemisahan inilah yang memungkinkan pesan galat tetap terlihat meski hasil kerja dialihkan ke berkas.

Ada tiga cara menulis ke stream stdout — semuanya menghasilkan keluaran yang sama:

php
<?php
echo "Lewat echo\n";
fwrite(STDOUT, "Lewat konstanta STDOUT\n");
file_put_contents("php://stdout", "Lewat wrapper php://stdout\n");

Output yang Dihasilkan:

text
Lewat echo
Lewat konstanta STDOUT
Lewat wrapper php://stdout

Konstruksi echo adalah cara paling umum; secara default echo menulis ke stdout sehingga untuk kebanyakan skrip kita tidak perlu menyebut stream-nya secara eksplisit. Konstanta STDOUT dipakai bersama fwrite() ketika kita ingin penulisan stream yang eksplisit dan seragam dengan penulisan ke STDERR. Wrapper php://stdout berguna untuk fungsi berbasis nama berkas seperti file_put_contents() yang tidak menerima resource secara langsung.

Mengirim Diagnostik ke stderr #

Pesan galat, peringatan, dan informasi progres seharusnya dikirim ke stderr — bukan ke stdout — agar tidak mencemari hasil kerja skrip. Perhatikan skrip pencatat sederhana berikut:

php
<?php
fwrite(STDOUT, "Memproses data...\n");
fwrite(STDERR, "Peringatan: dua baris terlewati\n");
fwrite(STDOUT, "Selesai.\n");

Output yang Dihasilkan:

text
Memproses data...
Selesai.

Baris peringatan tidak tampak pada blok output di atas karena blok tersebut hanya menangkap stdout — dan justru itulah intinya: data hasil kerja tetap bersih meski skrip berbicara banyak. Di terminal interaktif kedua stream sama-sama tampil di layar sehingga manusia melihat ketiga barisnya:

bash
php catat.php

Output yang Dihasilkan:

text
Memproses data...
Peringatan: dua baris terlewati
Selesai.

Shell menyediakan operator 2> untuk mengalihkan stream bernomor 2 (stderr) ke berkas. Hasil kerja tetap tampil di layar sementara diagnostik tersimpan rapi:

bash
php catat.php 2> galat.log
cat galat.log

Output yang Dihasilkan:

text
Memproses data...
Selesai.
Peringatan: dua baris terlewati

Perintah pertama menampilkan dua baris stdout saja karena stderr dialihkan ke galat.log; perintah kedua (cat) lalu menampilkan isi berkas tersebut. Variasi lain yang sering dipakai adalah 2> /dev/null untuk membuang pesan galat sepenuhnya.

Membaca stdin #

Skrip yang berperan sebagai filter membaca masukan dari stdin alih-alih meminta nama berkas. Fungsi stream_get_contents() membaca seluruh isi stdin sampai EOF dalam satu panggilan:

php
<?php
$data = stream_get_contents(STDIN);
echo "Diterima " . strlen($data) . " byte.\n";
echo strtoupper($data);

Karena contoh ini menunggu data dari stdin, sistem verifikasi chapter melewatkan eksekusinya. Mari kita uji dengan pipe sungguhan:

bash
echo "belajar php cli" | php kapital.php

Output yang Dihasilkan:

text
Diterima 16 byte.
BELAJAR PHP CLI

Perintah echo menambahkan satu karakter baris baru di akhir, sehingga panjang data yang diterima skrip adalah 16 byte. Untuk masukan baris demi baris — misalnya saat memproses berkas besar atau aliran yang belum selesai — gunakan fgets(STDIN) di dalam loop:

php
<?php
while (($line = fgets(STDIN)) !== false) {
    $line = trim($line);
    if ($line !== "") {
        echo "- $line\n";
    }
}

fgets() mengembalikan false saat EOF sehingga loop berhenti alami ketika input habis. Baris kosong dilewati agar daftar hasil tetap rapat:

bash
printf "makan\n\nminum\n" | php daftar.php

Output yang Dihasilkan:

text
- makan
- minum

Merangkai Pipeline #

Kekuatan sebenarnya dari pemisahan stream muncul saat beberapa perintah disambung dengan operator pipe |: stdout perintah pertama menjadi stdin perintah kedua. Skrip daftar.php dari bagian sebelumnya dapat langsung disambung ke tool standar seperti sort:

bash
printf "pear\napel\nmangga\n" | php daftar.php | sort

Output yang Dihasilkan:

text
- apel
- mangga
- pear

Tidak satu pun skrip di rantai ini perlu tahu keberadaan skrip lain. Skrip daftar.php hanya membaca stdin dan menulis ke stdout; sort mengurutkan hasilnya tanpa peduli siapa pengirimnya. Skrip grepper.php dari chapter sebelumnya pun bisa menjadi mata rantai serupa — asalkan hasil kerja selalu ditulis ke stdout dan pesan galat ke stderr, skrip Anda otomatis kompatibel dengan seluruh ekosistem command line.

Mendeteksi Terminal Interaktif #

Kadang skrip perlu berperilaku berbeda: bertanya kepada manusia saat dijalankan interaktif, tetapi bekerja senyap mungkin saat dipipakan. Fungsi posix_isatty() memeriksa apakah sebuah stream terhubung ke terminal sungguhan:

php
<?php
if (posix_isatty(STDIN)) {
    echo "Input dari terminal - mode interaktif\n";
} else {
    echo "Input dari pipe atau berkas - mode otomatis\n";
}

Output yang Dihasilkan:

text
Input dari pipe atau berkas - mode otomatis

Blok output di atas menampilkan cabang “pipe atau berkas” karena eksekusi otomatis chapter ini menangkap output lewat pipe, bukan terminal — persis seperti demonstrasi berikut:

bash
echo "data" | php mode.php

Output yang Dihasilkan:

text
Input dari pipe atau berkas - mode otomatis

Saat Anda menjalankan php mode.php langsung di terminal, posix_isatty(STDIN) mengembalikan true dan cabang interaktif yang tampil. Pola pemeriksaan ini juga dipakai untuk keputusan lain, misalnya menonaktifkan warna otomatis saat output bukan terminal — teknik yang akan kita praktikkan pada chapter Console Applications.

Ringkasan #

stdin adalah pintu masuk data, stdout adalah hasil kerja yang layak dipipakan, dan stderr adalah saluran diagnostik yang menjaga data tetap bersih. Baca masukan dengan stream_get_contents() atau loop fgets(), tulis hasil dengan echo atau fwrite(STDOUT, ...), kirim galat ke fwrite(STDERR, ...), dan gunakan posix_isatty() untuk membedakan mode interaktif dari mode otomatis. Stream yang tertata menjadikan skrip Anda warga yang baik di ekosistem shell — namun ada satu bahasa lagi yang dibaca shell dari skrip Anda: exit code, pembahasan chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

contoh.php

Unduh berkas
php
<?php

// contoh.php — rangkuman Chapter stdin, stdout, dan stderr.
// Jalankan dengan: php contoh.php
// Saat dijalankan tanpa input, pembacaan stdin langsung menerima EOF
// sehingga skrip tetap berakhir dengan rapi.

fwrite(STDOUT, "[stdout] mulai membaca stdin\n");

$data = stream_get_contents(STDIN);
fwrite(STDOUT, sprintf("[stdout] diterima %d byte\n", strlen($data)));

if (posix_isatty(STDIN)) {
    fwrite(STDERR, "[stderr] stdin adalah terminal\n");
} else {
    fwrite(STDERR, "[stderr] stdin bukan terminal (pipe atau EOF)\n");
}