5

I use the fish shell and would like to be able to "source" some shell scripts written with sh-compatible syntax, which fish cannot read. For example lots of software expects you to source some shell scripts to export helpful environment variables:

# customvars.sh
FOOBAR=qwerty
export FOOBAR

If I don't care about preserving local variable and function definitions, one workaround is to use exec to replace the current process with /bin/sh and then go back to fish

# We start out on fish
exec /bin/sh
# Running a POSIX shell now
. customvars.sh
exec /usr/bin/fish
# Back on fish
echo $FOOBAR

Is there a way to boil this pattern down to an one-liner that I can save in a function somewhere? If I try doing

exec /bin/sh -c '. vars.sh; /usr/bin/fish'

my terminal emulator window closes immediately instead of taking me back to an interactive fish prompt.

5
  • 1
    That should work unless vars.sh is not in your $PATH. Try exec /bin/sh -c '. ./vars.sh; exec fish' to source the vars.sh in the current directory. Commented May 19, 2015 at 15:18
  • You should write that as an answer. BTW, do you know why the interactive bash prompt recognizes the customvars in the current directory but the one liner doesn't? And is there a way to make the one liner more robust? When I abstract it as a function I don't want to have to remember to add the "./" at the start of the file names. It would also be nice if it didn't bail out if an error occurs.
    – hugomg
    Commented May 19, 2015 at 15:25
  • bash doesn't behave the POSIX way in that regard, except when in POSIX mode (called as "sh" or with --posix or with POSIXLY_CORRECT=1 or SHELLOPTS=posix in the environment). Instead, it looks for vars.sh in the current directory when not found in $PATH. In anycase, it's a bad idea to assume sh is bash. Commented May 19, 2015 at 15:30
  • 2
    . is a special builtin, so causes the shell to exit upon failure. Use command . instead of . to remove that special attribute. If you want to source the file in the current directory, you have to use ./. Just like if you want to run the ls in the current directory, you have to use ./ls. That's just the way it is. Commented May 19, 2015 at 15:48
  • Please write that as a separate answer :)
    – hugomg
    Commented May 19, 2015 at 15:51

3 Answers 3

4

The problem lies in how you're calling the . special builtin:

exec /bin/sh -c '. vars.sh; /usr/bin/fish'

In sh, if the argument doesn't contain any /, . searches for the file in $PATH. So above, it would look for vars.sh in $PATH instead of the current directory as you intended.

Also, . being a special builtin, its failure causes the shell to exit (when not interactive), so the next command (here fish) is not executed which is why your terminal emulator window goes away without a fish prompt.

That can be prevented by calling . as command . which removes the special attribute of special builtins.

Note that the behaviour of bash (the sh implementation of the GNU project) is different in that regard when not in POSIX mode (when not called as sh, nor with --posix, and when the environment doesn't contain POSIXLY_CORRECT= nor SHELLOPTS=posix):

bash's . doesn't cause the shell to exit upon failure and it searches for slash-less argument in the current directory if it can't find it in $PATH.

In any case, POSIX mode or not, if you want the vars.sh in the current directory, you need the ./vars.sh syntax. So it's

exec sh -c 'command . ./vars.sh; exec fish'
1

Personally, I would go for a different approach. Presumably, you need the file in bourne-type format for other reasons. Why not just source it in fish by changing the format on the fly? Something like:

source (sed -nr 's/(.+)=(.+)/set \1 \2/p' file | psub)

The above is the fish equivalent of

source <(sed -nr 's/(.+)=(.+)/set \1 \2/p' file)

For example:

> cat file
foo="baz"
export foo
> source (sed -nr 's/(.+)=(.+)/set \1 \2/p' | psub)
> echo $foo
baz

NOTE: This assumes that your sourced script only sets variables, as described in the question, and one variable definition per line. For more complex operations, this approach won't work.

6
  • 2
    Some "sourceable" scripts I need to work with also do things like changing the current directory. I feel that actually running the sh scripts is the only way to be 100% sure things will work
    – hugomg
    Commented May 19, 2015 at 15:49
  • @hugomg ah, that's different. I thought you were only dealing with setting variables. Please edit your question and clarify. The bit about changing directories is particularly relevant.
    – terdon
    Commented May 19, 2015 at 15:53
  • 4
    In any case, even with setting variables only, there are differences in syntax between sh and fish that this will not cover. Quoting is different, variable expansion is different... (think of PATH=${PATH}${PATH:+:}/opt/x/bin for instance). That reminds me of the sh2csh or csh2sh perl scripts that you'd use in the 90s for this kind of purpose, that never really quite worked. Commented May 19, 2015 at 16:17
  • @StéphaneChazelas yes, good point. I was just thinking of the specific, simple case given in the answer and thought of process substitution as a possible solution. I'll leave it here since it might help someone else with a simpler issue.
    – terdon
    Commented May 19, 2015 at 16:19
  • @terdon: (1) grep … | sed …?  Why not sed -rn 's/(.+)=(.+)/set \1 \2/p'?  (2) Did you mean export $foo?  (= export baz; probably not what you meant)  (3) “This assumes that the sourced script only sets variables” — one per line — it chokes on p2=company; p3=crowd (all on one line).  Also null assignments (value= doesn’t work, although value="" does) and strings containing =s (good_approximation="pi=3.1416").  (4) At the risk of piling on: can fish handle `…` or $(…) (which might be in the “sh-compatible” script)?  (5) What’s psub? Commented Oct 3, 2015 at 15:21
1

For the specific problem of having scripts in POSIX-compatible shell language that set environment variables and wanting to use them in fish, one existing solution is Bass. It is a script that works similarly to terdon's answer but can handle more corner cases.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .