Thuta Learning
Bash / Shell Scripting
ExercisesDevOps & Toolsbeginner

လေ့ကျင့်ခန်း — Subcommand များပါသော CLI Tool တစ်ခု တည်ဆောက်ခြင်း

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

  • လေ့ကျင့်ခန်း — Subcommand များပါသော CLI Tool တစ်ခု တည်ဆောက်ခြင်း concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • နမူနာ code ကို ကိုယ်တိုင် run ပြီး output စစ်နိုင်ရန်
  • Tutorial Platform project နှင့် production scenario တွင် မှန်ကန်စွာအသုံးချနိုင်ရန်

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

သင်နေ့စဉ်သုံးနေသော command-line tool အများစု — git, npm, docker စသည်တို့သည် command တစ်ခုတည်းမဟုတ်ဘဲ dispatcher တစ်ခုဖြစ်သည်- ပထမဆုံး argument က subcommand အမည်ကို ဖော်ပြပြီး ၎င်းနောက်ပိုင်း argument များအားလုံးသည် ထို subcommand ပိုင်ဖြစ်သည်။ ဤ scaffold သည် ထို ပုံစံအတိအကျကို အသေးစားပုံစံဖြင့် တည်ဆောက်ထားသည်။ case "$subcommand" in ... esac သည် ထို matching ကို ပြုလုပ်ရန် bash ၏ idiomatic နည်းလမ်းဖြစ်သည်၊ အကြောင်းမှာ if/elif/fi chain တစ်ခုနှင့်မတူဘဲ case block တစ်ခုသည် 'value က ဒါဆိုရင် ဒါလုပ်' ဟူသော branch များစာရင်းအဖြစ် flat ဖတ်ရပြီး၊ subcommand ဆယ်ခုအထိ ကျယ်ပြန့်လာသော်လည်း bracket များ အလုံးအရင်းအဖြစ် မပြောင်းလဲဘဲ၊ plain string comparison တစ်ခုတည်းတွင် မရှိသော pattern matching (glob, | ဖြင့် alternation) ကိုပါ support ပြုသည်။ case မတိုင်မီ ထားရှိသော shift သည် case ကိုယ်တိုင်ကဲ့သို့ပင် အရေးပါသည် — shift မရှိလျှင် cmd_add အတွင်းရှိ "$@" သည် subcommand အမည်ဖြင့်ပင် စတင်နေဆဲဖြစ်မည်ဖြစ်ပြီး handler တစ်ခုစီသည် မိမိပိုင်အမည်ကို သိထားရမည့်အပြင် ၎င်းကို manual ကျော်ဖြတ်ရန် လိုအပ်လာမည်။ shift ပါလာသောအခါ handler function တစ်ခုစီသည် မိမိပိုင် script တစ်ခုအနေဖြင့်ကဲ့သို့ မိမိအတွက်ရည်ရွယ်ထားသော argument များကိုသာ အတိအကျ ရရှိသည် — ဤအကြောင်းကြောင့်ပင် subcommand တစ်ခုစီကို case branch ထဲတွင် inline မလုပ်ဘဲ ကိုယ်ပိုင် function (cmd_list, cmd_add, cmd_done) ထဲတွင် wrap ထားခြင်းသည် dispatcher ကိုယ်တိုင်ကို တိုတောင်းအောင်ထားပေးပြီး handler တစ်ခုစီကို သီးခြား test လုပ်ခြင်း (သို့) reuse လုပ်ခြင်းကို ဖြစ်စေသည်။ ဤ scaffold က မဖြေရှင်းရသေးသည့်အချက်မှာ state ဖြစ်သည် — cmd_list သည် script run တစ်ခု၏ သက်တမ်းအတွင်းသာ memory ထဲတွင် ရှိနေသော hardcoded array ကို ဖတ်ရသောကြောင့် အလုပ်လုပ်နေသော်လည်း၊ task manager စစ်စစ်တစ်ခုက မနေ့က ထည့်ခဲ့သော task ကို မှတ်ထားရန် လိုအပ်ပြီး memory သည် script process ပိတ်သွားချိန်တွင် မကျန်ရှိနိုင်တော့ပါ — ၎င်းသည် ယခင် lesson များတွင် ကြုံခဲ့ရသည့် ပြဿနာများနှင့် ကွဲပြားစွာ မတူညီသော ပြဿနာအသစ်တစ်ခုဖြစ်သည်၊ အကြောင်းမှာ ယခင် exercise တိုင်းသည် run ခဲ့၊ တစ်ခုခု print ထုတ်ခဲ့၊ ပြီးဆုံးခဲ့ကြသောကြောင့်ဖြစ်သည်။

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

