Một script build hoạt động với đường dẫn ngắn nhưng hỏng khi tên thư mục có khoảng trắng. Bản thân công cụ build có thể không có lỗi; wrapper đã thay đổi số lượng hoặc nội dung đối số trước khi chuyển cho nó. Khi viết một hàm bọc lệnh trong CI, giữ nguyên ranh giới từng argument là một yêu cầu cần kiểm tra riêng.
Bài chạy ví dụ bằng GNU Bash 3.2.57 trên macOS, đối chiếu GNU Bash Reference Manual 5.3 ngày 09/11/2026. Các quy tắc trích dẫn được dùng ở đây đã được kiểm tra trên môi trường nêu trên. Không chạy pipeline trên hosted runner hoặc công cụ build bên ngoài.
In cả số lượng lẫn nội dung đối số
Một dòng log đã nối chuỗi có thể che việc hai argument bị nhập thành một hoặc một argument bị tách làm hai. Hàm kiểm tra dưới đây hiển thị rõ từng vị trí:
set -eu
show_args() {
printf 'argc=%s\n' "$#"
local position=1
local argument
for argument in "$@"; do
printf '%s=<%s>\n' "$position" "$argument"
position=$((position + 1))
done
}
forward() { show_args "$@"; }
join_first() { show_args "$*"; }
forward 'My Project' '' '*.txt'
join_first 'My Project' '' '*.txt'
Với IFS mặc định của shell mới, forward giữ ba argument: My Project, chuỗi rỗng và chuỗi literal *.txt. join_first đưa tất cả vào một argument duy nhất: My Project *.txt, có hai dấu cách giữa Project và dấu sao do phần tử rỗng nằm giữa.
Theo Special Parameters, "$@" bảo toàn từng positional parameter dưới dạng một word riêng khi dùng tại vị trí đối số như ví dụ. "$*" tạo một word ghép, với ký tự phân cách phụ thuộc IFS. Hai cách viết không thay thế được nhau chỉ vì cùng nằm trong dấu nháy kép.
Bỏ quote làm dữ liệu đi qua thêm phép mở rộng
Để quan sát khác biệt mà không phụ thuộc các file trong thư mục hiện tại, tạo một thư mục thử riêng rồi chạy tiếp trong cùng shell:
demo_dir=$(mktemp -d /tmp/aedev-args-demo.XXXXXX)
(
cd "$demo_dir"
touch a.txt b.txt
set -- 'My Project' '' '*.txt'
show_args "$@"
show_args $@
)
printf 'Demo directory: %s\n' "$demo_dir"
Lần có quote vẫn giữ ba argument. Lần không quote có bốn: My, Project, a.txt và b.txt. Chuỗi rỗng mất đi, khoảng trắng tách tên thư mục và dấu sao khớp tên file. Đây là fixture nhỏ đã chạy, không phải giả định rằng mọi thư mục đều có đúng hai file txt.
Word Splitting giải thích bước tách từ, còn phần filename expansion của cùng manual mô tả việc mở rộng mẫu tên file. Thứ tự các bước khiến lỗi phụ thuộc nội dung đầu vào lẫn các file đang tồn tại, nên nó có thể khó nhận ra khi chỉ thử trên một máy.
Áp dụng vào wrapper có trách nhiệm rõ ràng
Khi thay show_args bằng một lệnh thật trong forward, giữ "$@" ở vị trí cần chuyển tiếp các argument còn lại. Nếu wrapper tự đọc một tùy chọn trước, xử lý nó rồi dùng shift theo hợp đồng của script; sau đó mới chuyển phần còn lại. Đừng nối mọi thứ thành một biến chuỗi rồi cố đoán lại các ranh giới bằng dấu cách.
Không cần dùng eval để “khôi phục” một command line đã ghép. Giữ danh sách đối số từ đầu giúp wrapper trực tiếp hơn và tránh đưa dữ liệu qua một lượt phân tích mã shell mới. Bài không cung cấp parser tùy chọn tổng quát; khi script có nhiều tham số, hãy định nghĩa cú pháp nhận vào riêng.
Kiểm tra cả trường hợp không có đối số
Gọi forward không truyền gì cần đưa argc bằng 0. Gọi join_first không truyền gì lại có thể chuyển một chuỗi rỗng thành một argument ở cách dùng trong ví dụ. Với một CLI phân biệt “không chỉ định đường dẫn” và “đường dẫn rỗng”, khác biệt ấy có ý nghĩa thực tế.
Hãy thêm vào kiểm tra một argument có khoảng trắng, một chuỗi rỗng, một dấu sao literal và một giá trị có newline. Khi cần so chính xác, dùng mảng đối số mà chương trình đích nhận hoặc định dạng phân cách NUL; log dạng một dòng không đủ chứng minh dữ liệu giữ nguyên trong mọi trường hợp.
Wrapper tốt cần giữ được cùng số lượng và nội dung argument mà nó cam kết chuyển tiếp. Với Bash ở vị trí gọi lệnh thông thường, "$@" là cách thể hiện trực tiếp hợp đồng ấy.


