Shell – Is it possible to exec some commands in a subshell without immediately exiting afterwards

execfishshell

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.

Best Answer

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'
Related Question