Develop on macOS: don’t clutter your home folder like I did…

Mangle Kuo
8 min readApr 22, 2021

If you already did, go to the end of this article for a zsh script to help you tidy your home (directory).

This article aims at newbies to Linux. If you are not a newbie, skip the first section.

Why this happens

If you enjoy playing with all sorts of coding stuff as I do, you might run into the same problem: a messy-A-F home folder.

Here are some tips and knowledge I wish someone had taught me…

A screenshot of my user folder showing lots of hidden sub-folders from different developemtn enviroment I havee isntalled.

In the old days, programmers only develop for one platform, with one language and one framework. These days, programmers are asked to be more and more versatile.

And we all have times wanting to test if something can get something done quickly, so we follow the “getting started” instruction and install yet another chunk of 💩 onto our computer. Thanks to the “migration” feature of modern OS systems, even when we move on to the next machine, we carry the 💩 with us, and it just got more and more until we are drowned in the pool of 💩.

This most likely happens inside your home folder, aka the “~” folder, aka the “username” folder.

Personally, I have tried things like various Python environments, NodeJs, Vue.js, Docker, Android, Flutter, Arduino, .Net, Anaconda, Jupyter, etc. And they all leave their mark on my home folder.

It makes my home folder full of hidden dotfiles, which is kind of alright, but sometimes they even ask me to put my script file directly under my home folder!

While that probably make sense under Linux, as that's more easily accessible than the “desktop” folder when using the command line, it is not so much the case with macOS.

I personally think Linux are more lightweight and should be disposed of when needed, but I can’t do the same with my macOS. It just has too much personal data that I don’t have time to sort through. Therefore deleting the entire system and starting over would be catastrophic.

Prevention (stop new ones from being created)

What I found very useful is to create a “Dev” folder under my home folder and put all of my development-related files inside that. Under it, I have everything organised by folders of frameworks, like “Android”, “Flutter”, “DotNet”, “Arduino”, “NodeJs”, “Docker”, etc.

When any “Getting Started” asked you to create a new script file from your home folder, you go “F you” and create your own folder, then put it inside.

If they install any command-line interface (CLI, things you can type in the command-line to make things happen), I also find it very useful to move all of them under “Dev/bin/”. You can find out where something is on Linux using “which xxx”, for example:

manglekuo@MangleKuo ~ % which adb
/Users/manglekuo/Library/Android/sdk/platform-tools/adb

The given path is the path to the executable Linux binary file, which you can move around, and it should still work fine.

Providing you didn’t install any CLI with homebrew , which is a popular package manager on macOS that does things a bit differently, most CLIs you installed should be under any of the following folders:

/opt/local/bin
/opt/local/sbin
/usr/local/bin
/usr/local/sbin
/usr/bin
/usr/sbin
/bin
/sbin

You can check this by running echo $PATH from your command line. The result should be a long list of folders separated using “:”. This list of folders is where your command line search for executable Linux binary file (they are named “bin” for binaries or “sbin” for system binaries).

If it finds one, you will be able to type the name directly from any directories to use them instead of going to their parent folder and type ./xxx to execute them.

On a modern Mac, you can nano ~/.zshrc to see what your terminal do when it starts up. Likely you have already been asked to edit this file over and over again (or maybe the ~/.bash_profile before Apple deprecating bash and made zsh the official Mac shell), but since now you know a bit more about what it is about, you can maybe try to clean it up a bit.

This is my very messy ~/.zshrc as an example:

