- หากใช้ uv ร่วมกับ metadata ของสคริปต์แบบ inline ในขั้นตอนที่วางสคริปต์อัตโนมัติไว้ใน
~/bin/ แล้วเรียกใช้งาน จะช่วยให้จัดการสคริปต์ Python ได้เบาขึ้น
- หาก
~/bin อยู่ใน PATH สามารถกำหนด shebang เป็น #!/usr/bin/env -S uv run --script เพื่อเรียกใช้ไฟล์ตามชื่อเหมือนเป็นคำสั่งได้
- การตั้งค่านี้ทำให้ shell เรียก
uv run --script ก่อน แล้วส่งไฟล์สคริปต์ปัจจุบันเป็นอาร์กิวเมนต์
- uv จะละเว้นบรรทัด shebang แล้วเรียกใช้เนื้อหาที่เหลือเหมือนเป็น ไฟล์ Python ปกติ
- เมื่อให้สิทธิ์รันด้วย
chmod a+x {filname} แล้ว ก็สามารถใช้เป็นสคริปต์บรรทัดคำสั่ง Python แบบง่ายที่จัดการ dependencies ให้อัตโนมัติได้
การรัน uv ด้วย shebang
- ใช้ไดเรกทอรี
~/bin/ เพื่อสร้างและใช้งานสคริปต์หลายตัวสำหรับงานอัตโนมัติ
- หาก
~/bin ถูกเพิ่มไว้ใน PATH แล้ว จะสามารถรันสคริปต์ได้โดยตรงด้วยชื่อไฟล์เท่านั้น
- shebang ที่ใช้ในกรณีนี้มีดังนี้
#!/usr/bin/env -S uv run --script
ลำดับการรันและการตั้งค่าสิทธิ์
- shebang ด้านบนจะเรียก
uv run --script บน command line แล้วส่งไฟล์สคริปต์เป็นอาร์กิวเมนต์
- uv จะละเว้น shebang แล้วรันส่วนที่เหลือของไฟล์เป็นโค้ด Python ปกติ
- ก่อนรัน ต้องให้สิทธิ์ execute กับสคริปต์ก่อน
chmod a+x {filname}
- เมื่อตั้งค่าเสร็จแล้ว ก็สามารถรันสคริปต์บรรทัดคำสั่ง Python แบบง่ายที่จัดการ dependencies ให้อัตโนมัติได้ทันที
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
วันนี้เพิ่งรู้จัก env -S เป็นครั้งแรก และพอเห็น shebang ในบทความก็คิดทันทีว่า “บน Linux ไม่น่าจะใช้ได้ เพราะ shebang ส่งอาร์กิวเมนต์ได้แค่ตัวเดียว”
เช่น ถ้ารัน
foo.pyที่ขึ้นต้นด้วย#!/usr/bin/env -S uv run --scriptระบบปฏิบัติการจะส่งทั้งบรรทัด shebang เป็นอาร์กิวเมนต์เดียวจริง ๆ เหมือน/usr/bin/env '-S uv run --script' foo.pyแต่
-Sของenvจะแยกสตริงนั้นกลับออกเป็นหลายอาร์กิวเมนต์ จึงทำงานได้ เจ๋งและมีประโยชน์มากถ้าวาง
#!/usr/bin/guile \แล้วบรรทัดถัดไปเป็น-e main -sและปิดด้วย!#ก็จะได้เหมือน/usr/bin/guile -e main -s filenameสงสัยเหมือนกันว่าทำไมต้องทำแบบนี้ อาจเป็นเพราะ
env -Sเพิ่งถูกเพิ่มเข้ามาไม่นาน หรือใช้ไม่ได้บนทุกแพลตฟอร์มที่เขาสนใจhttps://www.gnu.org/software/guile/manual/html_node/The-Meta-Switch.html
makeแต่ใช้ YAML สำหรับการตั้งค่าตั้ง shebang เป็น
#!/usr/bin/env -s go run github.com/rliebz/tusk@latest -fแล้วใช้อินเทอร์พรีเตอร์เป็น gosh ซึ่งเป็น Go shell เช่นinterpreter: go run mvdan.cc/sh/v3/cmd/gosh@latest -sแบบนี้แค่มี Go ติดตั้งอยู่ ก็จะได้ CLI ที่รันได้บนทุกสถาปัตยกรรม
เช่น nix-shell ใช้
#!/usr/bin/env nix-shellแล้วใส่#!nix-shell --pure -i runghc ./default.nixในบรรทัดถัดไปเพื่อระบุ dependency และอาร์กิวเมนต์ จากนั้นจึงเขียนโค้ด Haskell ต่อได้กฎการแยกช่องว่างแปลก ๆ ของบรรทัด shebang เป็นบั๊กเก่าที่กลายเป็นรอยตำหนิของ Unix ซึ่งแก้ไม่ได้แล้ว
ทุกครั้งที่ต้องใช้ทริกแบบข้างต้น ก็อดคิดไม่ได้ว่าถ้าในยุค 1980 ใช้เวลาแค่ 30 นาที ก็คงเลี่ยงความหงุดหงิดอีกหลายปีหลังจากนั้นได้ บรรทัด shebang ควรถูกแยกอาร์กิวเมนต์แบบ
/bin/shมาตั้งแต่แรกไม่ใช่ shebang แต่ช่วงหลังใช้ uv เป็นเครื่องมือติดตั้ง
เพราะการแพ็กเกจและแจกจ่ายเครื่องมือ Python CLI ที่มี dependency ซับซ้อนทำได้ยาก เลยนำไปใช้สองวิธี
วิธีหนึ่งคือคัดลอกสคริปต์ติดตั้งแบบ
curl | shแล้วเพิ่มuv tool install --python python3.12 my-toolไว้ท้ายสุด เพื่อให้ผู้ใช้ติดตั้ง CLI ได้ด้วยคำสั่งบรรทัดเดียว อีกวิธีคือทำแพ็กเกจ “ตัวติดตั้ง” ขนาดเล็กบน PyPI ที่มี dependency แค่uvแล้วให้มันรันuv tool installทั้งสองวิธีจะติดตั้งเครื่องมือ CLI, Python 3.12 และ dependency ของ Python ทั้งหมดไว้ในสภาพแวดล้อมแยก ผู้ใช้ไม่ต้องจัดการ virtual environment และในแบบ
curl | shก็ไม่จำเป็นต้องมี Python ติดตั้งไว้ด้วยหลังจากนั้น issue บน GitHub ที่เกิดจากผู้ใช้ทำ dependency ซับซ้อนพังลดลงไปมาก
รายละเอียดเพิ่มเติมและลิงก์โค้ด: https://aider.chat/2025/01/15/uv.html
openssl-dev,gzip,ffiในระบบของผู้ใช้ปลายทาง และต้องคอมไพล์ Python เองไม่ใช่หรืออย่างน้อยตอนใช้ ASDF-VM ผมเคยเจอแบบนั้นเพราะเครื่องมือติดตั้ง Python ตัวอื่นที่อยู่ข้างใน
miseและaquaก็มีแนวคิด การติดตั้งเครื่องมือ ที่มีประโยชน์เช่นกันไหน ๆ ก็มีเรื่อง shebang ที่น่าสนใจแล้ว ผมเลยทำสิ่งนี้ขึ้นมาเพื่อรันไฟล์ Prolog
.plจากเชลล์/*usr/bin/env scryer-prolog "$0" "$@" ; exit #*/น่าจะใช้ได้กับภาษาสคริปต์ส่วนใหญ่ที่มีคอมเมนต์
/**/แต่ไม่รองรับคอมเมนต์#หรือ shebang#!โดยตรงคอมเมนต์ต้นฉบับ: https://github.com/mthom/scryer-prolog/issues/2170#issuecomment-1821713993
ไฟล์นี้เป็นทั้งไฟล์เชลล์ที่ถูกต้อง และเป็นไฟล์ Prolog ที่ถูกต้องในเวลาเดียวกัน ถ้ารันด้วยเชลล์ บรรทัดแรกจะหา
/usr/bin/envด้วย glob pattern/*usr/bin/envแล้วรัน/usr/bin/envจากนั้นenvจะรันscryer-prolog "test.pl"ส่วนฝั่ง Prolog จะมองบรรทัดแรกเป็นคอมเมนต์/* ... */และข้ามไปหลังจากนั้นเชลล์จะรัน
exitหลัง;เพื่อไม่ให้ตีความโค้ด Prolog ที่เหลือเป็นเชลล์ และ#จะทำให้เชลล์มองข้ามตัวปิดคอมเมนต์*/ของ Prolog เพื่อไม่ให้พ่นข้อผิดพลาดออกมาอาจเป็นสิ่งที่ดีที่สุดและแย่ที่สุดที่ผมเคยทำก็ได้
นอกจากนี้ โปรแกรม C ก็ทำให้รันได้โดยใช้บรรทัดแรกในรูปแบบ
//usr/bin/env sh -c ...ได้เช่นกัน ตอนรันจะ build ด้วยmakeแล้วรันไบนารีผลลัพธ์ ดังนั้นถ้าแก้ซอร์สแล้วรันใหม่ ก็จะเห็นเอาต์พุตที่เปลี่ยนไปทันทีสามารถใส่
#!/bin/shแล้วบรรทัดถัดไปใส่# the next line restarts using tcl \เพื่อให้รันexec tclsh "$0" "$@"ได้ใช้ประโยชน์จากจุดที่ใน TCL บรรทัดคอมเมนต์ก็สามารถต่อบรรทัดได้
บทความ Lazy self-installing Python scripts with uv ของ Trey Hunner มีรายละเอียดมากกว่านี้พร้อมตัวอย่าง
https://treyhunner.com/2024/12/lazy-self-installing-python-scripts-with-uv/
printf %.1s 0{1..80} $'\n'หรือfor i in {1..80}; do echo -n 0; done; echoตัวอย่าง ffmpeg ก็ไม่ค่อยแน่ใจว่าต่างจาก
ffmpeg -i in.mp4 -c:v copy -filter:a volumedetect -pass 1 -f null /dev/null && ffmpeg -i in.mp4 -c:v copy -filter:a "loudnorm" -pass 2 out.mp4ตรงไหนพูดตรง ๆ ฝั่ง Python ดูซับซ้อนกว่า
สำหรับงานเล็ก ๆ สามารถใช้ทริกนี้ร่วมกับ mise(mise-en-place) ได้: https://mise.jdx.dev/tasks/toml-tasks.html#shell-shebang
วิธีคือใส่
uv = 'latest'ไว้ในtoolsแล้วในบล็อกrunของงาน ประกาศ#!/usr/bin/env -S uv run --scriptกับdependencies = ["requests<3", "rich"]จากนั้นใส่โค้ด Python ลงไปเมื่อ 16 วันก่อนก็มีบทความและการถกเถียงที่เกี่ยวข้อง: Uv's killer feature is making ad-hoc environments easy https://news.ycombinator.com/item?id=42676432 (502 คะแนน, 417 คอมเมนต์)
เป็นทริกที่ควรจำไว้
ช่วงหลังผมเปลี่ยน alias ของ Python เป็น
uv run pythonแล้วค่อนข้างสบาย ไม่ต้องจัดการ.venvอะไรพวกนั้น และไม่ต้องกังวลเรื่องการติดตั้งในระบบหรือการติดตั้งแบบ global/user โดยเฉพาะบน Debian นี่ช่วยได้มากเวลาเปิด REPL ในโปรเจกต์หรือ environment ก็เรียกได้โดยไม่ต้อง
activateทำให้มีเรื่องให้คิดน้อยลงแต่ข้อเสียคือเวลารัน
.pyโดยตรงด้วย uv เส้นทางไฟล์โปรเจกต์/environment จะอิงจาก current working directory ไม่ใช่อิงจากตำแหน่งของไฟล์.pyมีสวิตช์แบบระบุชัดเจน
--projectจึงใช้ได้แบบuv run --project /script.pyแต่ถ้ามีสวิตช์โปรเจกต์แบบ relative โดยอิงจากไฟล์เป้าหมาย ก็น่าจะดีเพราะช่วยลดการเขียนซ้ำได้แม้จะเคยเจอกับดักอยู่บ้างตอนใช้
uvหรือuvxเป็น ตัวรันคำสั่ง แต่ส่วนใหญ่ก็ทำงานได้ดี และเวลาที่เป็นแบบนั้นก็ช่วยประหยัดเวลาได้มากในฐานะนักพัฒนา Python และคนที่ชอบลองโน่นลองนี่ ในโฮมไดเรกทอรีของผมมี shallow clone กับไดเรกทอรีทดสอบแบบใช้ครั้งเดียวกระจัดกระจายเต็มไปหมด จุดที่ช่วยลดภาระในการจัดการแบบนี้จึงมีประโยชน์มากจริง ๆ
ไอเดียนี้อยากเอาไปใช้ตามเลย สงสัยว่าใช้ส่วนขยายไฟล์แยกต่างหากที่ไม่ใช่
.pyหรือว่าใช้แค่ไฟล์.pyที่เปิดบิต executable และมี shebang เป็นสัญญาณว่ารันได้โดยตรงในฐานะสคริปต์ ไม่ใช่ผ่าน system Pythonไม่ใช่ว่าไม่มีเลย แต่ผมก็กำลังพยายามหาอย่างจริงจังอยู่ ถ้ารู้ว่าเจอกับดักอะไรบ้าง ผมอยากเพิ่มลงในรายการ
.pyแล้วเพิ่ม บรรทัด shebang ตามที่บทความพูดถึงใน Pipenv เคยมีปัญหาเมื่อ dependency บางตัวมี wheel ต่างกันตามแพลตฟอร์ม ส่วน Poetry ทำงานได้ดี
ไฟล์ lock ของ Pipenv ใส่เฉพาะ dependency ของแพลตฟอร์มที่ lock ไว้ แต่ Poetry เพิ่ม variant ของทุกแพลตฟอร์มลงในไฟล์ lock
ยังหาเอกสารไม่เจอว่า uv ทำงานอย่างไรในกรณีนี้
สาระสำคัญคือแบบนี้
PEP ที่เพิ่งเผยแพร่เมื่อไม่นานมานี้กำหนดวิธีให้สคริปต์ Python ประกาศ dependency ในคอมเมนต์ตอนต้นไฟล์ และ uv เป็นทั้งตัวรันสคริปต์ Python และตัวจัดการแพ็กเกจที่สแกน dependency เหล่านั้น จัดการให้ครบ แล้วรันสคริปต์ในสภาพที่สามารถ import โมดูลเหล่านั้นได้
ถ้าให้บรรทัด shebang ซึ่งเป็นคอมเมนต์บรรทัดแรกของสคริปต์ Python เรียกไฟล์ด้วย
uvแทนpythonก็จะได้ผลนี้โดยอัตโนมัติเมื่อ “รัน” สคริปต์ผลลัพธ์คือแม้แต่สคริปต์ Python ที่มี dependency ซึ่งต้องตั้งค่าก็สามารถรันได้เลย
$PATHและแม้ uv จะติดตั้ง dependency ให้ แต่ยังไม่ชัดเจนว่าวิธีนี้จะติดตั้ง Python interpreter ให้โดยอัตโนมัติด้วยหรือไม่
โดยส่วนตัวไม่เห็นว่ามันประหยัดเวลากว่าวิธีเดิมที่สร้าง environment แล้วเรียกใช้งาน
ยังไม่ค่อยแน่ใจด้วยว่า uv สามารถโหลด environment สำหรับ session แบบโต้ตอบได้หรือเปล่า หรือใช้เฉพาะสำหรับการรันสคริปต์เท่านั้น
ถ้าชอบ TypeScript ก็ใช้วิธีเดียวกันกับ Deno ได้เหมือนกัน Bun ก็น่าจะได้ แต่ยังไม่ได้ลองเอง
ใช้แบบ
#! /usr/bin/env -S deno runและถ้าต้องมี flag สิทธิ์ ก็เพิ่มแบบ#! /usr/bin/env -S deno run --allow-env --allow-read --allow-netได้