12 điểm bởi GN⁺ 2025-08-13 | 1 bình luận | Chia sẻ qua WhatsApp
  • Giới thiệu cách triển khai tính năng tự động hoàn thành bằng phím Tab trong BashZsh, có thể hiển thị mô tả ngay cả với những từ đã hoàn thành sẵn
  • Bash và Zsh dùng hai API tự động hoàn thành khác nhau, và chỉ Zsh mặc định hỗ trợ hiển thị mô tả trong gợi ý hoàn thành
  • Triển khai cấu trúc tạo ứng viên bằng _generate_foo_completions, rồi trả về bằng COMPREPLY trong Bash và compadd trong Zsh
  • Để mang tính năng mô tả của Zsh sang Bash, xử lý bằng cách nhúng mô tả vào chuỗi ứng viên rồi chỉ bỏ phần mô tả khi chỉ có một ứng viên duy nhất
  • Ngay cả khi chỉ có một ứng viên, bài viết còn cải tiến bằng cách cố ý thêm tính mơ hồ để khi nhấn <TAB> vẫn hiện mô tả
  • Script cuối cùng mang lại trải nghiệm người dùng giống nhau trên cả hai shell, đồng thời hỗ trợ hoàn thành một phần, hoàn thành đầy đủ và hiển thị mô tả ứng viên

Bối cảnh vấn đề

  • Tự động hoàn thành bằng Tab (tab-completion) rất hữu ích khi khám phá lệnh hoặc cờ, đặc biệt khi mới làm quen với API hay công cụ CLI
  • Zsh mặc định chỉ hiển thị mô tả khi có nhiều ứng viên, còn Bash thì chỉ làm được thông qua cấu hình riêng
  • Nhưng với những từ đã hoàn thành sẵn, cả hai shell đều không hiển thị mô tả
  • Vì vậy, để xem mô tả, người dùng phải làm quy trình bất tiện như sau
    • Xóa bớt một vài ký tự để nhiều ứng viên cùng khớp
    • Nhấn phím <TAB> để xem danh sách ứng viên
    • Tìm mô tả mong muốn bằng mắt
    • Gõ lại các ký tự đã xóa để chạy lệnh

Tổng quan cách giải quyết

  • Ngay cả khi chỉ có một ứng viên, vẫn thêm một ứng viên giả (dummy completion) để tạo ra sự mơ hồ
  • Làm như vậy sẽ khiến cả Bash lẫn Zsh đều in ra danh sách ứng viên kèm mô tả
  • Tuy nhiên, cần cẩn thận để phần mô tả không bị chèn vào lệnh thực tế
Quảng cáo

Khái niệm cơ bản

  • Tự động hoàn thành bằng Tab hoạt động theo cách nhận từ hiện tại và vị trí con trỏ khi nhấn <TAB>, rồi trả về danh sách các ứng viên có thể dùng
  • Bash: gán các ứng viên vào mảng COMPREPLY
  • Zsh: đăng ký các ứng viên bằng lệnh compadd
  • Hàm _generate_foo_completions sẽ in ra các chuỗi ứng viên; trong thực tế có thể sinh động dựa trên trạng thái của CLI

Hỗ trợ đồng thời Bash và Zsh

  • Cài đặt riêng cho từng shell bằng hai hàm _complete_foo_bash_complete_foo_zsh
  • Phân nhánh bằng if [ -n "${ZSH_VERSION:-}" ]; then ... elif [ -n "${BASH_VERSION:-}" ]; then ... fi
  • Người dùng chỉ cần thêm script vào .bashrc hoặc .zshrc rồi áp dụng

Hiển thị mô tả trong Zsh

  • Dùng định dạng tên: mô tả cho chuỗi ứng viên
  • Zsh: truyền tên và mô tả dưới dạng hai mảng song song bằng compadd -d raw -- $trimmed
  • Bash: chỉ truyền phần ứng viên đã bỏ mô tả vào COMPREPLY (mặc định không hỗ trợ mô tả)
Quảng cáo

Triển khai mô tả trong Bash

  • Khi có nhiều ứng viên, hiển thị nguyên chuỗi có kèm mô tả
  • Khi chỉ có một ứng viên, mới loại bỏ phần mô tả
  • Tận dụng cách Bash chỉ chèn tiền tố chung khi tự động hoàn thành để phần mô tả không bị đưa vào nội dung đang nhập thực tế

Hiển thị mô tả ngay cả với một ứng viên duy nhất

  • Để vẫn hiện mô tả khi nhấn <TAB> trên một từ đã hoàn thành, thêm ứng viên giả để tạo sự nhập nhằng có chủ đích
  • Zsh: thêm ứng viên giả vào cả hai mảng song song (raw, trimmed)
  • Bash: khi chỉ có một ứng viên thì thêm riêng phần tên vào trimmed

Kết quả cuối cùng

  • Khi có nhiều ứng viên, hiển thị cả tên lẫn mô tả
  • Khi chỉ có một ứng viên, vẫn có thể xem mô tả bằng <TAB>
  • Bash và Zsh đều mang lại trải nghiệm giống nhau
  • Ví dụ áp dụng:
    $ foo <TAB>  
    apple: a common fruit banana: starchy and high in potassium  
    apricot: sour fruit... cherry: small and sweet...  
    

