Subshell adalah salinan shell-mu yang jalan sebagai proses anak. Si anak mewarisi semua variabel dan folder induknya, tapi apa pun yang dia ubah gak balik ke induk. Bayangkan fotokopi dokumen: kamu boleh mencoret-coret fotokopiannya, dokumen aslinya tetap bersih. Kurung biasa ( ) artinya kerja di fotokopian. Kurung kurawal { ...; } artinya kerja langsung di dokumen asli.
🧪 Tebakan cepat: kamu menjalankan
X=1, lalu(X=99), laluecho $X. Yang tercetak 1 atau 99? Tebak dulu, jawabannya ada di bagian "Subshell ( ): kerja di salinan".
Subshell ( ): kerja di salinan
Semua contoh di pelajaran ini mulai dari folder /home/budi/proyek:
$ X=1
$ (X=99; echo "dalam: $X")
dalam: 99
$ echo "luar: $X"
luar: 1
$ (cd /tmp && pwd)
/tmp
$ pwd
/home/budi/proyek
- Di dalam kurung,
Xmemang jadi 99, tapi ituXmilik salinannya. Begitu kurungnya selesai, salinannya dibuang, danXmilik induk tetap 1. Itu jawaban tebakannya: tercetak1. cddi dalam kurung juga cuma memindahkan salinannya. Induknya tetap di/home/budi/proyek.
Ini berguna kalau kamu mau mengubah sesuatu sebentar tanpa mengotori shell-mu. Contohnya (export NODE_ENV=production; npm run build): setelah build selesai, NODE_ENV di terminalmu tetap seperti sebelumnya.
Grup { ...; }: kerja di aslinya
$ X=1
$ { X=99; echo "dalam: $X"; }
dalam: 99
$ echo "luar: $X"
luar: 99
Kurung kurawal cuma mengelompokkan perintah. Gak ada salinan, jadi perubahannya bertahan. Penulisannya rewel: harus ada spasi setelah { dan titik koma (atau baris baru) sebelum }. Kegunaan utamanya: satu redirect untuk banyak perintah, misalnya { echo "== laporan =="; date; } > laporan.txt.
$( ): ambil output jadi teks
Command substitution menjalankan perintah di subshell, lalu menaruh output-nya (tanpa baris baru di ujung) di tempat itu:
$ JUMLAH=$(wc -l < daftar.txt)
$ echo "ada $JUMLAH baris"
ada 3 baris
$ Y=$(X=5; echo "$X")
$ echo "Y=$Y X=$X"
Y=5 X=1
daftar.txtberisi 3 baris, jadiJUMLAHberisi teks3.$(X=5; echo "$X")juga subshell: output-nya (5) masuk keY, tapiX=5di dalamnya gak bocor keluar.- Bentuk lamanya pakai backtick,
`perintah`. Pakai$( ), karena bisa disarangkan tanpa escape:$(dirname $(which node)).
<( ): output perintah sebagai file
Ada perintah yang cuma mau menerima nama file, misalnya diff dan comm. Process substitution <(perintah) menjalankan perintah itu di subshell, lalu memberikan sebuah nama file khusus yang isinya output perintah tersebut:
$ cat a.txt
banana
apple
cherry
$ cat b.txt
apple
cherry
date
$ echo <(sort a.txt)
/dev/fd/63
$ comm -12 <(sort a.txt) <(sort b.txt)
apple
cherry
$ diff <(sort a.txt) <(sort b.txt)
2d1
< banana
3a3
> date
echo <(sort a.txt)menunjukkan apa yang sebenarnya diterima perintah: sebuah nama file seperti/dev/fd/63.comm -12mencetak baris yang ada di kedua file, dan dia butuh dua file yang sudah urut. Dengan<(sort ...), kamu gak perlu bikin file sementara. Hasilnya irisan yang sudah urut:appledancherry.diffmenunjukkan bedanya:bananacuma ada di a,datecuma ada di b.- Kebalikannya,
>(perintah), adalah "file" yang kalau ditulisi, isinya masuk ke perintah itu. Contohnyatee >(gzip > log.gz). <( )cuma ada di bash dan zsh. Di script#!/bin/sh, hasilnya error sintaks.
Pipe juga bikin subshell
Di bash, tiap bagian pipe jalan di subshell sendiri. Akibatnya:
$ X=1
$ echo 99 | read X
$ echo $X
1
$ read X < <(echo 99)
$ echo $X
99
read X di ujung pipe mengisi X milik salinannya, lalu salinannya dibuang. Dengan < <(echo 99), read jalan di shell induk dan membaca dari process substitution, jadi X benar-benar berubah. Alasan yang sama membuat while read baris; do ...; done < file lebih aman daripada cat file | while read baris.
| Pola | Subshell? | Mengubah induk? | Dipakai untuk |
|---|---|---|---|
( ... ) |
ya | gak | coba-coba cd atau variabel tanpa mengotori shell |
{ ...; } |
gak | ya | mengelompokkan perintah, satu redirect untuk semua |
$( ... ) |
ya | gak | mengambil output jadi teks |
<( ... ) |
ya | gak | memberi output ke perintah yang minta nama file |
a | b |
ya, tiap bagian | gak | menyambung output ke input |
⚠️ Jebakan yang sering ketemu
- Mengira perubahan di
( )bertahan.cd,export, dan variabel di dalam kurung hilang begitu kurungnya selesai. Pakai{ ...; }kalau mau bertahan. - Spasi dan titik koma di
{ }.{echo hi}dibaca sebagai perintah bernama{echo(command not found), dan di{ echo hi }tanda}dianggap argumenecho, jadi bash masih menunggu penutupnya. Tulis{ echo hi; }. - Mengisi variabel di ujung pipe.
... | read Xataucat file | while readkehilangan variabelnya. Pakai< <(...)atau< file. <( )di scriptsh. Kalau baris pertamanya#!/bin/sh, process substitution gak dikenal. Pakai#!/usr/bin/env bash.
TL;DR: Subshell adalah salinan shell: mewarisi variabel dan folder, tapi perubahannya gak balik ke induk. ( ... ) jalan di salinan, jadi X=1; (X=99); echo $X mencetak 1. { ...; } jalan di shell yang sama, perubahannya bertahan, dan butuh spasi setelah { serta titik koma sebelum }. $( ... ) mengambil output perintah jadi teks. <( ... ) memberi output perintah sebagai nama file (/dev/fd/63), misalnya comm -12 <(sort a.txt) <(sort b.txt) untuk baris yang ada di keduanya, atau diff <(sort a) <(sort b). Tiap bagian pipe juga subshell, jadi ... | read X gak mengubah X.