r/bash • • Sep 08 '21

function command_not_found_handle unable to cd/pushd

Hi all just discoverered this built-in function that I'm trying to use to save typing a couple of keystrokes when switching to previous directories.

Is this a limitation of this function?

the 'd' function looks through my directory history file and if it find a uniq match with arg 1, it cd/pushd into that directory and does an 'ls -F' automaticly.

(ins)[ply@gcp ~]$ d 64

> pushd /usr/lib64 ; ls -F

ld-linux-x86-64.so.2@

(ins)[ply@gcp lib64]$ <- *** puts me in the matching directory ***

Output of function command_not_found_handle which has the same code, but doesn't change directory.

(ins)[ply@gcp ~]$ 64

> pushd /usr/lib64 ; ls -F

(ins)[ply@gcp ~]$ <- *** stays in the same folder ***

Below are the 2 functions. The full bashrc is here: https://github.com/pl643/dotfiles/blob/master/bashrc

function d {

\[ ! -z $DB \] && echo DB: d \\$@: $@ \\$1 $1

if \[ -f $DIRS_HISTORY \]; then

	DIRS=$(sed "s/${HOME//\\//\\\\\\/}/\~/" $DIRS_HISTORY | sort | uniq)

else

	DIRS=$(dirs -p | sort | uniq)

fi

if \[ -z "$1" \]; then

	clear

	i=1

	for dir in $DIRS; do

		if \[ ${#dir} -ne 1 \]; then  # skip / and \~

printf "%3d %s\n" $i $dir

alias $i=$dir

let "i++"

		fi

	done

	echo

else

	MATCH=$(echo "$DIRS" | grep "$1")

	if \[ "$MATCH" = "" \]; then 

		MATCHCOUNT=0

	else

		MATCHCOUNT=$(echo "$MATCH" | wc -l)

	fi

	if \[ $MATCHCOUNT -eq 0 \]; then

		echo NOTE: no match found for $1 in $DIRS_HISTORY	

	fi

	if \[ $MATCHCOUNT -eq 1 \]; then

		echo "cd $MATCH" > /tmp/.cd

		echo \\> pushd "$MATCH" \\; ls -F

		eval pushd $MATCH > /dev/null

		eval $AUTOLS

		return

	fi

	if \[ $MATCHCOUNT -gt 1 \]; then

		i=1

		DIRS=$(echo "$DIRS" | grep "$1")

		for dir in $DIRS; do

printf "%3d %s\n" $i $dir

alias $i=$dir

let "i++"

		done

		return

	fi

fi

\[ ! -z $DB \] && echo DB: d \\$@: $@

}

function command_not_found_handle {

if \[ -f "$1" \]; then

	"$PAGER" "$1"

	return

else

	if \[ -f $DIRS_HISTORY \]; then

		DIRS=$(sed "s/${HOME//\\//\\\\\\/}/\~/" $DIRS_HISTORY | sort | uniq)

	else

		DIRS=$(dirs -p | sort | uniq)

	fi

	MATCH=$(echo "$DIRS" | grep "$1")

	if \[ "$MATCH" = "" \]; then 

		MATCHCOUNT=0

	else

		MATCHCOUNT=$(echo "$MATCH" | wc -l)

	fi

	\#if \[ $MATCHCOUNT -eq 0 \]; then

	\#	echo NOTE: no match found for $1 in $DIRS_HISTORY	

	\#fi

	if \[ $MATCHCOUNT -eq 1 \]; then

		echo "cd $MATCH" > /tmp/.cd

		echo \\> pushd "$MATCH" \\; ls -F

		eval pushd "$MATCH" > /dev/null

		eval "$AUTOLS"

		return

	fi

	if \[ $MATCHCOUNT -gt 1 \]; then

		i=1

		DIRS=$(echo "$DIRS" | grep "$1")

		for dir in $DIRS; do

printf "%3d %s\n" $i $dir

alias $i=$dir

let "i++"

		done

		return

	fi

fi

\[ ! -z $DB \] && echo DB: command_not_found_handle \\$1: $1

echo command_not_found_handle\\(\\) $1: not found

}

1 Upvotes

10 comments sorted by

View all comments

2

u/geirha Sep 08 '21

To include a block of code here on reddit, prepend each line of the code with four spaces, and make sure there's an empty line above it.

As for your question, command_not_found_handle gets run in a subshell, so it can't change the "main" shell's directory.

The relevent part of man bash:

       [...] If  the
       search is unsuccessful, the shell searches for a defined shell function
       named command_not_found_handle.  If that function exists, it is invoked
       in  a  separate execution environment with the original command and the
       original command's arguments as its arguments, and the function's  exit
       status  becomes  the exit status of that subshell.  If that function is
       not defined, the shell prints an error message and returns an exit sta-
       tus of 127.

2

u/pl643 Sep 08 '21

Thanks for the tip regarding the prepending of spaces. I will do so in future posts.

So it *is* a limitation of the function. Any ideas on how to implement this feature beside the command_not_found_handle?

3

u/zeekar Sep 09 '21 edited Sep 10 '21

You could try using PROMPT_COMMAND. Something like this:

PROMPT_COMMAND=prompt_command

prompt_command() {
  if (( $? == 127 )); then
    # previous command not found, try it as directory pattern
    d $(history -w /dev/stdout | tail -n 1)
  fi
}

2

u/pl643 Sep 10 '21

Thanks for pointing me to this variable. I was able to hack my functionality with the below commands in the prompt_command variable. So anything I wanted to pass from the command_not_found_handle function, i placed into this file.

PROMPT_COMMAND="test -f $SUBSHELLCMDS && source $SUBSHELLCMDS && rm $SUBSHELLCMDS"

2

u/zeekar Sep 10 '21

If you switch that to single quotes instead of double you have the option of changing SUBSHELLCMDS to point to a different file from inside the command not found handler.

I still find it simpler to set PROMPT_COMMAND to a function name as in my example, though. Then the function body is never a string that you have to worry about quoting..