သင်လက်ခံရရှိထားသော scaffold သည် dispatcher အလုပ်လုပ်ကြောင်း သက်သေပြီးသားဖြစ်သည် — ./task.sh list ကို run လိုက်လျှင် main သည် "list" ကို subcommand ထဲသို့ ဖတ်ယူပြီး shift ဖြင့် ဖယ်ထုတ်ကာ case block ထဲတွင် ကိုက်ညီအောင် ယှဉ်စစ်ပြီးနောက် cmd_list ကို ခေါ်သည်၊ ၎င်းသည် hardcoded TASKS array ကို loop ပတ်ကာ entry တစ်ခုစီကို 1-based index ဖြင့် print ထုတ်သည်။ ./task.sh add "Buy bread" ကို run လိုက်ပါက dispatch machinery တူတူပင် "Buy bread" ကို "$1" အဖြစ်ယူပြီး cmd_add ထဲသို့ မှန်ကန်စွာ လမ်းကြောင်းပြောင်းပေးသော်လည်း — function body သည် TODO comment တစ်ခုနှင့် echo တစ်ခုသာဖြစ်ပြီး list အတွက် ဘာမှ ဖြစ်ပျက်ခြင်း မရှိပါ။ သင့်လုပ်ငန်းမှာ main နှင့် case statement ကို လက်မထိဘဲ (၎င်းတို့သည် မှန်ကန်ပြီးသားဖြစ်သောကြောင့်) cmd_add နှင့် cmd_done ကို အမှန်တကယ် အလုပ်လုပ်အောင် ပြုလုပ်ရန်ဖြစ်သည်။ cmd_add အတွက် သိထားရမည့် အဓိကအချက်မှာ "$1" ကို in-memory TASKS array ထဲသို့ append လုပ်ခြင်းသည် ဤ process တစ်ခုတည်း၏ ကျန်ရှိသေးသော သက်တမ်းအတွင်းသာ မြင်ရမည်ဖြစ်ခြင်းဖြစ်သည် — script ပိတ်သွားသည်နှင့်တစ်ပြိုင်နက် ထို array သည် ပျောက်ကွယ်သွားမည်ဖြစ်ပြီး၊ ထို့ကြောင့် နောက် invocation တစ်ခုက ပြန်ဖတ်နိုင်ရန် task စာသားအသစ်ကို disk ပေါ်ရှိ file တစ်ခု (script ဘေးတွင် tasks.txt ကဲ့သို့) ထဲသို့ line အသစ်တစ်ခုအဖြစ် append လုပ်ရန် လိုအပ်သည်။ cmd_done "$1" အတွက်မူ bash ထဲတွင် file တစ်ခုမှ line တစ်ကြောင်းကို ဘေးကင်းစွာ 'ဖယ်ထုတ်ခြင်း' ဆိုသည်မှာ ဘာကို ဆိုလိုသည်ကို စဉ်းစားပါ — file ကို ဖတ်ပြီး ပေးထားသော task number နှင့် ကိုက်ညီသော line ကို filter ဖြင့် ဖယ်ထုတ်ကာ file ကို ပြန်ရေးခြင်း၊ (သို့) file ကို ဖတ်နေစဉ်အတွင်း ဖျက်ခြင်းအစား line ကို in place တွင် done ဟု flag ခြင်း စသည့်နည်းလမ်းများကို အသုံးပြုလေ့ရှိသည်။ နှစ်ခုစလုံးကို file တစ်ခုတည်းတွင် persist ဖြစ်အောင် ချိတ်ဆက်လိုက်သောအခါ cmd_list သည်လည်း hardcoded array အစား ထို file မှ ဖတ်ရန် လိုအပ်လာမည်ဖြစ်ပြီး၊ ထိုသို့မှသာ subcommand သုံးခုစလုံးသည် script ၏ run သီးခြားစီအတွက် source of truth တစ်ခုတည်း အပေါ် သဘောတူညီကြမည်ဖြစ်သည်။

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

