Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inlein execution fails if called from fish shell #12

Open
Marand- opened this issue Jun 1, 2016 · 6 comments
Open

inlein execution fails if called from fish shell #12

Marand- opened this issue Jun 1, 2016 · 6 comments

Comments

@Marand-
Copy link

Marand- commented Jun 1, 2016

I'm not sure if this is a fish problem, or if fish is exposing a problem with how inlein executes, but attempting to run inlein in a fish shell returns /bin/sh: 0: Can't open inlein and does nothing. Works fine in every other shell I've tried (mksh, zsh, csh, dash, bash, rc), and also works fine if I route it though dash with sh (which inlein).

No error occurs when using inlein to execute a script via shebang, nor does it error if I cd to ~/files/bin (where inlein is stored, which is on my $PATH) before running inlein.

Tested with Debian stable, on three different machines of differing configurations.

@hypirion
Copy link
Owner

hypirion commented Jun 11, 2016

Thanks for the report!

To understand how inlein works, we have to go through a series of hoops. The inlein "program" is actually a jar file (a java command line program). And a jar file is actually a zip file. And zip files have this interesting property that you can prepend more or less anything to the front of them and they will still work as expected.

So what inlein does is to prepend the following to the jar file:

:;exec java "-Xbootclasspath/a:$0" "-Dinlein.client.file=$0" inlein.client.Main "$@"
@java -Xbootclasspath/a:"%~f0" -Dinlein.client.file="%~f0" inlein.client.Main %*
@goto :eof

The first line is treated as a comment in Windows' batch language, and the @-lines are ignored in linux shells because of the exec call.

Apparently fish isn't too happy about this, which is understandable: It's a nasty hack. And unfortunately, we can't add in a shebang, as that would break Windows support.

I guess you know this already, but I'll just put it here for completeness: One can do two things to fix this for the fish shell. The first is just to define inlein as a function calling sh with inlein:

jeannikl@ares ~> function inlein
                     sh -c "inlein $argv"
                 end
jeannikl@ares ~> funcsave inlein
jeannikl@ares ~> inlein --ping
PONG

Another option is to make a proper script that execs the jar file. First, put the inlein "program" somewhere else:

mkdir -p ~/.inlein
mv ~/bin/inlein ~/.inlein/inlein.jar

Then, you can put in the following in in ~/bin/inlein and make it executable:

#!/usr/bin/env fish
exec java "-Xbootclasspath/a:$HOME/.inlein/inlein.jar" "-Dinlein.client.file=$HOME/.inlein/inlein.jar" inlein.client.Main $argv

Hope this is tolerable for you.

@hypirion hypirion added the docs label Jun 11, 2016
@Marand-
Copy link
Author

Marand- commented Jun 12, 2016

Funny enough, I'm actually familiar with that oddity of zip files; in addition to being useful for shell installers (lots of them do this), it also means you can "hide" zips inside some images, like GIFs.

I'm not sure exactly why it's failing with fish, but it's probably because it doesn't try to be compatible with sh/bash at all, so things like $@ and $0 may not exist. So, my question is, why not explicitly call sh -c as part of the exec? For example:

:;exec sh -c "java \"-Xbootclasspath/a:$0\" \"-Dinlein.client.file=$0\" inlein.client.Main $*"
@java -Xbootclasspath/a:"%~f0" -Dinlein.client.file="%~f0" inlein.client.Main %*
@goto :eof

I just tested doing this with my copy of inlein, and it seems to work correctly regardless of shell, because the one-liner now calls sh explicitly. It could also help with any other shells (present or future) that don't use the expected variables but still handle exec as expected.

It might need some adjustment because I don't do shell scripting enough to be sure I didn't do anything blatantly incorrect with $* vs $@ and all, but the idea should still be clear.

