Can fish, zsh and bash import the same configuration file?

I've recently been trying to switch from bash to zsh, or fish.

I have some alisa and PATH settings, but I don't want to manually copy them to zshrc or config.fish.

I tried writing them in a single file and using source ~/.myshrc to use them.

The alisa statement can be sourced normally. But when sourcing PATH in fish shell I got an error:

In fish, please use {$JAVA_HOME}. export
CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib

I know that fish and bash syntax are incompatible.

So is there a general syntax that allows me to modify the PATH in myshrc and then all three shells can use it?

myshrc file like this:

# alias 
alias apts="apt search"
alias sf="aptitude search -F '%c %p %d %D %I'"
alias apti="sudo aptitude install"
alias aptup="sudo aptitude update"
alias aptgr="sudo aptitude upgrade"
alias aptpu="sudo aptitude purge"

# transset xterm
transset -t -a 0.95 >> /dev/null 2>&1


# set npm path
export PATH=~/.npm-global/bin:$PATH

# set java path
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 
export JRE_HOME=${java_home}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH

# set android path
export ANDROID_HOME="/home/moly/Launcher/AndroidSDK/"
export PATH="${PATH}:${ANDROID_HOME}tools/:${ANDROID_HOME}platform-tools/"


Solution 1:

One hacky way to use your existing configuration while trying to transition to a new shell like fish is simply to end your ~/.bashrc with fish, like so:

# alias 
alias apts="apt search"
alias sf="aptitude search -F '%c %p %d %D %I'"
alias apti="sudo aptitude install"
alias aptup="sudo aptitude update"
alias aptgr="sudo aptitude upgrade"
alias aptpu="sudo aptitude purge"

# transset xterm
transset -t -a 0.95 >> /dev/null 2>&1


# set npm path
export PATH=~/.npm-global/bin:$PATH

# set java path
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 
export JRE_HOME=${java_home}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH

# set android path
export ANDROID_HOME="/home/moly/Launcher/AndroidSDK/"
export PATH="${PATH}:${ANDROID_HOME}tools/:${ANDROID_HOME}platform-tools/"

# start fish
exec fish

I don't think this is a very good solution, but this is the only one I know of to make fish inherit your bash environment without rewriting/translating your bashrc. While this is probably bad practice and could cause other sorts of issues, on the short term this could be what you need if you just want to try out fish in known territory with your aliases and PATH without spending time writing a configuration file perhaps for nothing.

I have no experience with zsh and don't know if it can equally inherit the bash environment like that, but wouldn't be surprised if it can.

Solution 2:

Short answer:

If you embrace the "fish way" of doing things, you can get your startup config down to just one line ...

Explanation:

First off, just to pass along some random knowledge (that I originally learned here in a related answer on Stack Overflow), code that is written in such a way that it runs the same way in two different languages is known as a polyglot. It's typically considered a "puzzle" of sorts, rather than a practical implementation. It's really not advised as a solution to your question.

Next up, my general recommendation is to embrace the fish way of doing things as much as possible. Sure, there are times you'll need to drop back to POSIX for compatibility reasons, but fish syntax is (IMHO) much cleaner.

I especially like that, under fish, my config files are almost empty. It's just not necessary under fish to have many of the items that you have in your bash startup.

Let's break down the four types of items you have in your startup config:

  • Aliases
  • $PATH changes
  • Other exported variables
  • Other Command(s)

Aliases

I recommend against putting aliases in your fish startup files. It's just not necessary. Instead, define the alias once at the commandline and use the -s (save) option. In your case:

alias -s apts="apt search"
alias -s sf="aptitude search -F '%c %p %d %D %I'"
alias -s apti="sudo aptitude install"
alias -s aptup="sudo aptitude update"
alias -s aptgr="sudo aptitude upgrade"
alias -s aptpu="sudo aptitude purge"

