How to change current working directory inside command_not_found_handle
Asked Answered
M

4

6

I'm trying to write a not found handle in Bash that does the following:

  1. If $1 exists and it's a directory, cd into it.
  2. If $1 exists inside a user defined directory $DEV_DIR, `cd into it.
  3. If the previous conditions don't apply, fail.

Right now I have something like this:

export DEV_DIR=/Users/federico/programacion/

function command_not_found_handle () {
    if [ -d $1 ]; then          # the dir exists in '.'
        cd $1
    else
        to=$DEV_DIR$1
        if [ -d $to ]; then
            cd $to
            echo `pwd`
        else
            echo "${1}: command not found"
        fi
    fi
}

And although it seems to be working (the echo pwd command prints the expected dir), the directory in the actual shell does not change.

I was under the impression that since this is a function inside my .bashrc the shell wouldn't fork and I could do the cd but apparently that's not working. Any tips on how to solve this would be appreciated.

Mowery answered 20/3, 2011 at 17:45 Comment(2)
Why do you need to do this? There is most likely a better way to accomplish whatever you have in mind.Pulmonate
I want to move between subdirs inside the directory where I keep all my projects in an easy way. Doing cd ~/prog; cd something gets a bit tiring after some time. I'd love to hear any other suggestions to doing this.Mowery
P
4

I think what's going on is that the shell fork()s after setting up any redirections but before looking for commands, so command_not_found_handle can't affect the interactive shell process.

Pejoration answered 20/3, 2011 at 17:49 Comment(1)
+1 Compare: f () { echo "$$ $BASHPID"; }; f; command_not_found_handle () { echo "$$ $BASHPID"; }; fooble (assuming you don't have anything called "fooble"). A new shell is forked. (Also, note that $SHLVL and $BASH_SUBSHELL don't vary if you include those in the functions for additional comparisons.)Pulmonate
P
2

What you seem to want to do may partly possible using the autocd feature:

shopt -s autocd

From man bash:

autocd - If set, a command name that is the name of a directory is executed as if it were the argument to the cd com‐ mand. This option is only used by interactive shells.

Otherwise, just create a function that you invoke by name that performs the actions you are trying to use command_not_found_handle for.

Pulmonate answered 20/3, 2011 at 21:39 Comment(3)
This works perfectly for directories inside . but the most important thing I want to get done is being able to move quickly to directories in other locations. I'll keep it in mind though, thanks.Mowery
@FedericoBuiles: What about the suggestion I made in the last sentence?Pulmonate
That'd require me to type the function name each time I want to swich (which would be effectively the same as setting up autocd). I do appreciate your effort though.Mowery
P
1

I've had the very same wish and the solution that I've been using for a while was opening a new tab in gnome terminal by issuing the command gnome-terminal --tab --working-directory="$FOLDER" from inside the command_not_found handle.

But today I've come up with a solution which is not tied to a specific terminal application, but has exactly the intended behaviour.

The solution uses the PROMPT_COMMAND, which is run before each prompt. The PROMPT_COMMAND is bound to a function responsible for checking for a file related to current shell, and cd'ing into the directory specified in that file.

Then, the command_not_found_handle fills in the file when a change in directory is desired. My original command_not_found_handle also checkout a git branch if the current directory is a git repository and the name matches an existing branch. But to keep focus on answering the current question, I've stripped that part of code.

The command_not_found_handle uses find for searching for the directory matching the given name and goes only 2 levels deep in the directory tree, starting from a configured list.

The code to be added to bash_rc follows:

PROMPT_COMMAND=current_shell_cd
CD_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/bash-cd/$$.cd"

current_shell_cd() {
    if [ -r "$CD_FILE" ]; then
        local CD_TARGET="$( cat "$CD_FILE" )"
        [ ! -z "$CD_TARGET" ] && cd "$CD_TARGET" 2>/dev/null
        rm "$CD_FILE"
    fi
}

command_not_found_handle () { 
    local COMMAND="$1";
    # List folders which are going to be checked
    local BASE_FOLDER_LIST=(
        "$HOME/Desenvolvimento"
        "/var/www/html"  
        "$HOME/.local/opt/"
    )
    local FOLDER=$(
        find "${BASE_FOLDER_LIST[@]}" \
             -maxdepth 2 -type d \
             -iname "$COMMAND" -print -quit )
    if [ ! -z "$FOLDER" -a -d "$FOLDER" ]
    then
        mkdir -p "$( dirname "$CD_FILE" )"
        echo "$FOLDER" > "$CD_FILE"
    else
        printf "%s: command not found\n" "$1" 1>&2
        return 127
    fi
}
Ptolemy answered 9/10, 2020 at 18:27 Comment(2)
The answer is genius! This must be accepted answer. The solution is in PROMPT_COMMAND.Clemmy
Related discussion – reddit.com/r/bash/comments/pkjelw/…Clemmy
P
0

It won't change directies if you run this program as a script in your main shell because it creates a sub-shell when it executes. If you source the script in your current shell then it will have the desired effect.

~/wbailey> source command_not_found.sh

That said, I think the following would achieve the same result:

wesbailey@feynman:~/code_katas> cd xxx 2> /dev/null || cd ..; pwd
/Users/wesbailey

just replace the ".." with your env var defined directory and create an alias in your .bashrc file.

Priestess answered 20/3, 2011 at 17:55 Comment(3)
That's not why it's not working. The function is defined in the current shell, but Bash forks a new shell to execute the function (which is not normally the case).Pulmonate
The idea of defining an alias is exactly what I don't want to do. I have 82 directories inside my $DEV_DIR so I don't want to be creating a new alias for each one.Mowery
@FedericoBuiles, he's talking about overriding cd with an alias that also tries to cd to your projects directory, not a separate alias for every entry in it. You'd maybe want to actually replace cd with a shell function, not an alias, so you could e.g. [ -d "$mydir/$1" ] before trying builtin cd to it. Also, pwd doesn't take a directory as an argument, so IDK exactly what Wes was going for.Appendix

© 2022 - 2024 — McMap. All rights reserved.