logoalt Hacker News

Go away Python

384 pointsby baalimagolast Tuesday at 8:50 AM349 commentsview on HN

Comments

hamishwhclast Tuesday at 11:09 AM

The 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...

show 5 replies
forgotpwd16last Tuesday at 3:39 PM

Go 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/_dbL...

show 2 replies
vovavililast Tuesday at 5:01 PM

>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.

show 6 replies
PaulRobinsonlast Tuesday at 11:22 AM

Mad 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.

show 2 replies
esjeonlast Tuesday at 1:17 PM

Expected 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/ )

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; }

show 3 replies
flufluflufluffylast Tuesday at 12:10 PM

I 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.

show 3 replies
lucideerlast Tuesday at 10:21 PM

> 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.

show 1 reply
utf_8xlast Tuesday at 9:16 PM

If 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 [email protected]
  
  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();
show 1 reply
fiyec30375last Tuesday at 4:44 PM

I 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.

show 10 replies
g947olast Tuesday at 1:43 PM

While 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.

show 2 replies
llmslave2last Tuesday at 10:41 AM

I 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.

show 1 reply
adonovanlast Tuesday at 4:36 PM

> 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.

show 2 replies
fsmvlast Tuesday at 2:25 PM

I 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 $? #*/
show 1 reply
magicalhippolast Tuesday at 11:13 AM

You 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...

show 1 reply
kazinatorlast Tuesday at 8:14 PM

That'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.)

w4rh4wk5last Tuesday at 10:52 AM

Back 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

show 1 reply
trvvlast Tuesday at 4:31 PM

You can lose the ugly "exit" at the end and space it right for formatting too

    // 2>/dev/null; exec go run "$0" "$@"
age123456gpglast Tuesday at 10:48 AM

Official stance about supporting interpreter mode for the reference https://github.com/golang/go/issues/24118

evanmoranlast Tuesday at 4:40 PM

In 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.

itopaloglu83last Tuesday at 2:17 PM

> 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.

rtpglast Tuesday at 12:42 PM

You 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

petercooperlast Tuesday at 3:00 PM

Cute 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
codelikeawolflast Tuesday at 3:26 PM

> 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.

show 1 reply
ikrenjilast Tuesday at 6:12 PM

I 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.

erlkonigyesterday at 2:17 AM

Fine 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.

marifjerenlast Tuesday at 2:06 PM

> 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.

show 2 replies
chrismorganlast Tuesday at 11:37 AM

One suggestion: change `exit` to `exit $?` so an exit code is passed back to the shell.

yawaraminlast Tuesday at 7:20 PM

I have a better idea: `ocaml script.ml` ;-)

Get started here: https://dev.to/yawaramin/practical-ocaml-314j

emersionlast Tuesday at 12:02 PM

The 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
show 1 reply
chrisweeklylast Tuesday at 3:29 PM

Related 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

via https://gelinjo.hashnode.dev/you-dont-need-nvm-sdkman-pyenv-...

jas39last Tuesday at 12:38 PM

May I...

augroup fix autocmd! autocmd BufWritePost *.go \ if getline(1) =~# '^// usr/bin/' \ | call setline(1, substitute(getline(1), '^// ', '//', '')) \ | silent! write \ | endif augroup END

dhermanlast Tuesday at 7:01 PM

Ha, 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.
show 2 replies
throw-12-16last Tuesday at 10:26 AM

I've been meaning to port some dotfiles utils over to go, I think I'll give this a shot.

esaymlast Tuesday at 11:51 PM

> 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.

alkhlast Tuesday at 7:56 PM

Have 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

":"""

rewilder12last Tuesday at 5:13 PM

I'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

fsiefkenyesterday at 2:21 AM

Arguably Crystal, Odin, Nim, Zig, V, Vale or Factor are even better!

javascripthateryesterday at 1:10 AM

Yo 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

commandersakilast Tuesday at 5:20 PM

One 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.

show 1 reply
chamomeallast Tuesday at 5:05 PM

For 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!!

networkedlast Tuesday at 1:23 PM

You can use 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 >>>
  // ...
show 1 reply
sharnolast Tuesday at 4:38 PM

You can also use jbang to run java scripts with dependencies

https://www.jbang.dev/

bushbabalast Tuesday at 3:36 PM

This 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!

arccylast Tuesday at 3:39 PM

i tried this and security pinged me about behavior based security rules firing because it looks like an infostealer...

liveoneggslast Tuesday at 1:32 PM

what's even cooler is when the language comes with first class support for this: https://www.erlang.org/docs/18/man/escript

Or the venerable https://babashka.org/

vjay15yesterday at 3:33 AM

really good post thanks lorentz :D

df0b9f169d54last Tuesday at 4:39 PM

See also https://blog.cloudflare.com/using-go-as-a-scripting-language... and 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.

spacecowlast Tuesday at 5:14 PM

> 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-func...

pvtmertlast Tuesday at 11:15 PM

came 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 :)

show 1 reply

🔗 View 17 more comments