bash
#!/usr/bin/env bash
# task.sh - a tiny CLI with subcommands (add, list, done)
# Usage: ./task.sh <command> [arguments]

# Pretend this is loaded from a real task file; hardcoded here for the demo
TASKS=("Buy milk" "Write report" "Call the plumber")

cmd_list() {
  echo "Tasks:"
  local i=1
  for t in "${TASKS[@]}"; do
    echo "  $i) $t"
    i=$((i + 1))
  done
}

cmd_add() {
  # TODO: append "$1" to TASKS and persist it so future runs see it
  echo "add: not yet implemented"
}

cmd_done() {
  # TODO: mark task number "$1" as complete (e.g. remove it or flag it)
  echo "done: not yet implemented"
}

main() {
  local subcommand="$1"
  shift

  case "$subcommand" in
    list)
      cmd_list
      ;;
    add)
      cmd_add "$@"
      ;;
    done)
      cmd_done "$@"
      ;;
    *)
      echo "Usage: task.sh {add|list|done} [arguments]" >&2
      exit 1
      ;;
  esac
}

main "$@"
You should see
$ ./task.sh list
Tasks:
  1) Buy milk
  2) Write report
  3) Call the plumber

$ ./task.sh add "Buy bread"
add: not yet implemented

$ ./task.sh foo
Usage: task.sh {add|list|done} [arguments]

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

./task.sh add "New task" ကို run သောအခါ ပေးထားသော စာသားကို task အသစ်တစ်ခုအဖြစ် ထည့်သွင်းပြီး ./task.sh list ကို နောက်တစ်ကြိမ် run သည့်အခါ ပေါ်လာစေရန် cmd_add ကို ဖြည့်စွက်ပါ — invocation တစ်ခုစီသည် process အသစ်တစ်ခုဖြစ်၍ ဆိုလိုသည်မှာ in-memory TASKS array ထဲသို့ append ရုံမျှမက file တစ်ခုထဲသို့ persist လုပ်ရန် လိုအပ်သည်။ ထို့နောက် ./task.sh done <number> က task ထိုနံပါတ်ကို (ဖယ်ထုတ်ခြင်း သို့ flag တပ်ခြင်း — သင့်ရွေးချယ်မှုအတိုင်း) persist လုပ်ထားသော file တွင် ပြီးစီးကြောင်း မှတ်သားနိုင်ရန် cmd_done ကို ဖြည့်စွက်ပါ။ ပြီးဆုံးသောအခါ list သည် hardcoded task သုံးခုအစား လက်ရှိ task စစ်စစ်များကို print ထုတ်သင့်ပြီး add နှင့် done တို့သည် 'not yet implemented' ဟု နောက်ထပ် မပြသင့်ဘဲ၊ မသိသော subcommand တစ်ခု run လိုက်ပါက ယခုကဲ့သို့ပင် usage message သို့ ကျရောက်နေဆဲဖြစ်သင့်သည်။

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