Also, another option that might be better long-term would be to just create separate files for each supported platform, so you could actually use shebangs and make more complicated scripts if needed for Win, OS X, and Linux/BSD/etc. Shouldn't be too difficult to write a script for each, and then as part of the release process, cat the appropriate script + new .jar together into a new inlein-win, inlein-osx, inlein-nix, etc. Not really necessary now but something to consider if the scripting needs get more complicated in the future.

I guess you know this already, but I'll just put it here for completeness: One can do two things to fix this for the fish shell. The first is just to define inlein as a function calling sh with inlein:

Yeah, I set up a function much like that while investigating the problem for the bug report, though I didn't think to mention it in the report. Using it as a workaround didn't bother me at all, but I still felt like it was better to report it than to quietly leave something slightly broken. Even if the above idea for tweaking the exec isn't workable as a general fix, it's a good thing to have documented, in case it ever crops up for others using fish or some other user of an odd shell.

@hypirion hypirion added the bug label Jun 12, 2016
@hypirion
Copy link
Owner

Oh, cool. I'll see if I can get it working in the more popular shells as well as fish though $*. I wasn't familiar enough with fish to know that $* was allowed.

@Marand-
Copy link
Author

Marand- commented Jun 12, 2016

Oh, cool. I'll see if I can get it working in the more popular shells as well as fish though $*. I wasn't familiar enough with fish to know that $* was allowed.

It's not, the trick is the exec sh -c at the start. I was using $* because I had problems using $@ like that, but $* was working for the purpose of verifying that it's possible to shove an sh call into the thing and get it to work with weird shells.

Using $* mostly works, except that quoted args do weird things with it versus using "@$". inlein --run script.clj "foo bar" baz ends up being script.clj foo bar baz instead of the desired script.clj "foo bar" baz. Meaning that in-script, (first *command-line-args*) returns foo instead of foo bar as expected.

So, it's almost there, but not quite. There's probably a way to make it pass the quoted args through to the sh invocation correctly, but my shell kung-fu is weak, so that was the best I could manage so far. I figured it's good enough to explain the idea of what I meant in case you or someone else knows how to improve on it

@hypirion
Copy link
Owner

hypirion commented Jun 12, 2016

Hmm. Here's an interesting thing I just discovered: I am able to get inlein running correctly with fish if I call it with its full path, but if I just use inlein (which presumably looks at the $PATH) then the command fails with "/bin/sh: 0: Can't open inlein". That's regardless of what the prelude contains.

@Marand-
Copy link
Author

Marand- commented Jun 12, 2016

Yeah, that's curious. That's the behaviour I mentioned in the first comment about it working if executed while in the directory that contains the executable. I have no clue what the reason for that is.

The error means that /bin/sh is being spawned and attempting to open inlein, which fails because it's not in the current directory. You can check that by cd'ing to the location of inlein and it'll work by name then. And, if you give a path, it works because you gave it a location to search.

It's the exact same behaviour as running sh foobar (or any other nonexistent filename) from any shell yourself. Thing is, I have absolutely no clue why it's doing that. It's normal behaviour for sh file without the -c switch, but it's confusing why fish is sending the file through sh at all. I've never seen it do that before, only with inlein.

I even tried making a : function in fish to emulate the sh behaviour, but it didn't matter. I can now do :; echo foo at the fish prompt and it works. I can also put it in a shell script, like this:

#!/usr/bin/fish
:; echo foo

and it works because I created a : function. But without that shebang, it tries passing it on to /bin/sh and explodes. I'm not sure, but it almost looks like fish is trying to "fix" bad script behaviour by calling sh, maybe as part of some workaround, and it's failing in this specific case.

It looks like the "/bin/sh: 0: Can't open inlein" error is just a side effect of the shell having to guess at how to handle an executable file without a shebang. sh and similar appear to treat files without a shebang as having an implicit #!/bin/sh shebang, while fish is doing something different. You can see that by making a file with just echo hello in it, no shebang and no odd characters, and seeing how the different shells react.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants