BATsh Tài Liệu Tham Khảo Nhanh [VI] Tiếng Việt
==============================================

----------------------------------------------------------------------
TÓM TẮT
  BATsh là một shell song ngữ chạy cú pháp batch cmd.exe và bash/sh trong
  cùng một tệp kịch bản. Nó tự động chuyển chế độ theo từng dòng/phần.
  Không cần shell bên ngoài -- triển khai bằng Perl thuần.
  Hỗ trợ đường ống, chuyển hướng, hàm và các mở rộng khai triển biến.

VÍ DỤ CHẾ ĐỘ HỖN HỢP
  :: phần CMD (token đầu tiên viết hoa)
  @ECHO OFF
  SET LANG=BATsh
  SET COUNT=3

  # phần SH (token đầu tiên viết thường)
  greet() { echo "Hello from $1 (bash mode)"; }
  greet $LANG
  for i in 1 2 3; do echo "  item $i of $COUNT"; done
  result=$(echo $LANG | perl -ne "print uc")
  echo "Uppercase: $result"

  :: lại phần CMD (đọc kết quả SH qua cầu nối)
  ECHO Back in CMD: %result%

  # Chạy bằng: perl lib/BATsh.pm script.batsh
  # Hoặc:      use BATsh; BATsh->run('script.batsh');
----------------------------------------------------------------------


BATsh là một shell song ngữ chạy cú pháp cmd.exe và bash/sh trong cùng một
kịch bản.  Kịch bản được chia thành các phần; mỗi phần được thực thi nguyên
văn bởi shell phù hợp.