add နှင့် done ကို in-memory TASKS array ထဲတွင်သာ ပြောင်းလဲအောင် implement လုပ်ခြင်း — script run တိုင်းသည် process အသစ်တစ်ခုဖြစ်၍ file ထဲသို့ ရေးမှတ်မထားပါက နောက် run တစ်ခုသို့ ဘာမျှ ဆက်လက် မတည်ရှိနိုင်ပါ

$1 ကို subcommand အဖြစ် ဖတ်ပြီးနောက် shift ကို လျစ်လျူရှုမိခြင်း — မလုပ်ပါက cmd_add သို့မဟုတ် cmd_done အတွင်းရှိ $@ တွင် subcommand အမည်ကိုယ်တိုင်ပါဝင်နေဆဲဖြစ်ပြီး မလိုလားအပ်သော argument အပိုတစ်ခု ဖြစ်နေမည်

GNU Bash Reference Manual — Conditional Constructs (case)Bash / Shell Scripting

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

  • add နှင့် done ကို in-memory TASKS array ထဲတွင်သာ ပြောင်းလဲအောင် implement လုပ်ခြင်း — script run တိုင်းသည် process အသစ်တစ်ခုဖြစ်၍ file ထဲသို့ ရေးမှတ်မထားပါက နောက် run တစ်ခုသို့ ဘာမျှ ဆက်လက် မတည်ရှိနိုင်ပါ
  • $1 ကို subcommand အဖြစ် ဖတ်ပြီးနောက် shift ကို လျစ်လျူရှုမိခြင်း — မလုပ်ပါက cmd_add သို့မဟုတ် cmd_done အတွင်းရှိ $@ တွင် subcommand အမည်ကိုယ်တိုင်ပါဝင်နေဆဲဖြစ်ပြီး မလိုလားအပ်သော argument အပိုတစ်ခု ဖြစ်နေမည်
  • နမူနာ code ကို production system ပေါ် တိုက်ရိုက်မစမ်းဘဲ local/test environment တွင် အရင်အတည်ပြုပါ။

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

./task.sh add "New task" ကို run သောအခါ ပေးထားသော စာသားကို task အသစ်တစ်ခုအဖြစ် ထည့်သွင်းပြီး ./task.sh list ကို နောက်တစ်ကြိမ် run သည့်အခါ ပေါ်လာစေရန် cmd_add ကို ဖြည့်စွက်ပါ — invocation တစ်ခုစီသည် process အသစ်တစ်ခုဖြစ်၍ ဆိုလိုသည်မှာ in-memory TASKS array ထဲသို့ append ရုံမျှမက file တစ်ခုထဲသို့ persist လုပ်ရန် လိုအပ်သည်။ ထို့နောက် ./task.sh done <number> က task ထိုနံပါတ်ကို (ဖယ်ထုတ်ခြင်း သို့ flag တပ်ခြင်း — သင့်ရွေးချယ်မှုအတိုင်း) persist လုပ်ထားသော file တွင် ပြီးစီးကြောင်း မှတ်သားနိုင်ရန် cmd_done ကို ဖြည့်စွက်ပါ။ ပြီးဆုံးသောအခါ list သည် hardcoded task သုံးခုအစား လက်ရှိ task စစ်စစ်များကို print ထုတ်သင့်ပြီး add နှင့် done တို့သည် 'not yet implemented' ဟု နောက်ထပ် မပြသင့်ဘဲ၊ မသိသော subcommand တစ်ခု run လိုက်ပါက ယခုကဲ့သို့ပင် usage message သို့ ကျရောက်နေဆဲဖြစ်သင့်သည်။

You'll know it worked when: $ ./task.sh list Tasks: 1) Buy milk 2) Write report 3) Call the plumber $ ./task.sh add "Buy bread" add: not yet implemented $ ./task.sh foo Usage: task.sh {add|list|done} [arguments]

လေ့ကျင့်ခန်း — Subcommand များပါသော CLI Tool တစ်ခု တည်ဆောက်ခြင်း | Thuta Learning