နားလည်ထားရမယ့် အချက်
run ပြီးသွားတဲ့ command တိုင်းက ကိန်းဂဏန်း exit status တစ်ခု (0 ကနေ 255 ကြား) ကို special variable ဖြစ်တဲ့ $? ထဲမှာ သိမ်းထားတယ် — ဒါက Unix ရဲ့ သက်တမ်းကြာနေတဲ့ convention တစ်ခုပါ- 0 ဆိုရင် အောင်မြင်တယ်၊ 0 မဟုတ်တဲ့ ဘယ်နံပါတ်ဆိုရင်မဆို တစ်ခုခု မှားနေတယ်လို့ ဆိုလိုပေမယ့် အဲဒီနံပါတ် တိတိကျကျ ဘာကို ဆိုလိုလဲဆိုတာကတော့ program တစ်ခုချင်းစီပေါ် မူတည်တယ် (1 က အထွေထွေ error, 127 က 'command not found', 130 က SIGINT နဲ့ kill ခံရတာ စသဖြင့်)။ $? ကို run ပြီးတဲ့ command နောက်တစ်ခုက ချက်ချင်း overwrite လုပ်လိုက်တဲ့အတွက် သိချင်ရင် echo တစ်ကြောင်းမှ မဖြတ်ခင် ချက်ချင်း capture လုပ်ဖို့ လိုတယ်။ စစ်ဆေးမထားရင် script မှာ ဒီလို failure ကို ဖမ်းဖို့ default protection ဘာမှ မရှိဘူး — command တစ်ခု fail သွားလည်း ဆက် run နေပြီး error state ကို တိတ်ဆိတ်စွာ ဆက်ပို့နေမယ်။ strict-mode trio က ဒီတိတ်ဆိတ်မှုကို ဆူညံအောင် ပြောင်းပေးတယ်။ set -e က command တစ်ခုခု nonzero နဲ့ ပြီးတာနဲ့ script ကို ချက်ချင်း ရပ်ပေးတယ် — ဒါပေမယ့် သိထားသင့်တဲ့ ချွင်းချက်တွေ ရှိတယ်- if/while/until ရဲ့ test အဖြစ် run နေတဲ့ command၊ ဒါမှမဟုတ် && , || ရဲ့ ဘယ်ဘက်က command အတွက်တော့ အလုပ်မလုပ်ဘူး၊ ဘာလို့ဆိုတော့ အဲဒီ construct တွေက failure ကို တမင် စစ်ဆေးနေတာဖြစ်လို့ မိမိကိုယ်တိုင် ကိုင်တွယ်မယ်လို့ မျှော်လင့်ထားလို့ပါ။ set -u က define မလုပ်ရသေးတဲ့ variable ကို ရည်ညွှန်းမိတိုင်း empty string အဖြစ် တိတ်ဆိတ်စွာ expand မလုပ်ဘဲ error တက်စေတယ် — ဒါက rm -rf "$TARGET_DIR/" "$OLD_DIR/" လိုမျိုး command တစ်ခုမှာ အလွန်အရေးကြီးတယ်၊ $OLD_DIR ကို spell မှားထား ဒါမှမဟုတ် unset ဖြစ်နေရင် ဒါက empty string အဖြစ် တိတ်ဆိတ်စွာ ပျောက်သွားပြီး command ကို ဘေးအန္တရာယ်ရှိတဲ့ ပုံစံအဖြစ် ပြောင်းသွားနိုင်လို့ပါ။ set -o pipefail ကတော့ pipeline တစ်ခုမှာ stage ဘယ်ခုမှမဆို fail ဖြစ်ရင် (နောက်ဆုံး command တစ်ခုတည်း မဟုတ်ဘဲ) pipeline တစ်ခုလုံးရဲ့ exit status ကို fail လို့ ပြောင်းသတ်မှတ်ပေးတယ် — မထားရင် false | true က default အားဖြင့် success လို့ ပြပါလိမ့်မယ်၊ bash က true ရဲ့ exit status ကိုသာ ကြည့်လို့ပါ။ script တစ်ခုရဲ့ အစမှာ set -euo pipefail ထားတာက bash မှာ 'မြန်မြန် ကျယ်ကျယ် fail ခြင်း' နဲ့ အနီးစပ်ဆုံးပဲ။
လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
database migration run ပြီး app server ကို restart ပြန်လုပ်တဲ့ deployment script တစ်ခုကို စဉ်းစားကြည့်ပါ — တစ်ခုပြီးမှ တစ်ခု လုပ်ရမယ့် command နှစ်ခု၊ order မှားရင် ရလဒ်ဆိုးတွေ ရှိနိုင်တယ်။ strict mode မထားဘူးဆိုရင် migration command က fail သွားရင် (ဥပမာ SQL file ထဲမှာ syntax error) bash ရဲ့ default အပြုအမူက fail သွားတဲ့ command ရဲ့ error message ကို print လုပ်ပြီး နောက်လိုင်းဆက် run သွားတာပါပဲ — ဆိုလိုတာက server ကို restart ဆက်လုပ်သွားလိမ့်မယ်။ အခုတော့ ပိုဆိုးတဲ့ အခြေအနေရောက်သွားပြီ- migration တစ်ဝက်တစ်ပျက်ပဲ ပြီးသေးတဲ့ database ကို ညွှန်းနေတဲ့ service တစ်ခု run နေတယ်၊ data ပျက်စီးနေတာ ဒါမှမဟုတ် column မရှိလို့ crash ဖြစ်တာတွေ ဖြစ်နိုင်တယ်၊ deploy log ကို သေချာဖတ်မှသာ ဘာဖြစ်သွားလဲဆိုတာ သိနိုင်မယ်။ script ထိပ်မှာ set -euo pipefail ထည့်လိုက်ရင် ဒါလုံးဝ ပြောင်းသွားမယ်- migration command က nonzero exit status ပြန်လိုက်တာနဲ့ script က အဲဒီနေရာမှာ ချက်ချင်း ရပ်သွားပြီး restart command ကို ရောက်တောင် မရောက်တော့ဘူး။ failure ကလည်း ကျယ်ကျယ်လောင်လောင်ပေါ်တယ် (bash က fail နေတဲ့ လိုင်းနဲ့ command ကို report လုပ်ပေးတယ်)၊ ချက်ချင်းလည်း ဖြစ်တယ်၊ ဒါကြောင့် deploy ကို စောင့်ကြည့်နေသူ (လူဖြစ်ဖြစ် CI pipeline ဖြစ်ဖြစ်) က အောင်မြင်ပုံပေါ်ပေမယ့် system ကို တိတ်ဆိတ်စွာ ပျက်စီးအောင်ချန်ထားခဲ့တဲ့ deploy အစား ရှင်းရှင်းလင်းလင်း fail နေတဲ့ deploy တစ်ခုကိုသာ မြင်ရမယ်။ ဒါက strict-mode trio ရဲ့ trade-off အတိအကျပါပဲ- script က ပိုမကြာခဏ ရပ်ချင်ရပ်မယ်၊ အဲဒီအစား ပြန်လည်မကုစားနိုင်တော့တဲ့ state ကို တိတ်ဆိတ်စွာ ဆက်မသွားတော့ဘူး။
အတူတူ စမ်းရေးကြည့်မယ်
#!/bin/bash
set -euo pipefail
echo "Running a command that succeeds..."
true
echo "Exit status of last command: $?"
echo "Running a command that fails, but caught with ||..."
false || echo "Caught with ||, exit status of false was: $?"
echo "--- set -e gotcha: a failing command inside an if condition does NOT stop the script ---"
if false; then
echo "this branch does not run"
else
echo "if caught the failure itself, script keeps going"
fi
echo "--- pipefail demo: without it, a failing command hidden earlier in a pipe is invisible ---"
if false | true; then
echo "pipeline looked like success"
else
echo "pipefail caught the failing 'false' even though 'true' ran last, exit status: $?"
fi
set +e
false
echo "set +e disabled strict mode, so we reached here. Exit status of false was: $?"
အထက်ပါ script ကို run လိုက်ရင် ဒီအတိုင်း output ထွက်ပါမယ်:
Running a command that succeeds...
Exit status of last command: 0
Running a command that fails, but caught with ||...
Caught with ||, exit status of false was: 1
--- set -e gotcha: a failing command inside an if condition does NOT stop the script ---
if caught the failure itself, script keeps going
--- pipefail demo: without it, a failing command hidden earlier in a pipe is invisible ---
pipefail caught the failing 'false' even though 'true' ran last, exit status: 1
set +e disabled strict mode, so we reached here. Exit status of false was: 1၅ မိနစ် စမ်းကြည့်
set -euo pipefail ပါတဲ့ script တစ်ခု ရေးပါ — file တစ်ခုကို backup location ကို copy လုပ်ပြီးရင် အထဲက စာလုံးတစ်လုံးကို grep နဲ့ ရှာပါ။ source filename ကို တမင် အမှားရေးထားပြီး script ကို run ကြည့်ပါ၊ backup file မရှိသေးတာနဲ့ grep ကို ဆက်မ run ဘဲ script က ချက်ချင်းရပ်တာကို သတိပြုပါ။
သတိလေးတစ်ချက်
set -e က failure အားလုံးကို ဖမ်းမယ်လို့ ထင်ခြင်း — if/while condition အတွင်း ဒါမှမဟုတ် && , || ရဲ့ ဘယ်ဘက်မှာဆိုရင် အဲဒီ context တွေက failure ကို စစ်ဆေးဖို့ မျှော်လင့်ထားတဲ့အတွက် တိတ်ဆိတ်စွာ အလုပ်မလုပ်ဘူး
variable တွေကို default မပေးရသေးခင် set -u ကို သုံးမိတာ — ${1:-} လိုမျိုး ရွေးချယ်နိုင်တဲ့ variable တွေအတွက် script crash သွားတတ်တယ်၊ unset variable တွေကို ${VAR:-default} နဲ့ safe fallback ပေးထားသင့်တယ်
GNU Bash Manual — The Set Builtin — Bash / Shell Scripting