Your aliases will be "permanently" defined at that point in all fish instances, with the advantage that they are "lazy loaded" (only loaded into memory when you run them the first time) rather than being loaded at all times.


$PATH modifications

Likewise, fish provides a handy helper with which you can add to your path once and have it take effect in all instances (both currently running and future).

fish_add_path "~/.npm-global/bin" "/usr/lib/jvm/java-17-openjdk-amd64/bin" "~/Launcher/AndroidSDK/tools" "~/Launcher/AndroidSDK/platform-tools"

This prepends those paths to the built-in fish universal variable$fish_user_paths which is automatically prepended to the system path. Note that, in your bash rc, you have appended the Android SDK paths, but that's probably not necessary. Typically, you will want to prepend user paths ahead of the system path.


Other exported variables

This part is a little contentious, because fish universal variables can have unintended side-effects when exported to other processes.

On one hand, one of the fish maintainers (@faho) mentions in this answer that:

In general, what you want is to just put the set -gx into ~/.config/fish/config.fish. That's fish's configuration file.

Fish also has "universal" variables, which are stored persistently, but they interact awkwardly with exporting so I wouldn't recommend it.

I've had others (see comments on this answer) who have worked extensively with fish advise against it as well.

On the other hand, 196 upvotes on this answer seem to indicate that folks like universals for this purpose. That's not to say that the majority is correct -- I've seen some really bad answers with a lot of upvotes.

However, I personally like using them to simplify my config files.

If you so choose, you can:

set -Ux JAVA_HOME "/usr/lib/jvm/java-17-openjdk-amd64"
set -Ux JRE_HOME "$JAVA_HOME/jre"
set -Ux --path CLASSPATH ".:$JAVA_HOME/lib:$JRE_HOME/lib"
set -Ux ANDROID_HOME "/home/moly/Launcher/AndroidSDK/"

Again, as these are universal variables, they only need to be set once on the commandline. You can then remove those statements from your fish startup.

Understanding universal/global variable shadowing:

Primarily, if a global variable is set (for instance, by the parent bash process from which you launch fish), then it will override a universal variable of the same name.

For instance (pathologic example, but can easily occur in the real world, especially if you aren't aware of the potential):

# Start in Fish
-> set -Ux JAVA_HOME "/usr/lib/jvm/java-17-openjdk-amd64"
-> set --show JAVA_HOME
$JAVA_HOME: set in universal scope, exported, with 1 elements
$JAVA_HOME[1]: |/usr/lib/jvm/java-17-openjdk-amd64|
-> bash
-> export JAVA_HOME="~/.local/share/jvm/java-17-openjdk-amd64"
-> fish
-> set --show JAVA_HOME
$JAVA_HOME: set in global scope, exported, with 1 elements
$JAVA_HOME[1]: |~/.local/lib/jvm/java-17-openjdk-amd64|
$JAVA_HOME: set in universal scope, exported, with 1 elements
$JAVA_HOME[1]: |/usr/lib/jvm/java-17-openjdk-amd64|

In that particular fish session, the Universal variable will be shadowed by the global one that was exported in bash.

See this Github issue of many that demonstrate the real world potential for problems.

Again, with that in mind, I personally believe the benefits of exported universal variables outweigh the risks, but I wanted to be sure to present both viewpoints.

Other commands

If you've followed along so far (and haven't fallen asleep -- I known I over-explain ...), then you'll realize that there's (potentially) only the one command that must remain in your fish startup:

transset -t -a 0.95 >> /dev/null 2>&1

Even then, I have a suggestion. Fish automatically sources any .fish file in ~/.config/fish/conf.d. I like to keep my config "modularized", so that I can tell at-a-glance what is being loaded at startup.

I would just create:

~/.config/fish/conf.d/xterm.fish:

transset -t -a 0.95 >> /dev/null 2>&1

At that point, you are running like I do -- no fish.config at all! (Well, other than the fact that the newer fish versions irritatingly, automatically create a completely unnecessary empty one if it doesn't exist ...)