Thuta Learning
Bash / Shell Scripting
AdvancedDevOps & Toolsbeginner

Subshell များနှင့် Process Substitution

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • Subshell များနှင့် Process Substitution concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • နမူနာ code ကို ကိုယ်တိုင် run ပြီး output စစ်နိုင်ရန်
  • Tutorial Platform project နှင့် production scenario တွင် မှန်ကန်စွာအသုံးချနိုင်ရန်

နားလည်ထားရမယ့် အချက်

subshell ဆိုတာ လက်ရှိ shell process ကို fork ထုတ်လိုက်တဲ့ child copy တစ်ခုပါ — ဖန်တီးဖို့ စရိတ်သက်သာပေမယ့် variable၊ working directory၊ trap အားလုံးရဲ့ ကိုယ်ပိုင် private copy ကို ကိုင်ထားတဲ့ လုံးဝ သီးခြား process တစ်ခုပါ။ bash က ဒါကို နည်းလမ်းနှစ်မျိုးနဲ့ ဖန်တီးတယ်- (...) နဲ့ တိုက်ရိုက် ပတ်ထားရင် တစ်ခု၊ pipe ရဲ့ ဘယ်ဘက်ဒါမှမဟုတ် ညာဘက်မှာ ရောက်နေတဲ့ command တိုင်းအတွက် implicit အနေနဲ့ တစ်ခု (command substitution $(...) ကလည်း အလားတူ အကြောင်းရင်းအတွက် subshell ထဲမှာပဲ run တယ်)။ subshell က ပြောင်းလဲလိုက်တာ ဘာဖြစ်ဖြစ် — variable တန်ဖိုး, cd, trap register — subshell ထွက်တာနဲ့ ပျောက်သွားတယ်၊ parent shell က ဘယ်တော့မှ မမြင်ရဘူး၊ ဘာလို့ဆိုတော့ သူတို့နှစ်ခုဟာ memory ကို မျှဝေထားတာ အစကတည်းက မဟုတ်ခဲ့လို့ပါ။ ဒါက အလွန်ဖြစ်လေ့ရှိတဲ့ အံ့သြစရာတစ်ခုကို ရှင်းပြနိုင်တယ်- cat file | while read -r line; do total=$((total+1)); done လို pipe ကနေ while read ထဲ ပို့တာမျိုးဟာ loop ပြီးတာနဲ့ total ကို တိတ်ဆိတ်စွာ ပျောက်စေတယ်၊ ဘာလို့ဆိုတော့ loop က pipe ရဲ့ ညာဘက်မှာ ရောက်နေလို့ ကိုယ်ပိုင် subshell ထဲမှာ run နေတာ — pipe ပိတ်တာနဲ့ အဲဒီ subshell ရဲ့ total နဲ့ ၎င်းထိထားတဲ့ variable အားလုံး ပျောက်ကုန်တယ်။ (bash မှာ shopt -s lastpipe ဆိုတဲ့ option ရှိပြီး pipeline ရဲ့ နောက်ဆုံး command ကို လက်ရှိ shell ထဲမှာပဲ run ခွင့်ပေးနိုင်ပေမယ့် default အနေနဲ့ ပိတ်ထားလို့ non-interactive script တွေအတွက်သာ သက်ဆိုင်တယ်၊ တမင် ဖွင့်မထားရင် အားကိုးလို့ မရဘူး။) process substitution <(command) နဲ့ >(command) ကတော့ ဒီပြဿနာတစ်ခုလုံးကို ရှောင်ပေးတယ်- command တစ်ခုရဲ့ output ကို bash တိုက်ရိုက် redirect လုပ်လို့ရတဲ့ file path တစ်ခုလို ပြောင်းပေးလို့ pipe လုံးဝ မပါတော့ဘဲ၊ ဒီနည်းနဲ့ ကျွေးထားတဲ့ loop ဟာ လက်ရှိ shell ထဲမှာပဲ run ပြီး ၎င်းသတ်မှတ်လိုက်တဲ့ variable တိုင်း နောက်ပိုင်းမှာလည်း ကျန်ခဲ့တယ်။

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