1. Phát Hiện Chế Độ
-------------------
  TOKEN ĐẦU TIÊN của dòng có nội dung đầu tiên trong một phần quyết định
  shell nào sẽ thực thi phần đó:

  Chế độ CMD: token đầu tiên hoàn toàn gồm [A-Z 0-9 _ - \ / : . @ %] VÀ chứa
              ít nhất một chữ hoa A-Z.

    ECHO hello          -> phần CMD (cmd.exe)
    SET FOO=bar baz     -> phần CMD  (không kiểm tra phần giá trị)
    @ECHO OFF           -> phần CMD
    IF "%X%"=="Y" (     -> phần CMD

  Chế độ SH: mọi thứ khác (có chữ thường, hoặc không có chữ cái nào).

    echo hello          -> phần SH  (bash/sh)
    export FOO=bar      -> phần SH
    if [ -f "$f" ]; then  -> phần SH
    #!/bin/sh           -> phần SH  (shebang là một dòng SH)

  Chú thích và dòng trống được thu vào phần hiện tại.
  Cú pháp chú thích:
    ::           chú thích kiểu CMD
    REM ...      chú thích kiểu CMD (không phân biệt hoa thường)
    @REM ...     chú thích kiểu CMD
    # ...        chú thích kiểu SH  (KHÔNG phải shebang #!)

2. Khởi Động Shell
------------------
  perl lib/BATsh.pm               # REPL tương tác
  perl lib/BATsh.pm script.batsh  # chạy một tệp kịch bản
  perl lib/BATsh.pm -e "echo hi"  # lệnh một dòng nội tuyến

  Từ Perl:
    use BATsh;
    BATsh->run('script.batsh');
    BATsh->run_string("echo hello");
    BATsh->repl();

3. Cầu Nối Biến Môi Trường
--------------------------
  Trước khi mỗi phần chạy, BATsh chèn %ENV hiện tại làm phần mở đầu (các dòng
  SET cho CMD, các dòng export cho SH).  Sau phần đó, môi trường cuối cùng
  của shell được đọc ngược lại vào %ENV.

  export FOO=hello   # SH đặt FOO
  ECHO %FOO%         # CMD đọc FOO qua cầu nối (Windows)

  SET BAR=world      # CMD đặt BAR
  echo $BAR          # SH đọc BAR qua cầu nối

4. SETLOCAL / ENDLOCAL
----------------------
  SETLOCAL           # chụp nhanh %ENV (do BATsh xử lý, không phải cmd.exe)
  SET TMP=local_val
  ECHO %TMP%
  ENDLOCAL           # khôi phục %ENV (TMP biến mất)

  Các phạm vi có thể lồng nhau.

5. Phát Hiện Ranh Giới Phần
---------------------------
  Một phần kết thúc khi độ sâu khối của nó trở về 0 VÀ dòng có nội dung
  tiếp theo thuộc về một chế độ khác.

  Phần CMD theo dõi độ sâu ( và ) ngoài dấu nháy:

    IF "%X%"=="Y" (     <- mở khối (độ sâu 1)
        ECHO yes
    ) ELSE (            <- đóng rồi mở lại (độ sâu giữ >=1)
        ECHO no
    )                   <- đóng khối (độ sâu 0) -> phần có thể kết thúc

  Phần SH theo dõi độ sâu từ khóa:

    for x in 1 2; do   <- mở khối (độ sâu 1)
        echo $x
    done                <- đóng khối (độ sâu 0) -> phần có thể kết thúc

  Các dòng bên trong một khối đang mở được thu vào phần hiện tại ngay cả khi
  token đầu tiên của chúng trông giống chế độ khác. Điều này cho phép:

    for x in A B; do
        ECHO $x          <- chữ hoa trong khối SH: vẫn là phần SH
    done

  Các cặp từ khóa SH:
    Mở (+1)    : if  for  while  until  case  function  select  {
    Đóng (-1)  : fi  done  esac  }
    Trung tính ( 0) : then  do  else  elif

6. Định Nghĩa Chương Trình Con
------------------------------
  :GREET
  echo "Hello $BATSH_ARG1"
  RET

  Nhãn bắt đầu bằng : và kết thúc bằng RET hoặc RETURN.
  Thân được trích xuất trước khi thực thi (không chạy nội tuyến).
  Thân có thể chứa các dòng CMD, dòng SH, hoặc hỗn hợp.

7. CALL và source
-----------------
  CALL :GREET world      # gọi chương trình con với đối số
  CALL other.batsh       # bao gồm/chạy tệp .batsh khác (CMD)
  source other.batsh     # bao gồm/chạy tệp .batsh khác (SH)
  . other.batsh          # ký pháp dấu chấm POSIX

  Đối số: $BATSH_ARG1 .. $BATSH_ARGn  (%BATSH_ARG1% trong CMD)
  Số lượng: $BATSH_ARGC

8. Perl API
-----------
  BATsh->run($file)            # chạy một tệp .batsh
  BATsh->run_string($source)   # chạy chuỗi nguồn
  BATsh->run_lines(@lines)     # chạy mảng các dòng
  BATsh->repl()                # REPL tương tác
  BATsh->classify_token($tok)  # 'CMD' hoặc 'SH'
  BATsh->setlocal()            # chụp nhanh %ENV
  BATsh->endlocal()            # khôi phục %ENV
  BATsh->call_sub($lbl, @args) # gọi chương trình con
  BATsh->source_file($file)    # bao gồm tệp .batsh
  BATsh->version()             # chuỗi phiên bản

9. Ghi Chú Nền Tảng
-------------------
  Windows: Cả phần CMD và SH đều chạy bằng Perl thuần -- không cần cmd.exe, bash, hay sh bên ngoài.
  UNIX:    Cả phần CMD và SH đều chạy bằng Perl thuần -- không cần cmd.exe, bash, hay sh bên ngoài.

10. Yêu Cầu
-----------
  Perl 5.005_03 trở lên.  Chỉ các mô-đun lõi (File::Spec, Carp).
  Không phụ thuộc CPAN.

11. Đường Ống CMD và Bộ Sửa Đổi Tham Số
---------------------------------------
  cmd1 | cmd2              # đường ống qua tệp tạm (Perl thuần)
  ECHO hello | perl -e "while(<STDIN>){print uc}"

  SET /P VAR=Prompt:       # đọc một dòng từ STDIN vào VAR

  Bộ sửa đổi dấu ngã của tham số batch (ví dụ khi %0=C:\scripts\deploy.bat):
    %~0   -> C:\scripts\deploy.bat  (chỉ bỏ dấu nháy)
    %~f0  -> C:/scripts/deploy.bat  (đường dẫn tuyệt đối đầy đủ)
    %~d0  -> C:                     (ký tự ổ đĩa)
    %~p0  -> /scripts/              (đường dẫn thư mục)
    %~n0  -> deploy                 (tên tệp không phần mở rộng)
    %~x0  -> .bat                   (phần mở rộng)
    %~dp0 -> C:/scripts/            (ổ đĩa + thư mục, phổ biến nhất)
    %~nx1 -> deploy.bat             (tên + phần mở rộng)

12. Hàm SH và Khai Triển
------------------------
  greet() {              # định nghĩa hàm
      echo "Hi $1"
  }
  function add {         # cú pháp thay thế
      echo $(( $1 + $2 ))
  }
  greet world            # gọi hàm
  add 3 4                # -> 7

  ${var%.*}    bỏ hậu tố ngắn nhất khớp với .*
  ${var%%.*}   bỏ hậu tố dài nhất  khớp với .*
  ${var#*.}    bỏ tiền tố ngắn nhất khớp với *.
  ${var##*.}   bỏ tiền tố dài nhất  khớp với *.
  ${var/a/b}   thay thế lần xuất hiện đầu tiên của a bằng b
  ${var//a/b}  thay thế tất cả lần xuất hiện của a bằng b
  ${var^^}     viết hoa tất cả
  ${var,,}     viết thường tất cả
  ${var:2:4}   chuỗi con từ vị trí 2, độ dài 4
  ${#var}      độ dài chuỗi
  ${var:-def}  giá trị nếu đã đặt, nếu không thì def

13. Chuyển Hướng I/O của SH
---------------------------
  cmd > file      ghi đè stdout
  cmd >> file     nối thêm vào stdout
  cmd < file      stdin từ tệp
  cmd 2> file     stderr ra tệp
  cmd 2>&1        gộp stderr vào stdout
  cmd > f 2>&1    cả stdout và stderr ra tệp

  Here-document (đầu vào trên stdin):
    cmd <<EOF       các dòng thân cho đến EOF; $VAR được khai triển
    cmd <<'EOF'     các dòng thân cho đến EOF; không khai triển (nguyên văn)
    cmd <<-EOF      như <<EOF, nhưng các ký tự TAB đầu dòng bị loại bỏ

14. Lệnh Phức Hợp SH
--------------------
  cmd1 && cmd2    chạy cmd2 chỉ khi cmd1 thành công
  cmd1 || cmd2    chạy cmd2 chỉ khi cmd1 thất bại
  cmd1 ; cmd2     chạy cmd2 vô điều kiện

15. Mảng SH
------------
  arr=(a b c)            # literal mảng theo chỉ số
  arr+=(d e)             # thêm phần tử
  arr[5]=x               # gán theo chỉ số
  declare -a list        # khai báo mảng theo chỉ số
  declare -A map         # khai báo mảng kết hợp
  map[key]=value         # gán mảng kết hợp
  map=([k1]=v1 [k2]=v2)  # literal mảng kết hợp

  ${arr[0]}    phần tử 0  ($arr giống ${arr[0]})
  ${arr[-1]}   phần tử cuối
  ${arr[@]}    tất cả phần tử (tách từ)
  ${arr[*]}    tất cả phần tử (một từ)
  ${#arr[@]}   số phần tử
  ${#arr[2]}   độ dài phần tử 2
  ${!arr[@]}   danh sách chỉ số (hoặc khóa)
  unset arr[1] # xóa một phần tử
  unset arr    # xóa toàn bộ mảng

16. Câu lệnh case SH
----------------------
  case $x in
      a|b|c)      echo "a, b hoặc c" ;;    # |  -- nhiều mẫu
      [0-9])      echo "một chữ số"  ;;    # [..]  lớp ký tự
      [!aeiou])   echo "phụ âm"      ;;    # [!..] lớp phủ định
      data*)      echo "tiền tố"     ;;    # *  và  ?  glob
      "*")        echo "dấu sao"     ;;    # có nháy = khớp đúng chữ
      *)          echo "mặc định"    ;;    # *)  mệnh đề mặc định
  esac

  Dấu kết thúc mệnh đề (sau mỗi thân):
    ;;    dừng tại đây (trường hợp thường)
    ;&    chạy tiếp thân mệnh đề kế tiếp
    ;;&   tiếp tục kiểm tra các mẫu còn lại

17. trap và Tín hiệu SH
--------------------------
  trap 'echo bye' EXIT       # chạy khi script kết thúc (hoặc khi exit)
  trap 'rm -f $tmp' EXIT     # trình xử lý được khai triển khi kích hoạt
  trap 'echo got INT' INT    # bắc cầu tín hiệu thật qua %SIG
  trap '' INT                # bỏ qua tín hiệu
  trap - INT                 # khôi phục hành vi mặc định
  trap                       # liệt kê trap hiện tại (hoặc: trap -p)

  SIGSPEC: tên (INT hoặc SIGINT), số (2), hoặc EXIT (= 0).
  EXIT chạy một lần ở cuối; tên thật cài trình xử lý %SIG của Perl.

Xem thêm: https://metacpan.org/dist/BATsh
