26

bash and fish scripts are not compatible, but I would like to have a file that defines some some environment variables to be initialized by both bash and fish.

My proposed solution is defining a ~/.env file that would contain the list of environment variables like so:

PATH="$HOME/bin:$PATH"
FOO="bar"

I could then just source it in bash and make a script that converts it to fish format and sources that in fish.

I was thinking that there may be a better solution than this, so I'm asking for better way of sharing environment variables between bash fish.

Note: I'm using OS X.


Here is an example .env file that I would like both fish and bash to handle using ridiculous-fish's syntax (assume ~/bin and ~/bin2 are empty directories):

setenv _PATH "$PATH"
setenv PATH "$HOME/bin"
setenv PATH "$PATH:$HOME/bin2"
setenv PATH "$PATH:$_PATH"
Tyilo
  • 5,981

7 Answers7

23

bash has special syntax for setting environment variables, while fish uses a builtin. I would suggest writing your .env file like so:

setenv VAR1 val1
setenv VAR2 val2

and then defining setenv appropriately in the respective shells. In bash (e.g. .bashrc):

function setenv() { export "$1=$2"; }
. ~/.env

In fish (e.g. config.fish):

function setenv; set -gx $argv; end
source ~/.env

Note that PATH will require some special handling, since it's an array in fish but a colon delimited string in bash. If you prefer to write setenv PATH "$HOME/bin:$PATH" in .env, you could write fish's setenv like so:

function setenv
    if [ $argv[1] = PATH ]
        # Replace colons and spaces with newlines
        set -gx PATH (echo $argv[2] | tr ': ' \n)
    else
        set -gx $argv
    end
 end

This will mishandle elements in PATH that contain spaces, colons, or newlines.

