နားလည်ထားရမယ့် အချက်
`for` loop သည် ကြိုတင်သိရှိ (သို့) တွက်ချက်ထားနိုင်သော item list ပုံသေတစ်ခုပေါ်တွင် ထပ်ခါလုပ်ပေးသည် — ကိုယ်တိုင်ရေးထားသော word များ၊ loop မစခင်ကတည်းက shell က matching filename များအဖြစ် expand လုပ်ပြီးသား `*.log` ကဲ့သို့ glob pattern၊ (သို့) word list မဟုတ်ဘဲ numeric counter တကယ်လိုအပ်ချိန် classic C-style `for ((i=0; i<n; i++))`။ `while` loop မှာမူ condition ကို ထပ်ခါစစ်ဆေးနေပြီး true ဖြစ်နေသရွေ့ run နေမည်ဖြစ်သည် — ၎င်းကြောင့် iteration ဘယ်နှစ်ကြိမ်ရှိမည်ကို ကြိုတင်မသိသည့်အခါများတွင် သဘာဝကျသော ရွေးချယ်မှုဖြစ်သည် — `while read` ဖြင့် line တစ်ကြောင်းချင်းစီ input ဖတ်ခြင်း (သို့) state တစ်ခု ပြောင်းသည်အထိ ထပ်ခါလုပ်ခြင်းစသည်။ `until` သည် `while` ၏ ကြေးမုံပုံရိပ်ဖြစ်ပြီး true အစား condition false ဖြစ်နေသရွေ့ run သည် — readability အတွက်သာ ရှိပြီး loop တစ်ခုသည် 'X မဖြစ်သေးသရွေ့ ဆက်လုပ်' ထက် 'X ဖြစ်တဲ့အထိ ဆက်လုပ်' ဟု သဘာဝကျစွာ ဖတ်ရသည့်အခါများအတွက် ဖြစ်သည်။ ၎င်းသုံးမျိုးထဲမှ မည်သည့်အရာတွင်မဆို `break` က condition ကို ထည့်မတွက်တော့ဘဲ loop ကို ချက်ချင်း ထွက်စေပြီး၊ `continue` က iteration လက်ရှိ၏ ကျန်ရှိသေးသော body ကို ကျော်ကာ နောက်တစ်ကြိမ်သို့ တိုက်ရိုက် ခုန်ကူးစေသည်။ ကနဦးမှာသိထားသင့်သော အသေးစိတ်တစ်ခုမှာ — ၎င်းက လူတိုင်းလိုလို ပထမဆုံးအကြိမ်တွင် အမှားစားတတ်သောကြောင့်ဖြစ်သည် — pipeline တစ်ခုသည် stage တစ်ခုစီအတွက် process သီးခြားစီ fork လုပ်သောကြောင့် `while read` loop ထဲသို့ command တစ်ခုကို pipe ထည့်ခြင်း (`cmd | while read ...`) သည် loop တစ်ခုလုံးကို subshell ထဲတွင် run စေသည် — ဆိုလိုသည်မှာ counter တစ်ခုကဲ့သို့ loop က ပြောင်းလဲလိုက်သော variable မည်သည့်အရာမဆို loop ပြီးသွားသည်နှင့် loop မစခင်က တန်ဖိုးအတိုင်း ချက်ချင်း ပြန်ရောက်သွားမည်ဖြစ်ပြီး၊ ၎င်းကို lesson ၏ ဥပမာကိုယ်တိုင်က တိုက်ရိုက် ပြသထားသည်။
လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
Log file တစ်ခုကို line တစ်ကြောင်းချင်းစီ process လုပ်ပြီး pattern နှင့် ကိုက်ညီသော line ဘယ်နှစ်ကြောင်းရှိလဲ ရေတွက်ခြင်းသည် `while read` ၏ classic use case ဖြစ်သည် — `while IFS= read -r line; do ...; done < file.txt` က line တစ်ခုစီကို (`IFS=` က ထိန်းသိမ်းပေးသော leading/trailing space များပါ) naive approach တစ်ခုတွေ့ရမည့် word-splitting အံ့သြစရာများမပါဘဲ ဘေးကင်းစွာ ဖတ်ပေးသည်၊ ၎င်းသည် lesson ၏ ဥပမာက `lines.txt` ကို ဖတ်ကာ `total` counter ကို 3 အထိ တိုးမြှင့်သည့်အချိန် တိတိကျကျ လုပ်ဆောင်သည့် အရာဖြစ်သည်။ ထို့ပြင် network call တစ်ခုကို ငါးကြိမ်အထိ retry လုပ်ရန် လိုအပ်သော script တစ်ခုမှာ C-style `for` loop ကို `break` နှင့် ပေါင်းစပ်သုံးရန် သဘာဝကျသည် — call အောင်မြင်တာနှင့် loop ကို ချက်ချင်း ရပ်လိုက်ပြီး attempt ငါးကြိမ်လုံးကို အမြဲ run နေရန် မလိုတော့ပါ။ ဥပမာ၏ နောက်ဆုံး block သည် subshell gotcha ကို အတိအကျ ပြသသည် — `printf` output ကို `while read` ထဲသို့ pipe ထည့်လိုက်ခြင်းက loop အတွင်း `count` ကို 3 အထိ တိုးပေးသော်လည်း၊ ထို loop သည် pipeline အတွက် fork ခံထားသော subshell ထဲတွင် run နေသောကြောင့် pipeline ပြီးသွားသည်နှင့် ပြင်ပရှိ `count` သည် 0 အတိုင်းသာ ကျန်ရှိနေမည်ဖြစ်သည် — ၎င်းသည် file ထဲမှ redirect (`< file.txt`) လုပ်ခြင်းက ရှောင်ရှားပေးနိုင်သော အံ့သြစရာအမျိုးအစားပင် ဖြစ်သည်။
အတူတူ စမ်းရေးကြည့်မယ်
#!/usr/bin/env bash
echo "for over a list:"
for color in red green blue; do
echo " color -> $color"
done
echo "for over a glob:"
mkdir -p /tmp/bash-lesson-loops
touch /tmp/bash-lesson-loops/a.log /tmp/bash-lesson-loops/b.log
for f in /tmp/bash-lesson-loops/*.log; do
echo " found -> $(basename "$f")"
done
echo "C-style for:"
for ((i = 1; i <= 3; i++)); do
echo " i -> $i"
done
echo "while loop:"
n=3
while [ "$n" -gt 0 ]; do
echo " countdown -> $n"
n=$((n - 1))
done
echo "until loop:"
m=1
until [ "$m" -gt 3 ]; do
echo " climbing -> $m"
m=$((m + 1))
done
echo "break and continue:"
for num in 1 2 3 4 5; do
if [ "$num" -eq 2 ]; then
continue
fi
if [ "$num" -eq 4 ]; then
break
fi
echo " num -> $num"
done
echo "while read from a file:"
printf 'line one\nline two\nline three\n' > /tmp/bash-lesson-loops/lines.txt
total=0
while IFS= read -r line; do
echo " read -> $line"
total=$((total + 1))
done < /tmp/bash-lesson-loops/lines.txt
echo "total lines counted: $total"
echo "the subshell gotcha (piped while read):"
count=0
printf 'x\ny\nz\n' | while IFS= read -r _; do
count=$((count + 1))
done
echo "count after piped loop: $count"
rm -rf /tmp/bash-lesson-loopsဤ script ကို run လိုက်လျှင် အောက်ပါအတိုင်း ထွက်ပါမည်:
for over a list:
color -> red
color -> green
color -> blue
for over a glob:
found -> a.log
found -> b.log
C-style for:
i -> 1
i -> 2
i -> 3
while loop:
countdown -> 3
countdown -> 2
countdown -> 1
until loop:
climbing -> 1
climbing -> 2
climbing -> 3
break and continue:
num -> 1
num -> 3
while read from a file:
read -> line one
read -> line two
read -> line three
total lines counted: 3
the subshell gotcha (piped while read):
count after piped loop: 0၅ မိနစ် စမ်းကြည့်
task1.txt, task2.txt, task3.txt ဟု အမည်ရှိ file သုံးခု ဖန်တီးသော script တစ်ခု ရေးပါ၊ for-loop glob ဖြင့် ၎င်းတို့ပေါ်တွင် loop လုပ်ပြီး filename တစ်ခုစီ print ပါ၊ ထို့နောက် counter ပါသော while loop တစ်ခုကို သီးခြားသုံးပြီး 5 မှ 1 အထိ countdown print ပါ။
သတိလေးတစ်ချက်
Command တစ်ခုကို while read ထဲသို့ pipe ထည့်ခြင်း (cmd | while read ...) ပြီးနောက် loop ထဲတွင် တိုးထားသော counter သည် ဘာကြောင့် မူလတန်ဖိုးအတိုင်း ပြန်ဖြစ်နေသလဲ မသိဖြစ်ခြင်း — pipe က loop ကို subshell ထဲတွင် run စေသောကြောင့်ဖြစ်သည်
read တွင် -r ထည့်ဖို့ မေ့ခြင်းကြောင့် input ထဲရှိ backslash များကို literal အတိုင်း မဖတ်ဘဲ interpret/strip ဖြစ်သွားခြင်း
GNU Bash Reference Manual — Looping Constructs — Bash / Shell Scripting