Start zsh with a custom zshrc

21,289

Solution 1

From the man pages:

STARTUP/SHUTDOWN FILES
       Commands are first read from /etc/zshenv; this cannot be overridden.  Subsequent  be‐
       haviour is modified by the RCS and GLOBAL_RCS options; the former affects all startup
       files, while the second only affects global startup files (those shown here  with  an
       path starting with a /).  If one of the options is unset at any point, any subsequent
       startup file(s) of the corresponding type will not be read.  It is also possible  for
       a  file  in  $ZDOTDIR  to  re-enable  GLOBAL_RCS.  Both RCS and GLOBAL_RCS are set by
       default.

       Commands are then read from $ZDOTDIR/.zshenv.  If the shell is a  login  shell,  com‐
       mands are read from /etc/zprofile and then $ZDOTDIR/.zprofile.  Then, if the shell is
       interactive, commands are read from /etc/zshrc and then $ZDOTDIR/.zshrc.  Finally, if
       the shell is a login shell, /etc/zlogin and $ZDOTDIR/.zlogin are read.

       When a login shell exits, the files $ZDOTDIR/.zlogout and then /etc/zlogout are read.
       This happens with either an explicit exit via the exit  or  logout  commands,  or  an
       implicit exit by reading end-of-file from the terminal.  However, if the shell termi‐
       nates due to exec'ing another process, the logout files are not read.  These are also
       affected  by  the  RCS and GLOBAL_RCS options.  Note also that the RCS option affects
       the saving of history files, i.e. if RCS is unset when the shell  exits,  no  history
       file will be saved.

       If  ZDOTDIR  is unset, HOME is used instead.  Files listed above as being in /etc may
       be in another directory, depending on the installation.

       As /etc/zshenv is run for all instances of zsh, it is important that it  be  kept  as
       small  as  possible.  In particular, it is a good idea to put code that does not need
       to be run for every single shell behind a test of the form `if [[  -o  rcs  ]];  then
       ...' so that it will not be executed when zsh is invoked with the `-f' option.

so you should be able to set the environment variable ZDOTDIR to a new directory to get zsh to look for a different set of dotfiles.

As the man page suggests, RCS and GLOBAL_RCS are not paths to rc files, as you are attempting to use them, but rather options you can enable or disable. So, for instance, the flag --rcs will enable the RCS option, causing zsh to read from rc files. You can use the following command-line flags to zsh to enable or disable RCS or GLOBAL_RCS:

  --globalrcs
  --rcs
  -d    equivalent to --no-globalrcs
  -f    equivalent to --no-rcs

To answer your other question:

is it possible to start zsh, run "source /path/to/file", then stay in the same zsh session?

Yes, this is pretty easy according to the above directions. Just run zsh -d -f and then source /path/to/zshrc.

Solution 2

while with ZDOTDIR, you can tell zsh to interpret a file called .zshrc in any directory of your choosing, having it interpret any file of your choosing (not necessarily called .zshrc) proves quite difficult.

In sh or ksh emulation, zsh evaluates $ENV; so you could add emulate zsh at the top of your /path/to/file and do:

ssh -t host 'zsh -c "ARGV0=sh ENV=/path/to/file exec zsh"'

Another very convoluted approach could be:

