Skip to content

Instantly share code, notes, and snippets.

@LeviSnoot
Last active June 20, 2025 15:19
Show Gist options
  • Save LeviSnoot/635bbef5ed41a7621946a1a70838269f to your computer and use it in GitHub Desktop.
Save LeviSnoot/635bbef5ed41a7621946a1a70838269f to your computer and use it in GitHub Desktop.
Bash script for installing fonts on Linux

Install fonts easily on Linux!

Inspired by this blog post by Pulkit Singh, this bash script aims to make font installs on Linux as stupid simple as possible.

Usage

./install-fonts.sh [--debug|-d] [--user|-u] <font-zip-url>
./install-fonts.sh [--debug|-d] [--user|-u]

If you don't provide a URL in the initial command, you will be prompted to provide one.

Use the -h/--help flag for more info:

./install-fonts.sh [-h|--help]
#!/bin/bash
#
# install-fonts.sh - Download and install fonts from a zip URL on Linux
#
# Credit to https://dev.to/pulkitsingh/install-nerd-fonts-or-any-fonts-easily-in-linux-2e3l
# for inspiring this script.
#
# Usage:
# ./install-fonts.sh [--debug|-d] [--user|-u] <font-zip-url>
# ./install-fonts.sh [--debug|-d] [--user|-u]
# (If no URL is given, you will be prompted to enter one.)
#
# Options:
# -d, --debug Enable verbose debug output for troubleshooting.
# -u, --user Install fonts only for the current user (no sudo/system-wide install).
# -h, --help Show this help message and exit.
#
# Features:
# - Downloads and extracts a zip file containing .ttf/.otf fonts.
# - Installs each font into a subdirectory of /usr/share/fonts/truetype or /usr/share/fonts/opentype
# based on the font family name (detected using fc-scan if available).
# - Falls back to user font directory if root access is unavailable.
# - Handles multiple font families in a single zip.
# - Provides clean, user-friendly output by default.
#
# Soft Dependencies:
# - fc-scan (from fontconfig)
# This tool allows the script to detect font family names for best organization.
# Without it, fonts will still be installed but may be grouped less cleanly.
#
# Example:
# ./install-fonts.sh https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/0xProto.zip
# ./install-fonts.sh --debug
#
set -e
if [[ -t 1 && -z "$NO_COLOR" ]]; then
RED="$(tput setaf 1 2>/dev/null || echo '\033[31m')"
GREEN="$(tput setaf 2 2>/dev/null || echo '\033[32m')"
YELLOW="$(tput setaf 3 2>/dev/null || echo '\033[33m')"
BLUE="$(tput setaf 4 2>/dev/null || echo '\033[34m')"
BOLD="$(tput bold 2>/dev/null || echo '\033[1m')"
RESET="$(tput sgr0 2>/dev/null || echo '\033[0m')"
else
RED=""
GREEN=""
YELLOW=""
BLUE=""
BOLD=""
RESET=""
fi
DEBUG=0
POSITIONAL=()
HELP_MSG="\ninstall-fonts.sh - Download and install fonts from a zip URL on Linux\n\nCredit to https://dev.to/pulkitsingh/install-nerd-fonts-or-any-fonts-easily-in-linux-2e3l for inspiring this script.\n\nUsage:\n ./install-fonts.sh [--debug|-d] [--user|-u] <font-zip-url>\n ./install-fonts.sh [--debug|-d] [--user|-u]\n (If no URL is given, you will be prompted to enter one.)\n\nOptions:\n -d, --debug Enable verbose debug output for troubleshooting.\n -u, --user Install fonts only for the current user (no sudo/system-wide install).\n -h, --help Show this help message and exit.\n\nFeatures:\n - Downloads and extracts a zip file containing .ttf/.otf fonts.\n - Installs each font into a subdirectory of /usr/share/fonts/truetype or /usr/share/fonts/opentype\n based on the font family name (detected using fc-scan if available).\n - Falls back to user font directory if root access is unavailable.\n - Handles multiple font families in a single zip.\n - Provides clean, user-friendly output by default.\n\nSoft Dependencies:\n - fc-scan (from fontconfig)\n This tool allows the script to detect font family names for best organization.\n Without it, fonts will still be installed but may be grouped less cleanly.\n\nExample:\n ./install-fonts.sh https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/0xProto.zip\n ./install-fonts.sh --debug\n ./install-fonts.sh --user https://example.com/myfont.zip\n"
USER_ONLY=0
while [[ $# -gt 0 ]]; do
case $1 in
-d | --debug)
DEBUG=1
shift
;;
-u | --user)
USER_ONLY=1
shift
;;
-h | --help)
echo -e "$HELP_MSG"
exit 0
;;
*)
POSITIONAL+=("$1")
shift
;;
esac
done
set -- "${POSITIONAL[@]}"
function debug_msg() {
if [[ $DEBUG -eq 1 ]]; then
echo "[DEBUG] $1"
fi
}
if ! command -v fc-scan >/dev/null 2>&1; then
echo -e "${NOTICE} For best font organization, please install: fc-scan${RESET}"
echo -e " This tool allows the script to detect font family names and organize fonts into the correct directories."
echo -e " The script will still work, but fonts may be grouped less cleanly."
echo
echo -e " To install fc-scan: sudo apt install fontconfig"
echo
fi
if [ -z "$1" ]; then
read -rp "Enter the URL to a font zip file: " FONT_URL
else
FONT_URL="$1"
fi
if [ -z "$FONT_URL" ]; then
echo -e "${ERROR}No URL provided. Exiting.${RESET}"
exit 1
fi
TEMP_DIR=$(mktemp -d)
debug_msg "Created temp dir: $TEMP_DIR"
trap 'rm -rf "$TEMP_DIR"' EXIT
echo -e "${BLUE}Downloading font zip from $FONT_URL...${RESET}"
wget -q -O "$TEMP_DIR/font.zip" "$FONT_URL"
debug_msg "Downloaded to $TEMP_DIR/font.zip"
echo -e "${BLUE}Extracting font archive...${RESET}"
unzip -q "$TEMP_DIR/font.zip" -d "$TEMP_DIR"
debug_msg "Unzipped to $TEMP_DIR"
mapfile -t FONT_FILES < <(find "$TEMP_DIR" -type f \( -iname '*.ttf' -o -iname '*.otf' \))
if [[ ${#FONT_FILES[@]} -eq 0 ]]; then
echo -e "${RED}Error: No .ttf or .otf font files found in the archive.${RESET}"
exit 2
fi
declare -A INSTALLED_SUMMARY
if [[ $DEBUG -eq 1 ]]; then
echo "Installing fonts..."
fi
for FONT_PATH in "${FONT_FILES[@]}"; do
EXT="${FONT_PATH##*.}"
FAMILY=""
if command -v fc-scan >/dev/null 2>&1; then
FAMILY=$(fc-scan --format='%{family[0]}\n' "$FONT_PATH" | head -n 1)
debug_msg "fc-scan family for $FONT_PATH: $FAMILY"
fi
if [[ -z "$FAMILY" ]]; then
FAMILY=$(basename "$FONT_PATH" | sed -E 's/[-_]*$//; s/\..*$//')
debug_msg "Fallback family for $FONT_PATH: $FAMILY"
fi
FAMILY_DIR=$(echo "$FAMILY" | tr '[:upper:]' '[:lower:]' | sed -E 's/[[:space:]]+/-/g')
if [[ "$EXT" == "ttf" ]]; then
SYS_DIR="/usr/share/fonts/truetype/$FAMILY_DIR"
USER_DIR="$HOME/.local/share/fonts/truetype/$FAMILY_DIR"
else
SYS_DIR="/usr/share/fonts/opentype/$FAMILY_DIR"
USER_DIR="$HOME/.local/share/fonts/opentype/$FAMILY_DIR"
fi
if [[ $USER_ONLY -eq 1 ]]; then
mkdir -p "$USER_DIR"
cp "$FONT_PATH" "$USER_DIR/"
debug_msg "Installed $(basename "$FONT_PATH") to $USER_DIR (user fonts)"
INSTALLED_SUMMARY["$FAMILY"]+="$(basename "$FONT_PATH") (user)"$'\n'
else
if sudo mkdir -p "$SYS_DIR" && sudo cp "$FONT_PATH" "$SYS_DIR/"; then
debug_msg "Installed $(basename "$FONT_PATH") to $SYS_DIR"
INSTALLED_SUMMARY["$FAMILY"]+="$(basename "$FONT_PATH") (system)"$'\n'
else
mkdir -p "$USER_DIR"
cp "$FONT_PATH" "$USER_DIR/"
debug_msg "Installed $(basename "$FONT_PATH") to $USER_DIR (user fonts)"
INSTALLED_SUMMARY["$FAMILY"]+="$(basename "$FONT_PATH") (user)"$'\n'
fi
fi
done
if [[ $DEBUG -eq 0 ]]; then
echo -e "${GREEN}Installed fonts:${RESET}"
for FAMILY in "${!INSTALLED_SUMMARY[@]}"; do
printf " ${BOLD}%s${RESET}:\n%s" "$FAMILY" "${INSTALLED_SUMMARY[$FAMILY]}"
done
fi
if [[ $USER_ONLY -eq 1 ]]; then
if [[ $DEBUG -eq 1 ]]; then
fc-cache -f -v "$HOME/.local/share/fonts"
else
fc-cache -f "$HOME/.local/share/fonts" >/dev/null 2>&1
echo -e "${GREEN}Fonts installed successfully!${RESET}"
fi
else
if [[ $DEBUG -eq 1 ]]; then
sudo fc-cache -f -v
else
sudo fc-cache -f >/dev/null 2>&1
echo -e "${GREEN}Fonts installed successfully!${RESET}"
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment