Open Browser Bookmarks from the Terminal

Ever wanted to open your browser bookmarks from the terminal? Maybe not, but if you have a lot of bookmarks and you also happen to spend a lot of time in the terminal, this can be a great way to quickly launch your bookmarks straight from the terminal.

The bash script for doing this is actually fairly beefy, but I’ve broken into two parts:

  1. A script that parses browser bookmarks into a normalized JSON format.
  2. A script that takes the normalized bookmarks and manages the fuzzy-finding and opening of URLs.

The main reason for splitting the script into these two parts is so that it can support multiple browsers. All it takes to support a different browser is to create a shell script for that browser that parses into the normalized structure, then plug that into the main script and viola.

Normalizing Chrome bookmarks

Let’s get started with the script to normalize the Chrome bookmarks into a structure that our main script can read. We are using jq extensively in this entire process including most importantly parsing the Chrome bookmarks.

I won’t explain every detail, but in essence we are collapsing the recursive nature of Chrome bookmarks into a flat list and creating a slug for each entry (e.g. “My bookmark” becomes “my-bookmark”).

/usr/local/bin/chrome-bookmarks
#!/usr/bin/env bashpath="$HOME/Library/Application Support/Google/Chrome/Default/Bookmarks"jq '.roots  | to_entries[].value  | recurse(.children[]?)  | select(.type == "url")  | {      name,      url,      slug: .name        | gsub("'\''"; "")        | gsub("[^a-zA-Z0-9]"; "-")        | gsub("-{2,}"; "-")        | gsub("(^-|-$)"; "")        | ascii_downcase    }  ' "$path"

The main script

Now that we have the script to read and normalize our Chrome bookmarks, we can create our main script to fuzzy-find and open our bookmarks. If called with an argument, the script will filter the results based on which results start with the given string and either open fzf if there are multiple results, or open the bookmark if it is an exact match.

/usr/local/bin/bm
#!/usr/bin/env bashlist_bookmarks() {	chrome-bookmarks}filter_bookmarks() {	jq -c --arg key "$1" --arg value "$2" 'select(.[$key] | startswith($value))'}find_bookmark() {	jq -c --arg key "$1" --arg value "$2" 'select(.[$key] == $value)'}# If bm is called without argument, show all bookmarksslug=${1:-}# Print raw JSON when requested for completionsif [[ "$slug" == '--json' ]]; then	list_bookmarks | jq -cM	exit 0fi# Filter the bookmarks by the provided slugbookmarks=$(list_bookmarks | filter_bookmarks 'slug' "$slug")# If no bookmarks matched the query, exit earlyif [[ -z "$bookmarks" ]]; then	echo 'No bookmark found matching the provided slug'	exit 1fi# If the slug matches an item exactly, open it without prompting to choose other# options.match=$(echo "$bookmarks" | find_bookmark 'slug' "$slug")if [[ -n "$match" ]]; then	echo "$match" | jq '.url' | xargs open	exit 0fi# Get the exact name of the bookmarkname=$(echo -e "$bookmarks" | jq -r '.name' | fzf --no-info)# Find the bookmark by name and open the URL in Chrome. If no bookmark is# selected from the list, exit with an error.if [[ -n "$name" ]]; then	echo "$bookmarks" | find_bookmark 'name' "$name" | jq '.url' | xargs openelse	exit 1fi

Here are some examples of how we might call our script in our terminal:

bmbm my-bookmark

Support for other browsers

As I mentioned, supporting other browsers is fairly trivial, one simply needs to create a shell script for the desired browser. I’ve created a version for the Arc browser which you can view in my dotfiles.

Shell completions

The script is setup in a way that makes shell completions very simple to add. Simply add the --json flag along with jq to format the results in a way that works for your shell’s completion mechanism.

Here is an example for the Fish shell:

~/.config/fish/completions/bm.fish
function __bm_complete_bookmarks    bm --json | jq -r '.slug'endcomplete -c bm -f -n __fish_use_subcommand -a '(__bm_complete_bookmarks)'