When using roc as a shell scripting language with shebang (#!/usr/bin/env roc) at the top, passing args is annoying because the args aren't passed to the script, but to the roc executable. The workaround I have used is to do #!/usr/bin/env -S roc --, so all args are passed to the script, but it would be nice to not have to do this.
A second minor annoyance is that when I create a script, I like to leave off file extensions. But roc automatically compiles to a file without extension, so it gets overwritten
I made #7405 for the shebang issue
so it gets overwritten
Right... should we error if we see that there is already a non-executable file with the name we want to use?
Kilian Vounckx said:
When using roc as a shell scripting language with shebang (
#!/usr/bin/env roc) at the top, passing args is annoying because the args aren't passed to the script, but to the roc executable.
Is this an issue with roc? all programming languages that can be used for scripting? a limitation of env?
Feels like we wouldn't be doing anything specific to make this worse.
Anton said:
so it gets overwritten
Right... should we error if we see that there is already a non-executable file with the name we want to use?
When running a script, we should never emit an executable. It should either go to a cache directory or stay completely in memory.
Only with build should we emit an executable. We probably should follow other languages and emit it in a subdirectory of some sort. (Eventually we will need a subdirectory for caching anyway)
I think it's a problem with env yeah. As far as I know, the only thing roc could do is not have the need for -- between roc args and script args, but I like it when using the commands explicitly.
That's why it's a minor annoyance :big_smile:
Ah, we have to be like cargo and if we don't recognize an arg, pass it to the child process
Or something like that
yeah we used to do something like that, but the concern was that it could lead to surprising behavior, e.g. a script that works fine but then we release a new version of roc which introduces a new cli flag, and now roc eats it and the old script suddenly breaks even though it was a minor release ofroc :sweat_smile:
I like that the workaround is maximally reliable
Consistency should trump something nice
I've ran into the same problem. I do agree many other programming languages seem to have the same problem, though I don't think it's all of them. With bash for instance, I never remember running into this problem.
I did a quick experiment with a short bash script that prints the arguments that get passed to it. Here's two invocations of it, both passing --version, an arg that bash itself parses:
$ bash ./foo.sh 1 2 3 --version \jj/
./foo.sh 1 2 3 --version
$ bash --version ./foo.sh 1 2 3 --version
GNU bash, version 5.2.37(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
So what bash seems to do is that it doesn't parse any args past the file path passed to it for execution. Maybe that would be an option for Roc as well? That approach will not result in broken scripts if Roc introduces a new flag.
What if the file doesn't end in .sh
I just checked, same behavior.
I guess the downside of this approach is that you will no longer be able to invoice a script with roc shebang directly and pass arguments for Roc itself. But maybe that's okay? Maybe this is wrong, but I'd expect there to be less of a need to pass arguments to Roc itself when running a script directly instead of building a binary. And if you really need to pass an argument to Roc, there's still the possibility to call roc explicitly.
That probably won't work if you run a script called check without a roc extension
Or version or any of our subcommands that are words
Bash doesn't have that
would they come through as full file paths or relative? :thinking:
e.g. would we see check or ./check or something like that
I think if it's in the same folder, we'd get just check
I'm on mobile, so I can't check
same :laughing:
Let me grab a laptop...
A file named check in /home/smores/dev/:
#!/bin/bash
echo $0
echo $@
When I run it:
~/dev 21s
❯ bash check 123 456
check
123 456
~/dev
❯ ./check 123 456
./check
123 456
So to translate, if we ran a Roc script called check with roc then it would be kinda hard for it to run as check with roc dev check
But roc check would say something like "main.roc is missing"
So I think for shebangs, we could just go for the "if the arg is not a flag or a known subcommand, pass all the args to the script" route
Since with a shebang it'll be pretty hard to run a file without a relative path
hm wait but I think this confirms what we want, right?
because if we had #!/usr/bin/env roc at the top, and then ran it with ./check, roc would receive the string "./check" rather than "check"
if I understand the implications of this correctly, I think it means when you're running the roc script using #!/usr/bin/env roc, the first argument will always be a path that begins with either / or .
which in turn means it would never collide with a subcommand, and we could always detect that case and do the correct thing
Yep, we're good
The only problem would be a non-shebang use case
You don't have to run it with a leading . or / though, you could just run check
If you have a Roc file without a file extension in the current dir and you want to run it with the dev backend, you have to run roc ./check
Oh yeah, if . is in PATH
Seems like there are gonna be tradeoffs
So might as well go for the option that's nice 99% of the time that lets you do ./check as a workaround
hm yeah I guess if it's in the PATH, that wouldn't work
I think that's the only scenario where it could come in as check
In fact, you can always do "$ROC ./$SCRIPT"
And it'll canonicalize
hm, at least as of 2014 some people were reporting that env wouldn't pass through more than one argument: https://stackoverflow.com/questions/16365130/what-is-the-difference-between-usr-bin-env-bash-and-usr-bin-bash#16365367
I'm not sure if that's still a thing though...I thought we'd determined at some point that all modern env implementations support passing through multiple arguments
all that said, I think the PATH consideration is an important one, because a major reason you might want to do the #! in the first place is that you have something on your system that's going to run a program on the path and you want to seamlessly replace that program with a roc script
You're right, but I think it's not generally considered good practice to put the current directory . in the PATH unless you're in some kind of virtual environment, and even then you probably have your scripts in a folder.
So almost all of those cases are probably running with relative/absolute paths anyway
well like I mean if I'm at the command line and I run roc, for example, if that resolved to a text file with #! at the top, I assume it would receive roc with no . or / in it
I'm not sure I follow. Are you talking about a behavior where roc run sans arguments would look for a local file?
so let's say I'm at the command line and I type foo and press enter
the shell is going to look in the PATH for something named foo
let's say foo turns out to be a text file that begins with #!/usr/bin/env roc
in that scenario, I think roc is going to receive foo and not ./foo or /path/to/foo or anything like that
and the point is that I think this is one of the important scenarios for #!/usr/bin/env roc to facilitate - where you have some (possibly thirtd-party) script that's going to execute foo from the PATH
and you want foo to run your roc program
I'll test that
~/dev
❯ cat /usr/bin/args-test
#!/usr/bin/env python3
import sys
print(f"Hi! The args are: {sys.argv}")
~/dev
❯ args-test
Hi! The args are: ['/usr/bin/args-test']
Seems like we're good?
This is on latest Pop OS on my Tuxedo laptop
Which is Ubuntu derivative
I just ran something similar on NixOS, seeing the same thing. Looks like env passes on an absolute path. Convenient!
It'd be nice to see if it's the same on MacOS, just to make sure different env implementations don't mess it up or anything.
Correction, it doesn't appear to be env turning the file into an absolute path, but rather the shebang magic. When I run env by hand it doesn't modify the command to an absolute path.
Probably not something we need to worry about, but good to know!
yeah works on macOS!
ok so that means as long as roc doesn't have any subcommands or flags that start with . or / (which of course would not happen), then we can have the rule of "all subcommands and flags must come before the filename, and as soon as we encounter something we think is a filename, everything after it gets forwarded as args to the Roc program"
and that would look fine except in the specific case where you were trying to do something like roc check to run a Roc script named check in the current directory, in which case don't do that :stuck_out_tongue:
Huh, ran into a bug while trying to test this: I made a file called roc-args-test and ran it, and roc built a binary over the file. It ran one time and then died forever :laughing:
Anyway, I'll double check but it gave just the file name
Yep, so I'm typing this on my phone with a laptop in the car...
#!/usr/bin/env roc
app [main] { pf: platform "basic-cli 0.17 URL" }
import pf.Arg
import pf.Stdout
main =
Task.await (Arg.list {}) \args ->
Stdout.line "Args are: $(Inspect.toStr args)"
> roc roc-args-test
Args are: ["roc-args-test"]
So if the file doesn't have a shebang and no extension, we show an error instead of running the app
If it has a shebang and no extension, we run it
So your expectations matched reality
So no need for a new GH issue on this
ok, so all we need to do is to change how we parse args in the roc cli?
We're gonna do that when we remove dev I presume, but yes
cool, sounds good to me!
Last updated: Jun 16 2026 at 16:19 UTC