Search code examples
bashpushd

bash script not playing nicely with other command line utilities


I created and added the following bash function to my bash config script a few weeks ago:

cd() {
  if [ "$PS1" ]
    then
    if [ "$1" ]
      then pushd "$1" >/dev/null && ls $LS_OPTIONS
      else pushd >/dev/null && ls $LS_OPTIONS
    fi
  else
    if [ "$1" ]
      then pushd "$1" >/dev/null
      else pushd >/dev/null
    fi
  fi
}

I haven't had problems with it until recently when it has prevented some other commands from behaving properly and I have to comment out the function. For example, when trying to clone a heroku app, I got:

environment: line 8: pushd: -P: invalid number
pushd: usage: pushd [-n] [+N | -N | dir]
environment: line 8: pushd: -P: invalid number
pushd: usage: pushd [-n] [+N | -N | dir]
environment: line 10: pushd: no other directory

And when trying to use rbenv to install ruby, it would throw an error, something like "pwd did not return a directory", until I commented out this function.

I know just enough bash to be dangerous and I'm not sure what in the function might be causing the headaches.


Solution

  • Overriding cd means that any code that expects the "regular" cd will use your function instead. The first problem is the that your function assumes the first argument will be the directory, but your error indicates that some uses are passing different options (like -P) as the first argument. You can fix this rather easily, simply by passing all arguments instead of just the first one. This also handles the zero argument case at the same time.

    cd() {
      if [ "$PS1" ]
        then
        pushd "$@" >/dev/null && ls $LS_OPTIONS
      else
        pushd "$@" >/dev/null
      fi
    }
    

    However, the -P in the error message indicates the next problem. cd and pushd don't take the same options, so code assuming it is calling cd can pass options that pushd doesn't recognize.

    $ help cd | head -1
    cd: cd [-L|[-P [-e]] [-@]] [dir]
    $ help pushd | head -1
    pushd: pushd [-n] [+N | -N | dir]
    

    pushd, however, can add to the directory stack without changing the directory, so you could use both commands in your function. The builtin command lets you call the original cd without getting stuck in infinite recursion.

    cd () {
      # Identify the directory argument, if any
      for arg; do
        case $arg in
          -L | -P | -e | -@) ;;
          *) dir="$arg"; break ;;
        esac
      done
    
      # Process the real cd command
      builtin cd "$@" && 
        if [ "$dir" ]; then
          # If there was a directory argument, push it on the stack
          pushd -n "$dir"
        fi &&
        if [ "$PS1 " ]; then
          # If it's an interactive shell, run ls
          ls $LS_OPTIONS
        fi
    }
    

    It may be simpler, though, to acknowledge that you really want to override pushd and train yourself to use it instead of cd.

    pushd () {
      builtin pushd "$@" &&
        if [ "$PS1" ]; then
          ls $LS_OPTIONS
        fi
    }