- Giới thiệu một mẹo giúp có thể chạy trực tiếp file Go như file thực thi
- Chỉ cần đặt dòng đầu tiên là
//usr/local/go/bin/go run "$0" "$@"; exit và cấp quyền thực thi, rồi có thể chạy bằng ./script.go
- Cách này không phải shebang mà tận dụng cơ chế shell fallback sang /bin/sh khi POSIX gặp ENOEXEC
- Shell sẽ thực thi dòng đầu tiên như một lệnh, còn trình biên dịch Go sẽ nhận diện nó là chú thích
// và bỏ qua
"$0" truyền vào đường dẫn của chính nó để go run build và chạy script, còn $@ dùng để truyền tham số
- Thư viện chuẩn mạnh mẽ và cam kết tương thích ngược của Go rất phù hợp cho mục đích scripting; chỉ cần dùng Go 1.x thì script có thể chạy ổn trong nhiều thập kỷ
- Có thể tránh được sự phức tạp trong quản lý phụ thuộc như virtual environment của Python, pip/poetry/uv, v.v.
Cách hoạt động của shebang giả
- shebang (
#!) là cách chỉ định trình thông dịch thông qua system call execve, nhưng kỹ thuật được giới thiệu trong bài này không phải shebang
- Dòng đầu tiên của file mã nguồn Go sẽ là
//usr/local/go/bin/go run "$0" "$@"; exit, phía dưới package main là mã Go thông thường
- Cấp quyền thực thi bằng
chmod +x script.go là có thể chạy như ./script.go
- Khi kiểm tra bằng
strace, có thể thấy lúc shell thử chạy ./script.go bằng execve thì kernel trả về ENOEXEC (Exec format error)
- Shell nhận ENOEXEC sẽ fallback sang
/bin/sh để diễn giải file đó như shell script
- Trong shell,
// không phải là chú thích mà được diễn giải là đường dẫn gốc (/), nên //usr/local/go/bin/go vẫn là một đường dẫn hợp lệ để thực thi
- Vì vậy dòng đầu tiên
//usr/local/go/bin/go run "$0" "$@"; exit sẽ được shell thực thi như một lệnh
"$0" truyền vào đường dẫn file đang được chạy, nên trong lệnh thực thi, "$0" sẽ trở thành đường dẫn script.go, để go run tự tìm chính nó rồi build và chạy
"$@" là phép mở rộng positional parameters từ đối số thứ nhất trở đi, cho phép các lệnh gọi như ./script.go -f flag0 here are some args
- Nếu không có
; exit thì sh sẽ tiếp tục diễn giải file Go theo từng dòng và sẽ lỗi ở các token như package
Vì sao Go phù hợp để scripting
- Cam kết tương thích ngược của Go là năng lực cốt lõi, nên chỉ cần dùng Go 1.x thì script đã viết có thể hoạt động lâu dài
- Thư viện chuẩn phát triển tốt cùng các công cụ tích hợp sẵn (formatter, linter, v.v.) được cung cấp mà không cần cấu hình riêng, giúp tối đa hóa khả năng chia sẻ script và tính di động
- Có thể chạy code mà không cần học virtual environment hay nhiều package manager khác nhau như pip, poetry, uv như với Python
- Nhờ các công cụ tích hợp sẵn trong hệ sinh thái Go và khả năng tích hợp IDE, có thể dùng formatter và linter mặc định mà không cần
.pyproject hay package.json
- Chỉ cần cài Go phiên bản mới là có thể chạy trên bất kỳ OS nào trong nhiều thập kỷ tới
So sánh với các ngôn ngữ biên dịch khác
- Rust có tốc độ biên dịch chậm, thư viện chuẩn yếu hơn nên gần như bắt buộc phải dùng dependency, đồng thời đòi hỏi tính hoàn hảo nên tốc độ phát triển chậm hơn
- Java và các ngôn ngữ JVM vốn đã có các ngôn ngữ scripting dựa trên JVM bytecode, và Kotlin scripting gọn nhẹ cũng có thể là một lựa chọn thay thế
- Trong số các ngôn ngữ biên dịch, Go sở hữu những đặc tính phù hợp nhất cho mục đích scripting
Vấn đề định dạng của gopls và cách khắc phục
gopls yêu cầu có khoảng trắng sau chú thích (//example → // example), nên dòng shebang giả sẽ bị phá vỡ
- Nếu có khoảng trắng thì sẽ thành
// usr/local/go/bin/go, và shell sẽ không còn nhận đó là đường dẫn nữa
- Cách khắc phục: dùng đề xuất từ thread HN, là dùng chú thích khối
/**/ thay cho //
- Viết theo dạng
/*usr/local/go/bin/go run "$0" "$@"; exit; */
- Bắt buộc phải có dấu chấm phẩy (
;) sau exit
1 bình luận
Ý kiến trên Hacker News
Phần tác giả nói “không muốn bận tâm tới pip vs poetry vs uv” thì thật ra uv hỗ trợ trực tiếp chính trường hợp sử dụng này
Chỉ cần cài Python phiên bản phù hợp và uv, kể cả phụ thuộc từ PyPI cũng được xử lý
Liên kết tài liệu chính thức của uv
#!/usr/bin/env -S uv run --python 3.14 --scriptLàm vậy thì ngay cả khi Python chưa được cài sẵn, uv cũng sẽ tải đúng phiên bản được chỉ định rồi chạy
Khi mới tiếp xúc với Clojure, đa số sẽ được khuyên dùng Leiningen, còn với Python thì tìm kiếm ra đủ thứ như venv, poetry, hatch, uv
uv đang dần trở thành xu hướng chính, nhưng vẫn chưa thật sự phổ biến
Trước đây tôi từng cài Go bằng apt rồi phải cài lại vì phiên bản quá cũ, nhưng chuyện đó được giải quyết nhanh hơn nhiều
Vấn đề môi trường ảo của Python vẫn còn phức tạp
Đây là một công cụ OSS viết bằng Rust, tự động quản lý phiên bản Python và venv
Chỉ cần cấu hình
pyproject.tomlrồi chạypyflow main.py, nó sẽ cài và khóa phụ thuộc như Cargo, đồng thời tự khớp phiên bản Python phù hợp với dự ánHồi đó Poetry và Pipenv đang phổ biến, nhưng vẫn chưa đủ tốt ở phần venv và quản lý phiên bản
Chủ yếu dùng
uv add, chỉ khi cần mới dùnguv pipNhưng
uv pipvẫn giữ nguyên giới hạn của pip — cách phân giải phụ thuộc thay đổi tùy theo thứ tự cài đặtuv pip install dep-arồi càidep-b, đổi thứ tự hai lệnh đó, hoặc cài cả hai cùng lúc đều cho ra kết quả khác nhauĐây gần như là vấn đề của pip, nhưng sự hỗn loạn trong quản lý gói Python vẫn còn nguyên
uv sẽ tự tải về
Go đã chủ động từ chối hỗ trợ shebang
Thay vào đó, cách được khuyến nghị là dùng
gorunCó thể chạy bằng mẹo POSIX như
/// 2>/dev/null ; gorun "$0" "$@" ; exit $?Nim, Zig, D cũng có thể dùng tương tự với tùy chọn
-run, còn Swift, OCaml, Haskell thì có thể chạy trực tiếp fileLiên kết thảo luận liên quan
go runyaegi GitHub
Bài kiểu “tôi không muốn biết pip, poetry, uv khác nhau ra sao, tôi chỉ muốn chạy code” rốt cuộc là vấn đề về mức độ thành thạo kỹ thuật
uv runvà PEP 723 đã giải quyết xong mọi chuyệnuv runxuất hiện quá muộnTôi dùng Python hơn 20 năm rồi mà vẫn luôn ngại các codebase có package bên ngoài hoặc venv
Nhờ
uv runnên toàn bộ dự án công ty của tôi đã chuyển sang đó, nhưng dự án cá nhân thì tôi đã sang Go rồiVề lâu dài tôi vẫn thích ngôn ngữ kiểu tĩnh hơn
Người dùng chỉ muốn chương trình chạy được
uv runvà PEP 723 đã giải quyết bài toán, nhưng việc vẫn phải biết đến uv khiến rào cản gia nhập còn tồn tạiChừng nào uv chưa trở thành công cụ mặc định chính thức, sẽ còn nhiều người rời bỏ Python
Tôi nghĩ đây là một ý tưởng thật sự thiên tài
Nhưng scripting cần một cảm giác công thái học khác với phần mềm để triển khai/phân phối
bash mang tính ngẫu hứng, Go hợp để sản phẩm hóa, Python nằm đâu đó ở giữa, Ruby gần với bash hơn, còn Rust nghiêng về phía Go
Script hữu ích khi cần ghép nhanh các lệnh hệ điều hành để xử lý tác vụ một lần
Go thiếu đi sự ngẫu hứng đó
Tôi từng thử chạy một ứng dụng gtk đơn giản trên Debian bằng uv, phụ thuộc có vẻ đều đúng nhưng vẫn không chạy được và cuối cùng Core Dump
Mỗi lần tôi thử lại Python theo hướng mới thì gần như đều gặp kiểu chuyện này
Go thì dài dòng hơn, nhưng một khi đã biên dịch xong thì nó cứ thế mà chạy
Điểm mấu chốt là liệu có thể kết thúc trong một file duy nhất hay không
Script Go dài 500 dòng vẫn làm được, nhưng bản thân ngôn ngữ này giả định nhiều file và module
Việc không hỗ trợ bang-line cũng vì lý do đó
Đằng nào
go runcũng tạo binary tạm thời, nên theo tôi thà build luôn rồi đặt vào/usr/local/bincòn hơnbash cũng chỉ là một lớp trừu tượng phía trên hệ điều hành giống như Python, chỉ khác là nó là shell mặc định nên tạo cảm giác như vậy
Đặc biệt theo hướng làm cho code do LLM viết ra dễ đọc hơn với con người
Tôi đồng ý rằng người mới dùng Python không cần biết sự khác nhau giữa pip, poetry và uv
Nhưng nếu đã viết blog về chủ đề này thì ít nhất phải biết uv giải quyết được vấn đề
Phê phán trong trạng thái thiếu hiểu biết thì không thuyết phục
Tôi cũng tò mò vì bản thân chưa hiểu hoàn toàn khái niệm của uv
Tôi thích viết script bằng Python
Làm việc nhanh, xử lý những thứ đơn giản mà không phải lo quá nhiều về kiểu hay bộ nhớ
Nhưng tôi không muốn dùng nó cho ứng dụng quy mô lớn
Hầu hết hệ thống đều có sẵn Python nên với script đơn giản là đủ dùng
Nghĩ đến chuyện phải cài Go thì tôi thấy dùng Python qua uv còn hợp lý hơn
Như chính tác giả nói là “ban đầu hơi mang tính troll”, nên cuối cùng đây vẫn là chuyện thích Go hơn thôi
Chỉ cần
node bla.jslà xongBạn cần biết hàm trả về gì, và khi đã quen ngôn ngữ thì các kiểu cơ bản có thể nhớ bằng trực giác
Ngôn ngữ kiểu tĩnh cũng vậy thôi
Nếu có nghĩ đến người khác thì đừng viết mã để phân phối bằng Python
Tôi tưởng sẽ là một bài chê Python, ai ngờ lại là một mẹo hữu ích
Nếu là ngôn ngữ dùng
//làm chú thích thì có thể áp dụng biến thể của mẹo nàyC/C++, Java, JavaScript, Rust, Swift, Kotlin, ObjC, D, F#, GLSL đều làm được
Đặc biệt ý tưởng làm demo đồ họa một file bằng GLSL khá thú vị
Ví dụ Shadertoy
Với C còn có thể dùng chú thích khối theo kiểu
/*/../usr/bin/env gcc "$0" "$@"; ./a.out; rm -vf a.out; exit; */Nó giống uv nhưng dành cho Swift
Swift cũng chính thức hỗ trợ shebang
#!Thời TCC trước đây tôi từng dùng kiểu này để “script bằng C”
Với dự án lớn thì build script sẽ đọc manifest, build xong rồi chạy
Nhưng vì khó kiểm soát môi trường nên không phù hợp cho công việc thực tế
Nó hỗ trợ shebang trực tiếp
Nếu muốn một ngôn ngữ công thái học hơn thì .NET 10 cũng có tính năng “run file directly”
Nó hỗ trợ shebang và tự động cài package ngay trong script
Có thể dùng chỉ thị
#:sdkđể chạy thẳng cả web appChỉ là phần biên dịch AOT vẫn còn hơi thô
Ban đầu tôi nghĩ đây sẽ là bài chê Python, nhưng ngược lại nó khiến tôi nghĩ nhiều hơn về hướng đi của hệ sinh thái ngôn ngữ
Việc ML bị trói vào Python theo tôi là một sai lầm lớn
Vì nó chậm, hệ thống kiểu bất tiện và khó phân phối
Giờ nên cân nhắc các lựa chọn khác như TypeScript, Go, Rust
Nhưng lý do ML chọn Python là vì FFI dựa trên C
NodeJS, Rust, Go đều yếu hơn ở FFI
Python có lợi thế lớn ở điểm này
Lý tưởng nhất là một ngôn ngữ đơn giản như Python nhưng có hệ thống kiểu tốt hơn và cơ chế phân phối tốt hơn
Tôi không muốn lấy một ngôn ngữ từ hệ sinh thái JS để thay Python
Lisp hay Lua (Torch) vốn phù hợp hơn, nhưng Python được chọn vì sự đơn giản
Tôi cũng đang phát triển một framework ML dựa trên Lisp, nhưng có lẽ rất khó được chấp nhận
Vấn đề tương thích phiên bản, thiếu semver, hệ sinh thái thiếu ổn định khiến nó còn tụt lại hơn cả JS
JS/Node đã trưởng thành trong 10 năm qua, còn Python dường như vẫn đứng ở năm 2012
Thật đáng tiếc khi ML lại tiêu chuẩn hóa trên Python
Khi làm công cụ CLI, Go nhanh hơn Python rất nhiều
Tôi đã quay lại Python vì chênh lệch LOC, nhưng mỗi lần chạy lại thấy nhớ Go
Có lẽ OCaml mới là lý tưởng, chỉ tiếc bộ công cụ quá cũ kỹ
Vấn đề của script Go là dòng đầu tiên không được có khoảng trắng
Vì
goplsép tự động định dạngTrong CI cũng cần giữ định dạng nhất quán, nên điều này có ý nghĩa thực tế
Nhưng vấn đề lớn hơn là không thể dùng go.mod
Tức là không thể ghim phiên bản phụ thuộc, nên khó đảm bảo tương thích