log-processing pipeline တစ်ခုကနေ byte count တွေ ပေါင်းနေတယ်ဆိုပါစို့၊ loop ပြီးတဲ့အခါ နောက်ဆုံး total ကို လိုချင်တယ် — threshold တစ်ခုနဲ့ နှိုင်းယှဉ်ဖို့ ဒါမှမဟုတ် report တစ်ခု log ချဖို့ လိုချင်တာလည်း ဖြစ်နိုင်တယ်။ ရိုးရိုးရှင်းရှင်း generate_sizes | while read -r size; do total=$((total+size)); done; echo "$total" လို့ ရေးရင် generate_sizes က line ဘယ်နှစ်ကြောင်း ထုတ်ပေးပေးပေး 0 (ဒါမှမဟုတ် loop မစခင် total ရဲ့ တန်ဖိုး) ချည်း ထွက်နေမှာပါ။ အကြောင်းရင်းကတော့ အထက်က subshell trap အတိအကျပါပဲ- while loop က pipe ရဲ့ ညာဘက်မှာ ရှိနေတာကြောင့် bash က ၎င်းကို fork ထုတ်ထားတဲ့ subshell ထဲမှာ run တယ်၊ အဲဒီ subshell ကိုယ်ပိုင် total ရှိပြီး loop ထဲက increment တိုင်းက အဲဒီ private copy ကိုပဲ update လုပ်တာပါ။ pipe ပိတ်ပြီး subshell ထွက်သွားတာနဲ့ အဲဒီ copy ပျောက်ကုန်တယ်၊ လက်ရှိ shell ထဲက total ကတော့ လက်တစ်ချက်မှ မထိခံရဘူး — loop မစခင်က ဘယ်လိုရှိရှိ (ပုံမှန် 0) ဆက်နေတယ်။ process substitution ကို ပြောင်းသုံးလိုက်ရင် — while read -r size; do total=$((total+size)); done < <(generate_sizes) — အဆင့်ဆင့် ပြေလည်သွားတယ်- loop က pipe ရဲ့ ဘယ်ဘက်မှာမှ ညာဘက်မှာမှ မရှိတော့တဲ့အတွက် subshell ဘာမှ မဖန်တီးတော့ဘူး၊ total ကို update လုပ်တိုင်း လက်ရှိ shell ထဲမှာ တိုက်ရိုက် ဖြစ်တယ်။ loop ပြီးတဲ့အခါ total ဟာ ပေါင်းလဒ်အစစ်ကို ကိုင်ထားတယ်၊ ဘာလို့ဆိုတော့ တစ်ခါမှ copy နှစ်ခု ရှိခဲ့ဖူးတာ မဟုတ်ဘူးလို့ပါ။

အတူတူ စမ်းရေးကြည့်မယ်

bash
#!/bin/bash
set -euo pipefail

count=0

echo "--- a subshell (...) runs in a child process ---"
(
  count=99
  echo "inside subshell, count is: $count"
)
echo "outside subshell, count is still: $count"

echo "--- piping into 'while read' also runs the loop in a subshell ---"
count=0
printf "1\n2\n3\n" | while read -r n; do
  count=$((count + n))
  echo "inside the pipe's while loop, running total: $count"
done
echo "outside the loop, count is still: $count (the total was lost!)"

echo "--- process substitution avoids the pipe, so no subshell is created for the loop ---"
count=0
while read -r n; do
  count=$((count + n))
  echo "inside the loop, running total: $count"
done < <(printf "1\n2\n3\n")
echo "outside the loop, count is now: $count (the total survived)"
You should see
အထက်ပါ script ကို run လိုက်ရင် ဒီအတိုင်း output ထွက်ပါမယ်:

--- a subshell (...) runs in a child process ---
inside subshell, count is: 99
outside subshell, count is still: 0
--- piping into 'while read' also runs the loop in a subshell ---
inside the pipe's while loop, running total: 1
inside the pipe's while loop, running total: 3
inside the pipe's while loop, running total: 6
outside the loop, count is still: 0 (the total was lost!)
--- process substitution avoids the pipe, so no subshell is created for the loop ---
inside the loop, running total: 1
inside the loop, running total: 3
inside the loop, running total: 6
outside the loop, count is now: 6 (the total survived)

