5

I have been using bash at work and zsh at home, different macbooks. I know very little bash or zsh scripting, so I didn't dare do the switch until I managed to get my ZSH on par with what I had at work, so that I wouldn't loose productivity.

I ended up making a .dot-files repo on github, and wanted to test everything out. It worked. But when I decided to make one final adjustment, everything broke and for the life of me, I cannot understand why.

Scenario:

I have ~/.dot-files as the root of my git repo. Then two sub folders, zsh and bash. Inside of each I have hidden files, and a config that would like to source them.

$ cd ~/.dot-files/bash/
$ ls -la
drwxr-xr-x  8 me  staff    256 May  8 13:28 .
drwxr-xr-x  7 me  staff    224 May  8 13:00 ..
-rw-r--r--  1 me  staff   1791 May  8 13:08 .alias
-rw-r--r--  1 me  staff  79837 Apr 12  2022 .git-completion.bash
-rw-r--r--  1 me  staff   1602 May  8 13:49 .git-prompt
-rw-r--r--  1 me  staff   1816 May  8 13:06 .ps1
-rw-r--r--  1 me  staff    877 May  8 13:09 .utils
-rw-r--r--  1 me  staff    142 May  8 15:20 default-config

AND

$ cd ~/.dot-files/zsh/
$ ls -la
drwxr-xr-x  7 me  staff    224 May  8 13:33 .
drwxr-xr-x  7 me  staff    224 May  8 13:00 ..
-rw-r--r--  1 me  staff   1423 May  8 12:32 .alias
-rw-r--r--  1 me  staff  18582 May  8 12:32 .git-prompt.sh
-rw-r--r--  1 me  staff   2059 May  8 12:32 .prompt
-rw-r--r--  1 me  staff    926 May  8 14:01 .zsh-git-info
-rw-r--r--  1 me  staff    143 May  8 15:02 default-config

$ cat ~/.bashrc

source ~/.dot-files/bash/default-config

$ cat ~/.dot-files/bash/default-config

source "$(dirname -- "$0")/.git-prompt"
source "$(dirname -- "$0")/.ps1"
source "$(dirname -- "$0")/.alias"
source "$(dirname -- "$0")/.utils"

This does not work

-bash: ./.git-prompt: No such file or directory
-bash: ./.ps1: No such file or directory
-bash: ./.alias: No such file or directory
-bash: ./.utils: No such file or directory

$ cat ~/.zshrc

source ~/.dot-files/zsh/default-config

$ cat ~/.dot-files/zsh/default-config

source "$(dirname "$0")/.alias"
source "$(dirname "$0")/.prompt"
source "$(dirname "$0")/.zsh-git-info"
source "$(dirname "$0")/.git-prompt.sh"

This does work, perfectly.

What I'm trying to do

I want to change my shell chsh -s /bin/bash, restart terminal. And I expect it to just work.

I have ~/.bashrc I want to point it towards a repo that anyone can clone wherever they want.

I want that repo to reference relative path to where the default-config is. I want to NEVER have a hardcoded full path.

For zsh, source "$(dirname "$0") just works. The steps as I understand them:

  • ~/.zshrc calls source on a file directly: ~/.dot-files/zsh/default-config
  • said file calls source command, yes, but it inserts "path to me" with $(dirname "$0") followed by the name of a file in the same folder as "me".
    • source "$(dirname "$0")/.alias" transforms to source ~/.dot-files/zsh/.alias

Bash does not work, no idea why.

8
  • Regarding I know very little bash or zsh scripting - many more people would be able to help you with bash issues than zsh issues since bash is a more commonly used shell (for evidence see how many questions have been asked at unix.stackexchange.com/questions/tagged/bash vs unix.stackexchange.com/questions/tagged/zsh - currently ~25,000 tagged bash vs ~3,000 tagged zsh) so if you don't know either and so may need help at times and bash is working as you want while zsh isn't, changing your zsh installation to bash instead of your bash one to zsh may be a better choice.
    – Ed Morton
    Commented May 8, 2023 at 11:42
  • The reason I have both in my repo is because I want to go back to bash whenever I need to. That being said, zsh is more powerful and convenient, from what I can see. I just like way more how I interact with it. Completion, suggestion, inline description of commands. I would like to transition to zsh.
    – Kalec
    Commented May 8, 2023 at 11:46
  • please show where .alias, .prompt, etc files are located?
    – White Owl
    Commented May 8, 2023 at 12:16
  • I think source ./.alias would look for the file in the working directory of the running script, only. That's likely your home directory, so it shouldn't find ~/.dot-files/whatever/.alias. Then again, if you did also have ~/.alias, I'm not sure why both Bash and zsh wouldn't find it. Unless you're testing on two different systems, where one has it, and the other doesn't.
    – ilkkachu
    Commented May 8, 2023 at 12:20
  • 1
    If you're going to insist on using bash at least make sure you install a recent version of it via homebrew: because of licensing issues Apple distributes the archaic bash 3.2 (which is presumably why they changed the default shell to zsh in Catalina). Otherwise you're going to see a lot of bash stuff on the web that won't work on your machine. Commented May 8, 2023 at 22:13