export PATH="/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin";
export PATH="$PATH:/opt/local/bin:/opt/local/sbin";
export PATH="$PATH:/usr/local/share/dotnet";
export PATH="$PATH:/Users/manglekuo/.dotnet/tools";
export PATH="$PATH:/Library/Apple/usr/bin";
export PATH="$PATH:/Library/Frameworks/Python.framework/Versions/2.7/bin";
export PATH="$PATH:/Library/Frameworks/Mono.framework/Versions/Current/Com$
export PATH="$PATH:/Applications/Wireshark.app/Contents/MacOS";
export PATH="$PATH:/Users/manglekuo/development/flutter/bin";
export PATH="$PATH:$HOME/bin";
export PATH="$HOME/.jenv/bin:$PATH";
eval "$(jenv init -)";
export JAVA_HOME="/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Conte$export LDFLAGS="-L/usr/local/opt/libffi/lib";
export CPPFLAGS="-I/usr/local/opt/libffi/include";
export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig";
export GUILE_LOAD_PATH="/usr/local/share/guile/site/3.0";
export GUILE_LOAD_COMPILED_PATH="/usr/local/lib/guile/3.0/site-ccache";
export GUILE_SYSTEM_EXTENSIONS_PATH="/usr/local/lib/guile/3.0/extensions";
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools

First aid (When you try to tidy up your home)

I created a zsh script to help with this problem. One thing with macOS Finder is that it does not give out the last modified date based on the files inside a directory but rather gives out the last modified date of the directory itself.

This script solves this problem by recursively searching through the directory and presenting you with the newest 5 files (or directories), so you can better judge how old this directory actually is.

Here is an example of what the output looks like:

You look through the printed result from bottom to top; the bottom is the oldest directory or files. The format is:

File_or_directory_name     File_size     File_last_modified_date

Looking at the output shown above, I can see that the .wingpersonal5 has last modified date 2014–08–27. I wouldn’t know if the files inside of it are old or not using Finder, but here I can see that the newest files within this directory are also old, from 2017. I remember this is an IDE for python I used when I was doing my University, and I know that I no longer need it. So I will delete it and move on.

To use this bash script yourself, download the bash file here or copy and paste from the end of this article. Change the configurations inside the file according to your need, then in Terminal, enter the directory this file is in and run:

% chmod 755 tidyhome.sh
% ./tidyhome.sh

(Not including the % sign)

The first line makes the permission of the file executable to the current user. The second line runs the script.

Download the zsh script or save the following as “tidyhome.sh”

#!/bin/zsh# v0.1.0
# made by Mangle Kuo
# manglekuo.com
# MIT License
################################################
################ CONFIGURATIONS ################
################################################
target=~/;# Folders that will get ignored one level into the targeting forlder
ignoredFolders="Desktop
.Trash
Public
Dev
Downloads
Documents
Applications
Library
Music
Pictures
Movies
Creative Could Files
Dropbox
Google Drive";
recursivelySearch=false;################################################
################## HOW TO USE ##################
################################################
# 1. Change the above configurations according to your need# 2. In Terminal, enter the directory this file is in and run:
# ```
# % chmod 755 tidyhome.sh
# % ./tidyhome.sh
# ```
# (Not including the % sign)# 3. You look through the printed result from bottom to top; the bottom is the oldest directory or files. The format is: `File_or_directory_name File_size File_last_modified_date`.# One thing with macOS Finder is that it does not give out the last modified date based on the files inside a directory but rather gives out the last modified date of the directory itself.# This script solves this problem by recursively searching through the directory and presenting you with the newest 5 files (or directories), so you can better judge how old this directory actually is.################################################
################################################
################################################
echo "tidyhome v0.1.0 made by Mangle Kuo (https://manglekuo.com)
under MIT License
NOTICE
- This script helps review directories, by displaying newest 5
files within a directory.
- If all 5 files have old last modified date, it probably means no
one is using the parent directory, which you should consider
deleting.
- By default, it only goes one level deep, open and edit this
shell script file to make it search recursively.
- Do not run this script using super user (su or sudo), this
script has only be tested on macOS Big Sur v11.4, I'm not
responsible for any damages this script might cause while
running it.
- This may include but not limited to: lost of files, bricked
computer, lost of lives, etc.
- ALWAYS CHECK BEFORE RUNNING SOMETHING YOU DOWNLOADED FORM THE
INTERNET";
echo;
echo "TL;DR: If things break, it's yoru fault.";
read -qs "REPLY?Agree and continue?(y/N)"
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
echo;
echo "Okay, bye~";
[[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell
fi;
echo;
echo;
echo "Target directory:\n\t$target";
echo "Ignored folders: \n$( echo $ignoredFolders | awk '{ print "\t"$1; }' )";
read -qs "REPLY?Ok?(y/N)"
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
echo;
echo "Okay, bye~";
[[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell
fi
echo;
echo;
read -qs "REPLY?Seach recursively?(y/N)"
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
recursivelySearch=true;
fi
ignoredFoldersArray=( ${(ps/\n/)ignoredFolders} );
# Convert string to array
shortenPath() {
# Replace the target part of path to nothing
echo "${1//$target}"
}
result=$(stat -f '%Sm£%N£%z' -t '%FT%T' $target.* $target* | sort -rn);
resultArray=( ${(ps/\n/)result} );
# Get an array of files and directories within the target directory, sorted by last modified date, in dec order
for FILE in $resultArray
do
fileProps=( ${(ps/£/)FILE} );
# Split the line into array using the pound sign
folderPath=$(shortenPath ${fileProps[2]});if [[ " ${ignoredFoldersArray[@]} " =~ " ${folderPath} " ]]; then
# When folderPath is included in the ignoredFoldersArray
echo "$folderPath is ignored.\n"
else
if [ -d ${fileProps[2]} ] && [ "$(ls -A ${fileProps[2]})" ]; then
# Check if is directory and directory not empty
if [ "$recursivelySearch" = true ]; then
# Do recurseve search
newestFiles=$(find ${fileProps[2]} -type f -print0 | xargs -0 stat -q -f "%Sm£%N£%z" -t '%FT%T' | sort -nr | head -5 | awk 'BEGIN {FS="£"} function human(x) { if (x<1000) {return x} else {x/=1024} s="kMGTEPZY"; while (x>=1000 && length(s)>1) {x/=1024; s=substr(s,2)} return int(x+0.5) substr(s,1,1) } {print "|----"$2"\t"human($3)"B\t"$1}' | column -s$'\t' -t);
# find to recursively search
# xargs for better efficiency
# stat to get last modified date, file name, file size
# sort to sort in dec order
# head to trim the result to only top 5
# awk to change the display order to file name, file size, last modified date
# awk also added the formatting for the file size
# colume to make displayed result pretty
echo -e "|$folderPath\t$(numfmt --to=iec-i --suffix=B ${fileProps[3]})\t${fileProps[1]}\n$newestFiles\n";
# numfmt to format the file size
else
# Don't do recurseve search
subDir="${fileProps[2]}";
resultSubDir=$(stat -q -f "%Sm£%N£%z" -t '%FT%T' $subDir/* | sort -rn | head -5 | awk 'BEGIN {FS="£"} function human(x) { if (x<1000) {return x} else {x/=1024} s="kMGTEPZY"; while (x>=1000 && length(s)>1) {x/=1024; s=substr(s,2)} return int(x+0.5) substr(s,1,1) } {print "|----"$2"\t"human($3)"B\t"$1}' | column -s$'\t' -t);
# stat to get last modified date, file name, file size
# sort to sort in dec order
# head to trim the result to only top 5
# awk to change the display order to file name, file size, last modified date
# awk also added the formatting for the file size
# colume to make displayed result pretty
echo -e "|$folderPath\t$(numfmt --to=iec-i --suffix=B ${fileProps[3]})\t${fileProps[1]}\n$(shortenPath $resultSubDir)";
# numfmt to format the file size
fi
else
# When it is not a directory, but a file
echo -e "|$(shortenPath ${fileProps[2]})\t$(numfmt --to=iec-i --suffix=B ${fileProps[3]})\t${fileProps[1]}"
# numfmt to format the file size

fi
fi
echo -e "====================================================";
done

--

--