Ankit Vadehra (Thoughts and Musings.)

GOTO a cd replacement tool for Linux

written on 31/01/2015

Projects. Projects. Projects. After 5 semesters, cooped up in the same place, SO-VERY-FAR away from home, i finally realize what "monotonous" means. Projects are all that remain, that are even remotely fun(..that is, apart from watching TV Shows :D ). Anyways, there's this project i wanted to do. It's been done by the awesome people over at YHatHQ. It's a Fuzzy Matcher and it uses the FuzzyWuzzy Python Library developed by the people at SeatGeek to handle their ticket stubs duplication and etc etc. Now, in the DataScience, OSEMN(Obtain Data, Scrub Data, Exploratory Analysis, Modeling and iNterpreting results) approach, the more perfect the data is, the better the classifier accuracy and stuff. So, the project seemed a good approach to remove duplicates and clean the dataset. Before moving to Python web-apps i wanted to create a fuzzy-finder for my Terminal. Also, when you're developing something, why not just go full-in, with whatever features you can muster. The only rule "It has to be done completely in BASH/SHELL". No, Python scripts to make certain tasks easier. Finally i come up with: goto: A 'cd' tool on steroids. :)

goto: A 'cd' tool on steroids.

goto is designed to be a complete replacement for cd, ie. the change directory tool in Linux. With, extra features. A Fuzzy-File Finder and a Directory Shortcut Creator. Something that can make switching between different folders/directories quite easy.

So, how does it work?

Usage:

goto -h||-help||h||help : Prints the Usage

goto <location_address> : Takes you to the <location_address>

goto s||-s <shortcut>   : Saves the Current Directory location as <shortcut>

goto l||-l              : Lists all the Bookmarks saved.

goto d||-d <shortcut>   : Deletes the <shortcut> saved.

goto p||-p <shortcut>   : Prints the GOTO directory for <shortcut>

goto f||-f||find||-find <term>     : Fuzzy-Finds all the file/folder matching REGEX in the working directory.
What the Functions do, and how are they constructed? okay. The program divides the approach on the basis of the number of parameters that could be passed.
0 Parameters: Something just went wrong. Print the usage.
1 Parameter: help, list or goto <location_address>.
2 Parameters: save_shortcut, delete_shortcut, print_shortcut and fuzzy_find.
Anything other than 0, 1, 2: Some error. Print Usage.
Now, i should be explaining the functions that take in a single parameter, but it is necessary to explain the process i take for saving the shortcuts as all the other approaches are kinda linked to that.
~$ goto s <shortcut>
The save function takes in the shortcut name and saves it in a directory at ~/.GOTO as a text file <shortcut>.skt with the current working directory inside.
Example: Let's say i'm in my downloads folder, and want to save it as a shortcut: d.
~$ goto ~/downloads
~$ goto s d
Shortcut Created.!
This creates a file d.skt with the location of downloads in it. Let's see:
~$ cat ~/.GOTO/d.skt
/home/ankitvad/downloads
After that, we can jump to downloads with:
~$ goto d
Hence, the whole thing has a directory-file saving approach. That makes the time/space complexity both O(n). Using a .csv file that could save the shortcut with location would be a much better approach but i just couldn't wrap my head around constant deleting and appending values in the file. A directory approach, is quite simple. Now to the single parameter functions. Here goes:
~$ goto <location_address>
This function is 2 folds. The value passed from the terminal is 1st checked in the shortcut directory, whether or not it exists. If it exists, we read the shortcut file and 'cd' to the saved directory location:
if [ -e "$savefile$first_parameter.skt" ]; then
	x="$savefile$first_parameter.skt"
	y="$(cat $x)"
	cd $y
fi
Example, in the previous example we created a shortcut for downloads as d. Now, when we do:
~$ goto d
It first checks whether d.skt exists or not. If it does, then we move to the value inside the file, else, we try moving directly to the value. This helps to replicate cd's normal features also.
if [ -e "$savefile$first_parameter.skt" ]; then
	x="$savefile$first_parameter.skt"
	y="$(cat $x)"
	cd $y
	#If not, just CD to that location.
elif [ -e "$first_parameter" ]; then
	cd "$first_parameter"
else
	echo -e "The Location/File/Folder $first_parameter does not exist."
fi
Example, we can move to locations like:
~$ goto ~/tmp
Which, takes us to: /home/ankitvad/tmp.

The second function is the goto l function that lists all the bookmarks saved till now.
if [ "$first_parameter" = "l" ] || [ "$first_parameter" = "-l" ] ; then
	if [ ! -e "$savefile" ]; then
	echo -e "You have not added any directory yet."
	mkdir $savefile
	usage
elif [ ! "$(ls $savefile)" ]; then
	echo -e "You have not added any directory yet."
	usage
else
	#Reverse the path. Delete everything after / and before .
	for file in $savefile*.skt; do
		x="$(echo "$file" | rev)"
		y=${x%%/*}
		x="$(echo -n ${y##*.} | rev)"
		echo -n "$x" 
		echo " :  $(cat $file)"
		echo -e " "
		done
fi
First it checks whether or not the shortcut bookmark location exists or not at ~/.GOTO/ If it does then a simple loop runs to chose all .skt files. Here is the place where the first complication arose. Technically,
for file in $savefile*.skt; do
choses the file along with the whole location. Example, let there be a shortcut file in ~/.GOTO called hello.skt which has the home directory saved in it. Then the variable file gives us:
/home/ankitvad/.GOTO/hello.skt
Whereas, we only require the filename hello. Now, a simple regex cut could easily remove everything including and before the 4th '/' and then everything including and after the '.'. But, the problem was the fact that i didn't know if the 4th forward-slash would be a good metric to cut. What if due to server sub-directory structure there were more than 4 slashes? So, i reversed the string. Deleted everything after the first slash '/' and before the '.'. That made it quite easy to get the shortcut name. This approach also makes it easier to look for files.

After that, the 2 parameter functions.
~$ goto d||p||find <shortcut>
which can respectively delete shortcuts, print shortcut locations and fuzzy find terms. These are quite self-explanatory and have an easy construction. Just printing and deleting the text files, based on searching. Now comes the fuzzy finder feature.

The Fuzz-Finder in Shell

The whole fuzzy finding can be broken into 2 parts.
regex_value="[A-Za-z0-9]*"
length=${#term}
x="$regex_value"
for (( i=0; i<$length; i++ )); do
	y="$x${term:$i:1}"
	x="$y$regex_value"
done 
regex_pattern="$x"
#i just need: [A-Za-z0-9]* after each literal, or before.
ls -R $pwd | grep -i $regex_pattern
The first part is creating the REGEX search able string. Let's say that the string to search is : st.cs. Now, technically this should return us: style.css file. For this we port it into the REGEX search able term:
[A-Za-z0-9]*s[A-Za-z0-9]*t[A-Za-z0-9]*.[A-Za-z0-9]*c[A-Za-z0-9]*s[A-Za-z0-9]*
This would match all strings that have "s, t, ., c, s" present. Also, after and before the characters we add the REGEX wild card: [A-Za-z0-9]* which means there can be any character before and after the search characters we provided.

The second part is looking for files that match the search pattern. For that we use:
ls -R $pwd | grep -i $regex_pattern
This command Recursively checks the file names of all files/folders present in the current working directory and feeds the value to the 'grep' tool which matches the name with the REGEX pattern and prints all the files found. After the file is found, a simple "find" can be used to print the location. Example, Let's say we are looking for "blog.html"
~$ goto find bl.ht
blog2.html
finallyupwithablog.html
blog.html
blog.html
~$ find -name blog.html
./caffeinepost/blog.html
./ankitvad.github.io/blog/blog.html
After this, a simple ~$ goto <location_address> command should suffice. And that's it. That's the whole program.

Installation and Stuff

The problem with running goto as a bash program is that it fails to execute the ~$ cd command. The logic behind that is the fact that the 'cd' runs as a child process of the bash program execution. So, even though the code changes the directory, it dies as the goto execution finishes and hence it reverts back to the original directory. To avoid this, we either need to create functions for the 'cd' command and add that in the .bashrc or instead of running goto as: bash goto we can run it as: . goto. This gives it independent-parent process priority. Jumping to the Makefile:
bold=`tput bold`
normal=`tput sgr0`

all:
	@echo "Run ${bold}'make install'${normal}."

install:
	bash install.sh
	
.PHONY: all install
Which jumps to the execution of install.sh:
#!/bin/bash
INSTALL=~/.local/bin
BASH_FILE=~/.bashrc
bold=`tput bold`
normal=`tput sgr0`
mkdir -p "$INSTALL"
cp goto "$INSTALL"
echo 'export PATH=$PATH:~/.local/bin/' >> "$BASH_FILE"
echo 'alias goto=". goto"' >> "$BASH_FILE"
echo -e "Added Stuff in .bashrc"
. ~/.bashrc
exit 0
This, creates the install directory for the 'goto' program. It then proceeds to write:
export PATH=$PATH:~/.local/bin/
alias goto=". goto"
To the .bashrc file. The alias is required so that we can run goto as
~$ . goto
instead of
~$ bash goto
The code along with the Makefile and installation file is available at Github.
ankitvad/goto

To install:
If You have "git" installed
~$ git clone "https://github.com/ankitvad/goto"
~$ cd goto
~$ make install
If "git" is not present
~$ wget -O goto.zip "https://github.com/ankitvad/goto/archive/master.zip"
~$ unzip goto.zip
~$ cd goto-master
~$ make install
If someone has a MAC system, please do tell me how it works out ?