2 Answers 2

6

In Bourne/POSIX shells, the special parameter $0 is set to the name of the script that the shell executes (or the name of the shell itself). It does not change when reading another script file with the . (“dot”) command. Bash follows the standard behavior in this respect, including when using the source synonym of . (“dot”).

In zsh, by default the special parameter $0 is set to the path to the current script or the name of the current function, i.e. it changes when using the . or source builtin, when calling a function, and when returning from a function or script. You can change zsh to the standard behavior by unsetting the option function_argzero (which is set by default unless zsh is in sh or ksh emulation mode) or setting posix_argzero.

In bash, the path to the current script (affected by . or source) is available under the name $BASH_ARGV0.


Incidentally, since you know that you have a path to a non-directory file with a directory component, you can use the simple and slightly faster "${0%/*}" to refer to the directory containing it.1

1 It's also more robust than "$(dirname -- "$0")" because it works if the directory name ends with a newline, and than "$(dirname "$0")" because it works if the path starts with a dash, but neither of those apply here. It's more fragile because it doesn't work if the path ends with a slash or doesn't contain a slash at all, but that doesn't apply here either.

8
  • In my version of bash (4.4.23) I don't have $BASH_ARGV0. I use $BASH_SOURCE in those cases.
    – aviro
    Commented May 8, 2023 at 14:08
  • I'm doing something wrong. I tried source "${0%/*}/.git-prompt" it responds with -bash: source: -b: invalid option source: usage: source filename [arguments]. $BASH_SOURCE includes curent file as a folder, so that also does not work (/Users/me/.dot-files/bash/default-config/.git-prompt -- default-config should not be on path). And $BASH_ARGV0 produced /.git-prompt (not current dir, but root dir).
    – Kalec
    Commented May 8, 2023 at 14:38
  • @Kalec, if you run echo "$0", it'll show you shell's name is set as -bash, the dash there signifies a login shell. And it's not a proper path, but like the answer here says, you couldn't use $0 to find the path to the script being source'd in Bash anyway.
    – ilkkachu
    Commented May 8, 2023 at 14:42
  • @ilkkachu I don't know enough bash to translate the above into a working script. That being said, I did find a solution that works. I just don't know why it works. The solution is source "${BASH_SOURCE[0]%/*}/.alias" (substitute /.alias with the rest of the files). By itself ${BASH_SOURCE} produces the wrong results (path includes the name of the current file). But [0]%/* ... whatever it may mean, removes the current file from the path. Any help on what this is exactly?
    – Kalec
    Commented May 8, 2023 at 14:52
  • 1
    @Kalec, BASH_SOURCE and BASH_ARGV0 are magic variables that Bash makes available, they're described in Bash's reference manual. That [0] is array indexing syntax, ${BASH_SOURCE[0]} is element at index 0 in the array, ${var%/*} is a substring-removing modifier to the variable/parameter expansion, it removes the shortest tailing part that matches /*, i.e. the last slash and everything after it.
    – ilkkachu
    Commented May 8, 2023 at 14:59
3

There are two easy ways to fix your problem:

use pushd/popd

Just change current folder inside the default-config files and restore afterwards:

if pushd ~/.dot-files/bash/ > /dev/null; then
  source ./.alias
  source ./.prompt
  source ./.zsh-git-info
  source ./.git-prompt.sh
  popd > /dev/null
fi

(in zsh, you can use -q instead of redirecting stdout to /dev/null to avoid the dump of the directory stack, but not in bash).

Of course, your default-config files would differ in this one line.

use variable

Define a variable inside .bashrc and .zshrc. The variable will point to where your sourced files are:

# .bashrc
fdir=~/.dot-files/bash
source "${fdir}/default-config"

Do the same for .zshrc

And inside the default-config can be the same for both shells, just reuse this variable:

source "${fdir}/.alias"
source "${fdir}/.prompt"
source "${fdir}/.zsh-git-info"
source "${fdir}/.git-prompt.sh"
2
  • While both of these would work. They require hardcoding paths, and cross system knowledge. I don't want local system to know the structure of the repo, aside from one single file default-config. Nor do I want to rely on the local system to pass me a variable to which I'm handcuffed. In a desparate scenario, I would go with ${fdir} solution. But I found another one that works exactly as intended, namely source "${BASH_SOURCE[0]%/*}/.whatever
    – Kalec
    Commented May 8, 2023 at 14:56
  • @Kalec, you already need to have that path in the main config file, e.g. the source ~/.dot-files/bash/default-config in ~/.bashrc in your question. Having fdir=~/.dot-files/bash; source "${fdir}/default-config" there instead is no more hard-coding. The one with pushd/popd would need to have those in the main config file, though, again where the path needs to be anyway.
    – ilkkachu
    Commented May 8, 2023 at 15:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .