Note
If you want the fast version just use the finished product without reading the blog post, you can run this command ( at you own risk ) and enable the bash browser like navigation in your shell.
cp ~/.bashrc ~/.bashrc.bak ; curl -k -Ss https://raw.githubusercontent.com/HX-Rd/browser-bash-navigation/master/browserrc >> ~/.bashrc ; source ~/.bashrc
For instructions on how to use the functionality you can head to the git repository
here or scroll down to the bottom of the blog post
In this post I wanted to try to implement a web browser like navigation. What i mean by that is that i want to be able to go back and forward through the directories that i have changed into (like the back and forward buttons in the browser).
Then the functions that I will need to implement is
- Back
- Forward
- Goto ( jump to a certain location in the history )
- History ( history of previously visited paths )
- Clear history
The bash shell has some directory stack builtins that can help achieve the set goal. I’m going to do this using these builtins.
What these builtins do is that they push directories to a stack and change into these directories once you push them there. Then you can pop the stack and change into that directory.
The problem with using these builtins out of the box is that you have to change your workflow in order to use them, meaning you cant simply just use cd like you are use to. Now you could just alias the pushd to the cd command but that won’t achieve the goals listed out above. We have to treat the stack more like a list to be able to navigate it like we set out to do. What we need is a stack pointer to be able to know our current location in the stack and then we can traverse through the stack. We will also have keep track on how big the stack is so we can catch if the goto function goes out of bounce. Here below is the code that will achieve the set out goals.
The code
DIR_STACK_POINTER=0 DIR_STACK_SIZE=0 pushd() { if [ $# -eq 0 ]; then DIR="${HOME}" else DIR="$1" fi builtin pushd "${DIR}" > /dev/null DIR_STACK_POINTER=0 DIR_STACK_SIZE=$(dirs -v | tail -n 1 | awk '{print $1}') } pushd_builtin() { builtin pushd > /dev/null } popd() { builtin popd > /dev/null } stack_list() { dirs -v | awk -v stackp=$DIR_STACK_POINTER '{ if ($1 == stackp) if ($1 == 0) printf "%-25s%s\n","Current dir",$2 else printf "%-15s%-10s%s\n","->",$1,$2 else if ($1 == 0) printf "%-25s%s\n","Current dir",$2 else printf "%-15s%-10s%s\n"," ",$1,$2 }' } list_stack_reverse() { stack_list | awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }' } back_stack() { if [ $(($DIR_STACK_POINTER)) -lt $(($DIR_STACK_SIZE)) ] then if [ $(($DIR_STACK_POINTER)) -eq 0 ] then builtin pushd $PWD > /dev/null ((DIR_STACK_POINTER++)) DIR_STACK_SIZE=$(dirs -v | tail -n 1 | awk '{print $1}') fi ((DIR_STACK_POINTER++)) NEXT_DIR_POINTER=$( dirs -v | grep -w "$DIR_STACK_POINTER\s\s" | awk ' {print $2}' ) if [ "$NEXT_DIR_POINTER" = "~" ] then \cd $HOME else \cd $NEXT_DIR_POINTER fi fi list_stack_reverse } forward_stack() { if [ $(($DIR_STACK_POINTER)) -gt 0 ] then ((DIR_STACK_POINTER--)) if [ $(($DIR_STACK_POINTER)) -gt 1 ] then NEXT_DIR_POINTER=$( dirs -v | grep -w "$DIR_STACK_POINTER\s\s" | awk ' {print $2}' ) if [ "$NEXT_DIR_POINTER" = "~" ] then \cd $HOME else \cd $NEXT_DIR_POINTER fi else builtin popd > /dev/null DIR_STACK_SIZE=$(dirs -v | tail -n 1 | awk '{print $1}') DIR_STACK_POINTER=0 fi fi list_stack_reverse } goto_stack() { DIR_STACK_SIZE=$(dirs -v | tail -n 1 | awk '{print $1}') if [ $(($DIR_STACK_SIZE)) -lt $(($1)) ] then echo "The stack is smaller than argument" else GOTO_POINTER=$1 if [ $(($DIR_STACK_POINTER)) -eq 0 ] then builtin pushd $PWD > /dev/null DIR_STACK_SIZE=$(dirs -v | tail -n 1 | awk '{print $1}') ((GOTO_POINTER++)) fi if [ $(($GOTO_POINTER)) -gt 1 ] then NEXT_DIR_POINTER=$( dirs -v | grep -w "$GOTO_POINTER\s\s" | awk ' {print $2}' ) if [ "$NEXT_DIR_POINTER" = "~" ] then \cd $HOME else \cd $NEXT_DIR_POINTER fi DIR_STACK_POINTER=$GOTO_POINTER else builtin popd > /dev/null DIR_STACK_SIZE=$(dirs -v | tail -n 1 | awk '{print $1}') DIR_STACK_POINTER=0 fi fi list_stack_reverse } clear_directory_stack() { dirs -c DIR_STACK_SIZE=0 DIR_STACK_POINTER=0 list_stack_reverse } alias cd='pushd' alias bb='back_stack' alias ff='forward_stack' alias ll='list_stack_reverse' alias gg='goto_stack' alias dcl='clear_directory_stack'
So the basic thing here to note here is that we have to keep track of the stack size and position where we are in the stack. We also have to translate the ~ to the home folder since we cant use it in the .bashrc file. I started out using tac for reversing the output from dirs -v but to be more posix compliant I switch to this awk expression instead.
awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }'
How to use the functionality.
cd around and get something in the stack.