The awkwardness in PATH is due to mixing up colon-delimited strings with true arrays. The preferred way to append to PATH in fish is simply set PATH $PATH ~/bin.

  • 1
    Will the PATH fix for fish work with paths with: spaces? newlines? other things to look out for? – Tyilo Dec 28 '14 at 23:28
  • [ is a builtin in both bash and fish, so PATH should not need to be set correctly to use [. The setenv sample should work with all characters in PATH except for colons and newlines. – ridiculous_fish Dec 30 '14 at 00:35
  • Replacing tr with /usr/bin/tr I and using the .env file provided in my question, I get a lot of errors when starting fish. I don't think the PATH handling is correct. – Tyilo Dec 30 '14 at 00:39
  • Oh, right, fish will space-separate it inside quotes. I'll update the comment. – ridiculous_fish Dec 31 '14 at 07:50
  • Yay it works now. I don't think any other shell (except fish?) allows paths with colons in PATH anyway. – Tyilo Dec 31 '14 at 16:38
  • Why would you translate spaces to newlines there? Note that the standard tr syntax would be tr ': ' '\n\n' or tr ': ' '[\n*]' here. echo -- $arg... would be more correct, though unlikely to make much difference in practice. – Stéphane Chazelas May 23 '20 at 07:11
10

There is (now?) an easier way, per @Zanchey's answer here

Fish Shell: How to set multiple environment variables from a file using export

The digest though is:

Fish:

echo -e "foo=3\nfoobar=4" > .env; export (cat .env); env | grep foo

Bash:

echo -e "foo=3\nfoobar=4" > .env; export $(cat .env | xargs); env | grep foo

with the difference being $ and the use of xargs

8

Most Unix systems use PAM. The pam_env module reads a file very much like your .env.

On Linux, pam_env reads a system file /etc/environment and a user file ~/.pam_environment. On OS X (and other *BSD, which likewise use OpenPAM), it appears that pam_env only reads the system file, so you can't set variables per user this way, only for all users.

5

Just create a function in $HOME/.config/fish/functions/env.fish with content:

for f in /etc/profile.d/*.sh
    sh $f
end

shared all vars created in bash in folder /etc/profile.d

for example: /etc/profile.d/golang.sh

#!/bin/sh
export GOPATH=$HOME/gopath
export GOROOT=$HOME/go
export GOBIN=$HOME/gobin

export PATH=$PATH:$GOROOT/bin:$GOBIN

just login on fish or bash, do echo $GOPATH $GOROOT $GOBIN $PATH and see the magic :)

3

Create a file where you will store all your environment variables, named for example ~/.config/env_variables. In this file, add export lines, like this:

# This file is meant to compatible with multiple shells, including:
# bash, zsh and fish. For this reason, use this syntax:
#    export VARNAME=value

export EDITOR=vim export LESS="-M" export GOPATH="$HOME/.local/share/gopath/" export PATH="$PATH:/custom/bin/"

In your ~/.config/fish/config.fish file, include:

source ~/.config/env_variables

In your ~/.bashrc file, include:

source ~/.config/env_variables
Flimm
  • 4,218
1

Expanding on @ridiculous_fish's answer, with the following function for bash it also works for arrays that contain spaces, such as $PATH if you have spaces in your paths. However setenv is already defined as a function in the fish standard library nowadays so I changed the name here to shenv

function shenv { 
        var="$1"
        shift
        export "$var=$(printf "%s\n" "$@" | paste -s -d: -)"
}
. ~/.env

Now in your .env file you can specify array components as separate arguments to the setenv command:

shenv PATH /my/path /my/path\ with\ spaces "/my/quoted path" $PATH

For fish @ridiculous_fish's simple implementation will do:

function shenv; set -gx $argv; end
source ~/.env
JanKanis
  • 1,069
0

If you don't mind fork another process, then:

env $(cat ~/.env) fish

I use this way to share the same .env file with multiple shells in multiple projects. The syntax is easy, and it is quite flexible since you can exit the shell to regain your original environment.

More example:

env $(cat .env) bash

env $(cat .env) zsh

env $(cat .env) sh

One drawback is that it assumes .env only in VAR=value syntax (no comment, newline, or quotes).

I prefer to keep .env as simple as possible by naming variables more sensemaking instead of writing lots of comments and newlines. However, if there are some cases that comments and newlines cannot be avoided, it can still be easily solved by piping output to grep, sed or awk.

Sorry, my solution may not solve the original question because of the syntax of .env, but I did found it useful for sharing the same .env with different shells. Hope it can help anyone of you :)

Weihang Jian
  • 1,227
  • I guess you mean env $(cat ~/.env) fish? – G-Man Says 'Reinstate Monica' May 23 '20 at 06:52
  • @G-ManSays'ReinstateMonica', the command substitution syntax in fish is with (cmd); $(cmd) is the POSIX syntax. – Stéphane Chazelas May 23 '20 at 07:06
  • That assumes ~/.env online contains lines in VAR=value syntax (no comment, no VAR="value", no empty lines...) – Stéphane Chazelas May 23 '20 at 07:07
  • @StéphaneChazelas: OK, but, since the answer is giving a command to start a fish, I assumed that it was meant to be run from bash. Perhaps the answer is what Jian meant, but, if he meant “start a fish process, and then use this command to start another fish process”, I believe he should clarify. – G-Man Says 'Reinstate Monica' May 23 '20 at 07:14
  • @StéphaneChazelas: And your second comment would be clearer if you said “values cannot contain space character(s), whether quoted/escaped or not.”   And I don’t see a problem with empty lines. – G-Man Says 'Reinstate Monica' May 23 '20 at 07:19
  • @G-ManSays'ReinstateMonica' fish's command substitution gets you one argument per line, empty or not (unless $IFS is unset, set to an empty list or list with one empty element), so empty lines will break it and spaces (as in VAR=my value) are not a problem. while POSIX substitution splits on $IFS and does globbing, so in POSIX syntax, you'd need something like IFS=$NL; set -o noglob; env $(cat ~/.env) fish to have something equivalent (which does remove empty lines though). – Stéphane Chazelas May 23 '20 at 07:22
  • @G-ManSays'ReinstateMonica' @StéphaneChazelas I am a fish user, so env (cat ~/.env) fish is what I would usually do, but I found that using $() would be less confusing as an answer. Thanks for the comments :) – Weihang Jian May 23 '20 at 13:25