၅ မိနစ် စမ်းကြည့်

နံပါတ်စာရင်းတစ်ခုကို while read နဲ့ ဖတ်တဲ့ script တစ်ခု ရေးပါ — တစ်ခါက command ကနေ pipe နဲ့ ပို့ကြည့်ပါ (total ပျောက်တာ တွေ့ရမယ်)၊ နောက်တစ်ခါ process substitution နဲ့ ကျွေးကြည့်ပါ (total ကျန်ခဲ့တာ တွေ့ရမယ်)။ ကွာခြားချက်ကို ကိုယ်တိုင်အတည်ပြုနိုင်ဖို့ နှစ်ခုစလုံးက နောက်ဆုံး print ထုတ်တဲ့ total ကို နှိုင်းယှဉ်ကြည့်ပါ။

သတိလေးတစ်ချက်

cmd | while read ...; done အတွင်းမှာ set လုပ်ထားတဲ့ variable ကို loop ပြီးတဲ့အခါလည်း ကျန်နေဦးမယ်လို့ မျှော်လင့်ခြင်း — loop က subshell ထဲမှာ run ခဲ့လို့ တိတ်ဆိတ်စွာ reset ဖြစ်သွားတယ်

<(...) process substitution ကို $(...) command substitution နဲ့ ရောနှောခြင်း — <(...) က redirection အတွက် file-like path တစ်ခု ထုတ်ပေးပြီး $(...) ကတော့ output ကို string အဖြစ် ဖမ်းယူတာ

GNU Bash Manual — Process SubstitutionBash / Shell Scripting

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • cmd | while read ...; done အတွင်းမှာ set လုပ်ထားတဲ့ variable ကို loop ပြီးတဲ့အခါလည်း ကျန်နေဦးမယ်လို့ မျှော်လင့်ခြင်း — loop က subshell ထဲမှာ run ခဲ့လို့ တိတ်ဆိတ်စွာ reset ဖြစ်သွားတယ်
  • <(...) process substitution ကို $(...) command substitution နဲ့ ရောနှောခြင်း — <(...) က redirection အတွက် file-like path တစ်ခု ထုတ်ပေးပြီး $(...) ကတော့ output ကို string အဖြစ် ဖမ်းယူတာ
  • နမူနာ code ကို production system ပေါ် တိုက်ရိုက်မစမ်းဘဲ local/test environment တွင် အရင်အတည်ပြုပါ။

လေ့ကျင့်ခန်း

နံပါတ်စာရင်းတစ်ခုကို while read နဲ့ ဖတ်တဲ့ script တစ်ခု ရေးပါ — တစ်ခါက command ကနေ pipe နဲ့ ပို့ကြည့်ပါ (total ပျောက်တာ တွေ့ရမယ်)၊ နောက်တစ်ခါ process substitution နဲ့ ကျွေးကြည့်ပါ (total ကျန်ခဲ့တာ တွေ့ရမယ်)။ ကွာခြားချက်ကို ကိုယ်တိုင်အတည်ပြုနိုင်ဖို့ နှစ်ခုစလုံးက နောက်ဆုံး print ထုတ်တဲ့ total ကို နှိုင်းယှဉ်ကြည့်ပါ။

You'll know it worked when: အထက်ပါ script ကို run လိုက်ရင် ဒီအတိုင်း output ထွက်ပါမယ်: --- a subshell (...) runs in a child process --- inside subshell, count is: 99 outside subshell, count is still: 0 --- piping into 'while read' also runs the loop in a subshell --- inside the pipe's while loop, running total: 1 inside the pipe's while loop, running total: 3 inside the pipe's while loop, running total: 6 outside the loop, count is still: 0 (the total was lost!) --- process substitution avoids the pipe, so no subshell is created for the loop --- inside the loop, running total: 1 inside the loop, running total: 3 inside the loop, running total: 6 outside the loop, count is now: 6 (the total survived)

Subshell များနှင့် Process Substitution | Thuta Learning