Subshells & Process Substitution
Shell menyediakan beberapa cara menggabungkan perintah — subshell, grouped commands, command substitution, dan process substitution. Paham perbedaannya menutup banyak "kenapa variabel saya hilang?" dan membuka pola yang elegan untuk tugas kompleks.
Subshell ( ... )
Kurung biasa menjalankan perintah di subshell — proses baru yang meng-inherit environment tapi perubahannya tidak berefek ke parent:
(cd /tmp && ls)
# cd terjadi di subshell saja — kamu masih di direktori semula
pwd
# masih direktori asal, bukan /tmp
# Variabel juga terisolasi
X=1
(X=99; echo "inside: $X") # inside: 99
echo "outside: $X" # outside: 1
Kapan pakai subshell:
- Isolasi
cd— berpindah sementara tanpa ganggu parent. - Isolasi variabel / env — eksperimen tanpa kontaminasi.
- Pipeline — kanan kiri pipe otomatis jadi subshell di bash.
# Simpan + restore env dengan mudah
(
export DEBUG=1
export PATH="/custom:$PATH"
./flaky-program
)
# Kembali ke PATH normal otomatis
Grouped Commands { ... ; }
Kurung kurawal tidak membuat subshell — perintah jalan di shell saat ini:
{ cd /tmp && ls ; } # cd BENERAN pindah
pwd # /tmp sekarang
X=1
{ X=99; echo "inside: $X"; } # inside: 99
echo "outside: $X" # outside: 99 (berubah!)
Syntax picky: butuh spasi setelah { dan titik koma / newline sebelum }.
Kapan pakai grouped:
- Gabungkan output beberapa perintah ke satu redirect:
{ echo "=== Header ==="; date; uname -a; } > info.txt
# Tanpa kurung, hanya uname yang ke-redirect
- Butuh side effects (perubahan variabel, cd) tetap bertahan.
Command Substitution $( ... ) vs Backtick
Ambil output perintah dan sisipkan sebagai string:
TODAY=$(date +%Y-%m-%d)
echo "Hari ini: $TODAY"
FILES=$(ls | wc -l)
echo "$FILES file di direktori ini"
Sintaks lama pakai backtick `cmd`. Hindari — $() lebih enak karena:
- Bisa di-nest —
$(outer $(inner))vs`outer \`inner\`` (harus escape). - Lebih mudah dibaca — backtick mudah terlihat seperti single quote.
- Quoting lebih waras — kurang jebakan escape.
# Nested: modern jelas
BRANCH=$(git rev-parse --abbrev-ref $(git symbolic-ref HEAD))
# Backtick: butuh escape berlapis, mudah salah
BRANCH=`git rev-parse --abbrev-ref \`git symbolic-ref HEAD\``
Process Substitution <(cmd) dan >(cmd)
Pola paling powerful dari seksi ini — perintah diperlakukan seakan-akan file.
<(cmd) — Output sebagai File
Shell buat file khusus (biasanya /dev/fd/63) yang berisi output perintah. Cocok untuk perintah yang butuh argumen file tapi kamu punya stream:
# Bandingkan output dua perintah
diff <(sort a.txt) <(sort b.txt)
# tanpa ini: harus buat file temp dulu
# Compare dua branch git
diff <(git show main:config.json) <(git show feature:config.json)
# grep dua sumber bersamaan
grep -F -f <(cat important-lines.txt) haystack.txt
# Feed output ke command yang baca argv
wc -l <(find . -name "*.js")
>(cmd) — Input sebagai Proses
Kebalikannya — sesuatu yang menulis ke "file" sebenarnya mengirim ke stdin perintah:
# tee ke banyak pipeline
ls -la | tee >(grep "\.log" > logs.txt) >(grep "\.js" > jsfiles.txt) > all.txt
# Satu ls → split ke tiga output berbeda
# Hash checksum sambil copy
tar -cz data/ | tee >(sha256sum > data.tar.gz.sha256) > data.tar.gz
Cek dukungan:
- Bash & zsh: ya.
sh/dash: tidak. Portable script harus pakai file temp.
Named Pipe (mkfifo)
Untuk komunikasi antar proses yang tahan lama (tidak sekali pakai seperti |):
mkfifo /tmp/mypipe
# Terminal 1: tulis
echo "hello" > /tmp/mypipe
# Terminal 2: baca
cat /tmp/mypipe
# → hello
Penulis akan blok sampai ada yang baca, dan sebaliknya. Berguna untuk IPC sederhana antar script. Process substitution sebenarnya jalan di atas konsep ini di balik layar.
Kapan Pakai Apa — Cheat Sheet
| Pola | Subshell? | Ubah parent? | Cocok untuk |
|---|---|---|---|
(cmd; cmd) |
Ya | Tidak | Isolasi cd/env/variabel |
{ cmd; cmd; } |
Tidak | Ya | Group redirect output |
$(cmd) |
Ya | Tidak | Ambil string dari output |
<(cmd) |
Ya | Tidak | Kasih output ke command yang baca file |
>(cmd) |
Ya | Tidak | Split output ke banyak consumer |
mkfifo |
Tidak | Tidak | IPC antar proses terpisah |
Contoh Praktis Gabungan
# Cek diff dua API response
diff <(curl -s https://api.example.com/v1/users | jq -S .) \
<(curl -s https://api.example.com/v2/users | jq -S .)
# Backup folder sambil hitung hash — tanpa file temp
tar -cz -C / etc/ | tee >(sha256sum > backup.sha256) | gzip > backup.tar.gz
# Install + save log tanpa kehilangan output di terminal
apt install nginx 2>&1 | tee >(grep -i error > install-errors.log)
# Jalankan migration di subshell supaya env bersih
(
export NODE_ENV=production
export DATABASE_URL="postgres://..."
npm run migrate
)
# NODE_ENV & DATABASE_URL kembali ke nilai sebelumnya
Tips
- Default ke
{ ... }untuk group, pakai( ... )hanya saat butuh isolasi. - Selalu
$()bukan backtick — lebih aman untuk nested. diff <(sort a) <(sort b)adalah pola emas untuk bandingkan dua stream.tee >(...)jauh lebih rapi dari redirect + file temp untuk split output.- Ingat:
|otomatis membuat subshell di bash.while read x; do ...; done < filelebih aman daricat file | while read, karena yang kedua subshell kehilangan perubahan variabel.