Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override pushd's and popd's automatic call to dirs?

My bash (4.1) directory stack usually has a dozen or more entries. I wanted to replace the output of dirs with dirs -v, so I would never have to play "guess the magic number" with pushd again.

My Partial Solution

I replaced dirs with a function that executed dirs -v using command:

dirs()
{
    # "command" builtin prevents infinite recusion by suppressing lookup
    # of function and alias names.
    command dirs -v "${@}"
}

(Update: At pneumatics' suggestion, I now use builtin instead of command. It doesn't solve this problem, but it's slightly safer.)

dirs output was now readable, but pushd and popd still produced the old vomit-of-slashes:

$ pushd ~/just/one/more/and/ill/quit/i/promise
~/just/one/more/and/ill/quit/i/promise ~/tmp/bash/bash-4.1...
may/give/rise/to/dom /oh/no/not/again /var/adm/log /omg/wt...
va/lang ~/doc/comp/java/api/java/util/regex barf barf barf...

I got the same (lack of) result after replacing my dirs function with an alias:

alias dirs='command dirs -v "${@}"'

The Workaround

I eventually got the output I wanted by overriding pushd and popd as well:

pushd()
{
    command pushd "${@}" >/dev/null &&
      dirs
}
# popd is similar.

This works, but it requires overriding three builtins instead of one. Also, this could be an imperfect impersonation of pushd and popd in some corner case I haven't thought of. I'd rather override dirs only.

My Question

Why didn't overriding dirs work? According to bash's man page, under both pushd and popd:

If the pushd command is successful, a dirs is performed as well.

So why did pushd and popd appear to invoke the builtin dirs instead of the function or the alias?

Footnote about the bash Documentation

The paragraphs saying "a dirs is performed as well" are missing from the bashref.* online manual, but they appear in the man pages bash.* and builtins.*. Both my bash 4.1 docs and the current 4.4 docs are inconsistent in the same manner, which suggests that 4.4 (if I had it) would behave the same.

like image 259
Kevin J. Chase Avatar asked Sep 27 '16 03:09

Kevin J. Chase


2 Answers

From my reading of the bash source code, both pushd and popd call the dirs builtin itself (without any args), rather than consulting defined aliases and functions.

While it seems rare that a builtin leverages external functionality, when a builtin does need external functionality, calling another builtin seems precedent. For example, when pushd changes directory, it calls the cd builtin. Or when declare needs to type cast a variable, it calls the set builtin.

How to work around this? Well, one could recompile bash from source: it'd be trivial to have pushd and popd pass the -v argument. One could also petition bash upstream to add an environment variable that defined the default dirs options when used as a helper to pushd/popd. But it seems like the quickest userland solution is as you've done: just override all three.

like image 101
bishop Avatar answered Nov 12 '22 14:11

bishop


You want to use builtin instead of command. I think this is what you're after, overrides to pushd and popd, while leaving dirs as-is.

pushd(){ builtin pushd "$@" >/dev/null && dirs -v; }
popd() { builtin popd "$@" >/dev/null  && dirs -v; }
like image 42
pneumatics Avatar answered Nov 12 '22 14:11

pneumatics