1 bình luận

 
GN⁺ 2025-08-13
Ý kiến Hacker News
  • Trong fish, nếu chương trình bạn quan tâm có cung cấp trang man thì chỉ cần chạy fish_update_completions
    • Lệnh này phân tích toàn bộ các trang man trong hệ thống và tạo các file tự động hoàn thành trong ~/.cache/fish/generated_completions/
    • Nếu trang man sơ sài hoặc không có, bạn có thể tự viết rồi đóng góp lên upstream
    • Định dạng của fish khá đơn giản nên chỉ cần xem tài liệu chính thức là đủ
    • Ví dụ: curl với -L → 'Follow redirects', -O → 'Write output to file named as remote file'
    • Khi chia sẻ màn hình, mọi người thường nghĩ tôi đang dùng zsh và các plugin, nhưng thực ra tôi dùng fish đẹp mắt chỉ với cấu hình mặc định
    • Tuy nhiên, sẽ tốt hơn nếu cải thiện việc car TAB bị mở rộng thành blkdiscard do tự động hoàn thành không theo tiền tố, khiến nó hoạt động sai ngay cả khi cargo không có trong PATH
    • Với các chương trình không có trang man mà chỉ cung cấp --help, tôi tự hỏi fish có tính năng tương tự _gnu_generic của zsh hay complete -F _longopt của bash hay không
    • Trong zsh cũng có script tạo tự động hoàn thành dựa trên trang man → zsh-manpage-completion-generator
    • Trên OpenSUSE, khi gõ zypper search fish-completion thì hiện ra hơn 200 gói, nên tôi thấy có gì đó hơi đáng ngờ
  • Việc bash auto-completion ngày càng “thông minh hơn” đến mức chặn hoàn thành file/thư mục nếu nó cho rằng tên file không phù hợp với vị trí con trỏ hiện tại là điều khá bất tiện
    • Tôi nghĩ tốt hơn là cứ luôn fallback về hoàn thành tên file
    • Vì chuyện này mà tôi từng cân nhắc tắt hết các script completion
    • Trong bash có các hàm như complete-filename được bind với M-/, cho phép chỉ hoàn thành tên file mà bỏ qua ngữ cảnh
    • Có lúc phần hoàn thành file của một lệnh cụ thể hỏng hoàn toàn, nên tôi phải lách bằng cách hoàn thành bằng ls trước rồi đổi lại lệnh
    • Khi tên file tồn tại nhưng không thể thực thi, sẽ bớt gây rối hơn nếu hiện thông báo như “file foo.exe exists but it isn't executable”
    • Sau khi chạy complete -r thì mọi thứ lại hoạt động đúng như mong muốn, nên có vẻ vấn đề nằm ở các script bash-completion
    • Nó giống kiểu UX khó chịu của kiểm tra phía frontend trên form web: mới gõ chữ cái đầu tiên vào email đã báo “Invalid email!”
  • Đây là bài tôi viết, mong là những người khác cũng thấy thú vị
    • Thay vì tải script completion của zsh mỗi lần, nếu cài vào $fpath thì nó sẽ được cache và tốc độ khởi động sẽ nhanh hơn
    • Khi phân phối qua Homebrew, có thể tự động cài completions
    • Completion của zsh khá đồ sộ nên khó học, nhưng ở công ty tôi đang dần cải thiện script wrapper ansible bằng cách thêm tự động hoàn thành theo từng playbook, tag, v.v.
  • Gần đây tôi bắt đầu dùng thư viện usage để phát triển CLI → tích hợp với clap, và có thể tạo completions, argparse, trang man
    • Tôi vẫn đang cân nhắc liệu có đáng để thay khối argparse trong script fish hay không, nhưng nó tốt hơn optparse rất nhiều
    • Tôi cũng đang làm một công cụ định nghĩa đặc tả CLI tương tự, và vừa mới thêm hỗ trợ fish
  • Giới thiệu fx.wtf, hỗ trợ tự động hoàn thành các trường JSON trong bash/zsh
    • Nhẹ hơn ijq, nhưng tùy tình huống có thể khá hữu ích
  • Hướng dẫn tạo completer bằng hàm tích hợp sẵn của zsh → zsh-completions-howto
  • Tôi tự hỏi liệu có cờ tiêu chuẩn nào giúp chương trình không cần phải tự viết script bash hay không
    • Trong zsh, có thể dùng _gnu_generic để có auto-completion đơn giản dựa trên --help
    • Có các ví dụ như clap_complete của Rust, tùy chọn --generate của ripgrep, hay tạo completion lúc chạy của PHP Symfony
    • Sẽ thật tốt nếu có một tiêu chuẩn --completion mà các thư viện parser dùng chung có thể tự động triển khai
  • Trong ksh, có thể triển khai completion đơn giản chỉ bằng định nghĩa mảng
    • Ví dụ: nếu đưa tên signal vào mảng complete_kill_1 thì sẽ có completion cho đối số đầu tiên của lệnh kill
    • Tôi không rõ đây là cú pháp ksh93 hay đã được backport sang oksh
  • Ví dụ về snippet completion zsh đơn giản tôi tự làm: trong hàm set-java-home, dùng _describe để hoàn thành danh sách ~/apps/java/* theo phiên bản
    • Cấu trúc đơn giản gần như chỉ là một dòng
  • Có lẽ vì chức năng chính của LLM là tự động hoàn thành văn bản, nên GitHub Copilot làm completion bash và zsh trong terminal của vscode khá tốt