I have been migrating my macos system’s homebrew setup over to home manager, and I ran into a problem: using fish as my interactive shell without changing the default login shell from zsh. Here’s one solution, which I hope can help someone else.
With homebrew, I managed this with my terminal, Wezterm. Wezterm has a configuration option which allows a command to be run instead of the user’s shell upon starting:
default_prog = { "/opt/homebrew/bin/fish", "-l" }
So initially, I tried to do the same with the installation of fish through nixpkgs:
default_prog = { "/Users/david/.nix-profile/bin/fish", "-l" }
While this would use fish as the interactive shell when launching Wezterm, it would skip the initialization of configuration files for both zsh and fish. The prompt was the default, the session variables were unset, there were no aliases or abbreviations, zoxide and fzf were not initializated, and so on. Perhaps this would have worked with other terminal emulators’ comparable options to set the shell they use.
My solution is a modification to the conditional block recommended by the Arch Linux wiki, which is also presented on the NixOS wiki entry for fish. Here’s that bit of bash first:
if [[ $(ps --no-header --pid=$PPID --format=comm) != "fish" && -z ${BASH_EXECUTION_STRING} && ${SHLVL} == 1 ]]
then
shopt -q login_shell && LOGIN_OPTION='--login' || LOGIN_OPTION=''
exec fish $LOGIN_OPTION
fi
The conditional block checks whether the parent process of the current shell is fish. This allows you to invoke zsh from fish. Without it, doing so would simply run exec fish -l
once more.
Unfortunately, as written, it presents two problems. First, being BSD-derived, MacOS's ps
command accepts different options. Second, this is a script intended for bash, not zsh. MacOS has used zsh as its default shell for years, and I did not want to change it to bash to try to solve this problem.
The solution I found is to add the following to programs.zsh.initExtra
:
programs.zsh = {
initExtra = ''
if [[ $(ps -o command= -p "$PPID" | awk '{print $1}') != 'fish' ]]
then
exec fish -l
fi
''
};
Breaking it down:
-
ps
lists the processes running with the same user and terminal as
the one invoking it. -
-o command=
(or, alternatively-o comm=
or-o 'comm='
)
replaces both the--format=comm
and--no-header
options. The
'=' after 'command' leaves off the header. -
-p $PPID
selects processes with aPID
equal to$PPID
, which is
a zsh-provided shell parameter of the parent process ID. -
| awk '{print $1}'
pipes the output of theps
command toawk
in order to select only the first column. This is necessary if the
parent process command was 'fish -l'. -
!= fish
compares the parent process command to 'fish' and, if it
wasn't fish, then it executes fish.
If anything seems amiss, or if you know of a better (or even just alternative) solution, please do share. I’d like to add the alternative conditional block to the NixOS wiki entry for fish. But I’ll wait to see whether anyone has suggestions here first.