2

In BASH, I use "pushd . " command to save the current directory on the stack. After issuing this command in couple of different directories, I have multiple directories saved on the stack which I am able to see by issuing command "dirs". For example, the output of "dirs" command in my current bash session is given below -

0 ~/eclipse/src
1 ~/eclipse
2 ~/parboil/src

Now, to switch to 0th directory, I issue a command "cd ~0". I want to create a bash alias command or a function for this command. Something like "xya 0", which will switch to 0th directory on stack. I wrote following function to achieve this -

xya(){
cd ~$1
}

Where "$1" in above function, is the first argument passed to the function "xya".

But, I am getting the following error -

-bash: cd: ~1: No such file or directory

Can you please tell what is going wrong here ?

Rakesh
  • 77
  • 4
  • An *alias* wouldn't have this problem, because they happen earlier at evaluation time than functions do. Several expansion phases are already finished before `$1` is replaced with anything. – Charles Duffy Oct 20 '19 at 17:27
  • @CharlesDuffy Can you please give an alias command for above problem which can accept arguments ? I tried with this alias command - alias xya='cd ~' followed by argument "1" but this results in switching to the home directory, instead of 1st directory on the stack – Rakesh Oct 20 '19 at 17:33
  • 1
    Honestly, aliases are awful, and I'd recommend using a function with `eval` instead. It's a different kind of awful, but at least one that gives you control. – Charles Duffy Oct 20 '19 at 17:36
  • @CharlesDuffy Can you give an example on how to use ``` eval ``` in above problem ? – Rakesh Oct 20 '19 at 17:43
  • I did better than that, I *wrote a whole answer* that demonstrates it. If it doesn't display below, perhaps reload the page? – Charles Duffy Oct 20 '19 at 17:43
  • @CharlesDuffy Yeah, sorry got it after reloading :) – Rakesh Oct 20 '19 at 17:48
  • What is your goal in defining such an alias? A single-letter name will only save you two keystrokes; `xya 0` doesn't save you *any* compared to `cd ~0`. – chepner Oct 20 '19 at 19:46

2 Answers2

4

Generally, bash parsing happens in the following order:

  • brace expansion
  • tilde expansion
  • parameter, variable, arithmetic expansion; command substitution (same phase, left-to-right)
  • word splitting
  • pathname expansion

Thus, by the time your parameter is expanded, tilde expansion is already finished and will not take place again, without doing something explicit like use of eval.


If you know the risks and are willing to accept them, use eval to force parsing to restart at the beginning after the expansion of $1 is complete. The below tries to mitigate the damage should something that isn't eval-safe is passed as an argument:

xya() {
  local cmd
  printf -v cmd 'cd ~%q' "$1"
  eval "$cmd"
}

...or, less cautiously (which is to say that the below trusts your arguments to be eval-safe):

xya() {
  eval "cd ~$1"
}
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • I think `eval "\\builtin cd ~$((0+$1))"` is eval safe and also avoids problems when `cd` is an alias or function. – Greg Nisbet Oct 20 '19 at 18:18
  • I agree; if restricting the input domain to numbers, that's safe. (The existing answer can also work with other inputs, like usernames, or be passed `1/foo` to change to the subdirectory `foo` under the directory name that results from expanding `~1`). – Charles Duffy Oct 20 '19 at 18:22
  • I don't see a reason to use `builtin` when our function doesn't itself go by the name `cd`; if the user has something overriding `cd`, wouldn't they want its logic to also apply when running `xya` to change directories? – Charles Duffy Oct 20 '19 at 18:24
  • I alias `cd` to a small function wrapping `pushd`. I don't know how many other people do. When used in conjunction with the alias `xya` I think it's more intuitive to replace the top of the directory stack rather than potentially add a new entry to it and change the indices of the entries in the directory stack. – Greg Nisbet Oct 20 '19 at 18:36
  • Gotcha. Whereas when I see `cd` wrapped, it's more frequently been to do things like check if we changed into a git repository and if so look up some metadata; if that was silently ignored, prompt data depending on it, or such, could be lost. Suppose the best course of action depends on the individual user's setup. – Charles Duffy Oct 20 '19 at 18:49
0

You can let dirs print the absolute path for you:

xya(){
    cd "$(dirs -${1-0} -l)"
}
William Pursell
  • 204,365
  • 48
  • 270
  • 300