12 ms·
Go away Python
- throw-12-16 10mo agoI've been meaning to port some dotfiles utils over to go, I think I'll give this a shot.
- llmslave2 10mo agoI love it. I'm using Go to handle building full stack javascript apps, which actually works great since esbuild can be used directly inside a Go program. The issue is that it's a dependency, so I settled for having a go mod file and running it directly with Go. If somehow these dependencies could be resolved without an explicit module configured (say, it was inline in the go file itself) it would be perfect. Alas, it will probably never happen. That being said...use Go for scripting. It's fantastic. If you don't need any third party libraries this approach seems really clean.
- ahartmetz 10mo ago>full stack Device drivers, task switching, filesystem, memory management and all?
- mstipetic 10mo agoDon't be that guy.
- ahartmetz 10mo agoI am going to be that guy. I make computers do things, but I never act like my stuff is the only stuff that makes things happen. There is a huge software stack of which my work is just the final pieces.
- mstipetic 10mo agoThe term "full stack" has a widely well understood meaning, you're being pedantic
- ahartmetz 10mo agoIt doesn't for me and I don't think that my subculture of computing uses similarly myopic terms.
- llmslave2 10mo agoWhere did your subculture come from, Pedanticville?
- konart 10mo ago>It doesn't for me And it's okay. It doesn't mean it should be this way for everyone else. It is pretty common (and been so for at least two decades) for web devs to differentiate like so: backend, frontend or both. This "both" part almost always is replaced by "full stack". When people say this they just mean they do both parts of a web app and have no ill will or neglect towards systems programmers or engineers working on a power plant.
- SunlitCat 10mo agoThe problem with calling it “full stack” (even if it has a widely understood meaning) is that it implicitly puts the people doing the actual lower-level work on a pedestal. It creates the impression that if this is already “full stack,” then things like device drivers, operating systems, or foundational libraries must be some kind of arcane magic reserved only for experts, which they aren’t. The term “full stack” works fine within its usual context, but when viewed more broadly, it becomes misleading and, in my opinion, problematic.
- bheadmaster 10mo agoI agree with you in sentiment - the term "full-stack" is odd and a little too grandiose for its meaning. But it is already established in the industry, and fighting it is unlikely to yield any positive outcomes.
- llmslave2 10mo agoYes. Yes, I'm doing all of that with Javascript :P
- dangoodmanUT 10mo agodwight shrute detected
- ahartmetz 10mo agoAt least spell my name correctly ffs, it's right there in the org chart
- age123456gpg 10mo agoOfficial stance about supporting interpreter mode for the reference https://github.com/golang/go/issues/24118 https://github.com/golang/go/issues/24118
- w4rh4wk5 10mo agoBack in the days, I've seen that with C files, which are compiled on the fly to a temporary file an run. Something like //usr/bin/gcc -o main "$0"; ./main "$@"; exit
- ernst_klim 10mo agoTcc even supports that with `#!/usr/local/bin/tcc -run`, although I don't understand people who use c or go for "scripting", when python, ruby, TCL or perl have much superior ergonomics.
- w4rh4wk5 10mo agoThis was a relatively old project that used a C program as build system / meta generator. All you needed was a working C compiler (and your shell to execute the first line). From there, it built and ran a program that generated various tables and some source code, followed by compiling the actual program. The final program used a runtime reflection system, which was set up by the generated tables and code from the first stage. The main reason was to do all this without any dependencies beyond a C compiler and some POSIX standard library.
- wiseowise 10mo ago[flagged]
- hamishwhc 10mo agoThe author’s point about “not caring about pip vs poetry vs uv” is missing that uv directly supports this use case, including PyPI dependencies, and all you need is uv and your preferred Python version installed: https://docs.astral.sh/uv/guides/scripts/#using-a-shebang-to-create-an-executable-file https://docs.astral.sh/uv/guides/scripts/#using-a-shebang-to...
- benrutter 10mo agoI thought that too, but I think the tricky bit is if you're a non-python user, this isn't yet obvious. If you've never used Clojure and start a Clojure project, you will almost definitely find advice telling you to use Leiningen. For Python, if you search online you might find someone saying to use uv, but also potentially venv, poetry or hatch. I definitely think uv is taking over, but its not yet ubiquitous. Ironically, I actually had a similar thing installing Go the other day. I'd never used Go before, and installed it using apt only to find that version was too old and I'd done it wrong. Although in that case, it was a much quicker resolution than I think anyone fighting with virtual environments would have.
- houzi 10mo agoDo you think a non-python user would piece it together if the shebang line reveals what tool to use?
- benrutter 10mo agoI think yes if that line was UV. But otherwise, of its just python, you have the issue that you need two tools, one for running scripts and one for managing dependencies and environments.
- idoubtit 10mo agoThat's my experience. I'm not a Python developer, and installing Python programs has been a mess for decades, so I'd rather stay away from the language than try another new tool. Over the years, I've used setup.py, pip, pipenv (which kept crashing though it was an official recommendation), manual venv+pip (or virtualenv? I vaguely remember there were 2 similar tools and none was part of a minimal Python install). Does uv work in all of these cases? The uv doc pointed out by the GP is vague about legacy projects, though I've just skimmed through the long page. IIRC, Python tools didn't share their data across projects, so they could build the same heavy dependencies multiple times. I've also seen projects with incomplete dependencies (installed through Conda, IIRC) which were a major pain to get working. For many years, the only simple and sane way to run some Python code was in a Docker image, which has its own drawbacks.
- magicalhippo 10mo agoYou can do the same[1] with .Net Core for those of us who like that. [1]: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/tutorials/file-based-programs#unix-shebang--support https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals...
- rr808 10mo agodotnet was always really good for this. There were a bunch of third party tools that have done this since the 90s like toolsack. I think Java can run uncompiled text scripts now too
- zahlman 10mo agoThat's explicit support rather than using the same // hack. The language is specifically ignoring a shebang even though it doesn't match the usual comment syntax.
- magicalhippo 10mo agoTrue, so less fun, but more practical in that it's supported.
- PaulRobinson 10mo agoMad genius stuff, this. However... scripting requires (in my experience), a different ergonomic to shippable software. I can't quite put my finger on it, but bash feels very scriptable, go feels very shippable, python is somewhere in the middle, ruby is closer to bash, rust is up near go on the shippable end. Good scripting is a mixture of OS-level constructs available to me in the syntax I'm in (bash obviously is just using OS commands with syntactic sugar to create conditional, loops and variables), and the kinds of problems where I don't feel I need a whole lot of tooling: LSPs, test coverage, whatever. It's languages that encourage quick, dirty, throwaway code that allows me to get that one-off job done the guy in sales needs on a Thursday so we can close the month out. Go doesn't feel like that. If I'm building something in Go I want to bring tests along for the ride, I want to build a proper build pipeline somewhere, I want a release process. I don't think I've thought about language ergonomics in this sense quite like this before, I'm curious what others think.
- dingdingdang 10mo agoTalking about Python "somewhere in the middle" - I had a demo of a simple webview gtk app I wanted to run on vanilla Debian setup last night.. so I did the canonical-thing-of-the-month and used uv to instantiate a venv and pull the dependencies. Then attempted to run the code.. mayhem. Errors indicating that the right things were in place but that the code still couldn't run (?) and finally Python Core Dumped.. OK. This is (in some shape or form) what happens every single time I give Python a fresh go for an idea. Eventually Golang is more verbose (and I don't particularly like the mod.go system either) but once things compile.. they run. They don't attempt running or require xyz OS specific hack.
- logicallee 10mo agoI haven't had the same issue with anaconda. Give it a try.
- dns_snek 10mo agoI've had similar issues with anaconda, once upon a time. I've hit a critical roadblock that ruined my day with every single Python dependency/environment tool except basic venv + requirements.txt, I think. That gets in the way the least but it's also not very helpful, you're stuck with requirements.txt which tends to be error-prone to manage.
- solumos 10mo ago> I started this post out mostly trolling, but the more I've thought about it's not a terrible idea. I feel like this is the unofficial Go motto, and it almost always ends up being a terrible idea.
- hu3 10mo agofor more terrible ideas in 2026 then!
- chrismorgan 10mo agoOne suggestion: change `exit` to `exit $?` so an exit code is passed back to the shell.
- deleted 10mo ago[deleted]
- emersion 10mo agoThe following would probably be more portable: ///usr/bin/env go run "$0" "$@"; exit Note, the exit code isn't passed through due to: https://github.com/golang/go/issues/13440 https://github.com/golang/go/issues/13440
- incognito124 10mo agoTo quote the blog in question: > How true this is, is a topic I dare not enter.
- loosescrews 10mo agoThe blog says that in regard to finding bash with env. My reading is that it does not make the same claim regarding finding go with env. bash is commonly found at /bin/bash (or a symlink there exists) as it is widely used in scripts and being available at that path is a well known requirement for compatibility. Go does not so much have a conical path and I have personally installed it at a variety of paths over the years (with the majority working with env). While I agree with the author of the blog that using env to find bash may or may not improve compatibility, I also agree with the parent comment that using env to find go probably does improve compatibility.
- zahlman 10mo agoDoes the third leading slash do something?
- emersion 10mo agoNo, it just felt a tad cleaner to have the comment slashes separate from the path leading slash.
- flufluflufluffy 10mo agoI don’t really understand the initial impetus. I like scripting in Python. That’s one of the things it’s good at. You can extremely quickly write up a simple script to perform some task, not worrying about types, memory, yada yada yada. I don’t like using Python as the main language for a large application.
- graemep 10mo agoIt seems to be Linux specific (does it even work on other unix like OSes?) and Linux usually has a system Python which is reasonably stable for things you need scripting for, whereas this requires go to be installed. You could also use shell scripting or Python or another scripting language. While Python is not great at backward compatibility most scripts will have very few issues. Shell scripts are backward compatible as are many other scripting languages are very backward compatible (e.g. TCL) and they areG more likely to be preinstalled. If you are installing Go you could just install uv and use Python. The article does say "I started this post out mostly trolling" which is part of it, but mostly the motivation would be that you have a strong preference for Go.
- exographicskip 9mo agoWorks on macos too (unix by way of bsd)
- mr_toad 10mo agoI love scripting in Python too. I just hate trying to install other people’s scripts.
- flanked-evergl 10mo agoIf they use https://packaging.python.org/en/latest/specifications/inline-script-metadata/ https://packaging.python.org/en/latest/specifications/inline... then it becomes a breeze to run with uv. Not even a thing.
- 10mo ago
- jas39 10mo agoMay I... augroup fix autocmd! autocmd BufWritePost *.go \ if getline(1) =~# '^// usr/bin/' \ | call setline(1, substitute(getline(1), '^// ', '//', '')) \ | silent! write \ | endif augroup END
- rtpg 10mo agoYou don't even need to end the file in `.go` or the like when using shebangs, and any self-respecting editor will be good at parsing out shebangs to identify file types (... well, Emacs seems to do it well enough for me) no need to name your program foo.go when you could just name it foo
- assanineass 10mo ago[dead]
- flanked-evergl 10mo agoUsing `uv` with python is significantly safer and better. At least you get null safety. Sure, you can't run at the speed of light, but at least you can have some decent non-halfarsed-retrofitted type checking in your script.
- tgv 10mo agoI think you're mistaking Go for some other language.
- kbolino 10mo agoIn what way does Python have more null safety than Go? Using None will cause exceptions in basically all the same places using nil will cause panics in Go, and Python similarly lacks the usual null-safe operators like traversal (?.), coalescing (??), etc. You can abuse the falsity of None to do things like `var or ""`, but this ground gets quite shaky when real bools get involved.
- api 10mo agoTangent but... I kinda like the Python language. What I don't like about Python is the way environments are managed. This is something I generally believe, but I think it's particularly important for things like languages and runtimes: the idea of installing things "on" the OS or the system needs to die. Per-workspace or per-package environment the way Go, Rust, etc. does it is correct. Installing packages globally is wrong. There should not be such a thing as "globally." Ideally the global OS should be immutable or nearly so, with the only exception being maybe hardware driver stuff. (Yes I know there's stuff like conda, but that's yet another thing to fix a fundamentally broken paradigm.)
- zahlman 10mo ago> This is something I generally believe, but I think it's particularly important for things like languages and runtimes: the idea of installing things "on" the OS or the system needs to die. Python has been trying to kill it for years; or at least, the Linux distros have been seeking Python's help in killing it on Linux for years. https://peps.python.org/pep-0668/ https://peps.python.org/pep-0668/ is the latest piece of this.
- api 10mo agoI feel like this principle could be codified as "the system is not a workspace." The use of the system as a workspace goes back to when computers were either very small and always personal only to one user, or when they were very big and administrated by dedicated system administrators who were the only ones with permission to install things. Both these conditions are obsolete.
- NetMageSCW 10mo agoBut the system is not a workspace acts like resources are free. Everything that’s wrong with a modern computer being slower than one from 30 years ago at running user applications has its roots in this kind of thing. It’s more obvious on mobile devices but desktops still suffer. Android needs more RAM and had worse power utilization until a lot was done to move toward native compiled code and background process control. Meanwhile Electron apps think it’s okay to run multiple copies of Javascript environments like working RAM is free and performance isn’t hurt.
- avidphantasm 10mo agoNow try to call some C++ code from your Go script…
- dana321 10mo agoRust can do that
- xg15 10mo agoSo the entire reason why this is not a "real" shebang and instead takes the roundtrip through the shell is because the Go runtime would trip over the # character? I think this points to some shortcomings of the shebang mechanism itself: That it expects the shebang line to be present and adhering a specific structure - but then passes the entire file with the line to the interpreter where the interpreter has to process (and hopefully ignore) the line again. I know that situations where one piece of text is parsed by multiple different systems are intellectually interesting and give lots of opportunities for cleverness - but I think the straightforward solution would be to avoid such situations. So maybe the linux devs should consider adding a new form for the shebang where the first line is just stripped before passing the file contents to the interpreter.
- wyufro 10mo agoIt doesn't pass the file contents at all, it passes the file path.
- kbolino 10mo agoYep, this is a common misunderstanding, and the blog post itself repeats it. The only way to "pass the file contents" would be through the standard input stream, but the script might want to use stdin like normal, so this isn't an option.
- eichin 10mo agoLinux already did one better: binfmt_misc (see https://blog.cloudflare.com/using-go-as-a-scripting-language-in-linux/ https://blog.cloudflare.com/using-go-as-a-scripting-language... for using it for a much cleaner way of using it to use gorun on executable *.go files.)
- esjeon 10mo agoExpected a rant, got a life-pro-tip. Enough for a good happy new year. That said, we can abuse the same trick for any languages that treats `//` as comment. List of some practical(?) languages: C/C++, Java, JavaScript, Rust, Swift, Kotlin, ObjC, D, F#, GLSL/HLSL, Groovy Personally, among those languages, GLSL sounds most interesting. A single-GLSL graphics demo is always inspiring. (Something like https://www.shadertoy.com/ https://www.shadertoy.com/ ) Also, let’s not forget that we can do something similar using block comment(`/* … */`). An example in C: /*/../usr/bin/env gcc "$0" "$@"; ./a.out; rm -vf a.out; exit; */ #include <stdio.h> int main() { printf("Hello World!\n"); return 0; }
- frizlab 10mo agoFor Swift there’s even a project[1] that allows running scripts that have external dependencies (posting the fork because the upstream is mostly dead). I think it’s uv’s equivalent, but for Swift. (Also Swift specifically supports an actual shebang for Swift scripts.) [1] https://github.com/xcode-actions/swift-sh https://github.com/xcode-actions/swift-sh
- kibwen 10mo agoYou don't need to abuse comments like this for Rust, because it supports shebangs directly.
- thechao 10mo agoFor C/++ just use "#!". When TCC first came out, we used this exact technique for "C scripting". It requires a dirty SO methodology (you can't really control linking well). For larger projects (the exe), the shebang points to a C build file, which when compiled, knows the root path; that C build script then looks for a manifest, builds, links, and fork()s. A good a/m timestamp library with direct ccache support can spin up as fast as a script even on big projects. Again, this is all a bad idea bc it's hard to control your environment. I guess we were doing all this in the mid 2000s? When did TCC come out?
- networked 10mo agoYou can use https://github.com/erning/gorun https://github.com/erning/gorun as a Go script runner. It lets you embed `go.mod` and `go.sum` and have dependencies in Go scripts. This is more verbose than Python's inline script metadata and requires manual management of checksums. gorun caches built binaries, so scripts start quickly after the first time. Example: #! /usr/bin/env gorun // // go.mod >>> // module foo // go 1.22 // require github.com/fatih/color v1.16.0 // require github.com/mattn/go-colorable v0.1.13 // require github.com/mattn/go-isatty v0.0.20 // require golang.org/x/sys v0.14.0 // <<< go.mod // // go.sum >>> // github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= // github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= // github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= // github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= // github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= // github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= // github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= // golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= // golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= // golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= // golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= // <<< go.sum package main import "github.com/fatih/color" func main() { color.Green("Hello, world!") } The shebang line can be replaced for compatibility with standard Go tooling: /// 2>/dev/null ; gorun "$0" "$@" ; exit $? // // go.mod >>> // ...
- karel-3d 10mo agoI was looking for something like this a few times! great
- liveoneggs 10mo agowhat's even cooler is when the language comes with first class support for this: https://www.erlang.org/docs/18/man/escript https://www.erlang.org/docs/18/man/escript Or the venerable https://babashka.org/ https://babashka.org/
- g947o 10mo agoWhile you are at it, might as well do this for C++ or assembly. You hate scripting so much and would rather go to great lengths to use a complied language and throw away all the benefits of a scripting language and scripting itself, just because you don't like the language, not because of technical merit. Congratulations, you just wasted yourself many hours of precious time. > The price of convenience is difficulties to scale Of course, they never scale. The moment you start thinking about scaling, you should stop writing code as throwaway scripts but build them properly. That's not an argument to completely get rid of Python or bash. The cost of converting Python code to Go is near zero these days if there is a need to do so. Enough has been said about premature optimization. > Anyone who's ever tried to get python working on different systems knows what a steep annoying curve it is. If you need 10 libraries of certain versions to run a few lines of Python code, nobody calls that a script anymore. It becomes a proper project that requires proper package management, just like Go.
- BobbyJo 10mo agoThere is a much larger gap in language ergonomics between python and C++ than between python and golang. Compile time and package management being some of the major downsides to C++. "You'd rather drive a compact car than an SUV? Might as well drive a motorcycle then!"
- array_key_first 10mo agoThe main problem with python for system scripts is that's even in that domain it's not a very good choice. Perl is right there, requires no installation, and is on practically every unix-like under the sun. Sure it's not a good language, or performant, or easy to extend, but neither is python, so who cares. And, if anything, it's a bit more expressive and compact than python, maybe to a fault.
- rubymamis 10mo agoWhat about Mojo?
- marifjeren 10mo ago> don't want to have virtual environments and learn what the difference between pip, poetry and uv is Oh come on, it's easy: Does the project have a setup.py? if so, first run several other commands before you can run it. python -m venv .venv && source .venv/bin/activate && pip install -e . else does it have a requirements.txt? if so python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt else does it have a pyproject.toml? if so poetry install and then prefix all commands with poetry run ... else does it have a pipfile? pipenv install and then prefix all commands with pipenv run ... else does it have an environment.yml? if so conda env create -f environment.yml and then look inside the file and conda activate <environment_name> else does it have a uv.ock? then uv sync (or uv pip install -e .) and then prefix commands with uv run.
- 9dev 10mo agoAnd you consider that easy?
- marifjeren 10mo agoNo, I'm being cheeky. It's not fun. It's a "15 standards" situation https://xkcd.com/927/ https://xkcd.com/927/
- zahlman 10mo ago> Oh come on, it's easy: (satire) If you've checked out a repo or unpacked a tarball without documentation, sure. If you got it from PyPI or the documentation indicates you can do so, then you just use your tooling of choice. Also, the pip+venv approach works fine with pyproject.toml, which was designed for interoperability. Poetry is oriented towards your own development, not working with someone else's project. Speaking of which, a project that has a pipfile, environment.yml, uv.lock etc. and doesn't have pyproject.toml is not being seriously distributed. If this is something internal to your team, then you should already know what to do anyway.
- NetMageSCW 10mo agoAny time you have to resort to the no true scotsman fallacy you are telling me everything I need to know to run in the other direction.
- itopaloglu83 10mo ago> Sidetrack: I actually looked up what the point of arg0 even is since I failed to find any usecases some months back and found this answer. I think arg0 was always useful especially when developing multifunctional apps like busybox that changes its behavior depending on the name it was executed as.
- fsmv 10mo agoI made one of these too! I decided not to use // because I use gofmt auto formatting in my editor and it puts a space between the // and the usr. This one isn't changed by gofmt: /*?sr/bin/env go run "$0" "$@"; exit $? #*/
- tandr 10mo agoIt works, but the best in me I cannot explain fully first 3 symbols. /*?sr/bin/env finds /usr by expanding *? to a first matching directory. But why not just /*usr/ instead?
- fsmv 10mo agoI think I was just trying to minimize accidentally matching the wrong thing. Both do work though and it is kinda nice to be more readable. If I remember right I think ? Is exactly one character only, or maybe it does non greedy .
- k776 9mo agoI wonder if bash has a way to override the default function calling mechanism. Since functions in bashrc would probably take priority, maybe something like this could be possible: (psuedo code) ``` execve(path, args) { if path.endsWith('.go') { go run path args } else { super($path, *args) } } ``` Then when you run `./script.go`, the function gets called and passes it so `go run`, while everything else goes through the existing execve function. That would be interesting, because then you could do this with absolutely anything. `./script.rb` `./script.py` `./script.c` etc
- beckit 9mo agoThis is a sweet idea, thank you! My suggestion for minimal/readable: /*usr/bin/env go run "$0" "$@"; exit;*/ - go fmt leaves it alone - it preserves the exit code in case env or go breaks - it "figures out" where go is, no need to hard code a path that breaks on yet another machine (where /usr/bin/env is "standard") ( The first example: - I'm not a big fan of the subtle extra work that the question mark in the original path imposes on the system. env is (almost) always going to be as /usr/bin/env and rarely would something else that matches exist. ls -d /?sr to see what outputs on your system. That being said, the extra work isn't very meaningful in this case... - The trailing $? seems unnecessary as the final exit will convey the return code from env go regardless of if the $? is present or not )
- jxbdbrhcb 10mo agoargv0 is very necessary for use cases like busybox
- petercooper 10mo agoCute trick! I pointlessly wondered if I could make it work with Ruby and you kinda can, if you can tolerate a single error message before the script runs (sadly # comments don't work as shells consider them comments too): =begin ruby $0; exit =end puts "Hello from Ruby" Not immediately useful, but no doubt this trick will pop up at some random moment in the future and actually be useful. Very basic C99 too, though I'm not sure I'd want to script with it(!): //usr/bin/cc $0 && ./a.out && exit
- Zababa 10mo ago>This second method is, by the way, argued to increase compatibility as we utilize env to locate bash, which may not be located at /bin/bash. How true this is, is a topic I dare not enter. At least it seems important on NixOS, I had to rewrite a few shebangs on some scripts that used /bin/bash and didn't work on NixOS.
- benchloftbrunch 10mo agoAnd on macOS if you need bash > 3.2
- codelikeawolf 10mo ago> Did you (rightfully) want to tear your eyes out when some LLM suggested that you script with .mjs? I respectfully disagree with this sentiment. JS is a fantastic Python replacement for scripts. Node.js has added all kinds of utility functions that help you write scripts without needing external dependencies. Bun, Deno, and Node.js can execute TS files (if you want to bring types into the mix). All 3 runtimes are sufficiently performant. If you do end up needing external dependencies, they're only a package.json away. I write all my scripts in JS files these days.
- jswny 10mo agoThe problem is that only Deno can type check single file scripts. Otherwise with Node and Bun you need a project to use tsc. Python can type check single file scripts (even with PEP 723 deps) with ty. Otherwise, I love TS for scripting, especially with Bun shell
- codelikeawolf 10mo agoFair point. If I'm writing scripts, I normally opt for a JS file with JSDoc annotations if I want autocomplete for types (you don't need a tsconfig/tsc installed in WebStorm). Most projects I've encountered with TS scripts were already TS projects to begin with.
- chrisweekly 10mo agoRelated tangent: I recently learned about Mise^1 -- a tool for managing multiple language runtime versions. It might ease some of the python environment setup/mgmt pains everyone complains about. It apparently integrates with uv, and can do automatic virtualenv activation.... 1. https://mise.jdx.dev/lang/python.html https://mise.jdx.dev/lang/python.html via https://gelinjo.hashnode.dev/you-dont-need-nvm-sdkman-pyenv-rustup-homebrew-or-even-nix-try-mise-instead https://gelinjo.hashnode.dev/you-dont-need-nvm-sdkman-pyenv-...
- bushbaba 10mo agoThis is great. Means my scripts in a golang repo can also be written in Golang vs bash/python. It can even import libs from my project. Awesome!
- arccy 10mo agoi tried this and security pinged me about behavior based security rules firing because it looks like an infostealer...
- forgotpwd16 10mo agoGo has explicitly rejected adding shebang support, mandating this hack, due to being considered "abuse of resources"[0]. Rather `gorun`, which is also called a mistake by Pike, is recommended instead. And can alter this method so not to need to hardcode a path. /// 2>/dev/null ; gorun "$0" "$@" ; exit $? >Good-old posix magic. If you ask an LLM, it'll say it's due to Shebangs. Well, ChatGPT gives same explanation as article, unsurprising considering this mechanic has been repeated many times. >none other fits as well as Go Nim, Zig, D, all have `-run` argument and can be used in similar way. Swift, OCaml, Haskell can directly execute a file, no need to provide an argument. [0]: https://groups.google.com/d/msg/golang-nuts/iGHWoUQFHjg/_dbLKomrPmUJ https://groups.google.com/d/msg/golang-nuts/iGHWoUQFHjg/_dbL...
- clktmr 10mo agoThere is also yaegi, a Go interpreter, which might be a better choice for small scripts than 'go run'. https://github.com/traefik/yaegi https://github.com/traefik/yaegi
- dolmen 10mo agoAlso, for Go oneliners: https://github.com/dolmen-go/goeval https://github.com/dolmen-go/goeval (Disclaimer: author here)
- deleted 10mo ago[deleted]
- dare944 10mo ago> I started this post out mostly trolling So your goal was to waste your reader's time. Thanks.
- trvv 10mo agoYou can lose the ugly "exit" at the end and space it right for formatting too // 2>/dev/null; exec go run "$0" "$@"
- adonovan 10mo ago> The one big problem: gopls. We need the first line of the script to be without spaces... Specifically the problem here is automated reformatting. Gopls typically does this on save as you are editing, but it is good practice for your CI system to enforce the invariant that all merged *.go files are canonically formatted. This ensures that the user who makes a change formats it (and is blamed for that line), instead of the hapless next person to touch some other spot in that file. It also reduces merge conflicts. But there's a second big (bigger) problem with this approach: you can't use a go.mod file in a one-off script, and that means you can't specify versions of your dependencies, which undermines the appeal to compatibility that motivated your post: > The primary benefit of go-scripting is [...] and compatibility guarantees. While most languages aims to be backwards compatible, go has this a core feature. The "go-scripts" you write will not stop working as long as you use go version 1.*, which is perfect for a corporate environment. > In addition to this, the compatibility guarantees makes it much easier to share "scripts". As long as the receiving end has the latest version of go, the script will run on any OS for tens of years in the future.
- knodi 10mo agoYes, you can but should you?
- sharno 10mo agoYou can also use jbang to run java scripts with dependencies https://www.jbang.dev/ https://www.jbang.dev/
- paulddraper 10mo agoGo is poor for cheap scripts for one reason: error handling. It’s great for “robust” code, not for quick things that you’re okay with exploding in the default way.
- df0b9f169d54 10mo agoSee also https://blog.cloudflare.com/using-go-as-a-scripting-language-in-linux/#current-state-of-go-scripting https://blog.cloudflare.com/using-go-as-a-scripting-language... and https://gist.github.com/posener/73ffd326d88483df6b1cb66e8ed1e0bd https://gist.github.com/posener/73ffd326d88483df6b1cb66e8ed1... . They explained the direct use of "go run" was not good in some scenario. Is that still applied today? > go run does not properly return the script error code back to the operating system and this is important for scripts, because error codes are one of the most common ways multiple scripts interact with each other and the operating system environment.
- evanmoran 10mo agoIn a similar way I changed all of my build and deployment scripts to Go not long ago. The actual benefit was utility functions used by the service could be shared in deployment. So I could easily share code to determine if services/dbs were online or to access cloud secrets in a uniform way. It also improved all the error checks to be much clearer (did the curl fail because it’s offline or malformed). Additionally, it is even more powerful when used with go modules. Make every script call a single function in the shared “scripts” module and they will all be callable from anywhere symmetrically. This will ensure all scripts build even if they aren’t run all the time. It also means any script can call scripts.DeployService(…) and they don’t care what dir they are in, or who calls it. The arguments make it clear what paths/configuration is needed for each script.
- fiyec30375 10mo agoI thought this was going to be a longer rant about how python needs to... Go away. Which, as a long time python programmer and contributor, and at one time avid proponent of the language, I would entertain the suggestion. I think all of ML being in Python is a collosal mistake that we'll pay for for years. The main reasons being it is slow, its type system is significantly harder to use than other languages, and it's hard to distribute. The only reason to use it is inertia. Obviously inertia can be sufficient for many reasons, but I would like to see the industry consider python last, and instead consider typescript, go, or rust (depending on use case) as a best practice. Python would be considered deprecated and only used for existing codebases like pytorch. Why would you write a web app in Python? Types are terrible, it's slow. There are way better alternatives.
- krzyk 10mo agoTypescript? Why replace a nice language like python with anything coming out of javascript?
- christophilus 10mo agoTypescript is a really nice language even though it sits on a janky runtime. I’d love a subset of typescript that compiles to Go or something like that.
- polynomial 10mo agoIsn't that what Project Corsa is supposed to solve?
- nish__ 10mo agoProject Corsa is a rewrite of the TypeScript compiler in Go. I don't think that's what's being asked for.
- fiyec30375 10mo agoTypescript is ubiquitous in web, and there are some amazing new frameworks that reuse typescript types on the server and client (trpc, tanstack). It's faster (than python), has ergonomic types, and a massive community + npm ecosystem. Bun advances the state of the art for runtime performance (which anthropic just bought and use for Claude code).
- vovavili 10mo ago>I don't want to have virtual environments and learn what the difference between pip, poetry and uv is. I don't care. I just want to run the code. So this is skill issue, the blog post. `uv run` and PEP 723 solved every single issue the author is describing.
- andrewmcwatters 10mo ago[dead]
- oceansky 10mo agoAny old enough language will have competing libraries that you need to learn in the long run too.
- saghm 10mo agoLibraries, yes. Tooling around packages/building/managing runtimes? I'm not convinced. Perl has been using CPAN like two decades now, and I wouldn't consider that ecosystem to exactly be an example of "there's only one way to do it". I feel like you're extrapolating in the wrong direction; older languages are less likely to have first party tooling for this, so they're more likely to have multiple ways of doing it, but I don't think there's much evidence that a language that started out with first party tooling will always follow that same trend. I don't think the issue with Python is it's age as much as the tooling it has was just really not good for a long time. People will probably want to replace subpar tooling regardless of the official status if there's a better alternative, but I'd expect that I'm the presence of good enough first-party tooling, people will eventually stop bothering. I do actually think Go is a bit of an illustrative example here because it started out with just `go get` and liberal use of vendoring, then accumulated a variety of attempted replacements (e.g. godep and dep, which confusingly were not the same thing), but eventually the first party tooling around modules became a thing and after some time it seems like pretty much everyone dropped those interim tools and standardized on the official tooling. I feel like this actually shows that the proliferation of tooling can actually be stopped even if it didn't exist early on, provided that there's a process for making it official.
- 10mo ago
- chamomeal 10mo agoFor another excellent scripting solution that has - fast startup (no compilation) - uses a real language - easy to upgrade beyond a script - has tons of excellent dependencies baked-in Look no further than babashka! It’s a clojure interpreter that has first class support scripting stuff. Great built in libs for shelling out to other programs, file management, anything http related (client and server), parsing, html building, etc. Babashka is my go-to tool for starting all new projects now. It has mostly everything you need. And if it’s missing anything, it has some of the most interesting and flexible dependency management of any runtime I’ve ever seen. Via the “pod protocol” any process (written in go/rust/java whatever) can be exposed as a babashka dependency and bundled straight in. And no separate “install dependencies” command is required, it installs and caches things as needed. And of course you keep all of the magic of REPL based development. It’s got built in nrepl support, so just by adding on ‘—nrepl-server 7888’ to your command, you can connect to it from your editor and edit the process live. I’m building my personal site this way and it’s just SO nice. Sorry for the rant but when superior scripting solutions come up, I have to spread the love for bb. It’s too good to not talk about!!
- rewilder12 10mo agoI've tried Go scripting but would still still prefer python (uv is a game changer tbh). My go-to for automation will always be powershell (on linux) though. It's too bad PowerShell has the MSFT ick keeping people away from adopting it for automation. I can convince you to give it a try if you let me
- spacecow 10mo ago> Sidetrack: I actually looked up what the point of arg0 even is since I failed to find any usecases some months back and found this answer[0]. Confused, and unsatisfied by the replies, I gave up trying to understand "why arg0?" as some sort of legacy functionality. I struggle to think of how the answers provided here could be clearer or more satisfactory. Why write an article if you're going to half-ass your research? Why even mention this nothingburger sidetrack at all...? (Bait?) [0] https://stackoverflow.com/questions/24678056/linux-exec-function-what-is-the-arg0-parameter-used-for https://stackoverflow.com/questions/24678056/linux-exec-func...
- mlmonkey 10mo agoI remember when I first experienced golang, I tried compiling it. The compilation command returned immediately, and I thought it had failed. So I tried again and same result. WTF? I thought to myself. Till I did an `ls` and saw an `a.out` sitting in the directory. I was blown away by how fast the golang compiler was.
- shevy-java 10mo agoTry the following in sh: ////////usr/local/go/bin/go Well, how about this: I use ruby or python. And not shell. Somehow I have been doing so since +25 years. Never regretted it. Never really needed shell either. (Ok, that's not entirely true; I refer to shell scripts. I do use bash as my primary shell, largely due to simplicity; I don't use shell scripts though, save for keeping a few legacy ones should I be at a computer that has no support for ruby, python or perl. But this is super-rare nowadays.)
- commandersaki 10mo agoOne thing I hate about Python executables, at least the ones I've seen installed in Debian/Ubuntu is that the ones in /usr/bin are wrappers to execute somewhere in your site-packages. I just want to see the full script where I execute it.
- kstrauser 10mo agoCan you show an example of that?
- commandersaki 10mo agoGood thing you asked before I destroyed this ubuntu instance I was messing with: root@t:~# cat $(which scapy) ++ which scapy + cat /usr/bin/scapy #!/usr/bin/python3 # EASY-INSTALL-ENTRY-SCRIPT: 'scapy==2.5.0','console_scripts','scapy' import re import sys # for compatibility with easy_install; see #2198 __requires__ = 'scapy==2.5.0' try: from importlib.metadata import distribution except ImportError: try: from importlib_metadata import distribution except ImportError: from pkg_resources import load_entry_point def importlib_load_entry_point(spec, group, name): dist_name, _, _ = spec.partition('==') matches = ( entry_point for entry_point in distribution(dist_name).entry_points if entry_point.group == group and entry_point.name == name ) return next(matches).load() globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) sys.exit(load_entry_point('scapy==2.5.0', 'console_scripts', 'scapy')())
- kstrauser 10mo agoGotcha. I do understand your point, but that's pretty common for Python. Say you're using poetry (https://python-poetry.org/docs/pyproject/#scripts https://python-poetry.org/docs/pyproject/#scripts) or Uv (https://docs.astral.sh/uv/concepts/projects/config/#command-line-interfaces https://docs.astral.sh/uv/concepts/projects/config/#command-...) or setuptools (https://setuptools.pypa.io/en/latest/userguide/entry_point.html#console-scripts https://setuptools.pypa.io/en/latest/userguide/entry_point.h...) to define "scripts" for your package. Those tools will autogenerate a file like the one you posted here that basically import the package and then call a specific function inside it. Almost all interesting Python scripts import another module, so it's really unusual to see all of a program's code in one file anyway. These autogenerated scripts just take it a step further.
- timcavel 10mo ago[dead]
- ikrenji 10mo agoI never had any trouble setting up or working with venvs. I don't even feel the need to learn what uv is because it's such a non problem for me.
- semiinfinitely 10mo agoskill issue
- dherman 10mo agoHa, I just tried the same trick with Rust: //$HOME/.cargo/bin/rustc "$0" && ${0%.rs} "$@" ; exit use std::env; fn main() { println!("hello, world!"); for arg in env::args() { println!("arg: {arg}"); } } Total hack, and it litters ./ with the generated executable. But cute.
- timcavel 10mo ago[dead]
- eolpin 10mo agoFortunately this hack isn't necessary for rust, shebangs are syntactically valid and single-file scripts can be executed with cargo nightly: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#script https://doc.rust-lang.org/nightly/cargo/reference/unstable.h...
- dherman 10mo agoCool!
- jswny 10mo agoI wish this was faster, in my tests it’s about 200ms startup consistently on my M4 MacBook. Otherwise very cool
- eolpin 10mo agoInteresting! That seems unexpected, for a minimal hello-world program I'm averaging 14ms after the first execution: ``` $ echo 'fn main() { println!("hello, world!") }' > file.rs; hyperfine --warmup 1 'cargo +nightly -Zscript file.rs' Benchmark 1: cargo +nightly -Zscript file.rs Time (mean ± σ): 14.0 ms ± 1.0 ms [User: 7.7 ms, System: 9.7 ms] Range (min … max): 12.2 ms … 17.5 ms 203 runs ```
- jswny 10mo ago
- yawaramin 10mo agoI have a better idea: `ocaml script.ml` ;-) Get started here: https://dev.to/yawaramin/practical-ocaml-314j https://dev.to/yawaramin/practical-ocaml-314j
- alkh 10mo agoHave to post this monstrocity that let's you either run a python script with uv or with python directly if uv is not installed(for some of my collegues) #!/usr/bin/env bash """:" if command -v uv > /dev/null then exec uv run --script "$0" "$@" else exec python3 "$0" "$@" fi ":"""
- kazinator 10mo agoThat's a neat trick; I don't think I've seen that before. It can be made to work for just about any language that has // comments. It does rely on // which is implementation-defined according to POSIX. In some system //usr could refer to some kind of network path. Last sentence here: 3.254 Pathname A string that is used to identify a file. In the context of POSIX.1-2024, a pathname may be limited to {PATH_MAX} bytes, including the terminating null byte. It has optional beginning <slash> characters, followed by zero or more filenames separated by <slash> characters. A pathname can optionally contain one or more trailing <slash> characters. Multiple successive <slash> characters are considered to be the same as one <slash>, except it is implementation-defined whether the case of exactly two leading <slash> characters is treated specially. [IEEE Std 1003.1, 2024 Edition] It really is better for a language to either have # comments, or else support #! as a special case in a file that is presented for execution. You're also not launching an extra shell instance. (Too bad this // trick cannot use the "exec" shell command to replace the shell with the go program.)
- utf_8x 10mo agoIf you want a more... ergonomic language, you can also use the new "run file directly" functionality in .NET 10. It supports shebangs directly and it will even install packages referenced in the script! #!/usr/bin/env dotnet run #:package Newtonsoft.Json@13.0.3 using Newtonsoft.Json; Console.WriteLine( JsonConvert.SerializeObject(new { Hello = "world" }) ); Even better, with the #:sdk directive, you can even serve a tiny web app directly from your "fancy shell script"... #!/usr/bin/env dotnet run #:sdk Microsoft.NET.Sdk.Web WebApplication .Create() .MapGet("/", () => "Hello from a shell script!") .Run();
- zmj 10mo agoI wrote my first one of these today (making a tool for agents in a c# codebase). Pretty good experience, though AOT does still have some rough edges.
- lucideer 10mo ago> I don't want to have virtual environments and learn what the difference between pip, poetry and uv is. I don't care. I just want to run the code. The author is right that every user approaching Python shouldn't need to figure this out. However, I do strongly believe every blogger setting out to write an opinion piece on this topic should at least attempt to figure this out before writing their article. Obvious ignorance does not a compelling point make. Especially when uv solves literally all of the problems you're describing.
- abbadadda 10mo ago> Especially when uv solves literally all of the problems you're describing. How does uv solve the “write once, run anywhere” problem like go? (I’m not being sarcastic, I don’t know that much about uv and perhaps am lacking the mental model for understanding this)
- lucideer 10mo ago> How does uv solve the “write once, run anywhere” problem like go? Here's an examply Python script from the uv website[0]: #!/usr/bin/env -S uv run --script # # /// script # requires-python = ">=3.12" # dependencies = ["httpx"] # /// import httpx print(httpx.get("https://example.com")) This will auto-install Python 3.12 (or greater) in a per-script virtual environment, along with the httpx package from pypi & immediately execute the script, on any system that has uv. [0] https://docs.astral.sh/uv/guides/scripts/#using-a-shebang-to-create-an-executable-file https://docs.astral.sh/uv/guides/scripts/#using-a-shebang-to...
- Quothling 10mo agoUV is a drop-in for pip, rather than a replacement. It functions differently than pip behind the scenes, which is why it's faster, but when you use it, it's basically still pip. Except it makes everything easy. You create your venv with uv venv, you update packages with uv sync, you run things without activating your venv with uv run... It uses the pyproject.toml, so it's very easy to share things like linters and build tools. Then when it's time to deploy, you can compile your pyproject.toml to a requirements.txt file and use it in any Python container image. Which is very handy when you work with something like Azure Container Functions which don't come with UV (and you probably don't want to use an UV image or install UV in your build process). I've been using it for so long now that I recently couldn't remember how to use Python without it, when one of our BI guys needed some help. Which was ridiculously embarrassing. I don't think it really compares to Go though. It's not as straight forward if you work on different python distributions. It's also not as easy to work with something like micro Python compared to targeting a specific embedded platform with Go.
- pvtmert 10mo agocame here to say the main trick has also been possible for C and Java. The C version has already been pointed out in sibling comments, while Java one requires more "tricks" in terms of bash substitution. I remember I built a such java "interpreter" during my first year of university (10+ years ago! time flies fast...) because the initial intro/101 courses always had one-off programs and the main language was java. although I no longer have the original source(s) it was something like ///usr/bin/env javac $0 && java ${0%%.java}; exit; # / since it's quite simple, I even had a funny named wrapper called "java-script" (pun intended) in my $PATH so that I could just write //usr/bin/env java-script # / at the top. As you can see I already confused fellow students with the great naming scheme I used :)
- qart 10mo agoThere is a talk called "Java for Small Coding Tasks" by the author of Core Java: https://www.youtube.com/watch?v=04wFgshWMdA https://www.youtube.com/watch?v=04wFgshWMdA Apparently, this is the way to do it: ///usr/bin/env java --source 25 "$0" "$@" ; exit $? I don't do Java. As one comment on the video said, 'This presentation is the epitome of the old saying: "When your only tool is a hammer, every problem looks like a nail"'
- esaym 10mo ago> My proposition for compatibility is to not use dependencies, and instead rely on the standard library Funny that was the whole attack angle python used against perl back in 2005,etc.
- javascripthater 10mo agoYo why do I need javascript to read your blog post when I can load it myself here as a normal page https://lorentz.app/modules/blog/content/go-shebang/_.html https://lorentz.app/modules/blog/content/go-shebang/_.html
- erlkonig 10mo agoFine advice overall. Except: Don't put suffixes on command names. Don't. It's a DOS thing that has no meaning in Unix. It confuses users. It breaks hiding implementation details. It encourages users to do the wrong thing. It makes changing what language a script's in have a ripple effect of breakage across everything else that uses it. Don't do it.
- fsiefken 10mo agoArguably Crystal, Odin, Nim, Zig, V, Vale or Factor are even better!
- vjay15 10mo agoreally good post thanks lorentz :D
- morisil 10mo agoSuboptimal choice. According to AutoCodeBench, for equivalent problem complexity, LLMs generate correct Kotlin code ~70% of the time versus ~40% for Python, and Go scores lower than Python. Kotlin can be executed as a script while providing super fast compilation phase next to evaluation phase, which is further reducing a chance of mistakes. I don't use tools anymore. I just let my LLMs output Kotlin script directly together with DSLs tailored to the problem space, reducing cognitive load for the machine. It works like a charm as a Claude Code replacement, not only coding autonomously in any language, but directly scripting DB data science, Playwright, etc., while reducing context window bloat.
- rcarmo 10mo agoDisregarding the clickbait, we’ve been able to do file suffix aliasing with zsh for a decade or so now—no need for that convoluted hack.
- rajaravivarma_r 10mo agoIf Python's standard library and its backward incompatible changes are the only problem, then Ruby will be a great replacement. The language is terse and the standard library is beautiful and consistent. The language doesn't have any large backward incompatible change from version 1.9 which was released 15 years ago.
- zenethian 10mo agoWhat a weird bunch of hacks just to stick it to Python.
- jasonm23 10mo agouse uv ... convince everyone to use uv, convince all docs in projects you use, to use uv. Reject EVERYTHING ELSE for running / building py pip, poetry, conda ... etc.
- mikeschinkel 9mo agoNice technique, but wondering if the trailing "; exit" results in consuming non-zero exit codes passed to os.Exit() in the Go code?