ssh -t host 'PS1='\''${${functions[zsh_directory_name]::="
    set +o promptsubst
    unset -f zsh_directory_name
    unset PS1
    . /path/to/file
 "}+}${(D):-}${PS1=%m%# }'\' exec zsh -o promptsubst -f

That one deserves a bit of an explanation.

${foo::=value} is a variable expansion that actually sets $foo. $functions is a special associative array that maps function names to their definitions.

With the promptsubst option, variables in $PS1 are expanded. So, upon the first prompt, the variables in that PS1 will be expanded.

The zsh_directory_name function is a special function that helps expanding the ~foo to /path/to/something and the reverse. That's used for instance with %~ in the prompt so that if the current directory is /opt/myproj/proj/x you can display it as ~proj:x by having zsh_directory_name do the mapping proj:x <=> /opt/myproj/proj/x. That's also used by the D parameter expansion flag. So if one expands ${(D)somevar}, that zsh_directory_name function will be called.

Here, we're using ${(D):-}, ${:-}, that is ${no_var:-nothing} expands to nothing if $no_var is empty, so ${(D):-} expands to nothing while calling zsh_directory_name. zsh_directory_name has previously been defined as:

zsh_directory_name() {
  set +o promptsubst
  unset -f zsh_directory_name
  unset PS1; . /path/to/file
}

That is, upon the first PS1 expansion (upon the first prompt), ${(D):-} will cause the promptsubst option to be unset (to cancel the -o promptsubst), zsh_directory_name() to be undefined (as we want to run it only once) $PS1 to be unset, and /path/to/file to be sourced.

${PS1=%m%# } expands (and assigns $PS1) to %m%# unless PS1 was already defined (for instance by /path/to/file after the unset), and %m%# happens to be the default value of PS1.

Share:
21,289

Related videos on Youtube

hjkatz
Author by

hjkatz

http://threevirtues.com/

Updated on September 18, 2022

Comments

  • hjkatz
    hjkatz over 1 year

    I want to be able to start zsh with a custom rc file similar to the command: bash --rc-file /path/to/file

    If this is not possible, then is it possible to start zsh, run source /path/to/file, then stay in the same zsh session?

    Note: The command zsh --rcs /path/to/file does not work, at least not for me...

    EDIT: In its entirety I wish to be able to do the following: ssh to a remote server "example.com", run zsh, source my configuration located at /path/to/file, all in 1 command. This is where I've struggled, especially because I'd rather not write over any configuration files on the remote machine.

    • drs
      drs almost 10 years
      Hi Katz, welcome to unix.SE. I've edited your question to add some formatting that makes it (slightly) easier to read. You can click "edit" to see how it works. I've also removed some extra things, like "Thank you" and your signature (all posts on the Stack Exchange network are automatically signed).
    • hjkatz
      hjkatz almost 10 years
      Thank you, I've now learned how to type code as such!
  • jayhendren
    jayhendren almost 10 years
    seems like your problem is not in using a custom zshrc but in not knowing how to use a shell over ssh. If you run ssh host "zsh -d -f; source /path/to/file", then ssh will run first zsh on the remote host, then source ... after the zsh shell exits. Instead what you want to do is ssh -t host zsh -d -f to drop into an interactive shell on the remote host, or if you don't want an interactive shell, do ssh host zsh -d -f -c \"source /path/to/file\; other command\; ... \".
  • hjkatz
    hjkatz almost 10 years
    No I understand how ssh works with interactive shells. The problem is that running ssh -t host "zsh -d -f; source /path/to/file drops me into an interactive (and barren) zsh shell.
  • hjkatz
    hjkatz almost 10 years
    This looks quite extravagant and I will give it a try as soon as possible. I'm hopeful this works!
  • jayhendren
    jayhendren almost 10 years
    I already told you why this is and how to fix it. When you run ssh -t host "zsh -d -f; source /path/to/file", the command source... is NOT run inside the zsh shell. It's run in your login shell after the zsh shell exits. To test this out, run ssh host "zsh; echo foo". You will see the output "foo" after you exit from the zsh shell.
  • hjkatz
    hjkatz almost 10 years
    Yes, this is the problem. I don't want a login shell, I want interactive with the source command rum automatically. Unfortunately this seems to be difficult.
  • jayhendren
    jayhendren almost 10 years
    I'm not sure you can do both, though you should be able to figure this out by reading the INVOCATION section of the zsh man pages. However, you can just set ZDOTDIR instead: ssh -t host ZDOTDIR=/path/to/rcfile zsh
  • hjkatz
    hjkatz almost 10 years
    I was thinking about this, but I want to leave as little trace as possible. I could write a logout script that puts the ZDOTDIR back to what it was originally, but I want there to be an obvious solution. Why is it that zsh is on par and better than bash in all cases except for a simple task? It's just silly...
  • Stéphane Chazelas
    Stéphane Chazelas almost 10 years
    @Le_Katz, ZDOTDIR=x zsh only sets ZDOTDIR for that zsh process (it adds ZDOTDIR=x to the environment that that zsh receives), there's nothing to put back after that zsh has exited (and .zlogout is only evaluated for login shells) you might still want to unset ZDOTDIR from within your startup scripts if you don't want other zsh instanced called by that zsh to inherit it.
  • hjkatz
    hjkatz almost 10 years
    @StéphaneChazelas Woohoo! Thank you for clarifying! This works splendidly :D
  • Chris Perkins
    Chris Perkins over 3 years
    The emulate trick is cool and simple. Hope it does not have any side effects