Files
2026-03-03 01:51:12 -06:00

11846 lines
458 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv bash)"
#######################################################
# ┏━╸╻ ╻╺┳╸┏━┓┏━╸┏┳┓┏━╸ ╻ ╻╻ ╺┳╸╻┏┳┓┏━┓╺┳╸┏━╸ ┏┓ ┏━┓┏━┓╻ ╻┏━┓┏━╸
# ┣╸ ┏╋┛ ┃ ┣┳┛┣╸ ┃┃┃┣╸ ┃ ┃┃ ┃ ┃┃┃┃┣━┫ ┃ ┣╸ ┣┻┓┣━┫┗━┓┣━┫┣┳┛┃
# ┗━╸╹ ╹ ╹ ╹┗╸┗━╸╹ ╹┗━╸ ┗━┛┗━╸ ╹ ╹╹ ╹╹ ╹ ╹ ┗━╸ ╹┗━┛╹ ╹┗━┛╹ ╹╹┗╸┗━╸
# https://sourceforge.net/projects/ultimate-bashrc/files/
# Extreme Ultimate .bashrc File sources are free and
# open software released under the Zero-Clause BSD License (0BSD)
# https://opensource.org/license/0BSD
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#######################################################
# To Install or Update:
# wget -O ~/.bashrc https://sourceforge.net/projects/ultimate-bashrc/files/_bashrc/download
# wget -O ~/.bashrc_help https://sourceforge.net/projects/ultimate-bashrc/files/_bashrc_help/download
# -or-
# curl -L --output ~/.bashrc https://sourceforge.net/projects/ultimate-bashrc/files/_bashrc/download
# curl -L --output ~/.bashrc_help https://sourceforge.net/projects/ultimate-bashrc/files/_bashrc_help/download
#######################################################
# Supported Optional Applications/Dependencies:
# 7z apg aria2 atuin aureport baca base64 bash-completion bashmarks bashtop bat
# batcat bc blesh bottom bpytop broot btm btop btrfs bzip2 ccat cmatrix cod
# colordiff commacd cracklib curl delta diff-so-fancy difftastic dircolors
# distrobox doas doasedit dua dust dym elinks enhancd exa exiftool eza fasd fd
# ffmpeg figlet fresh frogmouth fx fzf fzf-tab-completion fzy gawk gcal gdu gio
# git git-commander git-completion git-delta gitalias gitui glances glow gpg grc
# grv gtop gunzip gzip hBlock helix hstr htop icdiff ifconfig iftop imagemagick
# iotop jless jnv jp2a jq kdiff3 keepass keepassxc keeweb lazygit links links2
# lnav lolcat lscolors lsd lsof lsx lynx mc mcfly mdcat mdless meld micro
# mlocate moar most multitail mysql-colorize nano ncdu neovim nethogs nmon nnn
# nvtop openssl paru pkill pwgen qfc ranger rar rem resh restore-trash rhvoice
# ripgrep rsync shellcheck shred silver_searcher skim source-highlight sshpass
# tar termdown terminology thefuck tig tmux toilet trash-cli tree tuifi ugit
# vivid vizex vlock w3m wget wl-copy wl-paste xclip xdg-open xdotool xhost
# xprop xrdb xsel youtube-dl yt-dlp ytfzf ytop zellij zf zip zoxide
#
# Supported Optional Huds:
# neofetch, fastfetch, screenFetch, linux_logo, archey, pfetch
#
# Supported Optional Prompts:
# Trueline, Powerline, Powerline-Go, Powerline-Shell,
# Pureline, Starship, Bash Git Prompt, Liquid Prompt
#
# To Install Packages:
# pkginstall [package names separated by spaces]
#######################################################
# set -o errexit # Exit when a command fails
# set -o pipefail # Catch mysqldump fails
# set -o nounset # Exit when using undeclared variables
# set -o xtrace # Trace what gets executed (useful for debugging)
### ERROR TRAPPING
# alias debug="set -o nounset; set -o xtrace"
# error() { echo 'Error in ${1} on line ${2}: ${3}' }
# trap 'error "${BASH_SOURCE}" "${LINENO}"' ERR
### TEST FOR AN INTERACTIVE SHELL
# This file is sourced by all "interactive" bash shells on startup
# including shells such as scp and rcp that can't tolerate any output.
# There is no need to set anything past this point for scp and rcp,
# and it's important to refrain from outputting anything in those cases.
[[ $- != *i* ]] && return
[[ -z "$PS1" ]] && return
# Bash version check
if [ -n "$BASH_VERSION" ] && ((BASH_VERSINFO[0] < 4)); then
echo "This .bashrc file requires at least Bash 4.0"
return 1
fi
# Source global definitions from the available bashrc files
if [[ -f /etc/bashrc ]]; then
builtin source /etc/bashrc
elif [[ -f /etc/bash.bashrc ]]; then
builtin source /etc/bash.bashrc
fi
# Grant permission to the local root user to access the X server
# This is NOT recommended for regular use due to the security implications:
# https://stackoverflow.com/questions/63884968/why-is-xhost-considered-dangerous
#if [[ -x "$(command -v xhost)" ]]; then
# # 'xhost +local:root' allows the root user to connect to the X server
# # This might be needed for some X applications to work
# xhost +local:root > /dev/null 2>&1
#fi
#######################################################
# Default Bash Escape ANSI Color Codes
#######################################################
# Foreground Colors
BLACK="\033[0;30m"
RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[0;33m"
BLUE="\033[0;34m"
MAGENTA="\033[0;35m"
CYAN="\033[0;36m"
WHITE="\033[0;37m"
# Bright Foreground Colors
BRIGHT_BLACK="\033[1;30m"
BRIGHT_RED="\033[1;31m"
BRIGHT_GREEN="\033[1;32m"
BRIGHT_YELLOW="\033[1;33m"
BRIGHT_BLUE="\033[1;34m"
BRIGHT_MAGENTA="\033[1;35m"
BRIGHT_CYAN="\033[1;36m"
BRIGHT_WHITE="\033[1;37m"
# Background Colors
BG_BLACK="\033[0;40m"
BG_RED="\033[0;41m"
BG_GREEN="\033[0;42m"
BG_YELLOW="\033[0;43m"
BG_BLUE="\033[0;44m"
BG_MAGENTA="\033[0;45m"
BG_CYAN="\033[0;46m"
BG_WHITE="\033[0;47m"
# Bright Background Colors
BG_BRIGHT_BLACK="\033[1;40m"
BG_BRIGHT_RED="\033[1;41m"
BG_BRIGHT_GREEN="\033[1;42m"
BG_BRIGHT_YELLOW="\033[1;43m"
BG_BRIGHT_BLUE="\033[1;44m"
BG_BRIGHT_MAGENTA="\033[1;45m"
BG_BRIGHT_CYAN="\033[1;46m"
BG_BRIGHT_WHITE="\033[1;47m"
# Reset Color
RESET="\033[0m"
#######################################################
# Find a temp directory where local is preferred
#######################################################
# Loop through potential temporary directories in order of preference
for _TEMP_DIR_PATH in \
"${XDG_CACHE_HOME:-${HOME}/.cache}/tmp" \
"${HOME}/.tmp" \
"${HOME}/.temp" \
"${XDG_CACHE_HOME:-${HOME}/.cache}" \
"${TMPDIR}" \
"${TMP}" \
"/tmp" \
"/temp"; do
# Check if directory exists
if [[ -d "${_TEMP_DIR_PATH}" ]]; then
TEMPDIR_LOCAL="${_TEMP_DIR_PATH}"
break # Exit loop after finding first valid directory
fi
done
# Clean up
unset _TEMP_DIR_PATH
#######################################################
# Set the default editor
# Examples: vim, nvim, emacs, nano, micro, fresh, helix, pico,
# or gui apps like kate, gedit, notepadqq, or vscodium
# NOTE: In Git Bash, you can use something like "/c/Program\ Files/Notepad++/notepad++.exe"
# To change these without modifying this file, simply
# add these with your changes to one of these files:
# ~/.env
# ~/.envrc
# ~/.config/bashrc/config
# See section "Configuration and Extras" in the README:
# Link: https://sourceforge.net/projects/ultimate-bashrc/files/
#######################################################
# Declare associative array for caching command checks
declare -A _HASCOMMAND_CACHE
# Check if a command or alias exists (with caching for performance)
function hascommand() {
# If no arguments, just '--strict', or help requested, show help message
if [[ -z "${1}" || "${1}" == "--help" || "${1}" == "-h" || (${#} -eq 1 && "${1}" == "--strict") ]]; then
echo -e "${BRIGHT_CYAN}hascommand${RESET}: Check if a command or alias exists"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}hascommand${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}options${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}command${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Options:${RESET}"
echo -e " ${BRIGHT_GREEN}--strict${RESET}, ${BRIGHT_GREEN}-s${RESET} Check executables only (exclude aliases)"
echo -e " ${BRIGHT_GREEN}--no-cache${RESET} Skip cache (useful after installing new software)"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}hascommand${RESET} ${BRIGHT_YELLOW}ls${RESET} ${BRIGHT_BLUE}# Check any command or alias${RESET}"
echo -e " ${BRIGHT_CYAN}hascommand${RESET} ${BRIGHT_GREEN}--strict${RESET} ${BRIGHT_YELLOW}grep${RESET} ${BRIGHT_BLUE}# Check executable only${RESET}"
echo -e " ${BRIGHT_CYAN}hascommand${RESET} ${BRIGHT_GREEN}--no-cache --strict${RESET} ${BRIGHT_YELLOW}newcmd${RESET} ${BRIGHT_BLUE}# Force fresh check${RESET}"
return 2 # Return code 2 to indicate incorrect usage
fi
# Check for --no-cache flag
local USE_CACHE=true
if [[ "${1}" == "--no-cache" ]]; then
USE_CACHE=false
shift
fi
# Check for the '--strict' option
local STRICT=false
local COMMAND
if [[ "${1}" == "--strict" ]] || [[ "${1}" == "-s" ]]; then
STRICT=true
COMMAND="${2}"
else
COMMAND="${1}"
fi
# Create cache key
local CACHE_KEY="${STRICT}:${COMMAND}"
# Check cache first
if [[ "${USE_CACHE}" == true ]] && [[ -n "${_HASCOMMAND_CACHE[${CACHE_KEY}]}" ]]; then
[[ "${_HASCOMMAND_CACHE[${CACHE_KEY}]}" == "1" ]]
return $?
fi
# Perform the actual check
local RESULT
if [[ "${STRICT}" == true ]]; then
# Look for executable command using type -P
if type -P "${COMMAND}" &>/dev/null; then
RESULT=1
else
RESULT=0
fi
else
# Look for command or alias
if type "${COMMAND}" &>/dev/null; then
RESULT=1
else
RESULT=0
fi
fi
# Cache the result
_HASCOMMAND_CACHE[${CACHE_KEY}]="${RESULT}"
[[ "${RESULT}" == "1" ]]
return $?
}
# Helper to clear the cache if you install something mid-session
function hascommandclear() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}hascommandclear${RESET}: Clear the hascommand lookup cache"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}hascommandclear${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Run after installing new software to refresh command detection${RESET}"
return 0
fi
unset _HASCOMMAND_CACHE
declare -gA _HASCOMMAND_CACHE
echo "hascommand cache cleared"
}
# BEGIN_EDITOR_CONFIG
# Loop a list of common editors to check
for _EDITOR_NAME in \
fresh micro ne helix tilde jed vile zile joe nano nvim vim emacs vi jove mg ed
do
# Order matters: plain name first, then Flatpak and Snap host paths
for EDITOR in \
"${_EDITOR_NAME}" \
"/usr/local/bin/${_EDITOR_NAME}" \
"${HOME}/.local/bin/${_EDITOR_NAME}" \
"/run/host/usr/bin/${_EDITOR_NAME}" \
"/var/run/host/usr/bin/${_EDITOR_NAME}" \
"/snap/bin/${_EDITOR_NAME}"
do
# Accept commands, aliases, or executable absolute paths
if command -v "${EDITOR}" >/dev/null 2>&1 || [ -x "${EDITOR}" ]; then
# Default text editor for various command-line utilities
# (fallback if VISUAL is not set)
export EDITOR
# Default text editor for visual (full-screen) utilities
# (takes precedence over EDITOR)
export VISUAL="${EDITOR}"
# Specifies the editor to use with 'sudo -e' or 'sudoedit'
# (overrides VISUAL and EDITOR)
export SUDO_EDITOR="${EDITOR}"
# Specifies the editor for 'fc' command to edit and re-run
# commands from history (falls back to EDITOR)
export FCEDIT="${EDITOR}"
# Specifies a fallback editor for Emacs and its derivatives
# (Used when Emacs cannot start the primary editor defined by EDITOR)
export ALTERNATE_EDITOR="${EDITOR}"
# nnn default action for opening a file
# https://github.com/jarun/nnn
export NNN_OPENER="${EDITOR}"
break 2 # Found so exit both loops
fi
done
done
# Clean up
unset _EDITOR_NAME
# END_EDITOR_CONFIG
# We will default to use either Neovim https://neovim.io or vim instead of vi
# NOTE: vi is POSIX compliant but vim has more features and Neovim is more extensible
# http://www.viemu.com/a-why-vi-vim.html
# https://www.linuxfordevices.com/tutorials/linux/vim-vs-neovim
if hascommand --strict nvim; then
alias {v,vi,vim}='nvim'
alias svi='sudo nvim'
alias vis='nvim "+set si"'
elif hascommand --strict vim; then
alias {v,vi}='vim'
alias svi='sudo vim'
alias vis='vim "+set si"'
elif hascommand --strict vi; then
alias v='vi'
alias svi='sudo vi'
fi
# Set some defaults for nano
# NOTE: Depending on the version of nano you have, --linenumbers and --suspend is helpful
if hascommand --strict nano; then
alias {n,nano}='nano --smarthome --multibuffer --const --autoindent'
fi
# Set Micro editor true color support
# Link: https://micro-editor.github.io/
# Install: curl https://getmic.ro | bash
export MICRO_TRUECOLOR=1
# Create an alias for the Helix editor
# See hx --tutor or :tutor for a vimtutor-like introduction
# Link: https://github.com/helix-editor/helix
# Link: https://docs.helix-editor.com/title-page.html
if hascommand --strict helix; then
alias hx='helix'
fi
# Smart File Editor with Auto Privilege Management
# Uses the default editor or sudoedit for security based on file permissions
# It also provides visual feedback in color and even integrates with Tmux
# Syntax: edit [optional_filename]
alias e="edit"
function edit() {
# Local variable to track if the immutable attribute was modified
local IMMUTABLE_SET=""
# Track the current tmux tab name so restore_tab can reset it
local _CURRENT_TAB_NAME=""
# NOTE: The helper functions are defined inside this edit function to avoid
# cluttering the global namespace and tab completion. They are specific to
# the edit functionality and not intended to be used independently.
### Post-edit action function to handle specific file edits
# and reapply the immutable attribute if it was removed
function post_edit_action() {
# Convert the provided path to an absolute path
local ABSOLUTE_PATH=$(realpath "${1}" 2>/dev/null)
# Exit the function if the file does not exist
[[ -z "${ABSOLUTE_PATH}" ]] && return
# Use a case statement to match the filename with specific actions
case "${ABSOLUTE_PATH}" in
/etc/default/grub)
# Check if grub-mkconfig exists before updating grub configuration
if hascommand grub-mkconfig; then
if ask "${BRIGHT_YELLOW}Update grub configuration?${RESET}" Y; then
sudo grub-mkconfig -o /boot/grub/grub.cfg
echo -e "${BRIGHT_GREEN}Grub configuration updated${RESET}"
fi
fi
;;
# Apache Configuration
/etc/httpd/conf/httpd.conf|/etc/apache2/apache2.conf)
# Validate Apache Configuration
if hascommand apachectl; then
if apachectl configtest; then
if ask "${BRIGHT_YELLOW}Restart Apache to apply changes?${RESET}" Y; then
apacherestart
fi
else
echo -e "${BRIGHT_RED}Apache configuration test failed${RESET}"
fi
fi
;;
# Nginx Configuration
/etc/nginx/nginx.conf)
# Validate Nginx Configuration
if hascommand --strict nginx; then
if sudo nginx -t; then
if ask "${BRIGHT_YELLOW}Restart Nginx to apply changes?${RESET}" Y; then
ngrestart
fi
else
echo -e "${BRIGHT_RED}Nginx configuration test failed${RESET}"
fi
fi
;;
*php.ini|*/php/*/php.ini)
if hascommand --strict php; then
if ask "${BRIGHT_YELLOW}Restart web servers to apply PHP changes?${RESET}" Y; then
# Try to restart Apache and suppress errors
if hascommand --strict apachectl || hascommand --strict httpd || hascommand --strict apache2; then
apacherestart
fi
# Try to restart Nginx and suppress errors
if hascommand --strict nginx; then
ngrestart
fi
echo -e "${BRIGHT_GREEN}Attempted to restart web servers for PHP.ini changes${RESET}"
fi
fi
;;
/etc/ssh/sshd_config)
# Check if systemd is installed and systemctl is available
if hascommand --strict systemctl; then
if ask "${BRIGHT_YELLOW}Restart SSH service to apply changes?${RESET}" Y; then
# Directly execute the commands to restart and enable the SSH service
sudo systemctl restart sshd.service && sudo systemctl enable sshd.service
echo -e "${BRIGHT_GREEN}SSH service restarted${RESET}"
fi
fi
;;
/etc/vconsole.conf)
# Check if mkinitcpio exists before rebuilding the initial ramdisk environment
if hascommand --strict mkinitcpio; then
if ask "${BRIGHT_YELLOW}Rebuild initial ramdisk environment for vconsole?${RESET}" Y; then
sudo mkinitcpio -P
echo -e "${BRIGHT_GREEN}Initial ramdisk environment rebuilt${RESET}"
fi
fi
;;
"${HOME}/.bash_profile")
# If editing the .bash_profile file, reload it
if ask "${BRIGHT_YELLOW}Reload the new .bash_profile file?${RESET}" N; then
command clear
builtin source ~/.bash_profile
fi
;;
"${HOME}/.bashrc")
# If editing the .bashrc file, reload it
if ask "${BRIGHT_YELLOW}Reload the new .bashrc file?${RESET}" N; then
command clear
builtin source ~/.bashrc
fi
;;
"${HOME}/.tmux.conf"|"${XDG_CONFIG_HOME:-${HOME}/.config}/tmux/tmux.conf"|/etc/tmux.conf)
# If editing .tmux.conf, refresh tmux configuration
# Check if tmux is installed and running
if hascommand --strict tmux && tmux info &> /dev/null; then
tmux source-file "${1}"
echo -e "${BRIGHT_GREEN}Reloaded Tmux configuration from ${BRIGHT_MAGENTA}${1}${RESET}"
fi
;;
"${HOME}/.Xresources"|/etc/X11/Xresources)
# Check if xrdb exists and we are in X11 before merging .Xresources
if [[ $DISPLAY ]] && [[ $XDG_SESSION_TYPE == "x11" ]] && hascommand --strict xrdb; then
if ask "${BRIGHT_YELLOW}Merge X resources from ${ABSOLUTE_PATH}?${RESET}" Y; then
xrdb -merge "${1}"
echo -e "${BRIGHT_GREEN}Merged X resources from ${BRIGHT_MAGENTA}${1}${RESET}"
fi
fi
;;
# -- Post-edit file format validators (read-only, no restart needed) --
*.desktop)
# Validate .desktop file after editing
if hascommand desktop-file-validate; then
if desktop-file-validate "${1}"; then
echo -e "${BRIGHT_GREEN}Desktop file validation passed${RESET}"
else
echo -e "${BRIGHT_RED}Desktop file validation failed${RESET}"
fi
fi
;;
*.json)
# Validate JSON syntax after editing
if hascommand jq; then
if jq 'empty' "${1}" 2>/dev/null; then
echo -e "${BRIGHT_GREEN}JSON validation passed${RESET}"
else
echo -e "${BRIGHT_RED}JSON validation failed${RESET}"
# Show the actual error for context
jq 'empty' "${1}" 2>&1
fi
fi
;;
*.xml)
# Validate XML syntax after editing
if hascommand xmllint; then
if xmllint --noout "${1}" 2>/dev/null; then
echo -e "${BRIGHT_GREEN}XML validation passed${RESET}"
else
echo -e "${BRIGHT_RED}XML validation failed${RESET}"
# Show the actual error for context
xmllint --noout "${1}" 2>&1
fi
fi
;;
*.yaml|*.yml)
# Validate YAML syntax after editing
if hascommand yamllint; then
if yamllint -s "${1}" 2>/dev/null; then
echo -e "${BRIGHT_GREEN}YAML validation passed${RESET}"
else
echo -e "${BRIGHT_RED}YAML validation failed${RESET}"
# Show the actual errors for context
yamllint -s "${1}" 2>&1
fi
fi
;;
*.service|*.timer|*.socket|*.mount|*.path|*.target|*.slice)
# Validate systemd unit file after editing
if hascommand systemd-analyze; then
if systemd-analyze verify "${1}" 2>/dev/null; then
echo -e "${BRIGHT_GREEN}Systemd unit validation passed${RESET}"
else
echo -e "${BRIGHT_RED}Systemd unit validation failed${RESET}"
# Show the actual errors for context
systemd-analyze verify "${1}" 2>&1
fi
fi
;;
esac
}
### Helper function to use the more secure sudoedit only if it's possible
# since sudoedit cannot edit a file in a directory with write permissions
function sudo_edit() {
# Determine the directory containing the file (SC2155: split declaration)
local DIRNAME
DIRNAME=$(dirname "${1}")
[[ "${DIRNAME}" == "." ]] && DIRNAME="${PWD}"
# If inside a Tmux session, rename the tab temporarily
rename_tab "${1}"
# Pre-authenticate so the status message appears after any password prompt
if ! sudo -v; then
restore_tab
return 1
fi
# Show what we're about to edit or create
if [[ -f "${1}" ]]; then
echo -e "${BRIGHT_GREEN}Editing ${BRIGHT_CYAN}$(tilde_path "${1}")${RESET}"
else
echo -e "${BRIGHT_GREEN}Creating ${BRIGHT_CYAN}$(tilde_path "${1}")${RESET}"
fi
# Check if the directory is writable...
if [[ -w "${DIRNAME}" ]] || ! hascommand sudoedit; then
# Edit as root keeping environment variables (like default editor)
sudo --preserve-env "${EDITOR}" "${1}" && post_edit_action "${1}"
else
# Securely edit as root via temporary copy minimizing editor risks
sudoedit "${1}" && post_edit_action "${1}"
fi
# Restore the Tmux tab name
restore_tab
}
### Check and modify the immutable attribute of a file
# Parameters:
# $1 - The filename to check and potentially modify the immutable attribute
function immutable_remove() {
# Check if required commands exist
if ! hascommand chattr || ! hascommand lsattr; then
return
fi
# Check if the file and commands exists
if [[ -f "${1}" ]] && hascommand chattr && hascommand lsattr; then
# Extract file attributes using lsattr
local ATTRIBUTES=$(lsattr "${1}" 2>/dev/null | cut -d' ' -f1)
# Check if the immutable attribute is set (indicated by 'i')
if [[ "${ATTRIBUTES}" =~ i ]]; then
# Ask user if they want to temporarily remove the immutable attribute to allow editing
if ask "${BRIGHT_YELLOW}This file is immutable. Temporarily remove immutable attribute to edit?${RESET}" Y; then
# Try to remove the immutable attribute without using sudo first
if ! chattr -i "${1}" > /dev/null 2>&1; then
# If the command fails, inform the user and retry with sudo
sudo chattr -i "${1}" > /dev/null 2>&1
fi
# Set a flag to indicate the attribute was modified
IMMUTABLE_SET="true"
else
# If the user chooses not to edit the file, exit and show a message
echo -e "${BRIGHT_RED}Cannot edit the file without removing the immutable attribute.${RESET}"
# Exit the function with status 1 indicating an error
return 1
fi
fi
fi
}
### Reapply the immutable attribute if it was previously removed
function immutable_restore() {
# Check if the file and commands exists
if [[ -f "${1}" ]] && hascommand chattr; then
# If we changed the immutable attribute
if [[ "${IMMUTABLE_SET}" == "true" ]]; then
# Try to restore the immutable attribute without using sudo first
if ! chattr +i "${1}" > /dev/null 2>&1; then
# If the command fails, inform the user and retry with sudo
sudo chattr +i "${1}" > /dev/null 2>&1
fi
echo -e "${BRIGHT_GREEN}Immutable attribute reapplied to ${BRIGHT_MAGENTA}${1}${RESET}"
fi
fi
}
### Helper function to rename the current Tmux or console tab if supported
function rename_tab() {
# Get the simple editor name
local _EDITOR_SHORT_NAME=$(basename "${EDITOR%% *}")
# Get the filename (drop the path) for the new tab name
local _EDITING_FILENAME=$(basename "${1}")
# If in a Tmux session...
if [[ -n "${TMUX}" ]] && hascommand tmux; then
# Save the current tab text
_CURRENT_TAB_NAME=$(tmux display-message -p '#W')
# Rename the current tab
#tmux rename-window "✒️${_EDITING_FILENAME}"
tmux rename-window "${_EDITOR_SHORT_NAME}${_EDITING_FILENAME}"
# Check if we are in WezTerm
elif [[ "${TERM_PROGRAM}" == "WezTerm" ]]; then
# Rename the WezTerm tab
wezterm cli set-tab-title "${_EDITOR_SHORT_NAME}${_EDITING_FILENAME}"
# We are not in Tmux...
else
# Change the tab title if the terminal supports it
echo -ne "\033]2;${_EDITOR_SHORT_NAME}${_EDITING_FILENAME}\007"
echo -ne "\033]30;${_EDITOR_SHORT_NAME}${_EDITING_FILENAME}\007"
fi
}
### Helper function to restore the Tmux or Konsole tab name
function restore_tab() {
# If we are inside a Tmux session...
if [[ -n "${TMUX}" ]] && [[ -n "${_CURRENT_TAB_NAME}" ]] && hascommand tmux; then
# Restore Tmux tab name
tmux rename-window "${_CURRENT_TAB_NAME}"
unset _CURRENT_TAB_NAME
elif [[ "${TERM_PROGRAM}" == "WezTerm" ]]; then
# Restore WezTerm tab name
wezterm cli set-tab-title ""
# If we are in Konsole...
elif [[ -n "$KONSOLE_DBUS_SERVICE" ]]; then
# Reset the tab title to the default
echo -ne "\033]30;%d : %n\007"
# We are not in Tmux...
else
# This will reset the tab
echo -ne "\033]0;\007"
fi
}
### Replace home directory with ~ in displayed paths for privacy
function tilde_path() {
if [[ "${1}" == "${HOME}"* ]]; then
echo "~${1#"${HOME}"}"
else
echo "${1}"
fi
}
### Track whether the file path was already announced (e.g. by "Found")
local PATH_ANNOUNCED=""
### Check if the file is a symlink
local ACTUAL_FILE
if [[ -L "${1}" ]]; then
# Resolve the symlink to the actual file (SC2155: split declaration)
ACTUAL_FILE=$(resolvesymlink "${1}")
# Inform the user about the switch
echo -e "${BRIGHT_YELLOW}Editing actual file instead of symlink:${RESET} ${BRIGHT_CYAN}$(tilde_path "${ACTUAL_FILE}")${RESET}"
else
# If not a symlink, proceed with the original file
ACTUAL_FILE="${1}"
fi
### If it does not contain a path and is not a file
if [[ "$ACTUAL_FILE" != */* && ! -f "$ACTUAL_FILE" ]]; then
# Try to locate the full path using `which`
local FOUND_PATH=$(command which "$ACTUAL_FILE" 2>/dev/null)
# Update ACTUAL_FILE if a path was found
if [[ -n "$FOUND_PATH" ]]; then
ACTUAL_FILE="$FOUND_PATH"
echo -e "${BRIGHT_GREEN}Found ${BRIGHT_CYAN}$(tilde_path "${ACTUAL_FILE}")${RESET}"
PATH_ANNOUNCED="true"
fi
fi
### Check for special case of editing /etc/sudoers...
if [[ "${ACTUAL_FILE}" == "/etc/sudoers" ]] && hascommand visudo; then
echo -ne "${BRIGHT_RED}Security alert:"
echo -e "${BRIGHT_YELLOW} Using visudo to edit ${BRIGHT_CYAN}${ACTUAL_FILE}${RESET}"
if ! immutable_remove "${1}"; then
# For some reason, we are unable to remove the immutable attribute
return 1
fi
rename_tab "${ACTUAL_FILE}"
sudo visudo
restore_tab
immutable_restore "${1}"
return
### Check if EDITOR is set...
elif [ -z "${EDITOR}" ]; then
echo -ne "${BRIGHT_RED}Error: ${BRIGHT_CYAN}EDITOR environment variable is not set.${RESET}"
return 1
### No parameters passed, load the default editor...
elif [[ $# -eq 0 ]]; then
rename_tab 'New File'
"${EDITOR}"
restore_tab
return
### Check if file exists and has read/write permissions...
elif [[ -r "${ACTUAL_FILE}" ]] && [[ -w "${ACTUAL_FILE}" ]]; then
immutable_remove "${1}"
rename_tab "${ACTUAL_FILE}"
# Get the initial modification time of the file
# Use platform-appropriate stat flag (GNU vs BSD/macOS)
local _STAT_FMT='-c %Y'
[[ "$(uname)" == "Darwin" ]] && _STAT_FMT='-f %m'
local INITIAL_MTIME=""
if [[ -f "${ACTUAL_FILE}" ]]; then
INITIAL_MTIME=$(stat ${_STAT_FMT} "${ACTUAL_FILE}")
fi
# Show the file being edited (skip if already announced by "Found")
if [[ -z "${PATH_ANNOUNCED}" ]]; then
echo -e "${BRIGHT_GREEN}Editing ${BRIGHT_CYAN}$(tilde_path "${ACTUAL_FILE}")${RESET}"
fi
# Capture the exit status of the editor
"${EDITOR}" "${ACTUAL_FILE}"
local EDIT_STATUS=$?
# Check if the file was modified based on the modification time
if [[ -f "${ACTUAL_FILE}" ]]; then
if [[ "$(stat ${_STAT_FMT} "${ACTUAL_FILE}")" != "${INITIAL_MTIME}" ]]; then
post_edit_action "${ACTUAL_FILE}"
fi
else
post_edit_action "${ACTUAL_FILE}"
fi
restore_tab
immutable_restore "${1}"
return $EDIT_STATUS
### Check if the file exists but doesn't have write permission...
elif [[ -f "${ACTUAL_FILE}" ]]; then
echo -ne "${BRIGHT_RED}Insufficient permissions:"
echo -e "${BRIGHT_YELLOW} Using super user to edit ${BRIGHT_CYAN}${ACTUAL_FILE}${RESET}"
immutable_remove "${1}"
sudo_edit "${ACTUAL_FILE}"
immutable_restore "${1}"
return
### File doesn't exist, check if we can create it...
elif [[ -w "$(dirname "${ACTUAL_FILE}")" ]]; then
echo -e "${BRIGHT_GREEN}Creating ${BRIGHT_CYAN}$(tilde_path "${ACTUAL_FILE}")${RESET}"
# Attempt to edit the file
rename_tab "${ACTUAL_FILE}"
if "${EDITOR}" "${ACTUAL_FILE}"; then
return
else # There was an error...
# Retry with sudo_edit
restore_tab
echo -ne "${BRIGHT_RED}Insufficient permissions:"
echo -e "${BRIGHT_YELLOW} Retrying with super user to edit ${BRIGHT_CYAN}${ACTUAL_FILE}${RESET}"
immutable_remove "${1}"
sudo_edit "${ACTUAL_FILE}"
immutable_restore "${1}"
return
fi
### We need super user access to create the new file
else
echo -ne "${BRIGHT_RED}Insufficient permissions:"
echo -e "${BRIGHT_YELLOW} Using super user to create ${BRIGHT_CYAN}${ACTUAL_FILE}${RESET}"
sudo_edit "${ACTUAL_FILE}"
return
fi
}
# Command-line completion for the edit command
_edit_completion() {
local cur prev words cword
_init_completion -n = || return
case $prev in
edit|e)
_filedir
return
;;
esac
}
complete -F _edit_completion edit e
# Shortcut for edit using root permissions
alias se="sudoedit"
#######################################################
# Set default variable values
# These variables can be overridden in one of these environment variable files:
# ~/.env
# ~/.envrc
# ~/.config/bashrc/config
#######################################################
# BEGIN_BASHRC_CONFIG
# Determines if CTRL-h will show help
# Ctrl+h (for help) and Ctrl+Backspace share the same key binding
# in some terminal emulators so we default to skip this keybind
_SKIP_HELP_KEYBIND=true
# Show an installed information HUD on initial Bash load (if not skipped)
# Link: https://github.com/LinusDierheimer/fastfetch
# Link: https://ostechnix.com/neofetch-display-linux-systems-information/
# Link: https://github.com/KittyKatt/screenFetch
# Link: https://github.com/deater/linux_logo
# Link: https://github.com/dylanaraps/pfetch
_SKIP_SYSTEM_INFO=false
# If not skipped, shows pending updates (only in Arch, Manjaro, and Ubuntu)
# WARNING: This check for updates takes several seconds so the default is true
_SKIP_UPGRADE_NOTIFY=true
# Automatically launch TMUX terminal multiplexer in local, TTY, or SSH sessions
# https://github.com/tmux/tmux/wiki
# Since TMUX is pre-installed on so many systems, the default is to skip TMUX
_TMUX_LOAD_TTY=false
_TMUX_LOAD_SSH=false
_TMUX_LOAD_LOCAL=false
# OPTIONAL: Set and force the default TMUX session name for this script and tm
# If not specified, an active TMUX session is used and attached to
# If no active TMUX session exists, the current logged in user name is used
#_TMUX_LOAD_SESSION_NAME=""
# Terminology is a graphical EFL terminal emulator that can run in TTY sessions
# If installed, it can automatically be launched when starting a TTY session
# To split the window horizontally press Ctrl+Shift+PgUp
# To split the window vertically press Ctrl+Shift+PgDn
# To create Tabs press Ctrl+Shift+T and cycle through using Ctrl+1-9
# Link: https://github.com/borisfaure/terminology
# Link: https://linoxide.com/terminology-terminal/
_SKIP_TERMINOLOGY_TTY=false
# Blesh: Bash Line Editor replaces default GNU Readline
# Link: https://github.com/akinomyoga/ble.sh
# Link for configuration: https://github.com/akinomyoga/ble.sh/blob/master/blerc
# WARNING: Can be buggy with certain prompts (like Trueline)
_SKIP_BLESH=false
# Make sure the default file and directory permissions for newly created files
# in the home directory is umask 026 to improve security.
# (user=read/write/execute, group=read/execute, others=execute for directories)
# The default is to skip this security setting and not modify home permissions
_SKIP_UMASK_HOME=true
# Replaces Sudo with one of the two alternatives (if installed):
# RootDO (rdo) - A very slim alternative to both sudo and doas
# Link: https://codeberg.org/sw1tchbl4d3/rdo
# - OR -
# A port of OpenBSD's doas offers two benefits over sudo:
# 1) Its configuration file has a simple syntax and
# 2) It is smaller, requiring less effort to audit the code
# Link: https://github.com/Duncaen/OpenDoas or https://github.com/slicer69/doas
# Default value is skip and must be set to false manually for security reasons
_SKIP_SUDO_ALTERNATIVE=true
# If set to true, cd will not output the current absolute path under certain
# circumstances like when using the command cd - or using cdable_vars bookmarks
# Link: https://www.gnu.org/software/bash/manual/bash.html#index-cd
_SILENCE_CD_OUTPUT=false
# If set to true, will not load anything that modifies the ls command or colors
_SKIP_LS_COLORIZED=false
# LSD (LSDeluxe) is a rewrite of GNU ls with lots of added features like
# colors, icons, tree-view, more formatting options, git support, etc.
# Fonts: Install the patched fonts of powerline, nerd-font, and/or font-awesome
# Link: https://github.com/Peltoche/lsd
_SKIP_LSD=false
# eza/exa is a modern color replacement for ls that also has some Git support
# Link: https://github.com/eza-community/eza
# Link: https://github.com/ogham/exa
_SKIP_EXA=false
# grc Generic Colouriser
# Link: https://github.com/garabik/grc
_SKIP_GRC=false
# Use built-in aliases for grc Generic Colouriser instead of it's own includes
_GRC_USE_BASHRC_BUILTIN=false
# Choose your preferred picker to use with menus
# You can choose any picker like fzy, sk, fzf, peco, percol, pick, icepick,
# selecta, sentaku, zf, or even dmenu, rofi, or wofi UI pickers
_PREFERRED_PICKER=
# If set to true, will not source bash completion scripts
_SKIP_BASH_COMPLETION=false
# If set to true, will show a calendar when Bash is started
_SHOW_BASH_CALENDAR=false
# If GNU gcal is installed, use this local for holidays
# To show the possible options type: gcal -hh | grep 'Holidays in'
# Link: https://www.gnu.org/software/gcal/manual/gcal.html
# Link: https://unix.stackexchange.com/questions/164555/how-to-emphasize-holidays-by-color-in-cal-command
_GCAL_COUNTRY_CODE=US_AK
# Skip the birthday/anniversary reminder that shows a message in your teminal?
# Reads the birthday CSV file: ~/.config/birthdays.csv
# The first line is ignored (header) and the format is (year is optional):
# Month,Day,Year,"Message"
# Jan,1,1985,"This is a message!"
#
# Figlet and/or Toilet application is an optional dependency
# Install Arch/Manjaro: sudo pacman -S toilet
# Install Ubuntu/Debian: sudo apt-get install toilet
_SKIP_BDAY_REMINDER=false
# Set the location for the birthday/anniversary reminder CSV file
# The default location is "~/.config/birthdays.csv"
_BDAY_FILE="${XDG_CONFIG_HOME:-${HOME}/.config}/birthdays.csv"
# Set the preferred birthday reminder font here (default is "future"):
_BDAY_FONT=future
# Set to have the built in prompt use a faster but less precise Git method
# This might be necessary on slow connections or networked directories
# Also if set to true, will remove eza/exa's --git flag (use lsg for Git info)
_GIT_IS_SLOW=false
# Optional original prompt from 2014 version now with newly added Git support
# download the optional .bashrc_prompt script file and place it in either your
# home directory or as the file ~/.config/bashrc/prompt
# You will also need to make sure this setting is set to false
_SKIP_PROMPT_ORIGINAL=false
# If false, the built-in prompt will be one single line with an abbreviated path
# If true, the built-in prompt will split into two lines with a full path
_PROMPT_BUILTIN_FULL_PATH=false
# Trueline Bash (true 24-bit color and glyph support)
# This is the prefered prompt since it looks amazing,
# has so many features, is easily extended using functions,
# and is a single Bash script file that is easy to install.
# Link: https://github.com/petobens/trueline
# Install: wget https://raw.githubusercontent.com/petobens/trueline/master/trueline.sh -P ~/
# Fonts: https://github.com/powerline/fonts
_SKIP_PROMPT_TRUELINE=false
# Powerline-Go (this prompt uses no special glyphs)
# Link: https://github.com/justjanne/powerline-go
_SKIP_PROMPT_POWERLINE_GO=false
# Powerline-Shell (details about git/svn/hg/fossil branch and Python virtualenv environment)
# Link: https://github.com/b-ryan/powerline-shell
_SKIP_PROMPT_POWERLINE_SHELL=false
# Pureline (256 color written in bash script)
# Link: https://github.com/chris-marsh/pureline
# Install:
# git clone https://github.com/chris-marsh/pureline.git
# cp pureline/configs/powerline_full_256col.conf ~/.pureline.conf
_SKIP_PROMPT_PURELINE=false
# Starship Cross Shell Prompt (focus on compatibility and written in Rust)
# Link: https://starship.rs
# Install: sh -c "$(curl -fsSL https://starship.rs/install.sh)"
_SKIP_PROMPT_STARSHIP=false
# Oh-My-Git (only used for Git but has huge support for it, requires font)
# Link: https://github.com/arialdomartini/oh-my-git
# Install: git clone https://github.com/arialdomartini/oh-my-git.git ~/.oh-my-git
_SKIP_PROMPT_OH_MY_GIT=false
# Bash Git Prompt (shows git repository, branch name, difference with remote branch, number of files staged, changed, etc)
# Link: https://github.com/magicmonty/bash-git-prompt
# Install: git clone https://github.com/magicmonty/bash-git-prompt.git ~/.bash-git-prompt --depth=1
_SKIP_PROMPT_BASH_GIT_PROMPT=false
# Bash Powerline (no need for patched fonts, supports git, previous command execution status, platform-dependent prompt symbols)
# Link: https://github.com/riobard/bash-powerline
# Install: curl https://raw.githubusercontent.com/riobard/bash-powerline/master/bash-powerline.sh > ~/.bash-powerline.sh
_SKIP_PROMPT_BASH_POWERLINE=false
# Sexy Bash Prompt (supports git, 256 color)
# Link: https://github.com/twolfson/sexy-bash-prompt
# Install: (cd /tmp && ([[ -d sexy-bash-prompt ]] || git clone --depth 1 --config core.autocrlf=false https://github.com/twolfson/sexy-bash-prompt) && cd sexy-bash-prompt && make install)
_SKIP_PROMPT_SEXY_BASH_PROMPT=false
# Liquid Prompt (adaptive prompt with low color and no glyphs)
# Link: https://github.com/nojhan/liquidprompt
# Install: git clone --branch stable https://github.com/nojhan/liquidprompt.git ~/liquidprompt
_SKIP_PROMPT_LIQUIDPROMPT=false
# Original Powerline Status Line for Vim Bash Zsh fish tmux IPython Awesome i3 Qtile
# Link: https://github.com/powerline/powerline
# Install: https://medium.com/earlybyte/powerline-for-bash-6d3dd004f6fc
# NOTE: Requires Python and can be used with Trueline in Bash
# WARNING: This path may change or break in the future with new Python versions
_SKIP_PROMPT_POWERLINE=false
# Recognize the depreciated _PROMPT_BUILTIN_FASTER_GIT option
if [[ ${_PROMPT_BUILTIN_FASTER_GIT} = true ]]; then
_GIT_IS_SLOW=true
fi
# END_BASHRC_CONFIG
# Determine our kernel name
_KERNEL_NAME=$(printf '%.5s' "$(command uname -s)")
#######################################################
# Add Common Binary Directories to Path
#######################################################
# Add directories to the end of the path if they exist and are not already in the path
# Link: https://superuser.com/questions/39751/add-directory-to-path-if-its-not-already-there
function pathappend() {
for ARG in "$@"; do
if [[ -d "${ARG}" ]] && [[ ":${PATH}:" != *":${ARG}:"* ]]; then
PATH="${PATH:+"${PATH}:"}${ARG}"
fi
done
}
# Add directories to the beginning of the path if they exist and are not already in the path
function pathprepend() {
for ((i = $#; i > 0; i--)); do
ARG="${!i}"
if [[ -d "${ARG}" ]] && [[ ":${PATH}:" != *":${ARG}:"* ]]; then
PATH="${ARG}${PATH:+":${PATH}"}"
fi
done
}
# Append to an existing alias or otherwise create the new alias
aliasappend() {
# Display help if less than two arguments are provided
if [[ "$#" -lt 2 ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}aliasappend${RESET}: Append an argument to an existing alias or create a new one"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}aliasappend${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}alias_name${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}argument${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}aliasappend${RESET} ${BRIGHT_YELLOW}grep '--color=auto'${RESET}"
return 2
fi
if alias "${1}" &>/dev/null; then
# Extract the alias value, ensuring it is correctly handled
local ALIAS_VALUE=$(alias "${1}" | sed -E "s/^alias ${1}='(.*)'$/\1/")
# Check if the alias already contains the argument we want to append
if [[ "${ALIAS_VALUE}" != *"${2}"* ]]; then
# Correctly append the argument and redefine the alias with proper quoting
alias "${1}"="${ALIAS_VALUE} ${2}"
fi
else
# If the command is not an alias, create a new one with the provided argument
alias "${1}"="${1} ${2}"
fi
}
# Add the most common personal binary paths located inside the home folder
# (these directories are only added if they exist)
pathprepend "${HOME}/bin" "${HOME}/sbin" "${HOME}/.local/bin" "${HOME}/local/bin" "${HOME}/.bin"
# Check for the Rust package manager binary install location
# Link: https://doc.rust-lang.org/cargo/index.html
pathappend "${HOME}/.cargo/bin" "/root/.cargo/bin" "${HOME}/go/bin"
# If the GOPATH environment variable is not set and the go command exists...
if hascommand --strict go && [[ -z ${GOPATH+x} ]] && [[ -d "${HOME}/go" ]]; then
# Set GOPATH to the default directory
export GOPATH="${HOME}/go"
fi
#######################################################
# User Specific Environment Variables
#######################################################
if [[ -f "${HOME}/.envrc" ]]; then
builtin source "${HOME}/.envrc"
fi
if [[ -f "${HOME}/.env" ]]; then
builtin source "${HOME}/.env"
fi
# Where the framework files are installed
# AUR/opt installs set this externally; standalone installs use the XDG default
BASHRC_INSTALL_DIR="${BASHRC_INSTALL_DIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/bashrc}"
# If installed via package manager but not yet configured, nudge the user
if [[ "${BASHRC_INSTALL_DIR}" == "/opt/"* ]] && [[ ! -f "${HOME}/.config/bashrc/.installed" ]]; then
echo ""
echo "Extreme Ultimate .bashrc is installed but not yet configured."
echo "Run: setup-bashrc"
echo ""
echo "To restore your original .bashrc instead:"
echo " mv ~/.bashrc.backup.* ~/.bashrc"
echo ""
fi
if [[ -f "${BASHRC_INSTALL_DIR}/config" ]]; then
builtin source "${BASHRC_INSTALL_DIR}/config"
fi
#######################################################
# Bashrc Support
#######################################################
# Searches for text inside within the ~/.bashrc file
function findbashrc() {
# Display help if no arguments are provided
if [[ "$#" -eq 0 ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}findbashrc${RESET}: Search for text inside ~/.bashrc"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}findbashrc${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}pattern${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}pattern2${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}...${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}findbashrc${RESET} ${BRIGHT_YELLOW}'alias'${RESET} ${BRIGHT_BLUE}# Find all alias lines${RESET}"
echo -e " ${BRIGHT_CYAN}findbashrc${RESET} ${BRIGHT_YELLOW}'function' 'edit'${RESET} ${BRIGHT_BLUE}# Filter by multiple patterns${RESET}"
return 2
fi
# Start with the initial grep with a case-insensitive search
local RESULT="$(grep --color=always -n -i -e "$1" "${HOME}/.bashrc")"
# Loop through all additional arguments to further filter the results
local ARGUMENT
for ARGUMENT in "${@:2}"; do
RESULT="$(echo "${RESULT}" | grep --color=always -i -e "${ARGUMENT}")"
done
# Output the results
if [ -z "${RESULT}" ]; then
echo -e "${BRIGHT_RED}Error:${BRIGHT_CYAN} No matches found for specified patterns${RESET}"
return 1
else
echo "${RESULT}"
fi
}
# Opens ~/.bashrc in the default editor or searches it for provided patterns
alias ebrc='editbashrc'
function editbashrc() {
if [ $# -eq 0 ]; then
# If no arguments, open ~/.bashrc with the default editor
edit "${HOME}/.bashrc"
else
# Call findbashrc with all arguments passed to editbashrc
findbashrc "$@"
fi
}
# Change into the bashrc directory
function bashrc() {
if [[ $# -eq 0 ]]; then
# Set the target directory
local TARGET_DIR="${BASHRC_INSTALL_DIR}"
# Check if target directory exists
if [[ -d "${TARGET_DIR}" ]]; then
# Check for the presence of specific files or a non-empty subdirectory
if [[ -f "${TARGET_DIR}/aliases" ]] || \
[[ -f "${TARGET_DIR}/bashhelp" ]] || \
[[ -f "${TARGET_DIR}/config" ]] || \
[[ -f "${TARGET_DIR}/help" ]] || \
[[ -f "${TARGET_DIR}/README.md" ]] || \
[[ -f "${TARGET_DIR}/README.html" ]] || \
( [[ -d "${TARGET_DIR}/bashrc.d" ]] && \
[[ "$(ls -A "${TARGET_DIR}/bashrc.d")" ]] ); then
cd "${TARGET_DIR}"
else
cd "${HOME}"
fi
else
cd "${HOME}"
fi
else
# Call findbashrc with all passed parameters
findbashrc "$@"
fi
}
# Create an alias to reload this .bashrc file
alias {rbrc,reloadbashrc,bashrcreload}='command clear; builtin source ~/.bashrc'
# Find the help file for this .bashrc file (type hlp or press CONTROL-H)
if [[ -f "${BASHRC_INSTALL_DIR}/help" ]]; then
_BASHRC_HELP="${BASHRC_INSTALL_DIR}/help"
if [[ ${_SKIP_HELP_KEYBIND} == false ]]; then
bind -x '"\C-h":"less -f -r -n -S \"${_BASHRC_HELP}\""'
fi
elif [[ -f "${HOME}/.bashrc_help" ]]; then
_BASHRC_HELP="${HOME}/.bashrc_help"
if [[ ${_SKIP_HELP_KEYBIND} == false ]]; then
bind -x '"\C-h":"less -f -r -n -S \"${_BASHRC_HELP}\""'
fi
fi
# Alias to edit the global bashrc if it exists using the same aliases upper case
if [[ -f "/etc/bash.bashrc" ]]; then
alias {EBRC,EDITBASHRC}='sudoedit /etc/bash.bashrc'
fi
# Alias to show the help file
alias hlp='command less -f -r -n -S "${_BASHRC_HELP}"'
# Exports a new config file for bashrc with environment variable settings
function bashrcnewconfig() {
# Help message remains the same
if [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}bashrcnewconfig${RESET}: Export a new config file with environment variable settings"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}bashrcnewconfig${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}--test${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Options:${RESET}"
echo -e " ${BRIGHT_GREEN}--test${RESET}, ${BRIGHT_GREEN}-t${RESET} Output config to stdout instead of file"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}bashrcnewconfig${RESET} ${BRIGHT_BLUE}# Write to config file${RESET}"
echo -e " ${BRIGHT_CYAN}bashrcnewconfig${RESET} ${BRIGHT_GREEN}--test${RESET} ${BRIGHT_BLUE}# Preview in terminal${RESET}"
return 0
fi
# Determine if we're testing (outputting to stdout) or writing to file
local OUTPUT_TO_FILE=true
if [[ "$1" == "--test" ]] || [[ "$1" == "-t" ]] || [[ "$1" == "--testing" ]]; then
OUTPUT_TO_FILE=false
fi
# Determine config path (only needed if writing to file)
local CONFIG_PATH
if ${OUTPUT_TO_FILE}; then
if [[ -f "${HOME}/.envrc" ]]; then
CONFIG_PATH="${HOME}/.envrc"
elif [[ -f "${HOME}/.env" ]]; then
CONFIG_PATH="${HOME}/.env"
else
mkdir -p "${BASHRC_INSTALL_DIR}"
CONFIG_PATH="${BASHRC_INSTALL_DIR}/config"
fi
# Check if file exists and prompt for overwrite
if [[ -f "${CONFIG_PATH}" ]]; then
if ! ask "File ${CONFIG_PATH} already exists. Overwrite?" "N"; then
echo "Operation cancelled."
return 1
fi
fi
fi
# Function to write content (either to file or stdout)
write_content() {
if ${OUTPUT_TO_FILE}; then
cat > "${CONFIG_PATH}"
else
cat
fi
}
# Generate the content
{
cat << 'EOL'
#!/usr/bin/env bash
#######################################################
# Extreme Ultimate .bashrc Environment Variables
# ~/.env
# ~/.envrc
# ~/.config/bashrc/config
# NOTE: Type env to see a list of set variables
#######################################################
# Alias to edit this file
alias ebe="edit ${BASH_SOURCE}"
#######################################################
# Set/override the default editor
# Examples: vim, nvim, emacs, nano, micro, helix, pico
# or gui apps like kate, geany, gedit, notepadqq, or vscodium
# NOTE: In Git Bash, you can use something like "/c/Program\ Files/Notepad++/notepad++.exe"
#######################################################
EOL
# Extract and modify editor config section (between sentinel markers)
sed -n '/^# BEGIN_EDITOR_CONFIG$/,/^# END_EDITOR_CONFIG$/{//d;p;}' "${BASH_SOURCE}" | sed 's/^[[:space:]]*//' | while IFS= read -r LINE; do
if [[ "${LINE}" == *"export EDITOR"* ]] && [[ "${LINE}" != *"="* ]]; then
echo '# export EDITOR="nano"'
elif [[ "${LINE}" == *"export"* ]]; then
echo "# ${LINE}"
else
echo "${LINE}"
fi
done
# Add separator
echo "#######################################################"
echo "# Extreme Ultimate .bashrc Configuration"
echo "#######################################################"
# Extract and modify the config section (between sentinel markers)
sed -n '/^# BEGIN_BASHRC_CONFIG$/,/^# END_BASHRC_CONFIG$/{//d;p;}' "${BASH_SOURCE}" | while IFS= read -r LINE; do
if [[ "${LINE}" == "_BDAY_FILE="* ]]; then
echo "# _BDAY_FILE="
else
echo "${LINE}"
fi
done
} | write_content
if ${OUTPUT_TO_FILE}; then
echo "Configuration exported successfully to: ${CONFIG_PATH}"
fi
}
#######################################################
# Use these commands to keep the .bashrc immutable and write protected
# even from root so that other scripts and applications can't change it
#######################################################
alias bashrcprotect="sudo chattr +i ${HOME}/.bashrc"
alias bashrcunprotect="sudo chattr -i ${HOME}/.bashrc"
alias bashrccheckprotect='if [[ $(lsattr -R -l ~/.bashrc | grep " Immutable") ]]; then echo "Protected"; else echo "Not Protected"; fi;'
#######################################################
# Bashrc Updates
#######################################################
# Update the main .bashrc file and supporting files
alias bashrcupdateforce="download 'https://sourceforge.net/projects/ultimate-bashrc/files/_bashrc/download' '${HOME}' && command mv --interactive '${HOME}/_bashrc' '${HOME}/.bashrc'"
function bashrcupdate() {
# If installed via package manager, redirect to the package manager
if [[ "${BASHRC_INSTALL_DIR}" == "/opt/"* ]]; then
echo -e "${BRIGHT_YELLOW}This installation is managed by your package manager.${RESET}"
if hascommand yay; then
echo -e "${BRIGHT_YELLOW}Run: ${BRIGHT_CYAN}yay -Syu extreme-ultimate-bashrc${RESET}"
elif hascommand paru; then
echo -e "${BRIGHT_YELLOW}Run: ${BRIGHT_CYAN}paru -Syu extreme-ultimate-bashrc${RESET}"
else
echo -e "${BRIGHT_YELLOW}Run: ${BRIGHT_CYAN}pacman -Syu extreme-ultimate-bashrc${RESET}"
fi
return 0
fi
# Files to update
declare -A FILES_TO_UPDATE=(
["${HOME}/.bashrc"]="https://sourceforge.net/projects/ultimate-bashrc/files/_bashrc/download"
["${HOME}/.bashrc_help"]="https://sourceforge.net/projects/ultimate-bashrc/files/_bashrc_help/download"
["${BASHRC_INSTALL_DIR}/help"]="https://sourceforge.net/projects/ultimate-bashrc/files/_bashrc_help/download"
["${HOME}/README.md"]="https://sourceforge.net/projects/ultimate-bashrc/files/README.md/download"
["${BASHRC_INSTALL_DIR}/README.md"]="https://sourceforge.net/projects/ultimate-bashrc/files/README.md/download"
["${HOME}/README.html"]="https://sourceforge.net/projects/ultimate-bashrc/files/README.html/download"
["${BASHRC_INSTALL_DIR}/README.html"]="https://sourceforge.net/projects/ultimate-bashrc/files/README.html/download"
)
if ! hascommand axel && ! hascommand aria2c && ! hascommand curl && ! hascommand wget; then
if hascommand --strict xdg-open; then
xdg-open "https://sourceforge.net/projects/ultimate-bashrc/files/" > /dev/null 2>&1 & disown
elif hascommand --strict open; then # For macOS
open "https://sourceforge.net/projects/ultimate-bashrc/files/" > /dev/null 2>&1 & disown
elif hascommand --strict start; then # For Windows
start "" "https://sourceforge.net/projects/ultimate-bashrc/files/"
else
echo -e "${BRIGHT_YELLOW}Please install either wget, curl, aria2, or visit ${BRIGHT_CYAN}https://sourceforge.net/projects/ultimate-bashrc/${BRIGHT_YELLOW} to update.${RESET}"
fi
return
fi
# Backup the existing ~/.bashrc with a time date stamp
local BASHRC_BACKUP_FILE="${HOME}/.bashrc.$(date +"%Y-%m-%d_%H-%M-%S").backup"
echo -e "${BRIGHT_YELLOW}Backing up your .bashrc file to ${BRIGHT_CYAN}${BASHRC_BACKUP_FILE}${RESET}"
command cp "${HOME}/.bashrc" "${BASHRC_BACKUP_FILE}" || {
echo -e "${BRIGHT_RED}Backup of .bashrc failed. Please check your permissions and disk space.${RESET}"
return 1
}
# Create a temporary directory for downloads
# NOTE: Using _UPDATE_TMPDIR to avoid shadowing the global $TMPDIR env var
local _UPDATE_TMPDIR=$(mktemp -d)
if [[ ! -d "${_UPDATE_TMPDIR}" ]] || [[ -z "${_UPDATE_TMPDIR}" ]]; then
echo -e "${BRIGHT_RED}Failed to create a temporary directory. Check your system's temporary storage settings.${RESET}"
return 1
fi
# Use RETURN trap (not EXIT) so cleanup happens when the function returns,
# not when the entire shell exits (when local vars are out of scope)
trap 'rm -rf "${_UPDATE_TMPDIR}"' RETURN
# Loop through each file
for FILE in "${!FILES_TO_UPDATE[@]}"; do
# Get the download URL
local URL="${FILES_TO_UPDATE[${FILE}]}"
# If the file exists or it's the .bashrc which we will always update...
if [[ -f "${FILE}" ]] || [[ "$(command basename "${FILE}")" == ".bashrc" ]]; then
# Remove immutable attribute if set
local IS_IMMUTABLE=false
if hascommand lsattr && hascommand chattr && command lsattr -R -l "${FILE}" 2>/dev/null | command grep -q "Immutable"; then
echo -e "${BRIGHT_YELLOW}Removing immutable attribute from ${FILE}${RESET}"
sudo chattr -i "${FILE}"
IS_IMMUTABLE=true
fi
# Ensure temporary directory is empty
command rm -f "${_UPDATE_TMPDIR}"/*
# Download to temporary directory
echo "Updating ${FILE}"
download "${URL}" "${_UPDATE_TMPDIR}" || {
echo -e "${BRIGHT_RED}Failed to download ${FILE}${RESET}"
continue
}
# Now find the file that was just downloaded
local DOWNLOADED_FILE=$(command find "${_UPDATE_TMPDIR}" -type f -exec ls -1t {} + | command head -n1)
# Move downloaded file to the desired location
if [[ -f "${DOWNLOADED_FILE}" ]]; then
command mv "${DOWNLOADED_FILE}" "${FILE}"
echo -e "${BRIGHT_GREEN}Updated ${FILE} successfully${RESET}"
else
echo -e "${BRIGHT_RED}Download failed or file not found for ${FILE}${RESET}"
fi
# If this is the .bashrc file, make sure it does not contain errors!
if [[ "$(command basename "${FILE}")" == ".bashrc" ]]; then
bash -n "${HOME}/.bashrc"
if [ $? -ne 0 ]; then
echo -e "${BRIGHT_RED}New .bashrc contains errors, reverting backup.${RESET}"
command mv "${BASHRC_BACKUP_FILE}" "${HOME}/.bashrc"
return 1
fi
fi
# Restore the immutable flag if it was removed
if [[ "${IS_IMMUTABLE}" == true ]]; then
echo -e "${BRIGHT_YELLOW}Restoring immutable attribute to ${FILE}${RESET}"
sudo chattr +i "${FILE}"
fi
fi
done
echo -e "${BRIGHT_GREEN}Done!${RESET} ${BRIGHT_MAGENTA}Restart your terminal to see the changes.${RESET}"
}
#######################################################
# Change the default file and directory permissions for newly created files
# in the home directory for security (also see fixuserhome function)
#######################################################
# Only if we are not the root user...
if [[ ${_SKIP_UMASK_HOME} == false ]] && [[ ${EUID} -ne 0 ]]; then
# Set to umask 027 while leaving execute for directories
# - Grant full permissions to the user (owner)
# - Grant read and execute permissions to the group
# - Remove all permissions for others
umask u=rwx,g=rx,o=
fi
#######################################################
# General Aliases
# NOTE: To temporarily bypass an alias, we proceed the command with a \
# EG: if the ls command is aliased, to use the normal command you would type \ls
#######################################################
# Find text in man pages
alias findman='apropos'
# Find environment variables
alias findenv='printenv | grep -i'
# List available aliases with optional filter parameter
function findalias() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}findalias${RESET}: Search through defined aliases"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}findalias${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}pattern${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}findalias${RESET} ${BRIGHT_BLUE}# List all aliases${RESET}"
echo -e " ${BRIGHT_CYAN}findalias${RESET} ${BRIGHT_GREEN}git${RESET} ${BRIGHT_BLUE}# Find git-related aliases${RESET}"
echo -e " ${BRIGHT_CYAN}findalias${RESET} ${BRIGHT_GREEN}'ls|ll'${RESET} ${BRIGHT_BLUE}# Regex pattern${RESET}"
return 0
fi
# Assign the first argument to FILTER for filtering the output
local FILTER="${1}"
# Print the section heading for aliases
echo -e "${BRIGHT_RED}Aliases:${RESET}"
# List all aliases, format and color their output, then apply the filter
alias | awk -F'[ =]' '{print "\033[33m"$2"\033[0m\t\033[34m"$0"\033[0m";}' | grep -E "${FILTER}"
}
# List available functions with optional filter parameter
function findfunction() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}findfunction${RESET}: Search through defined functions"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}findfunction${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}pattern${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}findfunction${RESET} ${BRIGHT_BLUE}# List all functions${RESET}"
echo -e " ${BRIGHT_CYAN}findfunction${RESET} ${BRIGHT_GREEN}git${RESET} ${BRIGHT_BLUE}# Find git-related functions${RESET}"
echo -e " ${BRIGHT_CYAN}findfunction${RESET} ${BRIGHT_GREEN}find${RESET} ${BRIGHT_BLUE}# Find search functions${RESET}"
return 0
fi
# Assign the first argument to FILTER for filtering the output
local FILTER="${1}"
# Print the section heading for functions
echo -e "${BRIGHT_RED}Functions:${RESET}"
# List all user-defined functions, filter out private ones starting with
# an underscore, and apply the filter
compgen -A function | grep -v '^_.*' | grep -E "${FILTER}"
}
# Show a list of available aliases and functions with optional filtering
function a() {
# Store the first argument to FILTER
local FILTER="${1}"
# Combine the output of aliases and functions with optional filtering
{ findalias "${FILTER}"; echo; findfunction "${FILTER}"; } | command less --line-numbers --no-init
}
#######################################################
### DIRECTORY ALIASES
#######################################################
# This allows you to bookmark your favorite places across the file system
# Define a variable containing a path and you will be able to cd into it
# regardless of the directory you're in like this:
# export desktop="${HOME}/Desktop"
# cd desktop
shopt -s cdable_vars
# Declare an associative array for directory locations (with alternatives)
# NOTE: These aliases are case sensitive where lower case is the local user
# directory and upper case is the global system directory
declare -A ALIASES=(
["autostart"]="${XDG_CONFIG_HOME:-${HOME}/.config}/autostart"
["bin"]="${HOME}/.local/bin"
["BIN"]="/usr/bin"
["cache"]="${XDG_CACHE_HOME:-${HOME}/.cache}"
["config"]="${XDG_CONFIG_HOME:-${HOME}/.config}"
["CONFIG"]="/etc"
["desktop"]="$(command -v xdg-user-dir > /dev/null && xdg-user-dir DESKTOP || echo "${HOME}/Desktop")"
["docs"]="$(command -v xdg-user-dir > /dev/null && xdg-user-dir DOCUMENTS || echo "${HOME}/Documents")"
["documents"]="$(command -v xdg-user-dir > /dev/null && xdg-user-dir DOCUMENTS || echo "${HOME}/Documents")"
["DOCS"]="/usr/local/man /usr/local/share/man /usr/share/man"
["downloads"]="$(command -v xdg-user-dir > /dev/null && xdg-user-dir DOWNLOAD || echo "${HOME}/Downloads")"
["fonts"]="${XDG_DATA_HOME:-${HOME}/.local/share}/fonts ${HOME}/.fonts"
["FONTS"]="/usr/share/fonts"
["icons"]="${XDG_DATA_HOME:-${HOME}/.local/share}/icons ${HOME}/.icons"
["ICONS"]="/usr/share/icons"
["music"]="$(command -v xdg-user-dir > /dev/null && xdg-user-dir MUSIC || echo "${HOME}/Music")"
["pics"]="$(command -v xdg-user-dir > /dev/null && xdg-user-dir PICTURES || echo "${HOME}/Pictures")"
["pictures"]="$(command -v xdg-user-dir > /dev/null && xdg-user-dir PICTURES || echo "${HOME}/Pictures")"
["share"]="${XDG_DATA_HOME:-${HOME}/.local/share}"
["SHARE"]="/usr/share"
["shortcuts"]="${XDG_DATA_HOME:-${HOME}/.local/share}/applications ${HOME}/.gnome/apps"
["SHORTCUTS"]="/usr/share/applications /usr/local/share/applications"
["themes"]="${HOME}/.themes ${XDG_DATA_HOME:-${HOME}/.local/share}/themes"
["THEMES"]="/usr/share/themes"
["tmp"]="${HOME}/tmp ${XDG_CACHE_HOME:-${HOME}/.cache}/tmp ${XDG_CACHE_HOME:-${HOME}/.cache}"
["TMP"]="${TMPDIR:-/tmp}"
["videos"]="$(command -v xdg-user-dir > /dev/null && xdg-user-dir VIDEOS || echo "${HOME}/Videos")"
["wallpaper"]="${XDG_DATA_HOME:-${HOME}/.local/share}/wallpapers"
["WALLPAPER"]="/usr/share/backgrounds /usr/share/wallpapers"
["web"]="/srv/http /var/www/html /usr/share/nginx/html /opt/lampp/htdocs /usr/local/apache2/htdocs /usr/local/www/apache24/data"
)
# Save original IFS and set it to space for parsing directories
OLD_IFS="${IFS}"
IFS=' '
# Loop through the associative array and create aliases and exports for existing directories
for ALIAS in "${!ALIASES[@]}"; do
DIRECTORIES=(${ALIASES[${ALIAS}]})
for DIRECTORY in "${DIRECTORIES[@]}"; do
if [[ -d "${DIRECTORY}" ]]; then
alias "${ALIAS}"="cd \"${DIRECTORY}\""
export "${ALIAS}"="${DIRECTORY}"
break # Only set the first found directory
fi
done
done
# Restore original IFS
IFS="${OLD_IFS}"
# Clean up
unset OLD_IFS DIRECTORY DIRECTORIES ALIAS ALIASES
#######################################################
### GIT ALIASES
#######################################################
# If git is installed...
if hascommand --strict git; then
# Git Alias: Provides many useful Git alias commands
# This alias will install/update the Git alias commands file
# Link: https://github.com/GitAlias/gitalias
# Edit ~/.gitconfig and then include the path to this file like this:
# [include]
# path = gitalias.txt
alias gitalias='curl -L --output "${HOME}/gitalias.txt" https://raw.githubusercontent.com/GitAlias/gitalias/main/gitalias.txt && git config --global include.path "gitalias.txt"'
# When invoked without arguments gg will do a short Git status,
# otherwise it will just pass on the given arguments to the Git command.
# Status is likely to be the Git command one will execute the most,
# hence this simple enhancement does prove very useful in practice.
# - `??`: Untracked file (not in the repository)
# - `A`: Added to the index (staged for commit)
# - `M`: Modified (changes not staged for commit)
# - `D`: Deleted (deleted but not staged for commit)
# - `R`: Renamed (file has been renamed, not staged for commit)
# - `C`: Copied (file has been copied, not staged for commit)
# - `U`: Unmerged (conflict in merging changes)
# - `!!`: Ignored (file is ignored by Git)
# - `1`: Index and working tree match (status for submodules)
# - `2`: Working tree changed but index not updated (status for submodules)
# - `?`: Unknown (an error occurred while trying to obtain the status)
# git status --short --branch
function gg() {
# Check if the number of arguments is zero
if [[ $# -eq 0 ]]; then
# Call gitls when no arguments are provided
gitls
else
# Pass all arguments to the 'git' command
git "$@"
fi
}
# All Git aliases start with gg for speed
alias ggg='git status' # Standard git status
alias ggs='git status --short --branch' # Compact status with branch info
alias ggf='git fetch' # Fetch changes from remote
alias ggprune='git fetch --prune' # Remove references to deleted remote branches
alias ggp='git pull' # Fetch and merge changes
alias ggpu='git push' # Push changes to remote
alias ggm='git merge' # Merge branches
alias ggri='git rebase --interactive' # Start interactive rebase
alias ggsquash='git reset $(git merge-base $(git symbolic-ref refs/remotes/origin/HEAD | sed "s@^refs/remotes/origin/@@") $(git branch --show-current)) && \
git add --all && \
git commit --message "Squashed commits"' # Reset to default branch base and squash all commits
# Branch operations
alias ggb='gitbranch' # Change branch (custom function)
alias gg-='git checkout -' # Switch to previous branch
alias ggback='git checkout HEAD~' # Go back one commit
alias ggcb='git checkout -b' # Create new branch
alias ggcbforce='git checkout -B' # Create/reset branch
alias ggbdelete='ask "Are you sure you want to delete this branch? This cannot be undone." N || { echo "Canceled"; false; } && git branch -D' # Safe branch deletion
alias {gglb,ggbl}='git for-each-ref --sort=-committerdate refs/heads/ \
--format="%(committerdate:short) %(refname:short)" | head -n 25' # List branches sorted by last commit date
alias ggsb='git for-each-ref --format="%(refname:short)" refs/heads refs/remotes | sed "s/origin\///" | sort -u | grep -i' # Search branch names
# Staging and committing
alias gga='git add' # Stage files
alias ggaa='git add --patch' # Interactive staging
alias ggac='git add --all && git commit --verbose --message' # Stage all and commit
alias ggua='git reset' # Unstage files
alias ggundo='git reset --soft HEAD~1' # Undo last commit, keep changes staged
alias ggr='git restore' # Restore files to HEAD
alias ggrm='git rm' # Remove and stage deletion
alias ggc='git commit --verbose -m' # Commit with message
alias ggca='git commit --amend --verbose' # Modify last commit
# Searching and diffing
alias ggfind='git log --pretty=format:"%C(yellow)%h%Cred%d %Creset%s%Cblue [%cn]" --grep' # Search commit messages
alias ggd='git diff' # Show unstaged changes
alias ggdc='git diff --cached' # Show staged changes
alias ggdd='git remote update > /dev/null 2>&1 && \
git status -uno | grep -q "Your branch is behind" && \
git fetch origin; \
git diff origin/$(git symbolic-ref refs/remotes/origin/HEAD | sed "s@^refs/remotes/origin/@@")..HEAD' # Compare with default branch
alias ggds='git diff --stat' # Show changed files stats
# Stash operations
alias ggst='git stash' # Stash changes
alias ggsta='git stash --patch' # Interactive stash
alias ggpop='git stash pop' # Apply stashed changes
alias ggstlist='git stash list' # List stashes
# Repository information
alias ggls='git ls-files' # List all tracked files
alias ggrv='git remote --verbose' # List remote repositories
alias gglast='git show --stat' # Show last commit changes
# Git alias for log tailored for local projects or office use
# Reference for Git Pretty Formats:
# - %h: Abbreviated commit hash
# - %ad: Author date (format can be customized)
# - %s: Commit summary
# - %b: Body of the commit message.
# - %an: Author name
# - %ae: Author email
# - %cn: Committer's name (the person who applied the commit)
# - %ce: Committer's email
# - %d: Ref names (branches or tags associated with the commit)
# - %H: Commit hash
# - %h: Abbreviated commit hash
# - %p: Parent hashes of a commit (useful for merge commits)
# - %G?: GPG signature status code
# - %GS: GPG signatures signer
alias ggl='git log --oneline --date=short --decorate --pretty=format:"%C(magenta)%h%C(reset) - %C(green)(%cr)%C(reset) %s%C(auto)%d%C(reset) %C(bold brightblue)<%an>%C(reset)"'
# Git alias for more detailed list log that is better for online collaberation
# The last 1 character field is the GPG signature for the commit
# - G: Good signature
# - B: Bad signature
# - U: Good signature with unknown validity
# - X: Good signature that has expired
# - Y: Good signature made by an expired key
# - R: Good signature made by a revoked key
# - E: Signature cannot be checked (e.g., missing key)
# - N: No signature
alias ggll='git log --graph --topo-order --date=iso8601-strict --no-abbrev-commit --decorate --all --boundary --pretty=format:"%C(green)%ad %C(magenta)%h%Creset -%C(auto)%d%Creset %s %C(brightblue)[%aN <%aE>]%C(cyan)(%cN)%C(reset) %C(brightyellow)%G?%C(reset)"'
# Vacuum the local Git repository database, reduce it's size, and clean out logs
# Link: https://stackoverflow.com/questions/2116778/reduce-git-repository-size
alias ggclean='git reflog expire --all --expire=now && git gc --prune=now --aggressive'
# Check PHP syntax on all modified and untracked PHP files in the current branch
# Useful for catching syntax errors before committing
function phpcheckbranch() {
# Get all modified PHP files (staged + unstaged changes)
local PHP_FILES=$(git diff --name-only HEAD 2>/dev/null | grep '\.php$')
# Get all untracked PHP files (new files not yet added to git)
local UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null | grep '\.php$')
# Combine modified and untracked files into one list
if [[ -n "${UNTRACKED}" ]]; then
if [[ -n "${PHP_FILES}" ]]; then
PHP_FILES=$(printf "%s\n%s" "${PHP_FILES}" "${UNTRACKED}")
else
PHP_FILES="${UNTRACKED}"
fi
fi
# Exit early if no PHP files found
if [[ -z "${PHP_FILES}" ]]; then
echo -e "${BRIGHT_YELLOW}No modified PHP files found${RESET}"
return 0
fi
# Track results
local FAILED=0
local TOTAL=0
# Check each PHP file using the phpcheck function
while IFS= read -r FILE; do
if [[ -f "${FILE}" ]]; then
((TOTAL++))
phpcheck "${FILE}"
if [[ $? -ne 0 ]]; then
((FAILED++))
fi
echo "" # Add spacing between file checks
fi
done <<< "${PHP_FILES}"
# Print summary
echo -e "${BRIGHT_CYAN}════════════════════════════════${RESET}"
if [[ ${FAILED} -gt 0 ]]; then
echo -e "${BRIGHT_RED}✗ Failed: ${FAILED}/${TOTAL} file(s)${RESET}"
return 1
else
echo -e "${BRIGHT_GREEN}✓ All ${TOTAL} PHP file(s) passed!${RESET}"
return 0
fi
}
# Find all Git repos in the current directory recursively
# Note: Excludes hidden and temp directories
# You can find all git repos on the entire system using: locate .git | grep "/.git$"
alias gitrepos="find . -type d -not -path '*/.git/*' -not -path '*/tmp/*' -not -path '*/temp/*' -not -path '*/.*' -exec test -e '{}/.git' ';' -print -prune"
# Git Auto-Completion
# Link: https://github.com/git/git/tree/master/contrib/completion
# Install: wget -O ~/git-completion.bash https://github.com/git/git/raw/master/contrib/completion/git-completion.bash
if [[ -f "${HOME}/git-completion.bash" ]]; then
builtin source "${HOME}/git-completion.bash"
fi
# If eza with Git support is installed...
# Link: https://eza.rocks
if hascommand --strict eza; then
# If Git is not slow, always use the eza Git repo flag globally
if [[ ${_GIT_IS_SLOW} == false ]]; then
alias {eza,exa}='command eza --git'
else
alias {eza,exa}='command eza --git --git-repos-no-status'
fi
if [[ ${_SKIP_EXA} == false ]]; then
alias lsg='eza --long --all --links --group --modified --classify --group-directories-first --color=auto --color-scale'
fi
# If exa with Git support is installed...
# Link: https://github.com/ogham/exa
elif hascommand --strict exa; then
# Add Git support to Exa only if Git is not slow
if [[ ${_GIT_IS_SLOW} == false ]]; then
alias exa='command exa --git'
fi
if [[ ${_SKIP_EXA} == false ]]; then
alias lsg='exa --long --all --links --group --modified --classify --group-directories-first --color=auto --color-scale'
fi
fi
# If lsd with Git support is installed...
# Link: https://github.com/Peltoche/lsd
if hascommand --strict lsd; then
# Add icons if unicode and the icon paramter is supported
if [[ -n $(command lsd --help | grep -e '--git\s' 2> /dev/null) ]]; then
alias lsd='command lsd --git'
fi
fi
# Nearly everything you can do in Git but in a terminal UI
# Link: https://github.com/Extrawurst/gitui
if hascommand gitui; then
alias ggu='gitui'
fi
# Tig ncurses-based text-mode interface for git
# Link: https://jonas.github.io/tig/
if hascommand tig; then
alias gitt='tig'
fi
# Git Commander
# Link: https://github.com/golbin/git-commander
# Install: npm install -g git-commander
if hascommand git-commander; then
alias gitc='git-commander'
fi
# GRV - Git Repository Viewer
# Link: https://github.com/rgburke/grv
if hascommand grv; then
alias gitrv='grv'
fi
# LazyGit - Terminal UI for git commands
# Link: https://github.com/jesseduffield/lazygit
if hascommand lazygit; then
alias lg='lazygit'
fi
# Ugit - Git Undo
# Link: https://github.com/Bhupesh-V/ugit
# Install: sh -c "$(curl -fsSL https://raw.githubusercontent.com/Bhupesh-V/ugit/master/install)"
if hascommand ugit; then
alias {gitundo,ggundo}='ugit'
fi
# List Git files by last modified date
# Link: https://stackoverflow.com/questions/14141344/git-status-list-last-modified-date
alias gitmodifieddate='IFS=""; git status -s | while read -n2 mode; read -n1; read file; do echo "$mode" "$(stat -c %y "$file")" "$file"; done | sort -k1,4'
# Returns you to the Git project's top level
alias cg='cd "$(git rev-parse --show-toplevel)"'
# Displays git status with human-readable descriptions aligned in columns
function gitls() {
local COUNT=0 # Will keep the file count
local TOGGLE=0 # Toggle variable for alternating row colors
# Getting the current branch name
local CURRENT_BRANCH
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Not a git repository ${BRIGHT_MAGENTA}(or any parent up to mount point /)${RESET}"
return 1 # Return with an error status code
fi
echo -e "${BRIGHT_RED}Current branch: ${BRIGHT_WHITE}${CURRENT_BRANCH}${RESET}"
# Storing the output of git status --porcelain in a variable
local GIT_STATUS=$(git status --porcelain | sort -k1,1 -k2)
if [[ -z "${GIT_STATUS}" ]]; then
echo -e "${BRIGHT_CYAN}No changes found in the repository${RESET}"
return 0 # Return with a success status code
fi
# Find the maximum length of the filenames
local MAX_FILENAME_LENGTH=0
local FILENAME FILENAME_LENGTH
while IFS= read -r LINE; do
FILENAME="${LINE:2}"
FILENAME_LENGTH=${#FILENAME}
# Compare the current filename length with the current max
MAX_FILENAME_LENGTH=$((MAX_FILENAME_LENGTH < FILENAME_LENGTH ? FILENAME_LENGTH : MAX_FILENAME_LENGTH))
done <<< "${GIT_STATUS}"
# Cap the MAX_FILENAME_LENGTH at 80 if it exceeds it
if [[ ${MAX_FILENAME_LENGTH} -gt 80 ]]; then
MAX_FILENAME_LENGTH=80
fi
# Running git status with the porcelain flag
local CODE DESCRIPTION COLOR BACKGROUND
while IFS= read -r LINE; do
# Count the files
((COUNT++))
# Extracting the status code and the file name
CODE="${LINE:0:2}"
FILENAME="${LINE:2}"; FILENAME="${FILENAME# }"
# Matching the status code with the corresponding description
DESCRIPTION=""
case "${CODE}" in
"??") DESCRIPTION="Untracked file (not in the repository)";;
"A ") DESCRIPTION="Added (staged for commit)";;
"AM") DESCRIPTION="Added (staged) with unstaged modifications";;
"AD") DESCRIPTION="Added (staged) then deleted in working tree";;
"M ") DESCRIPTION="Modified (staged for commit)";;
"MM") DESCRIPTION="Modified (staged) with additional unstaged modifications";;
" M") DESCRIPTION="Modified (changes not staged for commit)";;
"D ") DESCRIPTION="Deleted (staged for commit)";;
" D") DESCRIPTION="Deleted (not staged for commit)";;
"R ") DESCRIPTION="Renamed (staged for commit)";;
"C ") DESCRIPTION="Copied (staged for commit)";;
"T ") DESCRIPTION="Type changed (staged for commit)";;
"U ") DESCRIPTION="Unmerged (conflict in merging changes)";;
"UU") DESCRIPTION="Unmerged (both modified -- merge conflict)";;
"AA") DESCRIPTION="Unmerged (both added -- merge conflict)";;
"DD") DESCRIPTION="Unmerged (both deleted -- merge conflict)";;
"!!") DESCRIPTION="Ignored (file is ignored by Git)";;
*) DESCRIPTION="Status: ${CODE}";;
esac
# Determine if the file is a directory
if [[ -d "${FILENAME}" ]]; then
COLOR="${BRIGHT_BLUE}"
else
COLOR="${BRIGHT_YELLOW}"
fi
# Applying alternating background colors
if [[ ${TOGGLE} -eq 0 ]]; then
BACKGROUND="\033[100m" # Dark grey background
TOGGLE=1
else
BACKGROUND="\033[49m" # Default background
TOGGLE=0
fi
# Printing the filename and description with the chosen colors
printf "${BACKGROUND}${COLOR}%-*s${BRIGHT_CYAN}%s${RESET}\n" "${MAX_FILENAME_LENGTH}" "${FILENAME}" " ${DESCRIPTION}"
# Loop through the next git status line
done <<< "${GIT_STATUS}"
# Printing the total count in purple and green
echo -e "${MAGENTA}Total files:${RESET} ${COUNT}"
}
# Update all Git repositories in the specified directory with depth as a parameter
function gitupdaterepos() {
# Set the depth level for the search with 1 as default if not specified
local DEPTH_LEVEL=${1:-1}
# Check if the necessary commands (git, sed, sort) are available in the environment
if hascommand git && hascommand sed && hascommand sort; then
# Save the current directory to a variable
local CURRENT_DIR=$(command pwd)
# Alert the user to the current directory and the action to be taken
echo -e "${BRIGHT_YELLOW}=>${RESET} You are about to update Git repositories in: ${CURRENT_DIR}"
echo -e "${BRIGHT_BLUE}=>${RESET} Listed repositories:"
# Use IFS and readarray to handle spaces in file paths properly
local IFS=$'\n'
local REPOS=()
readarray -t REPOS < <(command find "${CURRENT_DIR}" -mindepth 1 -maxdepth $((DEPTH_LEVEL + 1)) -type d -name '.git' | command sed 's|/.git$||' | command sort -u)
# Display the directories to be updated
local REPO
for REPO in "${REPOS[@]}"; do
echo "${REPO}"
done
# Prompt the user for confirmation to proceed with updating
local RESPONSE
read -r -p "Are you sure you want to proceed? (y/N): " RESPONSE
if [[ "${RESPONSE,,}" != "y" ]]; then
echo -e "${BRIGHT_RED}=>${RESET} Update canceled"
return
fi
# Iterate over each repository directory and attempt a 'git pull' operation
local REPO_DIR
for REPO_DIR in "${REPOS[@]}"; do
builtin cd "${REPO_DIR}" || { echo -e "${BRIGHT_RED}=>${RESET} Error entering ${REPO_DIR}"; continue; }
echo -e "${BRIGHT_YELLOW}=>${RESET} Updating Git repo in ${REPO_DIR}..."
# Execute a git pull and display success or failure messages
git pull && echo -e "${BRIGHT_GREEN}=>${RESET} Successfully updated ${REPO_DIR}" || echo -e "${BRIGHT_RED}=>${RESET} Failed to update ${REPO_DIR}"
# Return to the original directory after each update
builtin cd - > /dev/null
done
else
# If required commands are not available, alert the user
echo -e "${BRIGHT_RED}=>${RESET} Required commands Git, sed, and/or sort are not installed"
fi
}
# Change the Git branch - If no branch is specified as an argument, then
# the user is prompted to select from a list the available branches
# Syntax: gitbranch [optional_branch_name]
function gitbranch() {
# Check if the current directory is a Git repository
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
# No arguments passed
if [[ $# -eq 0 ]]; then
# Check if there is a remote server
if git remote -v | grep -q 'origin'; then
# Prompt the user for action
if ask "${BRIGHT_WHITE}Download ${BRIGHT_YELLOW}remote${BRIGHT_WHITE} branch names?${BRIGHT_RED} Could be slow for large repos.${RESET}" N; then
# Fetch latest remote branches; handle errors
git fetch origin 2>&1
local RAW_REMOTE_OUTPUT REMOTE_BRANCHES
RAW_REMOTE_OUTPUT=$(git ls-remote --refs --sort=-committerdate origin 2>&1)
if [[ $? -ne 0 ]]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}${RAW_REMOTE_OUTPUT}${RESET}"
return 1
fi
REMOTE_BRANCHES=$(echo "${RAW_REMOTE_OUTPUT}" | awk '{sub("refs/heads/", ""); print $2}')
# Use createmenu for selection
git checkout "$(echo "${REMOTE_BRANCHES}" | createmenu)"
else
# Use local branches for selection via createmenu
git checkout "$(git branch --sort=-committerdate | cut -c 3- | createmenu)"
fi
else
# No remote server, use local branches only
git checkout "$(git branch --sort=-committerdate | cut -c 3- | createmenu)"
fi
else
# Argument passed, just switch to that branch
git checkout "${@}"
fi
else
# Not a Git repo
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}Current directory is not a Git repository${RESET}"
fi
}
# Forces Git to overwrite local files and resets the branch (or master)
# Important: If you have any local changes, they will be lost (if they're tracked)!
function gitresetbranch() {
# Make sure we are in a Git repo
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}Current directory is not a Git repository${RESET}"
return 1
fi
# Default to current branch if not specified
local BRANCH="${1:-$(git rev-parse --abbrev-ref HEAD)}"
# Check if a branch name was provided, and confirm if using the current branch
if [ -z "${BRANCH}" ]; then
echo -e "${BRIGHT_RED}Operation cancelled.${RESET} ${BRIGHT_CYAN}Please specify a branch.${RESET}"
return 1
fi
# Confirm action, as local changes will be lost
echo -e "${BRIGHT_BLUE}Selected branch to reset is: ${BRIGHT_YELLOW}${BRANCH}${RESET}"
if ask "${BRIGHT_RED}WARNING: ${BRIGHT_CYAN}If you have any local changes, they will be lost! ${BRIGHT_MAGENTA}Are you sure?${RESET}" 'N'; then
git checkout "${BRANCH}" && git fetch --all && git reset --hard "origin/${BRANCH}"
else
echo -e "${BRIGHT_RED}Operation cancelled${RESET}"
return 0
fi
}
# Retrieve a specific version of a file from Git history
function gitfileversion() {
# Constants
local NO_ARGS=0
local NO_GIT_REPO_ERROR=1
local NO_FILE_PATH_ERROR=2
local FILE_NOT_IN_GIT_ERROR=3
local NO_SELECTION_MADE=4
# Display help
function display_help() {
echo -e "${BRIGHT_BLUE}Usage:${RESET} git_file_version ${BRIGHT_GREEN}<file path>${RESET} [${BRIGHT_YELLOW}output file${RESET}]"
echo -e "${BRIGHT_BLUE}Description:${RESET} Retrieves a specific version of a file from Git history"
echo -e " ${BRIGHT_GREEN}<file path>${RESET} - Path of the file in the Git repository"
echo -e " ${BRIGHT_YELLOW}<output file>${RESET} - Optional. Path to save the older version of the file. If not provided, copies to clipboard"
}
# Check for no arguments
if [[ $# -eq ${NO_ARGS} ]]; then
display_help
return ${NO_ARGS}
fi
# Check if inside a Git repository
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}Not inside a Git repository${RESET}"
return ${NO_GIT_REPO_ERROR}
fi
# Check for missing file path
if [[ -z "$1" ]]; then
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}File path not provided${RESET}"
display_help
return ${NO_FILE_PATH_ERROR}
fi
# Check if the file exists in the Git repo
if ! git ls-files --error-unmatch "$1" > /dev/null 2>&1; then
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}File '${BRIGHT_YELLOW}$1${BRIGHT_CYAN}' not found in Git repository${RESET}"
return ${FILE_NOT_IN_GIT_ERROR}
fi
# Display the commit menu and get selection
local COMMIT_HASH
COMMIT_HASH=$(git log --pretty=format:"%h - %s (%cr)" -- "$1" | createmenu)
if [[ -z "${COMMIT_HASH}" ]]; then
echo -e "${BRIGHT_RED}No selection made. Exiting.${RESET}"
return ${NO_SELECTION_MADE}
fi
# Extract the hash from the selection
COMMIT_HASH=${COMMIT_HASH%% *}
# Retrieve the file for the selected commit
if [[ -n "$2" ]]; then
# Save to file if second parameter is provided
git show "${COMMIT_HASH}:$1" > "$2"
else
git show "${COMMIT_HASH}:$1" | clipboard
fi
}
# Helper to create git branch names
gitbranchhelp() {
local BRANCH_PREFIXES=(
"bugfix/"
"develop/"
"feature/"
"hotfix/"
"master/"
"main/"
"release/"
"support/"
"wip/"
)
local BRANCH_DESCRIPTIONS=(
"Non-critical bug fixes."
"Ongoing development work."
"New features or enhancements."
"Critical production bug fixes."
"Stable, production-ready code."
"Stable, production-ready code."
"Pre-release preparations."
"Support for older releases."
"Work in progress."
)
# Prompt the user to select the type of branch from the list
echo -e "${BRIGHT_YELLOW}Select the type of branch:${RESET}"
for i in "${!BRANCH_PREFIXES[@]}"; do
echo -e "$((i+1)). ${BRIGHT_CYAN}${BRANCH_PREFIXES[${i}]}${RESET} - ${BRANCH_DESCRIPTIONS[${i}]}"
done
# Read the user's selection for the branch type
local BRANCH_TYPE_NUMBER
echo -ne "${BRIGHT_YELLOW}Enter the number corresponding to the branch type:${RESET} "
read -r BRANCH_TYPE_NUMBER
# Validate the selection
if ! [[ "${BRANCH_TYPE_NUMBER}" =~ ^[1-9]$ ]]; then
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}Invalid selection${RESET}"
return 1
fi
local BRANCH_PREFIX="${BRANCH_PREFIXES[$((BRANCH_TYPE_NUMBER-1))]}"
# Get the project code
local PROJECT_CODE
echo -ne "${BRIGHT_YELLOW}Enter the project code ${BRIGHT_CYAN}(e.g., PROJ)${BRIGHT_YELLOW} or press return to skip${RESET}: "
read -r PROJECT_CODE
# Get the ticket number
local TICKET_NUMBER
echo -ne "${BRIGHT_YELLOW}Enter the ticket number ${BRIGHT_CYAN}(e.g., 12345)${BRIGHT_YELLOW} or press return to skip${RESET}: "
read -r TICKET_NUMBER
# Get the description
local DESCRIPTION
echo -ne "${BRIGHT_YELLOW}Enter the description ${BRIGHT_CYAN}(use spaces or dashes, will be formatted automatically)${RESET}: "
read -r DESCRIPTION
# Format the description: replace spaces with dashes, remove duplicate dashes
DESCRIPTION=$(echo "${DESCRIPTION}" | tr '[:space:]' '-' | tr -s '-')
# Construct the branch name parts
local BRANCH_NAME="${BRANCH_PREFIX}"
# Append project code if provided
if [[ -n "${PROJECT_CODE}" ]]; then
BRANCH_NAME+="${PROJECT_CODE}"
fi
# Append ticket number with a dash if project code is present or ticket number is provided
if [[ -n "${TICKET_NUMBER}" ]]; then
if [[ -n "${PROJECT_CODE}" ]]; then
BRANCH_NAME+="-${TICKET_NUMBER}"
else
BRANCH_NAME+="${TICKET_NUMBER}"
fi
fi
# Append description with a dash if previous parts are present
if [[ -n "${DESCRIPTION}" ]]; then
if [[ -n "${PROJECT_CODE}" || -n "${TICKET_NUMBER}" ]]; then
BRANCH_NAME+="-${DESCRIPTION,,}"
else
BRANCH_NAME+="${DESCRIPTION,,}"
fi
fi
# Remove any trailing dashes
BRANCH_NAME=$(echo "${BRANCH_NAME}" | sed 's/-$//')
# Copy the commit message to the clipboard
echo "${BRANCH_NAME}" | clipboard
# Display the final branch name
echo -e "${BRIGHT_GREEN}Your branch name is:${RESET}\n${BRANCH_NAME}"
}
# Helper to create git commit messages
gitcommithelp() {
local FULL_COMMIT_MESSAGE
# Define commit types with colored labels
local COMMIT_TYPES=(
"🔧 ${BRIGHT_CYAN}chore${RESET}: updating grunt tasks etc; no production code change"
"📝 ${BRIGHT_CYAN}docs${RESET}: changes to the documentation"
"${BRIGHT_CYAN}feat${RESET}: new feature for the user, not a new feature for build script"
"🐛 ${BRIGHT_CYAN}fix${RESET}: bug fix for the user, not a fix to a build script"
"♻️ ${BRIGHT_CYAN}refactor${RESET}: refactoring production code, eg. renaming a variable"
"💅 ${BRIGHT_CYAN}style${RESET}: formatting, missing semi colons, etc; no production code change"
"🧪 ${BRIGHT_CYAN}test${RESET}: adding missing tests, refactoring tests; no production code change"
"🚧 ${BRIGHT_CYAN}wip${RESET}: work in progress"
"🔀 ${BRIGHT_CYAN}merge${RESET}: choose this if this is a merge"
)
# Corresponding commit prefixes
local COMMIT_PREFIXES=(
"🔧 chore: "
"📝 docs: "
"✨ feat: "
"🐛 fix: "
"♻️ refactor: "
"💅 style: "
"🧪 test: "
"🚧 wip: "
)
# Prompt the user to select the type of commit from the list
echo -e "${BRIGHT_YELLOW}What type of commit is this?${RESET}"
for i in "${!COMMIT_TYPES[@]}"; do
echo -e "$((i+1)). ${COMMIT_TYPES[${i}]}"
done
# Read the user's selection
local COMMIT_TYPE_NUMBER
echo -ne "${BRIGHT_YELLOW}Enter the number corresponding to the commit type${RESET}: "
read -r COMMIT_TYPE_NUMBER
# Validate the selection
if ! [[ "${COMMIT_TYPE_NUMBER}" =~ ^[1-9]$ ]]; then
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}Invalid selection${RESET}"
return 1
fi
# Handle special cases (merge and wip)
if [[ "${COMMIT_TYPE_NUMBER}" -eq 9 ]]; then
# Merge commit
local SOURCE_BRANCH
echo -ne "${BRIGHT_YELLOW}Enter the ${BRIGHT_CYAN}source branch${BRIGHT_YELLOW} you are merging ${BRIGHT_CYAN}from${BRIGHT_YELLOW}${RESET}: "
read -r SOURCE_BRANCH
# Ensure the branch was provided
if [[ -z "${SOURCE_BRANCH}" ]]; then
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}No source branch specified${RESET}"
return 1
fi
local DESTINATION_BRANCH
echo -ne "${BRIGHT_YELLOW}Enter the ${BRIGHT_CYAN}destination branch${BRIGHT_YELLOW} you are merging ${BRIGHT_CYAN}into${BRIGHT_YELLOW}${RESET}: "
read -r DESTINATION_BRANCH
# Ensure the branch was provided
if [[ -z "${DESTINATION_BRANCH}" ]]; then
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}No destination branch specified${RESET}"
return 1
fi
# Construct the merge commit message
FULL_COMMIT_MESSAGE="🔀 Merged branch ${SOURCE_BRANCH} into ${DESTINATION_BRANCH}"
elif [[ "${COMMIT_TYPE_NUMBER}" -eq 8 ]]; then
# WIP commit
FULL_COMMIT_MESSAGE="🚧 work in progress [$(date +%H:%M)]"
else
# Regular commit
# Get the corresponding commit prefix
local COMMIT_PREFIX="${COMMIT_PREFIXES[$((COMMIT_TYPE_NUMBER-1))]}"
# Prompt the user for a commit message
local COMMIT_MESSAGE
echo -e "${BRIGHT_YELLOW}Enter a commit message ${BRIGHT_MAGENTA}(use present tense and do not capitalize the first letter)${RESET}:"
echo -ne "${BRIGHT_GREEN}>${RESET} "
read -r COMMIT_MESSAGE
# Ensure a commit message was provided
if [[ -z "${COMMIT_MESSAGE}" ]]; then
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}No commit message was specified${RESET}"
return 1
fi
# Combine the prefix and message
FULL_COMMIT_MESSAGE="${COMMIT_PREFIX}${COMMIT_MESSAGE}"
fi
# Copy the commit message to the clipboard
echo "${FULL_COMMIT_MESSAGE}" | clipboard
# Display the final commit message
echo -e "${BRIGHT_GREEN}Your commit message is:${RESET}\n${FULL_COMMIT_MESSAGE}"
}
# Set defaults for Git (~/.gitconfig)
function gitfixsettings() {
local CHECKMARK="\r${BRIGHT_GREEN}${RESET}"
# If GitAlias (see gitalias alias) is installed...
if [[ -f "${HOME}/gitalias.txt" ]]; then
# Tell Git to include this file
git config --global include.path "gitalias.txt"
echo -e "${CHECKMARK} Found gitalias.txt"
fi
# If a global ignore file exists...
if [[ -f "${HOME}/.gitignore_global" ]]; then
# Globally exclude common files that you dont want to track
git config --global core.excludesfile ~/.gitignore_global
echo -e "${CHECKMARK} Found .gitignore_global"
fi
# Set the user name
local GIT_USER_NAME
read -r -e -i "$(git config --get user.name)" -p "Enter your user name (ENTER to skip): " GIT_USER_NAME
if [ -z "${GIT_USER_NAME}" ]; then
GIT_USER_NAME="User"
fi
git config --global user.name "${GIT_USER_NAME}"
# Set the email address
local GIT_USER_EMAIL
read -r -e -i "$(git config --get user.email)" -p "Enter your email (ENTER to skip): " GIT_USER_EMAIL
if [ -z "${GIT_USER_EMAIL}" ]; then
GIT_USER_EMAIL="user@example.com"
fi
git config --global user.email "${GIT_USER_EMAIL}"
# You should add this manually with your own information
# git config --global user.signingkey <gpg-key-id>
# Set the default branch name to be main (instead of master) in new repos
# git config --global init.defaultBranch main
# Enable colorful output for better readability
git config --global color.ui auto
# Check integrity of objects during operations like fetching or receiving
git config --global transfer.fsckobjects true
git config --global fetch.fsckobjects true
git config --global receive.fsckobjects true
# Automatically fix minor mistakes when you mistype a command
# 0 - No autocorrection; only provides suggestions
# 30 - Executes after 3 seconds
# immediate - Executes immediately without confirmation
# prompt - Asks for confirmation before executing the suggestion
git config --global help.autocorrect prompt
# Automatically set up a tracking branch (upstream branch) when you push
# a local branch to a remote for the first time, so you will not have
# to manually run "git push --set-upstream origin <branch>" each time
git config --global push.autoSetupRemote true
# Speed up git status in large repositories by using Git fsmonitor
# Note: This can cause issues on much older out of date Git versions
git config --global core.fsmonitor true
# Set the default Git editor to your default Bash editor
git config --global core.editor "${EDITOR}"
echo -e "${CHECKMARK} Default editor set to ${EDITOR}"
# Set the default diff algorithm
# The patience diff algorithm handles reordering changes better,
# it can be slower for larger diffs compared to Myers
# The Histogram algorithm performs better with large codebases
# but may produce slightly less intuitive results
git config --global diff.algorithm patience
# git config --global diff.algorithm histogram
# Set the dafault pager
git config --global core.pager "less --ignore-case --LONG-PROMPT --LINE-NUMBERS"
# Set defaults for diff and merge
git config --global merge.conflictstyle "diff3"
git config --global diff.colorMoved "default"
# Set this if icdiff is installed
if hascommand --strict icdiff; then
git config --global icdiff.options '--highlight --line-numbers --strip-trailing-cr'
echo -e "${CHECKMARK} Found icdiff"
fi
# If delta is installed...
if hascommand delta; then
# delta - Beautiful side by side colored diff with Git support and syntax highlighting
# This diff tool also uses more advanced diff algorithms with more features
# Link: https://github.com/dandavison/delta
# Info: Add listed settings to your ~/.gitconfig
# NOTE: use n and N to move between diff sections
git config --global core.pager "delta"
# Enable delta for interactive diffs
git config --global interactive.diffFilter "delta --color-only --features=interactive"
# Enable navigation using 'n' and 'N' for diff sections
git config --global delta.navigate true
# Enable delta features: side-by-side diff view, line numbers, decorations, and word-diff
git config --global delta.features "side-by-side line-numbers decorations word-diff"
# Side-by-side diff format
git config --global delta.side-by-side "true"
# Display line numbers in diffs
git config --global delta.line-numbers "true"
# Use word-diff and define word boundaries with regex
git config --global delta.word-diff-regex '\w+'
# Set how delta handles whitespace errors (underline them in bold red)
git config --global delta.whitespace-error-style "underline bold red"
# Limit the length of lines to highlight whitespace issues
git config --global delta.whitespace-line-length-limit 120
# Customize commit decoration (blue outline, raw commit message style)
git config --global delta.decorations.commit-decoration-style "blue ol"
git config --global delta.decorations.commit-style "raw"
# Customize hunk header (blue box for decoration, red file name, green line numbers)
git config --global delta.decorations.hunk-header-decoration-style "blue box"
git config --global delta.decorations.hunk-header-file-style "red"
git config --global delta.decorations.hunk-header-line-number-style "#067a00"
git config --global delta.decorations.hunk-header-style "file line-number syntax"
# Disable plus/minus markers in interactive mode
git config --global delta.interactive.keep-plus-minus-markers "false"
# Omit file names in the delta output
git config --global delta.decorations.file-style "omit"
# (Optional) Disable showing GPG signature in `git log` (commented out)
# git config --global log.showSignature false
echo -e "${CHECKMARK} Found delta"
# Check for Difftastic
elif hascommand difft; then
# Difftastic supports over 30 languages and compares based on syntax
# git -c diff.external=difft diff # View uncommitted changes
# git -c diff.external=difft show --ext-diff # Most recent commit
# git -c diff.external=difft log -p --ext-diff # Recent commits current branch
# Link: https://github.com/Wilfred/difftastic
# Link: https://difftastic.wilfred.me.uk/git.html?highlight=page#difftool
git config --global diff.external difft
# Define difftastic as a custom difftool with the necessary command
git config --global difftool.difftastic.cmd 'difft "$MERGED" "$LOCAL" "abcdef1" "100644" "$REMOTE" "abcdef2" "100644"'
# Additional configurations for difftastic
git config --global difftool.prompt false
git config --global pager.difftool true
git config --global diff.tool difftastic
# Set up aliases "git dlog", "git dshow", and "git ddiff"
git config --global alias.dlog '-c diff.external=difft log -p --ext-diff'
git config --global alias.dshow '-c diff.external=difft show --ext-diff'
git config --global alias.ddiff '-c diff.external=difft diff'
echo -e "${CHECKMARK} Found Difftastic"
# Check for diff-so-fancy
elif hascommand diff-so-fancy; then
# Configure diff-so-fancy for Git diff outputs
# diff-so-fancy simplifies Git diff outputs, making them easier to read
git config --global core.pager "diff-so-fancy | $(command which less) --tabs=4 -RFX"
# Interactive diff filter setting for diff-so-fancy
git config --global interactive.diffFilter "diff-so-fancy --patch"
# Setting color enhancements for better visual differentiation in diffs
git config --global color.ui true
git config --global color.diff-highlight.oldNormal "red bold"
git config --global color.diff-highlight.oldHighlight "red bold 52"
git config --global color.diff-highlight.newNormal "green bold"
git config --global color.diff-highlight.newHighlight "green bold 22"
git config --global color.diff.meta "11"
git config --global color.diff.frag "magenta bold"
git config --global color.diff.commit "yellow bold"
git config --global color.diff.old "red bold"
git config --global color.diff.new "green bold"
git config --global color.diff.whitespace "red reverse"
# Confirm diff-so-fancy is configured for output
echo -e "${CHECKMARK} Configured diff-so-fancy for Git diffs"
fi
# If we are in a desktop environment and Ultracompare is not installed...
# You will be prompted when you launch Ultracompare to automatically set up
# Git, so just skip the other diff configurations since this app is paid
# Link: https://www.ultraedit.com/products/ultracompare-linux/
if ([[ -n "$DISPLAY" ]] || [[ -n "$WAYLAND_DISPLAY" ]]) && [[ -n "$XDG_CURRENT_DESKTOP" ]]; then
echo -e "${CHECKMARK} User is in desktop environment ${XDG_CURRENT_DESKTOP}"
# If UltraCompare is installed...
# You will be prompted when you launch UltraCompare to automatically set up
# Git, so just skip the other diff configurations since this app is paid
# Link: https://www.ultraedit.com/products/ultracompare-linux/
if hascommand --strict ucx; then
echo -e "${CHECKMARK} Found UltraCompare"
# If Meld is installed...
# Link: https://meldmerge.org/
elif hascommand --strict meld; then
# These settings will not alter the behaviour of "git diff"
# Link: https://stackoverflow.com/questions/34119866/setting-up-and-using-meld-as-your-git-difftool-and-mergetool
# You use "git difftool" in exactly the same way as you use git diff
# Example: git difftool <COMMIT_HASH> file_name
# Example: git difftool <BRANCH_NAME> file_name
# Example: git difftool <COMMIT_HASH_1> <COMMIT_HASH_2> file_name
git config --global diff.tool meld
git config --global difftool.meld.path "$(command which meld)"
git config --global difftool.meld.cmd 'meld "$LOCAL" "$REMOTE"'
git config --global difftool.prompt false
# "git mergetool" allows you to use a GUI merge program to resolve merge conflicts
# Before using git mergetool you perform a merge in the usual way with git
# then Mergetool can now be used to resolve the merge conflicts
git config --global merge.tool meld
git config --global mergetool.meld.path "$(command which meld)"
git config --global mergetool.prompt false
# Choose which starting edit position you'd prefer; $MERGED for the file
# which contains the partially merged file with the merge conflict info
# or $BASE for the shared commit ancestor of $LOCAL and $REMOTE
git config --global mergetool.meld.cmd 'meld "$LOCAL" "$MERGED" "$REMOTE" --output "$MERGED"'
#git config --global mergetool.meld.cmd 'meld "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED"'
# Also set up the alias "git meld" which works on Cygwin
git config --global alias.meld '!git difftool -t meld --dir-diff'
echo -e "${CHECKMARK} Found meld"
# If KDiff3 is installed...
# Link: https://kdiff3.sourceforge.net/
elif hascommand --strict kdiff3; then
git config --global diff.guitool kdiff3
git config --global difftool.kdiff3.path "$(command which kdiff3)"
git config --global difftool.kdiff3.trustExitCode false
git config --global merge.tool kdiff3
git config --global mergetool.kdiff3.path "$(command which kdiff3)"
git config --global mergetool.kdiff3.trustExitCode false
echo -e "${CHECKMARK} Found KDiff3"
fi
fi
# Create an alias s for the git status command
git config --global alias.s "status"
# Create an alias l for a compact log format:
# --oneline : shows each commit as a single line
# --graph : adds an ASCII graph of branch and merge history
# --decorate : adds extra information such as branch names, tags, etc.
git config --global alias.l "log --oneline --date=short --graph --decorate --pretty=format:'%C(red)%h%C(reset) - %C(green)(%cr)%C(reset) %s%C(yellow)%d%C(reset) %C(bold blue)<%an>%C(reset)'"
# Stage git commits with an interactive prompt
git config --global alias.aa "add -p"
# Make an empty commit with no content
# (to trigger kick off a CI build or some other integration)
git config --global alias.empty "commit --allow-empty -m"
}
fi
#######################################################
### SYSTEMD ALIASES
#######################################################
# Check if systemd is installed
if hascommand --strict systemctl; then
# Get a list of all services
alias services='systemctl list-units --type=service --state=running,failed'
alias servicesall='systemctl list-units --type=service'
# Find what systemd services have failed
alias {failed,servicefailed}='systemctl --failed'
# Get the status of a services
alias servicestatus='sudo systemctl status'
# Start a service and enable automatic startup at boot
alias serviceenable='sudo systemctl enable --now'
# Disable a service
alias servicedisable='sudo systemctl disable'
# Start a service
alias servicestart='sudo systemctl start'
# Stop a service
alias servicestop='sudo systemctl stop'
# Forcefully terminate a service
alias servicekill='sudo systemctl kill'
# Stop and restart a service
alias servicerestart='sudo systemctl restart'
# Reload a service's configuration (soft restart)
alias servicereload='sudo systemctl reload'
# Tell how long it took to boot the system
alias boottime='systemd-analyze'
# Tell how long it took to boot the system
alias boottimelist='systemd-analyze blame'
# Generate a visual boot time analysis chart as a graphic SVG image
alias bootchart='systemd-analyze plot > ./boot.svg && echo "Boot chart saved to ${PWD/#$HOME/~}/boot.svg"'
# View kernel messages from current boot
alias bootmessages='journalctl -b -k'
# View kernel messages from previous boot
alias bootprevious='journalctl -b -1 -k'
# Filter for warning and errors
alias booterrors='journalctl -b -k -p warning..emerg'
# List boots
alias bootlist='journalctl --list-boots'
# Clear system log entries from the systemd journal
alias {clearsystemlogs,cleansystemlogs}='echo -ne "${BRIGHT_BLUE}Before${RESET}: "; journalctl --disk-usage; sudo journalctl --rotate; sudo journalctl --vacuum-time=1s; echo -ne "${BRIGHT_BLUE}After${RESET}: "; journalctl --disk-usage'
# Fast reboot user space and not the kernel
alias rebootfast='systemctl soft-reboot'
# If SSH is installed...
if hascommand --strict ssh; then
# Create aliases to start/enable and stop/disable the SSH server
alias sshstatus='systemctl status sshd.service'
alias sshstart='sudo systemctl start sshd.service && sudo systemctl enable sshd.service'
alias sshstop='sudo systemctl stop sshd.service && sudo systemctl disable sshd.service'
alias sshrestart='sudo systemctl restart sshd.service && sudo systemctl enable sshd.service'
fi
# If gpm is installed...
# Link: https://github.com/telmich/gpm
if hascommand --strict gpm; then
alias ttymouseon='sudo systemctl enable --now gpm'
alias ttymouseoff='sudo systemctl stop gpm && sudo systemctl disable gpm'
alias ttymousestatus='sudo systemctl status gpm'
fi
# Flushing and restart the DNS cache if installed and running
function flushdns() {
# Check if systemd-resolved is available
if hascommand --strict systemd-resolve; then
# Check if systemd-resolved service is active
if systemctl is-active systemd-resolved >/dev/null; then
# Flush DNS cache and restart systemd-resolved
sudo systemd-resolve --flush-caches
sudo systemctl restart systemd-resolved
echo "DNS cache flushed and systemd-resolved restarted"
else
echo "systemd-resolved is not active, unable to flush DNS cache"
fi
# Check if dnsmasq is available
elif hascommand --strict dnsmasq; then
# Check if dnsmasq process is running
if pgrep -x "dnsmasq" >/dev/null; then
# Restart dnsmasq to flush DNS cache
sudo systemctl restart dnsmasq
echo "DNS cache flushed and dnsmasq restarted"
else
echo "dnsmasq is not running, unable to flush DNS cache"
fi
else
echo "No supported DNS caching service found"
fi
}
# Browse and manage systemd services
findservice() {
local service
local services_list
# Get only the service names (first column, stripping status symbols)
services_list=$(systemctl list-units --type=service --all --no-legend --plain | awk '{print $1}' | sed 's/^●//')
# Pickers with preview support
local PICKER_FOUND=false
for picker in sk fzf; do
if hascommand "$picker"; then
PICKER_FOUND=true
service=$(echo "$services_list" | "$picker" --header="Select service" \
--preview 'systemctl status {}' | xargs)
break
fi
done
# Fallback to pickers without preview
if ! ${PICKER_FOUND}; then
for picker in fzy peco percol pick icepick selecta sentaku zf; do
if hascommand "$picker"; then
PICKER_FOUND=true
service=$(echo "$services_list" | "$picker" | xargs)
break
fi
done
fi
# No picker found
if ! ${PICKER_FOUND}; then
echo "Error: No fuzzy finder installed (fzf, sk, fzy, peco, etc.)"
return 1
fi
# User cancelled the picker
if [ -z "$service" ]; then
return 0
fi
echo "Service: $service"
echo "1) Status 2) Start 3) Stop 4) Restart 5) Enable 6) Disable"
read -p "Action: " -n 1 action
echo
case $action in
1) systemctl status "$service" ;;
2) sudo systemctl start "$service" ;;
3) sudo systemctl stop "$service" ;;
4) sudo systemctl restart "$service" ;;
5) sudo systemctl enable "$service" ;;
6) sudo systemctl disable "$service" ;;
esac
}
fi
#######################################################
### CHANGING AND LISTING DIRECTORIES
#######################################################
# Aliases for faster pushd and popd: type "p" for pushd and "p-" for popd
# Link: https://opensource.com/article/19/8/navigating-bash-shell-pushd-popd
alias p='pushd'
alias p-='popd'
# Allow us to clear the directory stack
alias dirsclear='dirs -c'
# If you want to see the numeric position of each directory in the stack,
# you can use the -v (vertical) option - as DT suggests, use this by default
alias dirs='dirs -v'
# If Ranger is installed, maintain the last selected directory...
# Link: https://github.com/ranger/ranger
# g - navigation and tabs
# r - :open_with command
# y - copy (yank)
# d - cut (delete)
# p - paste
# o - sort
# . - filter_stack
# z - settings / zz - filter
# u - undo
# M - linemode
# +, -, = for setting access rights to files
if hascommand --strict ranger; then
# Override ranger to change to the most recently selected directory
function ranger() {
# If the temporary directory location is set...
if [[ -n "${TEMPDIR_LOCAL}" ]] && [[ -d "${TEMPDIR_LOCAL}" ]]; then
# Use system ranger and save last directory on exit
command ranger --choosedir="${TEMPDIR_LOCAL}/.rangerdir" "$@"
# If last directory file exists, change to it
if [[ -f "${TEMPDIR_LOCAL}/.rangerdir" ]]; then
local LAST_DIR_LOCATION="$(< "${TEMPDIR_LOCAL}/.rangerdir")"
if [[ -d "${LAST_DIR_LOCATION}" ]]; then
command cd "${LAST_DIR_LOCATION}" || {
echo -e "${BRIGHT_RED}Error:${BRIGHT_CYAN} Unable to change directory to '${LAST_DIR_LOCATION}'${RESET}"
return 1
}
command rm -f "${TEMPDIR_LOCAL}/.rangerdir" > /dev/null 2>&1
fi
fi
else
# Launch without directory persistence if TEMPDIR_LOCAL is not set
command ranger "$@"
fi
}
# Set a quick shortcut key to launch ranger
alias r='ranger'
fi
# If nnn is installed, add an alias for a directory listing
# Link: https://github.com/jarun/nnn/
# The following command installs or updates all plugins:
# Install: curl -Ls https://raw.githubusercontent.com/jarun/nnn/master/plugins/getplugs | sh
# Plugins are installed to ${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins
if hascommand --strict nnn; then
if [[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/nnn/plugins/finder" ]]; then
export NNN_PLUG='f:finder;o:fzopen;p:mocplay;d:diffs;t:nmount;v:imgview'
alias nnnplugins='curl -Ls https://raw.githubusercontent.com/jarun/nnn/master/plugins/getplugs | sh'
fi
#alias nnn='nnn -e'
alias nnn='nnn -Headr'
fi
# TUIFI Manager
# Link: https://github.com/GiorgosXou/TUIFIManager
# Install: pip3 install tuifimanager --upgrade
if hascommand tuifi; then
alias ff="tuifi"
fi
# If lsx enhanced directory navigation is found, source it
# Link: https://github.com/souvikinator/lsx
if [[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/lsx/lsx.sh" ]]; then
builtin source "${XDG_CONFIG_HOME:-${HOME}/.config}/lsx/lsx.sh"
# If lsx is installed, alias it to see hidden directories
if hascommand lsx; then
alias l='lsx -a'
elif hascommand ls-x; then
alias l="ls-x -a"
alias lsx="${GOPATH}/bin/ls-x"
elif [[ -x "${GOPATH}/bin/ls-x" ]]; then
alias l="${GOPATH}/bin/ls-x -a"
alias lsx="${GOPATH}/bin/ls-x"
fi
fi
# List folders recursively in a tree
if hascommand --strict tree; then
alias treed='command tree -CAFd'
fi
# List files recursively in a tree
if hascommand broot; then
# If broot is installed...
# Link: https://github.com/Canop/broot
# Link: https://computingforgeeks.com/broot-easy-directory-trees-navigation-in-linux/
# To generate this source file, type: broot --install
if [[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/broot/launcher/bash/br" ]]; then
# Source the br function
builtin source "${XDG_CONFIG_HOME:-${HOME}/.config}/broot/launcher/bash/br"
# If Git is installed, enable the Git functionality
if hascommand --strict git; then
alias {lb,tree}='br --sizes --dates --show-root-fs --hidden --show-git-info'
else
alias {lb,tree}='br --sizes --dates --show-root-fs --hidden'
fi
else
# If Git is installed, enable the Git functionality
if hascommand --strict git; then
alias {lb,tree}='broot --sizes --dates --show-root-fs --hidden --show-git-info'
else
alias {lb,tree}='broot --sizes --dates --show-root-fs --hidden'
fi
fi
elif [[ $_SKIP_LSD = false ]] && hascommand lsd; then
# If lsd is installed...
# Link: https://github.com/Peltoche/lsd
alias tree='command lsd --all --blocks permission,user,size,date,name --group-dirs first --header --long --tree'
elif [[ $_SKIP_EXA = false ]] && hascommand exa; then
# If exa is installed...
# Link: https://github.com/ogham/exa
if hascommand --strict git; then
alias tree='exa --all --git --group-directories-first --header --long --tree'
else
alias tree='exa --all --group-directories-first --header --long --tree'
fi
elif hascommand tree; then
# If tree is installed...
# Link: https://www.tecmint.com/linux-tree-command-examples/
alias tree='command tree -CAhF --dirsfirst'
fi
# Common aliases for directory listing commands
alias lw='ls -xAh' # wide listing format
alias lm='ll -alh | command less -S' # pipe through less
alias lr='ls -lRh' # recursive ls
alias l.='ll -d .*' # show hidden files
alias lfile="ls -l | grep -v '^d'" # files only
alias ldir="ls -la | grep '^d'" # directories only
# Conditional aliases based on the availability of specific directory tools
if [[ $_SKIP_LS_COLORIZED = false ]]; then
# If eza (with Git support) is installed...
# Eza is the fastest ls replacements with so many features
# Link: https://eza.rocks
if [[ $_SKIP_EXA = false ]] && hascommand eza; then
# Flags for nearly all directory listings
_EZA_COMMON_FLAGS="--all --classify --color=auto --color-scale=all --icons=auto"
# Flags for all long listings (which include common and Git flags)
#_EZA_LONG_FLAGS="${_EZA_COMMON_FLAGS} --group-directories-first --long --links --group --modified"
_EZA_LONG_FLAGS="${_EZA_COMMON_FLAGS} --group-directories-first --long --links --group --modified"
alias ls="eza ${_EZA_COMMON_FLAGS} --group-directories-first"
alias labc="eza ${_EZA_COMMON_FLAGS} --grid --sort name"
alias ll="eza ${_EZA_LONG_FLAGS}"
alias lx="eza ${_EZA_LONG_FLAGS} --sort extension"
alias lk="eza ${_EZA_LONG_FLAGS} --sort size"
alias lt="eza ${_EZA_LONG_FLAGS} --sort modified"
alias lc="eza ${_EZA_LONG_FLAGS} --changed --sort changed"
alias lu="eza ${_EZA_LONG_FLAGS} --accessed --sort accessed"
alias new="eza ${_EZA_LONG_FLAGS} --sort modified | tail -10 | tac"
alias lr="eza ${_EZA_COMMON_FLAGS} --grid --group-directories-first --recurse"
alias ltree="command eza --icons=auto --all --group-directories-first --header --long --tree"
# Cleanup variables to avoid leaking them into the environment
unset _EZA_COMMON_FLAGS _EZA_LONG_FLAGS
# If lsd is installed, use it instead
# Link: https://github.com/Peltoche/lsd
elif [[ $_SKIP_LSD = false ]] && hascommand lsd; then
alias ls='lsd -AF --group-dirs first'
alias ll='lsd --almost-all --header --long --group-dirs first'
alias labc='lsd -lAv --header'
alias lx='lsd -lAXh --header'
alias lk='lsd -lASrh --header'
alias lt='lsd -lAtrh --header'
alias lc='command ls -lAcrh --color=always'
alias lu='command ls -lAurh --color=always'
alias new='lsd -lAtr --almost-all | tail -10 | tac'
alias ltree='command lsd --almost-all --blocks permission,user,size,date,name --group-dirs first --header --long --tree'
# If exa (depreciated but still on some older distros) is installed...
# Link: https://github.com/ogham/exa
elif [[ $_SKIP_EXA = false ]] && hascommand exa; then
# Add icons to exa if unicode and the icon paramter is supported
if [[ $(locale charmap) == 'UTF-8' ]]; then
if command exa --help | grep -q -- "--icons" &> /dev/null; then
aliasappend 'exa' '--icons'
fi
fi
# Flags for nearly all directory listings
_EXA_COMMON_FLAGS="--all --classify --color=auto --color-scale"
# Flags for all long listings (which include common and Git flags)
_EXA_LONG_FLAGS="${_EXA_COMMON_FLAGS} --group-directories-first --long --links --group --modified"
alias ls="exa ${_EXA_COMMON_FLAGS} --group-directories-first"
alias labc="exa ${_EXA_COMMON_FLAGS} --grid --sort name"
alias ll="exa ${_EXA_LONG_FLAGS}"
alias lx="exa ${_EXA_LONG_FLAGS} --sort extension"
alias lk="exa ${_EXA_LONG_FLAGS} --sort size"
alias lt="exa ${_EXA_LONG_FLAGS} --sort modified"
alias lc="exa ${_EXA_LONG_FLAGS} --changed --sort changed"
alias lu="exa ${_EXA_LONG_FLAGS} --accessed --sort accessed"
alias new="exa ${_EXA_LONG_FLAGS} --sort modified | tail -10 | tac"
alias lr="exa ${_EXA_COMMON_FLAGS} --grid --group-directories-first --recurse"
alias ltree="command exa --all --group-directories-first --header --long --tree"
# Clean up variables to avoid leaking them into the environment
unset _EXA_COMMON_FLAGS _EXA_LONG_FLAGS
# If Natls is installed, use it instead
# Link: https://github.com/willdoescode/nat
# Install: cargo install natls
elif hascommand natls; then
alias ls='natls --gdf --name'
alias ll='natls --gdf --name --long'
alias labc='natls --name'
alias lx='command ls -FlsaXBh --color=auto'
alias lk='natls --gdf --long --size'
alias lt='natls --gdf --long --modified'
alias lc='command ls -Flsacrh --color=auto'
alias lu='command ls -Flsaurh --color=auto'
alias new='command ls -latr | tail -10 | tac'
alias l.='command ls -Flsd .* --color=auto'
alias lr='command ls -lRh --color=auto'
else
# Use ls with command line options
alias ls='ls -aFh --color=always --group-directories-first'
alias labc='ls -lap' # sort alphabetically
alias lx='ll -laXBh' # sort by extension
alias lk='ll -laSrh' # sort by size
alias lt='ll -latrh' # sort by date
alias lc='ll -lacrh' # sort by change time
alias lu='ll -laurh' # sort by access time
alias new='ls -latr | tail -10 | tac' # list recently created/updated files
alias ltree='command tree -CAhF --dirsfirst' # tree view
if [[ $_SKIP_GRC = false ]] && hascommand grc; then
# If grc Generic Colouriser is installed
# Link: https://github.com/garabik/grc
alias ll='grc ls -l --all --classify --group-directories-first --human-readable --color=always'
else
# Use standard long listing format
alias ll='llcolor'
fi
fi
else
alias ls='ls -aFh --color=always' # do add built-in colors to file types
alias ll='ls -Fals' # long listing
alias labc='ls -lap' # sort alphabetically
alias lx='ll -laXBh' # sort by extension
alias lk='ll -laSrh' # sort by size
alias lt='ll -latrh' # sort by date
alias lc='ll -lacrh' # sort by change time
alias lu='ll -laurh' # sort by access time
alias new='ls -latr | tail -10 | tac' # list recently created/updated files
fi
# List all files larger than a given size
# llfs +10k will find and display all files larger than 10 kilobytes in the currect directory
alias llfs='_llfs(){ find . -type f -size "$1" -exec ls --color --classify --human-readable -l {} \; ; }; _llfs'
# Show colors with the dir command
if hascommand --strict dir; then
alias dir='dir --color=auto --almost-all --human-readable --group-directories-first -F'
alias vdir='vdir --color=auto'
fi
# Show the previous path
alias pwd-='echo ${OLDPWD}'
# Show full path of file or wildcard
alias fullpath='find "`pwd`" -name'
# List the PATH environment variable directories
alias path='echo -e ${PATH//:/\\n}'
# When changing a directory, don't show an extra line with the directory
# Link: https://askubuntu.com/questions/1316485/how-do-i-stop-cd-command-from-printing-absolute-path-everytime
if [[ $_SILENCE_CD_OUTPUT = true ]]; then
alias cd='>/dev/null cd'
fi
# Change to the home directory
alias home='cd ~'
# Allow changing directory when missing a space
alias cd..='cd ..'
# Allow changing directory back when missing a space
alias cd-='cd -'
# Go back directories using dot style
alias ..='up 1'
alias ...='up 2'
alias ....='up 3'
alias .....='up 4'
alias ......='up 5'
# Go back directories using dot dot number style
alias ..1='up 1'
alias ..2='up 2'
alias ..3='up 3'
alias ..4='up 4'
alias ..5='up 5'
# Changes directories and immediately lists the contents with information
cdd() {
# Use enhancd if available, otherwise fallback to builtin cd
if type __enhancd::cd &>/dev/null; then
__enhancd::cd "$@" || return
else
builtin cd "$@" || return
fi
# Directory contents - using user's aliases
local COUNT=$(builtin command ls -A | wc -l)
if [[ ${COUNT} -gt 20 ]]; then
ls # User's ls alias
else
ll # User's ll alias (likely ls -al)
fi
# Directory stats (fast using find with maxdepth)
local DIRS=$(builtin command find . -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l)
local FILES=$((COUNT - DIRS))
local LARGE=$(builtin command find . -maxdepth 1 -type f -size +100M 2>/dev/null | wc -l)
# Get total size if not too many files (performance)
local SIZE_INFO=""
[[ ${COUNT} -le 100 ]] && SIZE_INFO=", total size $(builtin command du -sh . 2>/dev/null | builtin command cut -f1)"
# Show stats and size on same line
builtin echo -e "📊 ${DIRS} dirs, ${FILES} files${SIZE_INFO}"
[[ ${LARGE} -gt 0 ]] && builtin echo " ⚠️ ${LARGE} files over 100MB"
# Project detection - check for various project markers
local PROJECT_TYPE=""
[[ -f package.json ]] && PROJECT_TYPE="${PROJECT_TYPE}📦 Node"
[[ -f Cargo.toml ]] && PROJECT_TYPE="${PROJECT_TYPE}🦀 Rust"
[[ -f go.mod ]] && PROJECT_TYPE="${PROJECT_TYPE}🐹 Go"
[[ -f requirements.txt ]] || [[ -f setup.py ]] || [[ -f pyproject.toml ]] && PROJECT_TYPE="${PROJECT_TYPE}🐍 Python"
[[ -f pom.xml ]] || [[ -f build.gradle ]] && PROJECT_TYPE="${PROJECT_TYPE}☕ Java"
[[ -f Makefile ]] && PROJECT_TYPE="${PROJECT_TYPE}🔨 Make"
[[ -f docker-compose.yml ]] || [[ -f Dockerfile ]] && PROJECT_TYPE="${PROJECT_TYPE}🐳 Docker"
[[ -f .env ]] && PROJECT_TYPE="${PROJECT_TYPE}🔐 Env"
# Only show project line if we detected something
[[ -n "${PROJECT_TYPE}" ]] && builtin echo "🏗️ Project: ${PROJECT_TYPE}"
# Git status (only if git repo exists)
if [[ -d .git ]] || builtin command git rev-parse --git-dir > /dev/null 2>&1; then
# Get branch and change counts
local BRANCH=$(builtin command git branch --show-current 2>/dev/null || echo "detached")
local CHANGES=$(builtin command git status -s | wc -l)
local STAGED=$(builtin command git diff --cached --numstat | wc -l)
local UNSTAGED=$(builtin command git diff --numstat | wc -l)
local UNTRACKED=$(builtin command git ls-files --others --exclude-standard | wc -l)
# Build compact status line
local GIT_INFO="🌿 Git: ⎇ ${BRANCH}"
[[ ${CHANGES} -gt 0 ]] && GIT_INFO="${GIT_INFO} | ${CHANGES} changes"
[[ ${STAGED} -gt 0 ]] && GIT_INFO="${GIT_INFO} (↑${STAGED} staged)"
[[ ${UNTRACKED} -gt 0 ]] && GIT_INFO="${GIT_INFO} (◆${UNTRACKED} untracked)"
builtin echo "${GIT_INFO}"
fi
}
#######################################################
### FIND FILES OR FILE INFORMATION
#######################################################
# Searches for directories (can use wildcards)
# Example: finddir config
# Example: finddir "This has spaces"
alias finddir='find . -type d -iname'
# Recursively find all files modified in the last 24 hours (current directory)
alias find24='find . -mtime -1 -ls'
# Find all the symlinks containing search text (i.e. "/backup")
alias findlinks="find . -type l -printf '%p -> ' -exec readlink -f {} ';' | grep -E"
# To count how many files are in your current file system location:
alias countfiles='find . -type f | wc -l'
# To see if a command is aliased, a file, or a built-in command
alias check="type -t"
# If the mlocate package is installed
if hascommand locate; then
# Case insensitive search and display only files present in your system
alias locate='sudo \locate -i -e'
# Update the locate database before locating a file
# --require-visibility 0 ensures only accessible files are indexed
alias ulocate='sudo updatedb --require-visibility 0 && locate'
# Always update the locate (mlocate) database as root
alias updatedb='sudo updatedb --require-visibility 0'
# Display the number of matching entries
alias locount='sudo \locate -c'
fi
#######################################################
### FILE MANAGEMENT
#######################################################
# Safety net for changing permisions on /
alias chown='chown --preserve-root'
alias chmod='chmod --preserve-root'
alias chgrp='chgrp --preserve-root'
# Alias to make a file executable
# A combination of the letters ugoa controls which users' access to the
# file will be changed: the user who owns it (u), other users in the
# file's group (g), other users not in the file's group (o), or all users
# (a). If none of these are given, the effect is as if (a) were given,
# but bits that are set in the umask are not affected.
alias mx='chmod a+x'
# Make parent directories as needed
alias mkdir='mkdir -p'
# When copying files, prompt before overwriting a file
alias cp='cp -i'
# Copy file(s) recursively with permissions, ownership, timestamps, symbolic
# links, devices, special files, security context, and extended attributes
if hascommand --strict rsync; then
alias clone='rsync --archive --human-readable --progress'
# Clone and synchronize directories deleting files not present at source
alias clonesync='rsync --archive --delete --human-readable --progress'
else
alias clone='cp --recursive --preserve=all --verbose'
fi
# When moving files, prompt for confirmation if the destination path exists
# Use -f if you want to skip all prompts (-i option is ignored)
alias mv='mv -i'
# Prompt whether to remove destinations
alias ln='ln -i'
# Prompt before every removal
alias rm='rm -I --preserve-root'
# Remove a directory and all files
alias rmd='command rm --recursive --force --verbose'
# When shredding files, shred no matter permissions and remove the file(s)
alias shred='shred --force --remove --verbose'
# Type any text into a file
alias typefile='echo "Press CTRL+d when done:" && echo "$(</dev/stdin)" >'
# ExifTool removes embedded Exif data (like location) from images, videos, and docs
# https://exiftool.org/
# https://linuxhandbook.com/remove-exif-data/
if hascommand --strict exiftool; then
alias cleanmeta='exiftool -overwrite_original -all='
fi
# Create a timestamped backup of a file or directory
# Usage: bak [file_or_directory]
function bak() {
# Display help if no parameter provided
if [[ -z "${1}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}bak${RESET}: Create a timestamped backup of a file or directory"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}bak${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}file_or_directory${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}bak${RESET} ${BRIGHT_YELLOW}config.yml${RESET} ${BRIGHT_BLUE}# Creates config.yml.2024-03-15.bak${RESET}"
echo -e " ${BRIGHT_CYAN}bak${RESET} ${BRIGHT_YELLOW}myproject/${RESET} ${BRIGHT_BLUE}# Creates myproject.2024-03-15.bak/${RESET}"
return 1
fi
# Check if source exists
if [[ ! -e "$1" ]]; then
echo -e "${BRIGHT_RED}ERROR:${RESET} ${BRIGHT_CYAN}$1${RESET} does not exist"
return 1
fi
# Remove trailing slash for directories
local SOURCE="${1%/}"
local DATE_STAMP=$(date +"%Y-%m-%d")
local BACKUP_BASE="${SOURCE}.${DATE_STAMP}"
local BACKUP_PATH="${BACKUP_BASE}.bak"
# If backup already exists, add incrementing number
if [[ -e "${BACKUP_PATH}" ]]; then
local COUNTER=1
while [[ -e "${BACKUP_BASE}.${COUNTER}.bak" ]]; do
((COUNTER++))
done
BACKUP_PATH="${BACKUP_BASE}.${COUNTER}.bak"
fi
# Perform the backup
local CP_STATUS
if [[ -d "${SOURCE}" ]]; then
cp -r "${SOURCE}" "${BACKUP_PATH}"
CP_STATUS=$?
else
cp "${SOURCE}" "${BACKUP_PATH}"
CP_STATUS=$?
fi
if [[ ${CP_STATUS} -eq 0 ]]; then
echo -e "${BRIGHT_GREEN}Created backup:${RESET} ${BRIGHT_CYAN}${BACKUP_PATH}${RESET}"
else
echo -e "${BRIGHT_RED}Failed to create backup${RESET}"
return 1
fi
}
# Restore a backup of a file or directory
# Usage: unbak [file_or_directory]
function unbak() {
# Display help if no parameter provided
if [[ -z "${1}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}unbak${RESET}: Restore a backup of a file or directory"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}unbak${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}original_name${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}unbak${RESET} ${BRIGHT_YELLOW}config.yml${RESET} ${BRIGHT_BLUE}# Select from config.yml.*.bak${RESET}"
echo -e " ${BRIGHT_CYAN}unbak${RESET} ${BRIGHT_YELLOW}myproject${RESET} ${BRIGHT_BLUE}# Select from myproject.*.bak/${RESET}"
return 1
fi
# Remove trailing slash for directories
local TARGET="${1%/}"
# Find all backup files sorted by modification time (most recent first)
local BACKUPS=""
if [[ "$(uname)" == "Darwin" ]]; then
# macOS: use stat with -f for format
BACKUPS=$(find . -maxdepth 1 -name "${TARGET}.*.bak" -print0 2>/dev/null | xargs -0 stat -f "%m %N" 2>/dev/null | sort -rn | cut -d' ' -f2-)
else
# Linux/GNU: use stat with --format
BACKUPS=$(find . -maxdepth 1 -name "${TARGET}.*.bak" -print0 2>/dev/null | xargs -0 stat --format="%Y %n" 2>/dev/null | sort -rn | cut -d' ' -f2-)
fi
# Fallback: use ls if stat method failed or returned empty
if [[ -z "${BACKUPS}" ]]; then
BACKUPS=$(ls -t "${TARGET}".*.bak 2>/dev/null)
fi
# Check if any backups were found
if [[ -z "${BACKUPS}" ]]; then
echo -e "${BRIGHT_RED}ERROR:${RESET} No backups found for ${BRIGHT_CYAN}${TARGET}${RESET}"
return 1
fi
# Count the number of backups
local BACKUP_COUNT=$(echo "${BACKUPS}" | wc -l)
# Select backup to restore
local SELECTED_BACKUP
if [[ ${BACKUP_COUNT} -eq 1 ]]; then
# Only one backup exists, use it directly
SELECTED_BACKUP="${BACKUPS}"
echo -e "Found backup: ${BRIGHT_CYAN}${SELECTED_BACKUP}${RESET}"
else
# Multiple backups exist, let user choose
echo -e "Found ${BRIGHT_YELLOW}${BACKUP_COUNT}${RESET} backups for ${BRIGHT_CYAN}${TARGET}${RESET}:"
SELECTED_BACKUP=$(echo "${BACKUPS}" | createmenu)
# Check if user canceled the selection
if [[ -z "${SELECTED_BACKUP}" ]]; then
echo "Operation canceled."
return 1
fi
fi
# Show what we're about to do and confirm
if [[ -e "${TARGET}" ]]; then
echo -e "${BRIGHT_YELLOW}WARNING:${RESET} ${BRIGHT_CYAN}${TARGET}${RESET} will be overwritten"
fi
if ask "Restore from ${SELECTED_BACKUP}?" Y; then
# Remove existing target if it exists
if [[ -e "${TARGET}" ]]; then
rm -rf "${TARGET}"
fi
# Restore the backup
local CP_STATUS
if [[ -d "${SELECTED_BACKUP}" ]]; then
cp -r "${SELECTED_BACKUP}" "${TARGET}"
CP_STATUS=$?
else
cp "${SELECTED_BACKUP}" "${TARGET}"
CP_STATUS=$?
fi
if [[ ${CP_STATUS} -eq 0 ]]; then
echo -e "${BRIGHT_GREEN}Restored${RESET} ${BRIGHT_CYAN}${TARGET}${RESET} from ${BRIGHT_CYAN}${SELECTED_BACKUP}${RESET}"
else
echo -e "${BRIGHT_RED}Failed to restore backup${RESET}"
return 1
fi
else
echo "Operation canceled."
fi
}
# Show mount points in a pretty, human readable format
# NOTE: Not aliased to 'mount' because that breaks actual mount operations
alias mounts='command mount | column -t'
alias m='mounts'
alias um='umount'
# Your Linux system's filesystem table (or fstab) is a configuration table
# designed to automate mounting and unmounting file systems to a machine
# WARNING: Modifications to this file can make your system unbootable!
alias fstab='sudo cp /etc/fstab /etc/fstab.backup; edit /etc/fstab'
# Display disk space available and show file system type
if hascommand --strict duf; then
# duf Disk Usage/Free Utility
# Link: https://github.com/muesli/duf
# Link: https://linuxhint.com/use-duf-command-in-linux/
#alias df='duf -hide-fs squashfs'
alias lll='duf -only local'
elif hascommand --strict vizex; then
# Visualize disk space usage for every partition and media on the user's machine
# Link: https://github.com/bexxmodd/vizex
alias lll='vizex'
else
# df provides valuable information on disk space utilization
# Link: https://www.geeksforgeeks.org/df-command-linux-examples/
if hascommand --strict grc; then
alias lll='colourify \df --human-readable --print-type --exclude-type=squashfs --exclude-type=tmpfs --exclude-type=devtmpfs --exclude-type=efivarfs'
else
alias lll='command df --human-readable --print-type --exclude-type=squashfs --exclude-type=tmpfs --exclude-type=devtmpfs --exclude-type=efivarfs'
fi
fi
# Get the block size for a partition
# Example: blocksize /dev/sda
alias blocksize='sudo blockdev --getbsz'
# dua parallel disk space analyzer in interactive mode TUI/GUI (in color)
# Link: https://github.com/Byron/dua-cli
# Install: curl -LSfs https://raw.githubusercontent.com/byron/dua-cli/master/ci/install.sh | \sh -s -- --git byron/dua-cli --target x86_64-unknown-linux-musl --crate dua
if [[ -f "${HOME}/.cargo/bin/dua" ]]; then
alias diskspace="${HOME}/.cargo/bin/dua i"
elif hascommand dua; then
alias diskspace='dua i'
# gdu fast parallel disk usage analyzer written in Go
# Link: https://github.com/dundee/gdu
# Install: go install github.com/dundee/gdu/v5/cmd/gdu@latest
elif hascommand gdu; then
alias diskspace='gdu'
# Ncdu is a disk usage analyzer with an ncurses interface
# Link: https://dev.yorhel.nl/ncdu
elif hascommand ncdu; then
alias diskspace='ncdu'
# diskonaut gives a visual treemap of what is taking up your disk space
# Link: https://github.com/imsnif/diskonaut
elif hascommand diskonaut; then
alias diskspace='diskonaut'
# Dust is like du written in Rust and more intuitive
# Link: https://github.com/bootandy/dust
elif hascommand dust; then
alias diskspace='dust -xd1'
# List all folders disk space sorted by largest space
else
alias diskspace='du -S | sort -n -r | more'
fi
# Just show the size of the current folder or a specified folder
alias totalsize='du -sh'
# List disk space of immediate folders one level deep
alias folders='du -kh --max-depth=1'
# f3 - test and check real capacity for USB devices (backup your drive first)
# Link: https://github.com/AltraMayor/f3
# Link: https://www.linuxbabe.com/command-line/f3-usb-capacity-fake-usb-test-linux
if hascommand --strict f3probe; then
alias usbtest='sudo f3probe --destructive --time-ops'
fi
# Alias to show MySQL's data directory location
if hascommand --strict mysqld; then
alias mysqldatadir='mysqld --verbose --help 2>/dev/null | grep ^datadir | column --table'
fi
# Alias to launch a document, file, or URL in it's default X application
if hascommand --strict xdg-open; then
alias open='runfree xdg-open'
fi
# Source fzf if installed via Git
[ -f ~/.fzf.bash ] && builtin source ~/.fzf.bash # Source if installed via Git
# Alias to fuzzy find files in the current folder(s), preview them, and launch in an editor
if hascommand --strict fzf; then
# Preview text files in a directory
if hascommand --strict xdg-open; then
preview() {
open $(fzf --info=inline --query="${1}" --preview="$PAGER {}")
}
else
preview() {
edit $(fzf --info=inline --query="${1}" --preview="$PAGER {}")
}
fi
# Find log files using file previews
if [[ -d /var/log ]]; then
# Alias to find and view log files
if hascommand bat; then
# Use bat for preview if available for better syntax highlighting
alias findlog='(cd /var/log && _LOG_SELECTED=$(sudo \find . -maxdepth 10 -iname "*.log" | \sort -f | sudo fzf --layout=reverse-list --info=inline --preview="sudo bat --color=always {}"); if [ -n "${_LOG_SELECTED}" ]; then echo -e "${BRIGHT_CYAN}Log file: ${BRIGHT_YELLOW}$(sudo \realpath "${_LOG_SELECTED}")${RESET}"; logview "$(sudo \realpath "${_LOG_SELECTED}")"; fi)'
else
# Fall back to the default PAGER
alias findlog='(cd /var/log && _LOG_SELECTED=$(sudo \find . -maxdepth 10 -iname "*.log" | \sort -f | sudo -E fzf --layout=reverse-list --info=inline --preview="sudo ${PAGER} {}"); if [ -n "${_LOG_SELECTED}" ]; then echo -e "${BRIGHT_CYAN}Log file: ${BRIGHT_YELLOW}$(sudo \realpath "${_LOG_SELECTED}")${RESET}"; logview "$(sudo \realpath "${_LOG_SELECTED}")"; fi)'
fi
fi
fi
# Alias for Midnight Commander (mc) to exit into current directory
# NOTE: Use mcc for the shell command line features as the subshell is very slow
# https://stackoverflow.com/questions/39017391/how-to-make-midnight-commander-exit-to-its-current-directory
# https://unix.stackexchange.com/questions/57439/slow-start-of-midnight-commander
# https://midnight-commander.org/ticket/3580
if [[ -f "/usr/lib/mc/mc-wrapper.sh" ]]; then
alias mcc='builtin source /usr/lib/mc/mc-wrapper.sh'
alias mc='builtin source /usr/lib/mc/mc-wrapper.sh --nosubshell'
elif hascommand --strict mc; then
alias mcc='command mc'
alias mc='command mc --nosubshell'
fi
# Check shell script syntax
if hascommand --strict shellcheck; then
alias schk='shellcheck'
fi
# Alias to view log files in real time (usually in /var/log)
# Check for lnav (The Log File Navigator)
# Link: https://lnav.org
if hascommand --strict lnav; then
alias logview='sudo \lnav -r -t'
alias logs='sudo \lnav -r -t /var/log/*.log' # All logs in /var/log
# Check for multitail (Multiple Tail)
# Link: https://www.vanheusden.com/multitail/
elif hascommand --strict multitail; then
alias logview='sudo \multitail -c -s 2'
alias logs='sudo \multitail -c -s 2 /var/log/*.log'
# Check for tail
elif hascommand --strict tail; then
alias logview='sudo tail -f'
alias logs="find /var/log -type f -exec file {} \; | grep 'text' | cut -d' ' -f1 | sed -e's/:$//g' | grep -v '[0-9]$' | xargs tail -f"
# If no log viewer is installed, use less as a fallback
else
alias logview='sudo \less +F'
fi
# Show logs in color
if hascommand --strict multitail; then
alias multitail='command multitail -c'
fi
#######################################################
### DATE AND TIME
#######################################################
# Show the time
alias now='date +"%T"'
# Show the short date
alias today='date +"%Y-%m-%d"'
# Stop watch
alias stopwatch='date && echo "Press CTRL+D to stop" && time read'
# Countdown timer and stop watch
# Link: https://github.com/trehn/termdown
# Fonts: banner3, big, computer, doh, letters, roman, small, standard, univers
# Link: http://www.figlet.org/examples.html
if hascommand --strict termdown; then
alias termdown='termdown --blink --end --critical 10 --font doh'
alias countdown="termdown --help | sed -n '/Options\:/q;p'"
alias timer='termdown'
alias clock='termdown --time'
fi
# Change a file's (or files using a wildcard) accessed and modified time to now
# NOTE: There is no file creation date in Unix, only access, modify, and change
alias filetimenow='touch -a -m'
#######################################################
### CPU, MEMORY, AND PROCESSES
#######################################################
# Display amount of free and used memory
alias free='free -h'
# When reporting a snapshot of the current processes:
# a = all users
# u = user-oriented format providing detailed information
# x = list the processes without a controlling terminal
# f = display a tree view of parent to child processes
#alias ps='ps auxf'
# Show top ten processes
alias cpu='ps aux | sort -r -nk +4 | head | $PAGER'
# Show CPU information
alias cpuinfo='lscpu | $PAGER'
# Show the USB device tree
if hascommand --strict lsusb; then
alias usb='lsusb -t'
fi
# Show the PCI device tree
if hascommand --strict lspci; then
alias pci='lspci -tv'
fi
# Alias top
# Link: https://ostechnix.com/some-alternatives-to-top-command-line-utility-you-might-want-to-know/
# Link: https://www.linuxlinks.com/alternativestotop/
# List of preferred top commands in order of preference
# Iterate over a list of preferred top commands in order of preference/features
_TOP_COMMANDS=("btop" "bpytop" "bashtop" "nmon" "glances" "ytop" "gtop" "htop")
for _TOP_COMMAND in "${_TOP_COMMANDS[@]}"; do
# Check if the command exists and is executable
if hascommand --strict "${_TOP_COMMAND}"; then
# Create an alias for the 'top' command using the found command
alias top="${_TOP_COMMAND}"
# Exit the loop once the first matching command is found
break
fi
done
# Clean up and don't leave the extra variables in the environment
unset _TOP_COMMANDS
unset _TOP_COMMAND
# Alias bottom
# Link: https://github.com/ClementTsang/bottom
if hascommand --strict btm; then
alias bottom='btm'
fi
# nvtop is a task monitor for NVIDIA, AMD and Intel GPUs
# Link: https://github.com/Syllo/nvtop
if hascommand --strict nvtop; then
alias gpu='nvtop'
fi
# Show jobs
alias j='jobs -l'
# Change the cursor to a crosshair to select a window (requires xprop)
if hascommand --strict xprop; then
alias windowinfo='xprop'
fi
# Get active X-window process ID after a 3 second delay (requires xdotool)
if hascommand --strict xdotool; then
alias activewinpid='sleep 3 && xdotool getactivewindow getwindowpid'
fi
# Alias to clear RAM memory cache, buffer and swap space
# Link: https://www.tecmint.com/clear-ram-memory-cache-buffer-and-swap-space-on-linux/
alias flushcache="sudo free -h && \\
sudo su -c \"echo 3 >'/proc/sys/vm/drop_caches' && \\
swapoff -a && \\
swapon -a && \\
printf '\n${BRIGHT_YELLOW}%s${RESET}\n\n' 'Ram-cache and Swap Cleared'\" root && \\
free -h"
#######################################################
### NETWORKING
#######################################################
# yt-dlp fork of youtube-dl - Set the default download folder
# Link: https://github.com/yt-dlp/yt-dlp
if hascommand --strict yt-dlp; then
alias yt-dlp='yt-dlp'
alias ytd='yt-dlp'
# Youtube-dl - Use best settings
# Link: https://github.com/ytdl-org/youtube-dl
elif hascommand --strict youtube-dl; then
alias youtube-dl="youtube-dl --format 'best[vcodec*=avc]'"
alias ytd='youtube-dl'
fi
# Checking for the presence of download utilities to create a download alias
# and an optional alias for downloading the LS_COLORS file to ~/.dircolors
# that requires the dircolors command used by ls to set color directory output
if hascommand aria2c; then
# aria2c is a lightweight multi-protocol & multi-source command-line
# download utility that supports HTTP/HTTPS, FTP, SFTP, BitTorrent, and
# Metalink with multiple connections and enhanced control over connections
# Link: https://aria2.github.io/
alias aria2c='aria2c --max-connection-per-server=5 --continue=true --async-dns=false'
if hascommand --strict dircolors; then
alias download-dircolors='aria2c --max-connection-per-server=5 --continue=true --async-dns=false -d "${HOME}" -o .dircolors https://raw.githubusercontent.com/ahmadassaf/dircolors/master/LS_COLORS'
fi
elif hascommand --strict wget; then
# wget is a non-interactive command-line file downloader for HTTP, HTTPS,
# and FTP that supports resuming downloads on more unstable connections
# Link: https://www.gnu.org/software/wget/
if hascommand --strict dircolors; then
alias download-dircolors='wget --continue -O "${HOME}/.dircolors" https://raw.githubusercontent.com/ahmadassaf/dircolors/master/LS_COLORS'
fi
elif hascommand --strict curl; then
# curl supports data transfer from or to a server using multiple protocols
# like HTTP, HTTPS, and FTP, and features resuming and redirect following
# Link: https://curl.se/
if hascommand --strict dircolors; then
alias download-dircolors='curl -C - -L -o "${HOME}/.dircolors" https://raw.githubusercontent.com/ahmadassaf/dircolors/master/LS_COLORS'
fi
fi
# Resume wget by default
if hascommand --strict wget; then
alias wget='wget -c'
fi
# Stop pinging after sending 5 ECHO_REQUEST packets
alias ping='ping -c 5'
# Do not wait for ping interval 1 second, go fast
alias fastping='ping -c 100 -i.2'
# Show open ports
alias ports='netstat -tulanp'
# Display and monitor the disk IO usage
# Link: https://www.geeksforgeeks.org/iotop-command-in-linux-with-examples/
if hascommand iotop; then
alias iotop='sudo iotop -o -a'
fi
# If nmap is installed, set an alias for a network scan of a host (takes a while)
# Scan delay slows things down but reduces throttling, anti-ddos, auto-block
# Link: https://nmap.org/
# Example: netscan localhost
if hascommand nmap; then
alias netscan='sudo nmap --scan-delay 1.1s -v --resolve-all -A -sTUV'
fi
# Get local IP addresses
if hascommand --strict ip; then
alias iplocal="ip -br -c a"
else
alias iplocal="ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'"
fi
# Get public IP address (several options below)
alias ipexternal='wget -O - -q icanhazip.com && echo'
# alias ipexternal='wget -qO- ifconfig.me/ip && echo'
# alias ipexternal='curl ipinfo.io/ip && echo'
# Make it easy to disable and reenable the Teamviewer service
# so that it's not constantly running and only when you need it
if hascommand --strict teamviewer; then
alias teamviewerstart='sudo teamviewer --daemon start'
alias teamviewerstop='sudo teamviewer --daemon stop'
fi
# Check SSL certificate expiration for a domain
# Usage: sslcheck [domain] [port]
function sslcheck() {
# Display help if no parameter provided
if [[ -z "${1}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}sslcheck${RESET}: Check SSL certificate expiration for a domain"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}sslcheck${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}domain${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}port${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}sslcheck${RESET} ${BRIGHT_YELLOW}google.com${RESET} ${BRIGHT_BLUE}# Uses default port 443${RESET}"
echo -e " ${BRIGHT_CYAN}sslcheck${RESET} ${BRIGHT_YELLOW}example.com 8443${RESET} ${BRIGHT_BLUE}# Custom port${RESET}"
return 1
fi
# Check if openssl is available
if ! hascommand --strict openssl; then
echo -e "${BRIGHT_RED}ERROR:${RESET} openssl is required but not installed"
return 1
fi
local DOMAIN="$1"
local PORT="${2:-443}"
# Remove protocol prefix if provided
DOMAIN="${DOMAIN#https://}"
DOMAIN="${DOMAIN#http://}"
# Remove trailing slash and path
DOMAIN="${DOMAIN%%/*}"
echo -e "Checking SSL certificate for ${BRIGHT_CYAN}${DOMAIN}:${PORT}${RESET}..."
# Get certificate expiry date
local CERT_INFO
CERT_INFO=$(echo | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:${PORT}" 2>/dev/null | openssl x509 -noout -dates 2>/dev/null)
if [[ -z "${CERT_INFO}" ]]; then
echo -e "${BRIGHT_RED}ERROR:${RESET} Could not retrieve certificate from ${BRIGHT_CYAN}${DOMAIN}:${PORT}${RESET}"
echo -e "Possible causes: domain doesn't exist, no SSL, connection refused, or timeout"
return 1
fi
# Extract the expiry date
local EXPIRY_DATE
EXPIRY_DATE=$(echo "${CERT_INFO}" | grep 'notAfter=' | cut -d= -f2)
if [[ -z "${EXPIRY_DATE}" ]]; then
echo -e "${BRIGHT_RED}ERROR:${RESET} Could not parse certificate expiry date"
return 1
fi
# Calculate days until expiry (cross-platform)
local EXPIRY_EPOCH
local NOW_EPOCH
local DAYS_LEFT
# Normalize date string (handle double spaces in day)
EXPIRY_DATE=$(echo "${EXPIRY_DATE}" | sed 's/ */ /g')
if [[ "$(uname)" == "Darwin" ]]; then
# macOS date syntax
EXPIRY_EPOCH=$(date -j -f "%b %d %T %Y %Z" "${EXPIRY_DATE}" "+%s" 2>/dev/null)
else
# Linux/GNU date syntax
EXPIRY_EPOCH=$(date -d "${EXPIRY_DATE}" "+%s" 2>/dev/null)
fi
NOW_EPOCH=$(date "+%s")
if [[ -n "${EXPIRY_EPOCH}" ]]; then
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
else
# Fallback: just show the date without days calculation
echo -e "Certificate expires: ${BRIGHT_YELLOW}${EXPIRY_DATE}${RESET}"
echo -e "${BRIGHT_YELLOW}(Could not calculate days remaining)${RESET}"
return 0
fi
# Display result with color based on urgency
local COLOR
local STATUS
if [[ ${DAYS_LEFT} -lt 0 ]]; then
COLOR="${BRIGHT_RED}"
STATUS="EXPIRED"
echo -e "${COLOR}Certificate ${STATUS}${RESET} $((-DAYS_LEFT)) days ago"
elif [[ ${DAYS_LEFT} -lt 7 ]]; then
COLOR="${BRIGHT_RED}"
STATUS="CRITICAL"
echo -e "${COLOR}${STATUS}:${RESET} Certificate expires in ${COLOR}${DAYS_LEFT}${RESET} days"
elif [[ ${DAYS_LEFT} -lt 30 ]]; then
COLOR="${BRIGHT_YELLOW}"
STATUS="WARNING"
echo -e "${COLOR}${STATUS}:${RESET} Certificate expires in ${COLOR}${DAYS_LEFT}${RESET} days"
else
COLOR="${BRIGHT_GREEN}"
echo -e "${COLOR}OK:${RESET} Certificate expires in ${COLOR}${DAYS_LEFT}${RESET} days"
fi
echo -e "Expiry date: ${BRIGHT_CYAN}${EXPIRY_DATE}${RESET}"
}
#######################################################
### VISUAL
#######################################################
# Colorize the grep command output for ease of use (good for log files)
alias grep='grep --color=auto'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
# Show the currectdisplay server
alias whichdisplay='echo -e "\033[1;33m${XDG_SESSION_TYPE^}\033[0m"'
# Colorize messages for the kernel ring buffer
alias dmesg='dmesg --color'
# Clear the screen with both clear and cls shorthand
alias cls='clear'
# Search and play YouTube videos in the terminal
if hascommand --strict ytfzf; then
alias yt='ytfzf -l -t'
fi
# Convert images to ASCII in color
if hascommand --strict jp2a; then
alias jp2a='jp2a --color'
fi
# List fonts for toilet
if hascommand --strict toilet; then
alias toiletfont='ls /usr/share/figlet'
alias toiletfontlist='for file in /usr/share/figlet/*.?lf; do toilet -f $(basename "$file") $(basename "$file"); done'
fi
# Launch the cmatrix screen saver (if installed)
if hascommand --strict cmatrix; then
alias matrix='cmatrix -b'
fi
# Wrap text breaking at whitespace only
# use -w or --width=WIDTH to set line width (default 80)
if hascommand --strict fold; then
alias wrap='fold --spaces'
fi
# Fix spacing and reformat text while preserving paragraphs
if hascommand --strict fmt; then
alias unwrap='fmt'
fi
#######################################################
### FILE SYSTEMS
#######################################################
# Aliases for BtrFS file systems
if hascommand --strict btrfs; then
alias btrcheck='sudo btrfs fi show' # Check status of raid drives
alias btrstats='sudo btrfs device stats' # Show device statistics
alias btrscrub='sudo btrfs scrub start' # Start a scrub
alias btrpause='sudo btrfs scrub cancel' # Cancel or pause a scrub
alias btrresume='sudo btrfs scrub resume' # Resume a paused scrub
alias btrstatus='sudo btrfs scrub status' # Show status of a scrub
alias btrdefragfile='sudo btrfs filesystem defragment -vf' # Defrag a file
alias btrdefragdir='sudo btrfs filesystem defragment -rvf' # Defrag a directory
fi
# Aliases for Ext3 file systems
if hascommand --strict mkfs.ext3; then
alias ext3check='sudo fsck.ext3' # Check and repair Ext3 filesystem
alias ext3stats='sudo dumpe2fs -h' # Show statistics for Ext3 filesystem
alias ext3trim='sudo fstrim -v' # Trim unused blocks on Ext3 filesystem
fi
# Aliases for EXT4 file systems
if hascommand --strict mkfs.ext4; then
alias ext4check='sudo fsck.ext4' # Check and repair EXT4 filesystem
alias ext4stats='sudo dumpe2fs -h' # Show statistics for EXT4 filesystem
alias ext4trim='sudo fstrim -v' # Trim unused blocks on EXT4 filesystem
fi
# Aliases for F2FS file systems
if hascommand --strict mkfs.f2fs; then
alias f2fscheck='sudo fsck.f2fs' # Check and repair F2FS filesystem
alias f2fsstats='sudo dump.f2fs' # Show statistics for F2FS filesystem
alias f2fstrim='sudo fstrim -v' # Trim unused blocks on F2FS filesystem
fi
# Aliases for XFS file systems
if hascommand --strict mkfs.xfs; then
alias xfscheck='sudo xfs_repair' # Check and repair XFS filesystem
alias xfsstats='sudo xfs_info' # Show information about XFS filesystem
alias xfstrim='sudo fstrim -v' # Trim unused blocks on XFS filesystem
fi
# Aliases for ZFS file systems
if hascommand --strict zpool; then
alias zfscheck='sudo zpool scrub' # Check and repair ZFS pool
alias zfsstats='sudo zpool iostat' # Show ZFS pool statistics
alias zfstrim='sudo zpool trim' # Trim unused blocks on ZFS pool
alias zfsstatus='sudo zpool status' # Check status of ZFS pool
alias zfsscrub='sudo zpool scrub' # Start a scrub on ZFS pool
alias zfspause='sudo zpool scrub -s' # Pause a scrub on ZFS pool
alias zfsresume='sudo zpool scrub' # Resume a paused scrub (just re-run scrub)
alias zfsscrubstatus='sudo zpool status -v' # Show status of ZFS pool scrub
# NOTE: ZFS does not support traditional defragmentation.
# 'zfs send -R' sends a replication stream (raw binary) to stdout -- not defrag.
fi
# Aliases for NTFS filesystems
if hascommand --strict ntfsfix; then
alias ntfscheck='sudo ntfsfix' # Check and repair NTFS filesystem
alias ntfsstats='sudo ntfsinfo' # Show information about NTFS filesystem
fi
# Aliases for exFAT, FAT12, FAT16, and FAT32 filesystems
# I'm on a diet, so no jokes please. This whole section. lol
if hascommand --strict dosfsck; then
alias fatcheck='sudo dosfsck' # Check and repair FAT filesystem
alias fatstats='sudo fsck.fat -i' # Show information about FAT filesystem
fi
#######################################################
### DISTROBOX
#######################################################
# If Distrobox is installed...
# Link: https://github.com/89luca89/distrobox
# Link: https://github.com/89luca89/distrobox/blob/main/docs/usage/usage.md
if hascommand --strict distrobox; then
# Create aliases for the most common commands
alias db='distrobox'
alias {dbe,distrobox-enter}='_distrobox-enter'
alias dbl='distrobox list'
alias dbls='distrobox-list-simple'
alias dbs='distrobox stop'
alias dbsa='distrobox-stop-all'
alias dbhe='distrobox-host-exec'
alias {dbup,distrobox-upgrade}='_distrobox-upgrade'
alias {dbc,distrobox-check}='docker system df -v'
# List only the names of each container
# This takes no arguments and will mostly be used in scripts
function distrobox-list-simple() {
distrobox-list | awk -F '|' '{print $2}' | tail -n +2 | sed 's/^[ \t]*//;s/[ \t]*$//'
}
# Choose an installed container
# This takes no arguments and will mostly be used in scripts
function distrobox-pick() {
distrobox-list-simple | createmenu
}
# Loop through and stop all containers
function distrobox-stop-all() {
local _BOX
while IFS= read -r _BOX; do
[[ -n "${_BOX}" ]] && \distrobox stop --yes "${_BOX}"
done <<< "$(distrobox-list-simple)"
}
function _distrobox-enter() {
if [ $# -eq 0 ]; then
command distrobox enter "$(distrobox-pick)"
else
command distrobox enter "$@"
fi
}
function _distrobox-upgrade() {
if [ $# -eq 0 ]; then
command distrobox upgrade --all
else
command distrobox upgrade "$@"
fi
}
fi
#######################################################
### MISCELLANEOUS
#######################################################
# Raw kernel ring buffer messages
alias kernelmessages='sudo dmesg'
# Filter kernel messages for problems
alias kernelerrors='sudo dmesg | grep -iE "error|fail|warn|critical|panic|oops|segfault|timeout|refused|denied|unable|cannot|invalid|corrupt|bad|broken|missing|not found|no such|permission denied"'
# Alias to show the current TTY (CTRL+ALT+1 through 7)
alias whichtty='tty | sed -e "s:/dev/::"'
# Conditionally set alias for checking failed login attempts
if hascommand aureport; then
# Use aureport to generate a report of failed authentication attempts
alias checkloginfailures='sudo \aureport -au --failed | command less'
elif hascommand lastb; then
# If aureport is not available, check for the lastb command
alias checkloginfailures='sudo \lastb | command less'
else
# If neither executable command is available, check for system log files
if [[ -f /var/log/auth.log ]]; then
# Use grep to search for 'FAILED LOGIN' entries in auth.log
alias checkloginfailures="sudo \grep 'FAILED LOGIN' /var/log/auth.log | command less"
elif [[ -f /var/log/secure ]]; then
# Use grep to search for 'FAILED LOGIN' entries in secure
alias checkloginfailures="sudo \grep 'FAILED LOGIN' /var/log/secure | command less"
#else
# # Provide feedback if no methods are available for checking login failures
# echo "Error: No common methods or logs found for checking login failures"
fi
fi
# Spell check a word with DidYouMean
# Link: https://github.com/hisbaan/didyoumean
if hascommand --strict dym; then
alias spell='dym -n 10 -y'
fi
# Alias's for safe and forced reboots
alias rebootsafe='sudo shutdown -r now'
alias rebootforce='sudo shutdown -r -n now'
# If OpenSSL is installed...
if hascommand --strict openssl; then
# If base64 isn't available, use openssl's version to do the same thing
if ! hascommand --strict base64; then
alias base64='openssl base64'
fi
# If base64 isn't available, use openssl's version to do the same thing
if ! hascommand --strict md5sum; then
alias md5sum='openssl md5'
fi
# SHA1
alias sha1='openssl sha1'
fi
# Update the fireware on Linux automatically and safely using fwupdmgr
# (used by companies like Corsair, Dell, HP, Intel, Logitech, etc.)
# Install: pkginstall fwupdmgr
# Link: https://fwupd.org
if hascommand --strict fwupdmgr; then
alias firmwareupdate='fwupdmgr get-devices && fwupdmgr refresh && fwupdmgr get-updates && fwupdmgr update'
fi
# When updating virus definitions, do it as root
if hascommand --strict freshclam; then
alias freshclam='sudo freshclam'
fi
# Check passwords with cracklib
# Link: https://www.cyberciti.biz/security/linux-password-strength-checker/
if hascommand --strict cracklib-check; then
alias pwcheck='cracklib-check<<<'
fi
# Check for the availability of web browsers
# Link: https://www.geeksforgeeks.org/using-lynx-to-browse-the-web-from-the-linux-terminal/
# Link: https://wiki.archlinux.org/title/ELinks
# Link: https://w3m.sourceforge.net/
# Link: http://www.aboutlinux.info/2007/02/links2-cross-platform-console-based-web.html
# Link: https://www.tecmint.com/command-line-web-browsers/
for TERMINAL_BROWSER in "w3m" "lynx" "elinks" "links2" "links"; do
if hascommand "${TERMINAL_BROWSER}"; then
# Show the Extreme Ultimate .bashrc README file in the available browser
if [[ -f "${BASHRC_INSTALL_DIR}/README.html" ]]; then
alias readme="${TERMINAL_BROWSER} ${BASHRC_INSTALL_DIR}/README.html"
break # Exit the loop once the first available browser is found
elif [[ -f "${HOME}/README.html" ]]; then
alias readme="${TERMINAL_BROWSER} ~/README.html"
break # Exit the loop once the first available browser is found
fi
fi
done
# If the readme alias is still not set, try markdown readers
if [[ $(type -t readme) != 'alias' ]]; then
# Check for the availability of markdown viewers
# Link: https://github.com/Textualize/frogmouth
# Link: https://github.com/charmbracelet/glow
# Link: https://github.com/swsnr/mdcat
# Link: https://github.com/ttscoff/mdless
for TERMINAL_MARKDOWN_VIEWER in "frogmouth" "glow" "mdcat" "mdless"; do
if hascommand "${TERMINAL_MARKDOWN_VIEWER}"; then
# If README.md exists in .config/bashrc, set 'readme' alias
if [[ -f "${BASHRC_INSTALL_DIR}/README.md" ]]; then
alias readme="${TERMINAL_MARKDOWN_VIEWER} ${BASHRC_INSTALL_DIR}/README.md"
break # Exit the loop once the first available browser is found
# If README.md exists in home, set 'readme' alias
elif [[ -f "${HOME}/README.md" ]]; then
alias readme="${TERMINAL_MARKDOWN_VIEWER} ~/README.md"
break # Exit the loop once the first available browser is found
fi
fi
done
fi
# If glow is installed, set up some aliases for it
# Link: https://github.com/charmbracelet/glow
if hascommand glow; then
# Use glow's pager option
alias glow='glow --all --pager'
# Local only version for security
alias glowsafe='glow --all --pager --local'
fi
# If mdcat is installed, use pagination
# Link: https://github.com/swsnr/mdcat
if hascommand mdcat; then
alias mdcat='mdcat --paginate'
fi
# jless is a command-line JSON viewer
# Link: https://jless.io/
if hascommand --strict jless; then
alias json='jless'
# jnv - Interactive JSON viewer and jq filter editor
# Link: https://github.com/ynqa/jnv
elif hascommand --strict jnv && hascommand --strict jq; then
alias json='jnv'
# fx is a JavaScript Object Notation (JSON) viewer
# Link: https://github.com/antonmedv/fx
elif hascommand --strict fx; then
alias json='fx'
# jq - Pretty Print JSON Files in the terminal
# Link: https://itsfoss.com/pretty-print-json-linux/
elif hascommand --strict jq; then
alias json='jq'
fi
# Pretty print JSON
if hascommand --strict jq; then
alias jsonformat='jq'
fi
# baca TUI ebook reader
# Link: https://github.com/wustho/baca
# Install: pip install baca
if hascommand --strict baca; then
alias ebook=baca
fi
if hascommand RHVoice-test; then
# RHVoice is a free and open-source multilingual speech synthesizer
# Link: https://rhvoice.org
alias say='RHVoice-test --rate 130 --volume 100 --profile lyubov <<<'
alias {saycb,sayclipboard}='clipboard | RHVoice-test --rate 130 --volume 100 --profile lyubov'
alias saygreet='echo "$(HOUR=$(date +%H); echo "$( [ "$HOUR" -lt 12 ] && echo "Good morning" || { [ "$HOUR" -lt 17 ] && echo "Good afternoon" || { [ "$HOUR" -lt 21 ] && echo "Good evening" || echo "Good night"; }; }) $(getent passwd "${USER}" | cut -d ":" -f 5 | cut -d "," -f 1 || echo "${USER}")! It is $(date +"%-I. %M. %p" | sed "s/AM/A. M./; s/PM/P. M./"). $(grep -q "^To " /var/mail/${USER} 2>/dev/null && echo "You have new messages." || echo "")")" | RHVoice-test --rate 130 --volume 100 --profile lyubov'
elif hascommand espeak; then
# Speak with female voice
# Link: https://thomashunter.name/posts/2012-05-21-female-voice-using-espeak
alias say='espeak -ven-us+f4 -s170'
alias {saycb,sayclipboard}='clipboard | espeak -ven-us+f4 -s170'
alias saygreet='echo "$(HOUR=$(date +%H); echo "$( [ "$HOUR" -lt 12 ] && echo "Good morning" || { [ "$HOUR" -lt 17 ] && echo "Good afternoon" || { [ "$HOUR" -lt 21 ] && echo "Good evening" || echo "Good night"; }; }) $(getent passwd "${USER}" | cut -d ":" -f 5 | cut -d "," -f 1 || echo "${USER}")! It is $(date +"%-I. %M. %p" | sed "s/AM/A. M./; s/PM/P. M./"). $(grep -q "^To " /var/mail/${USER} 2>/dev/null && echo "You have new messages." || echo "")")" | espeak -ven-us+f4 -s170'
fi
# Aliases to modify GRUB
# https://www.howtogeek.com/196655/how-to-configure-the-grub2-boot-loaders-settings/
alias grubedit='edit /etc/default/grub'
alias grubsave='sudo update-grub'
# Shows all the script files in a directory and which shell they require
alias scanscripts='grep -E -r "^#!/" ./* 2> /dev/null'
# Vlock - lock all terminals
# Install: sudo apt install vlock
# Install: sudo pacman -S kbd
# https://odysee.com/@DistroTube:2/lock-your-terminal-sessions-with-vlock:0
if hascommand --strict vlock; then
# Alias Vlock to lock all terminals and can be typed with one hand
alias lok='vlock --all'
fi
# Alias thefuck that corrects errors in previous console commands
# Link: https://github.com/nvbn/thefuck
if hascommand --strict thefuck; then
eval "$(thefuck --alias fix)"
fi
# Alias to restart KDE Plasma desktop without rebooting or logging out
if hascommand --strict kstart5 && hascommand --strict kquitapp5; then
alias {plasmarestart,kderestart}='kquitapp5 plasmashell && runfree kstart5 plasmashell'
fi
#######################################################
# Set compression aliases with automatic parallel processing if installed
# Note: Use [-P] to preserve full paths
# To encrypt the output, pipe it to the built-in encrypt function:
# mk... - [files_or_directories_to_compress] | encrypt [encrypted_file.ext.gpg]
# Example: mkgz - file1 file2 dir1 | encrypt archive.tar.gz.gpg
# Usage:
# mkbz2 [-P] [archive.tar.bz2] [files_or_directories_to_compress]
# Create a bz2 compressed tar archive (using pbzip2 if available)
# unbz2 [-P] [archive.tar.bz2]
# Extract a bz2 compressed tar archive (using pbzip2 if available)
# mkgz [-P] [archive.tar.gz] [files_or_directories_to_compress]
# Create a gz compressed tar archive (using pigz if available)
# ungz [-P] [archive.tar.gz]
# Extract a gz compressed tar archive (using pigz if available)
# mkxz [-P] [archive.tar.xz] [files_or_directories_to_compress]
# Create an xz compressed tar archive (using pixz if available)
# unxz [-P] [archive.tar.xz]
# Extract an xz compressed tar archive (using pixz if available)
# mkzst [-P] [archive.tar.zst] [files_or_directories_to_compress]
# Create a zstd compressed tar archive (using all CPU cores)
# unzst [-P] [archive.tar.zst]
# Extract a zstd compressed tar archive (using all CPU cores)
# mkzip [archive.zip] [files_or_directories_to_compress]
# Create a zip archive with maximum compression
# unzip [archive.zip]
# Extract a zip archive
# mk7z [archive.7z] [files_or_directories_to_compress]
# Create a 7z archive with ultra compression and multi-threading
# un7z [archive.7z]
# Extract a 7z archive (supports many formats)
# mktar [-P] [archive.tar] [files_or_directories_to_compress]
# Create an uncompressed tar archive
# untar [-P] [archive.tar]
# Extract an uncompressed tar archive
# mkiso [archive.iso] [files_or_directories_to_compress]
# Create an ISO image
# uniso [archive.iso]
# Extract an ISO image
#######################################################
# Check for pbzip2 parallel block-sorting file compressor
# Link: https://github.com/ruanhuabin/pbzip2
# Note: bzip2 can reduce files by 10-15% more than gzip on average but is slower
# and has better integrity checking even if slightly less common
if hascommand --strict pbzip2; then
alias mkbz2='tar --use-compress-program="pbzip2 -9" -cvf'
alias unbz2='tar --use-compress-program=pbzip2 -xvf'
else
alias mkbz2='tar --use-compress-program="bzip2 -9" -cvf'
alias unbz2='tar -xvjf'
fi
# Check for pigz parallel implementation of the gzip file compressor
# Link: https://zlib.net/pigz/
# Note: gzip is significantly faster for both compression and decompression
# and is more widely supported (virtually universal)
if hascommand --strict pigz; then
alias mkgz='tar --use-compress-program="pigz -9" -cvf'
alias ungz='tar --use-compress-program=pigz -xvf'
else
alias mkgz='tar -cvzf'
alias ungz='tar -xvzf'
fi
# Check for pixz (parallel xz) which automatically indexes during compression
# Link: https://github.com/vasi/pixz
if hascommand --strict pixz; then
alias mkxz='tar --use-compress-program="pixz -9" -cvf'
alias unxz='tar --use-compress-program=pixz -xvf'
elif hascommand --strict xz; then
alias mkxz='tar --use-compress-program="xz -T0 -9" -cvf'
alias unxz='tar -xvJf'
fi
# Check for zstd with better compression ratios and built-in multi-threading
# Link: https://github.com/facebook/zstd
# Note: Generally considered one of the best modern compression tools
if hascommand --strict zstd; then
alias mkzst='tar --use-compress-program="zstd -T0 -19" -cvf'
alias unzst='tar --use-compress-program="zstd -T0" -xvf'
fi
# Check for zip compression utility (unzip is included and not needed to alias)
# Link: http://infozip.sourceforge.net/Zip.html
# Note: Best for greatest multi-platform compatibility
if hascommand --strict zip; then
alias mkzip='zip -9r'
fi
# Check for 7-zip (p7zip) compression utility
# Link: https://sourceforge.net/projects/p7zip/
# Note: Excellent compression ratio and supports many formats
if hascommand --strict 7z; then
alias mk7z='7z a -mx=9 -mmt=on'
alias un7z='7z x'
alias uniso='7z x'
fi
# ISO image creation and extraction
if hascommand --strict mkisofs; then
alias mkiso='mkisofs -o'
elif hascommand --strict genisoimage; then
alias mkiso='genisoimage -o'
fi
# Standard tar without compression
alias mktar='tar -cvf' # Create tar file
alias untar='tar -xvf' # Extract tar file
#######################################################
# Tmux Terminal Multiplexor Support
# Link: https://github.com/tmux/tmux/wiki
#######################################################
# Aliases for tmux terminal multiplexer if installed
if hascommand tmux; then
# Enhance clear to also clear the TMUX scrollback buffer
if [[ -n "${TMUX}" ]]; then
alias clear='clear && tmux clear-history'
fi
# If you connect to a session with a different resolution terminal
# this will freeze the size of the Tmux session window and not auto-resize
#tmux resize-window -A &> /dev/null
# Load TMUX with default session defined by _TMUX_LOAD_SESSION_NAME
# If TMUX is already running, switch to a session name passed in as a parameter
# TIP: Use CTRL+d to detach your session which closes but leaves the session
# running. CTRL+d will also exit bash once outside of TMUX.
# alias tm='tmux a -t main || tmux new -s main'
function tm() {
# Get the passed in or default session name
if [[ -n "${@}" ]]; then
local SESSION_NAME="${@}"
elif [[ -n "${_TMUX_LOAD_SESSION_NAME}" ]]; then
local SESSION_NAME="${_TMUX_LOAD_SESSION_NAME}"
elif [[ "$(tmux list-sessions 2> /dev/null | wc -l)" -gt 0 ]]; then
local SESSION_NAME="$(tmux ls -F "#{session_name}" | createmenu)"
else
local SESSION_NAME="$(whoami)"
fi
# Create the session if it doesn't exist
TMUX='' tmux -u new-session -d -s "${SESSION_NAME}" 2> /dev/null
# Attach if outside of TMUX
if [[ -z "$TMUX" ]]; then
tmux -u attach -t "${SESSION_NAME}" 2> /dev/null && exit
# Switch if we are already inside of TMUX
else
tmux -u switch-client -t "${SESSION_NAME}" 2> /dev/null
fi
}
function tmsessiongroup() {
# Tmux allows you to create "session groups" - multiple sessions that can all attach to the same set of windows
# (Allow multiple monitors to attach to the same session but independently view separate panes)
# Link: https://unix.stackexchange.com/questions/282365/using-multiple-terminal-x-windows-with-one-tmux-session
# To switch from one to the other using xdotool:
# xdotool search --name 'session1:0:' windowactivate
# xdotool search --name 'session2:1:' windowactivate
# Get a list of sessions
local _TMUX_OPEN_SESSIONS="$(tmux ls -F "#{session_name}" 2> /dev/null)"
# If the chosen session is blank
if [ -z "${_TMUX_OPEN_SESSIONS}" ]; then
# Show an error and exit
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}No session found${RESET}"
return 1
fi
# If a new session name was not passed in...
if [ $# -eq 0 ]; then
# Get the name of the last session
local _TMUX_LAST_SESSION=$(echo "${_TMUX_OPEN_SESSIONS}" | head -n 1)
# Generate a new session name based on the last session
local _COUNTER=2
local _TMUX_RANDOM_NAME
while [ -z "${_TMUX_RANDOM_NAME}" ]; do
if echo "${_TMUX_OPEN_SESSIONS}" | grep -qxF "${_TMUX_LAST_SESSION}${_COUNTER}"; then
let _COUNTER=_COUNTER+1
else
_TMUX_RANDOM_NAME="${_TMUX_LAST_SESSION}${_COUNTER}"
fi
done
# Ask for the new session name from a menu
read -e -i "${_TMUX_RANDOM_NAME}" -p "New Session Name: " _TMUX_NEW_SESSION
else
# Use the passed in session name
local _TMUX_NEW_SESSION="${@}"
fi
# Make sure the new session name doesn't already exist
if echo "${_TMUX_OPEN_SESSIONS}" | grep -qxF "${_TMUX_NEW_SESSION}"; then
# Show an error and exit
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}Session ${BRIGHT_YELLOW}${_TMUX_NEW_SESSION}${BRIGHT_CYAN} already exists${RESET}"
return 1
fi
# If the chosen session is blank
if [[ -z "${_TMUX_NEW_SESSION}" ]]; then
# Show an error and exit
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}You must specify a new session name${RESET}"
return 1
fi
# Change the prompt
local _PS3_OLD="${PS3}"
PS3="Choose an existing session to connect to: "
# Ask for the session to connect to
local _TMUX_EXISTING_SESSION=$(echo "${_TMUX_OPEN_SESSIONS}" | sort | createmenu)
# Put the prompt back to its original value
PS3="${_PS3_OLD}"
# If the chosen session is blank
if [ -z "${_TMUX_EXISTING_SESSION}" ]; then
# Show an error and exit
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}You must specify an existing session${RESET}"
return 1
fi
# Create a session group
if tmux -u new-session -t "${_TMUX_EXISTING_SESSION}" -s "${_TMUX_NEW_SESSION}"; then
return 0
else
# Show an error and exit
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}Could not connect to session ${BRIGHT_YELLOW}${_TMUX_EXISTING_SESSION}${RESET}"
return 1
fi
}
# Detach from Tmux - and optionally run a command if specified
# For example, detach and log into an SSH server already running Tmux
# so you don't run a Tmux session inside of your local Tmux session
# Example: tmd ssh username@server.net
function tmd() {
# If Tmux is running...
if [[ -n "$TMUX" ]]; then
# If no parameters were pass in...
if [ $# -eq 0 ]; then
# Detach from the session
tmux detach
else
# Detach and then execute the parameters
tmux detach -E "$*"
fi
# Tmux is NOT running so no need to detach...
else
# Only do something if a command is passed in...
if [ $# -gt 0 ]; then
# Run the command
eval "${@}"
fi
fi
}
# List and choose a buffer (clipboard history)
alias {tmb,tmc}='tmux choose-buffer'
# List all the sessions
alias tmlist='tmux ls'
# List all the connected clients
alias tmclients='tmux lsc'
# Create a new session
alias {tmnew,tmjoin}='tm'
# Attach to an existing session
#alias tmattach='tmux -u new-session -A -s'
alias tmattach='tmux -u attach-session -t "$(tmux ls -F "#{session_name}" | createmenu)"'
# Rename a session: tmrename [session] [new_name]
alias tmrename='tmux rename -t'
# Kill a session
alias tmkill='tmux kill-session -t'
# Kill all sessions
alias tmreset='tmux kill-server'
# List all the bound keys
alias tmlistkeys='grep -E "^(bind-key|bind)\s+" ~/.tmux.conf | sort'
# Tmux is not installed, but Zellij is and is very user friendly
elif hascommand zellij; then
# Redirect these Tmux aliases to Zellij instead
alias {tm,tmnew,tmjoin}='znew'
alias tmlist='zlist'
alias tmattach='zattach'
alias tmkill='zkill'
alias tmreset='zreset'
# Tmux is not installed, but Screen is
# Link: https://linuxize.com/post/how-to-use-linux-screen/
elif hascommand screen; then
alias tm="echo 'Tmux is not installed, but screen is installed on this system.'"
fi
#######################################################
# Zellij Terminal Multiplexor Support
# Link: https://zellij.dev/documentation/introduction.html
#######################################################
# Aliases for Zellij terminal multiplexer if installed
if hascommand zellij; then
# Load Zellij with a default session named the logged in user
# You can also pass in a specific session name as a parameter
function znew() {
# Get the passed in or default session name
if [[ -n "${@}" ]]; then
local SESSION_NAME="${@}"
elif [[ "$(zellij list-sessions 2> /dev/null | wc -l)" -gt 0 ]]; then
local SESSION_NAME=""
else
local SESSION_NAME="$(whoami)"
fi
# Create the session if it doesn't exist
zellij --session "${SESSION_NAME}" 2> /dev/null || zellij attach "${SESSION_NAME}"
}
# Aliases for Zellij
alias {zj,zjoin}='znew'
alias zlist='zellij list-sessions'
alias zattach='zellij attach "$(zellij list-sessions | createmenu)"'
alias zkill='zellij kill-session'
alias zreset='zellij kill-all-sessions'
# Include the bash completion and aliases from Zellij (i.e. zr, zrf, ze, zef)
builtin source <(zellij setup --generate-completion bash) >/dev/null 2>&1
fi
#######################################################
# Abduco lets programs run independently of the controlling terminal
# Note: CTRL+\ will detach the session
# Link: https://www.brain-dump.org/projects/abduco/
#######################################################
if hascommand abduco; then
# Start or connect to an abduco session
function aa() {
if [[ ${#} -eq 0 ]]; then
if [[ $(abduco | wc -l) -gt 1 ]]; then
abduco -a "$(abduco | tail -n +2 | sed 's:\s\+:\t:g' | cut -f 5 | createmenu)" bash
else
abduco -c "${USER}" bash
fi
else
abduco -A "${@}" bash
fi
}
# Connect to an abduco session read-only
function aaro() {
abduco -Ar "${@}" bash
}
# List any abduco sessions
alias aals='abduco'
# Kill all abduco sessions
alias aareset='pkill abduco'
fi
#######################################################
# Easy Cross-Platform Package Management Aliases
#######################################################
# Depending on the installed package managers, set up some package aliases
if hascommand --strict paru; then # Arch
# Link: https://github.com/Morganamilo/paru
# Link: https://itsfoss.com/paru-aur-helper/
# NOTE: To get search results to start at the bottom and go upwards, enable BottomUp in paru.conf
alias has='paru -Si'
alias pkgupdateall='paru -Syyu --sudoloop --noconfirm --newsonupgrade && if type flatpak >/dev/null 2>&1; then flatpak update --appstream && flatpak update --assumeyes --noninteractive; fi && if type snap >/dev/null 2>&1; then sudo snap refresh; fi && if type tldr >/dev/null 2>&1; then tldr --update; fi'
alias pkgupdate='paru --sync --sudoloop --noconfirm'
alias pkginstall='paru --sync --sudoloop --noconfirm'
alias pkgremove='paru --remove'
alias pkgclean='paru --clean'
alias pkgsearch='paru --bottomup'
alias pkglist='paru -Qe'
alias pkglistmore='paru -Q' # Also includes dependencies
alias aurcheck='paru -Qua' # | sort | command less --no-init --ignore-case --LONG-PROMPT --LINE-NUMBERS'
elif hascommand --strict yay; then # Arch
# Link: https://github.com/Jguer/yay
alias has='yay -Si'
alias pkgupdateall='yay -Syyu --sudoloop && if type flatpak >/dev/null 2>&1; then flatpak update --appstream && flatpak update; fi && if type snap >/dev/null 2>&1; then sudo snap refresh; fi && if type tldr >/dev/null 2>&1; then tldr --update; fi'
alias pkgupdate='yay -S'
alias pkginstall='yay -S'
alias pkgremove='yay -Rns'
alias pkgclean='yay -Yc'
alias pkgsearch='yay'
alias pkglist='yay -Qe'
alias pkglistmore='yay -Q' # Also includes dependencies
alias aurcheck='yay -Qua' # | sort | command less --no-init --ignore-case --LONG-PROMPT --LINE-NUMBERS'
elif hascommand --strict pamac; then # Manjaro
# Link: https://wiki.manjaro.org/index.php/Pamac
alias has='sudo pamac info'
alias pkgupdateall='sudo pamac upgrade -a && if type tldr >/dev/null 2>&1; then tldr --update; fi'
alias pkgupdate='sudo pamac update'
alias pkginstall='sudo pamac install'
alias pkgremove='sudo pamac remove'
alias pkgclean='sudo pamac remove --orphans'
alias pkgsearch='sudo pamac search -a'
alias pkglist='pacman -Qe'
alias pkglistmore='pacman -Q' # Also includes dependencies
elif hascommand --strict pacman && [[ -f /etc/arch-release ]]; then # Arch (No AUR)
# Link: https://archlinux.org/pacman/
alias has='pacman -Q --info'
alias pkgupdateall='sudo pacman -Syyu && if type flatpak >/dev/null 2>&1; then flatpak update --appstream && flatpak update; fi && if type snap >/dev/null 2>&1; then sudo snap refresh; fi && if type tldr >/dev/null 2>&1; then tldr --update; fi'
alias pkgupdate='sudo pacman -S'
alias pkginstall='sudo pacman -S'
alias pkgremove='sudo pacman -Rns'
alias pkgclean='pacman -Qtdq | sudo pacman -Rns -'
alias pkgsearch='pacman -Ss'
alias pkglist='pacman -Qe'
alias pkglistmore='pacman -Q' # Also includes dependencies
elif hascommand --strict dnf; then # RedHat/Fedora
# Link: https://fedoraproject.org/wiki/DNF
alias has='dnf info'
alias pkgupdateall='sudo dnf upgrade --refresh'
alias pkgupdate='sudo dnf upgrade'
alias pkginstall='sudo dnf install'
alias pkgremove='sudo dnf remove'
alias pkgclean='sudo dnf autoremove'
alias pkgsearch='sudo dnf search'
alias pkglist='dnf list installed'
alias pkgdependencies='yum whatprovides'
elif hascommand --strict yum; then # RedHat/Fedora
# Link: https://access.redhat.com/articles/yum-cheat-sheet
alias has='yum info'
alias pkgupdateall='sudo yum clean all && yum -y update'
alias pkgupdate='sudo yum update'
alias pkginstall='sudo yum install'
alias pkgremove='sudo yum remove'
alias pkgclean='sudo yum autoremove'
alias pkgsearch='sudo yum search'
alias pkglist='yum list installed'
alias pkgdependencies='yum whatprovides'
elif hascommand --strict nala; then # Debian/Ubuntu/Raspbian
# Link: https://gitlab.com/volian/nala
# Link: https://itsfoss.com/nala/
alias has='nala show'
alias pkgupdateall='sudo nala update && sudo nala upgrade && if type pacstall >/dev/null 2>&1; then pacstall --upgrade; fi'
alias pkgupdate='sudo nala update'
alias pkginstall='sudo nala install --install-suggests'
alias pkgremove='sudo nala remove'
alias pkgclean='sudo nala clean --fix-broken'
alias pkgsearch='sudo nala search'
alias pkglist='sudo nala list --installed'
alias pkgmirrors='sudo nala fetch'
elif hascommand --strict apt; then # Debian/Ubuntu/Raspbian
# Link: https://itsfoss.com/apt-command-guide/
alias has='apt show'
alias pkgupdateall='sudo apt update --assume-yes && sudo apt upgrade --assume-yes && if type pacstall >/dev/null 2>&1; then pacstall --upgrade; fi && if type tldr >/dev/null 2>&1; then tldr --update; fi'
alias pkgupdate='sudo apt-get install --only-upgrade'
alias pkginstall='sudo apt install'
alias pkgremove='sudo apt remove'
alias pkgclean='sudo apt autoremove'
alias pkgsearch='sudo apt search'
alias pkglist='sudo apt list --installed'
alias pkgcheck='sudo apt update --assume-yes && apt list --upgradable'
elif hascommand --strict apt-get; then # Debian/Ubuntu
# Link: https://help.ubuntu.com/community/AptGet/Howto
alias has='apt-cache show'
alias pkgupdateall='sudo apt-get update && sudo apt-get upgrade && if type pacstall >/dev/null 2>&1; then pacstall --upgrade; fi && if type tldr >/dev/null 2>&1; then tldr --update; fi'
alias pkgupdate='sudo apt-get install --only-upgrade'
alias pkginstall='sudo apt-get install'
alias pkgremove='sudo apt-get remove'
alias pkgclean='sudo apt-get autoremove'
alias pkgsearch='sudo apt-cache search'
alias pkglist='sudo dpkg -l'
elif hascommand --strict zypper; then # SUSE
# Link: https://en.opensuse.org/SDB:Zypper_usage
alias has='zypper info'
alias pkgupdateall='sudo zypper patch'
alias pkgupdate='sudo zypper up'
alias pkginstall='sudo zypper in'
alias pkgremove='sudo zypper rm'
alias pkgclean='sudo zypper packages --orphaned'
alias pkgsearch='sudo zypper se'
alias pkglist='zypper se --installed-only'
elif hascommand --strict eopkg; then # Solus
# Link: https://getsol.us/articles/package-management/basics/en/
alias has='sudo eopkg info'
alias pkgupdateall='sudo eopkg upgrade'
alias pkgupdate='sudo eopkg upgrade'
alias pkginstall='sudo eopkg install'
alias pkgremove='sudo eopkg remove'
alias pkgsearch='sudo eopkg search'
alias pkglist='eopkg li -l'
elif hascommand --strict emerge; then # Gentoo (Portage)
# Link: https://www.linode.com/docs/guides/portage-package-manager/
alias has='equery files'
alias pkgupdateall='sudo emerge --sync && sudo emerge --update --deep --with-bdeps=y --newuse @world && sudo emerge --depclean && sudo revdep-rebuild'
alias pkgupdate='sudo emerge -u'
alias pkginstall='sudo emerge'
alias pkgremove='sudo emerge --depclean'
alias pkgsearch='sudo emerge --search'
alias pkglist='equery list "*"'
elif hascommand --strict slackpkg; then # Slackware
# Link: https://www.linux.com/training-tutorials/intro-slackware-package-management/
alias has='slackpkg info'
alias pkgupdateall='slackpkg update && slackpkg install-new && slackpkg upgrade-all'
alias pkgupdate='upgradepkg'
alias pkginstall='installpkg'
alias pkgremove='removepkg'
alias pkglist='pkgtool'
elif hascommand --strict urpmi; then # Mandrake
# Link: https://wiki.mageia.org/en/URPMI
alias has='urpmq --summary -Y'
alias pkgupdateall='urpmi --auto-update'
alias pkgupdate='urpmi'
alias pkginstall='urpmi'
alias pkgremove='urpme'
alias pkgsearch='urpmq --summary -Y'
alias pkglist='rpm -qa'
elif hascommand --strict apt-cyg; then # Cygwin
# Link: http://stephenjungels.com/jungels.net/projects/apt-cyg/
alias has='apt-cyg show'
alias pkgupdateall='apt-cyg update'
alias pkgupdate='apt-cyg update'
alias pkginstall='apt-cyg install'
alias pkgremove='apt-cyg remove'
alias pkgsearch='apt-cyg find'
alias pkglist='cygcheck --check-setup'
elif hascommand --strict brew; then # macOS
# Link: https://brew.sh/
alias has='brew info'
alias pkgupdateall='brew update'
alias pkgupdate='brew update'
alias pkginstall='brew install'
alias pkgremove='brew uninstall'
alias pkgclean='brew cleanup'
alias pkgsearch='brew search'
alias pkglist='brew list'
fi
# If this is an Arch based distrobution with pacman...
if hascommand --strict pacman && [[ -d /etc/pacman.d/ ]]; then
# Install a list of packages with regex
# https://wiki.archlinux.org/title/pacman#Installing_specific_packages
function pkginstallregex() {
if [ $# -eq 0 ]; then
echo "No regex provided"
return 1
else
sudo pacman -S $(pacman -Ssq "${@}")
fi
}
# Clean the pacman and helper package caches
alias pacman-clean-cache='sudo echo -ne "${BRIGHT_YELLOW}Before:${RESET} "; sudo du -sh /var/cache/pacman/pkg/ 2>/dev/null; yes | sudo pacman -Scc && command -v yay &> /dev/null && yes | yay -Sc || true && command -v paru &> /dev/null && yes | paru -Sc || true; echo -ne "\n${BRIGHT_GREEN}After:${RESET} "; sudo du -sh /var/cache/pacman/pkg/ 2>/dev/null'
# To mark a package as explicitly installed or only a dependency
alias pkgmarkasexplicit='sudo pacman -D --asexplicit'
alias pkgmarkasdependency='sudo pacman -D --asdeps'
# Show all packages and their install reason
alias pkgreasons="pacman -Qi | awk -F': ' '/^Name/ { name = \$2; } /^Install Reason/ { reason = \$2; } /^$/ { printf \"%s: %s\\n\", name, reason; }'"
# Check for default configuration file default backups
alias pacnew='sudo true && echo "Pacman backup configuration files found:"; sudo find /etc -type f \( -iname \*.pacnew -o -iname \*.pacsave \) | sort -t"/" -k2.2r -k2.1'
# Force remove a package ignoring required dependencies
# NOTE: Also can be typed as sudo pacman -Rdd for short
alias pkgforceremove='sudo pacman -Rd --nodeps'
# Force remove a package ignoring required dependencies and then reinstall
function pkgforcereinstall() {
if [[ -z "${1}" ]]; then
echo -e "${BRIGHT_RED}Error:${RESET} Package name required"
return 1
fi
sudo pacman -Rd --nodeps "${1}" && sudo pacman -S "${1}"
}
# Search for a package containing a file
alias pkgsearchcontainingfile='sudo pacman -Fy'
# List all the local files in an installed package
alias pkglocalpackagefiles='pacman -Ql'
# Verify the presence of the files installed by a package
alias pkgverifylocalpackage='sudo pacman -Qkk'
# Verify all packages
# Link: https://unix.stackexchange.com/questions/659756/arch-linux-reinstall-all-broken-packages-after-poweroff-during-system-upgrade
alias pkgverifyall="pacman -Qk 2>/dev/null | grep -v ' 0 missing files'"
# Show the latest Arch linux update news
alias archnews='w3m https://www.archlinux.org/ | sed -n "/Latest News/,/Older News/p" | "${PAGER}"'
# Pacseek - browse and search through the Arch Linux package databases and AUR
# Link: https://github.com/moson-mo/pacseek
if hascommand --strict pacseek; then
alias pkg='pacseek'
fi
# If perl is installed (preset on most systems)...
# Link: https://wiki.archlinux.org/title/Perl
# Install: sudo pacman -S perl
if hascommand --strict perl; then
# Use a much more detailed package listing with descriptions (AUR separated)
# pkglist [search] will search for all installed packages instead
# Perl pipeline: collapse padding, wrap groups in parens, then slurp-mode
# regex reduces each package block to: Name (Group) - Description
alias pkglist &>/dev/null && unalias pkglist
function pkglist() {
if [ $# -eq 0 ]; then
# Native Arch packages (from official repos)
# -Q = query local, -e = explicit, -n = native (repo), -i = info
echo -e "${BRIGHT_BLUE}=============== ${BRIGHT_YELLOW}Native Arch Packages${BRIGHT_BLUE} ===============${RESET}"
pacman -Qeni \
| perl -pe 's/ +/ /gm' \
| perl -pe 's/^(Groups +: )(.*)/\1(\2)/gm' \
| perl -0777 -pe 's/^Name : (.*)\nVersion :(.*)\nDescription : ((?!None).*)?(?:.|\n)*?Groups :((?! \(None\)$)( )?.*)?(?:.|\n(?!Name))+/$1$4 - $3/gm' \
| grep -A1 --color -P '^[^\s]+'
# AUR / Chaotic-AUR / custom repository packages (not in official repos)
# -m = foreign (not found in any configured repo)
echo -e "\n${BRIGHT_BLUE}=============== ${BRIGHT_YELLOW}Arch User Repository (AUR)${BRIGHT_BLUE} ===============${RESET}"
pacman -Qemi \
| perl -pe 's/ +/ /gm' \
| perl -pe 's/^(Groups +: )(.*)/\1(\2)/gm' \
| perl -0777 -pe 's/^Name : (.*)\nVersion :(.*)\nDescription : ((?!None).*)?(?:.|\n)*?Groups :((?! \(None\)$)( )?.*)?(?:.|\n(?!Name))+/$1$4 - $3/gm' \
| grep -A1 --color -P '^[^\s]+'
else
# If a search parameter was specified, grep all explicit packages
pacman -Qei \
| perl -pe 's/ +/ /gm' \
| perl -pe 's/^(Groups +: )(.*)/\1(\2)/gm' \
| perl -0777 -pe 's/^Name : (.*)\nVersion :(.*)\nDescription : ((?!None).*)?(?:.|\n)*?Groups :((?! \(None\)$)( )?.*)?(?:.|\n(?!Name))+/$1$4 - $3/gm' \
| grep -A1 --color -P '^[^\s]+' \
| grep -i "${@}"
fi
}
fi
# If pacman-contrib (contributed scripts and tools for pacman) is installed...
# Install: sudo pacman -S pacman-contrib
if hascommand --strict paccache; then
# Add aliases to find dependencies
alias pkgdependencies='pactree --color' # --unique --depth 1
alias pkgwhatuses='pactree --reverse --color'
# The checkupdates script (also from Arch pacman-contrib)
# NOTE: The benefit to this is it does NOT need SUDO/ROOT access
alias pkgcheck='checkupdates | sort | command less --no-init --ignore-case --LONG-PROMPT --LINE-NUMBERS'
# Alias to fix Arch Pacman install error "invalid or corrupted package" with a
# new PGP key, clear anything older than the last 3 installs, and remove locks
# Link: https://odysee.com/@DistroTube:2/solved-pacman-wouldn't-let-me-run-an:0
alias pacmanfix='sudo rm -f /var/lib/pacman/db.lck && sudo paccache -r && sudo pacman -Sy archlinux-keyring'
alias pacmanfixkeys='sudo rm -f /var/lib/pacman/db.lck; sudo rm -R /etc/pacman.d/gnupg/; sudo rm -R /root/.gnupg/; sudo gpg --refresh-keys && sudo pacman-key --init && sudo pacman-key --populate && sudo pacman -Sy archlinux-keyring'
fi
fi
# If apt package manager is installed, add aliases to find dependencies in Ubuntu/Debian
if hascommand --strict apt-cache; then
alias pkgdependencies='apt-cache depends'
# If apt-rdepends is installed (sudo apt install apt-rdepends)
if hascommand --strict apt-rdepends; then
alias pkgwhatuses='apt-rdepends'
fi
fi
# Aliases for flatpak packages if installed
if hascommand --strict flatpak; then
alias flatpakhas='flatpak info'
alias flatpakcheck='flatpak update --appstream && flatpak remote-ls --updates'
alias flatpakupdate='flatpak update --appstream && flatpak update'
alias flatpakinstall='flatpak install'
alias flatpakremove='flatpak uninstall'
alias flatpakwipe='flatpak uninstall --delete-data'
alias flatpakclean='flatpak repair && flatpak uninstall --unused'
alias flatpaksearch='flatpak search'
alias flatpaklist='flatpak list --columns=name,app:f,version:e,description:e --app'
alias flatpaksize='flatpak list --columns=app:f,name,size:f,installation:e'
alias flatpakremotes='flatpak remotes --show-details'
# Create missing or recreate broken Flatpak icons (might require restart)
if [ -d "/var/lib/flatpak/exports/share/applications/" ]; then
alias flatpakmakeicons='command cp /var/lib/flatpak/exports/share/applications/*.desktop ~/.local/share/applications/'
else
alias flatpakmakeicons='find /var/lib/flatpak/app/ -type f -iname "*.desktop" -exec cp {} ~/.local/share/applications \;'
fi
# Create an executable backup script for Flatpak apps and custom permissions
flatpakbackup() {
# Require sudo rights unless we're only showing help
if [[ ! "${1}" =~ ^(-h|--help)$ ]] && (( EUID != 0 )); then
if ! sudo -v 2>/dev/null; then
echo -e "${BRIGHT_RED}Error:${RESET} root (sudo) privileges are needed to read system-level Flatpak overrides"
return 1
fi
fi
# Handle command line arguments
case "${1}" in
-h|--help)
echo -e "${BRIGHT_CYAN}flatpakbackup${RESET}: Generate bash commands to backup and restore Flatpak permissions"
echo -e "${BRIGHT_WHITE}Description:${RESET} Captures system/user global overrides and app-specific overrides"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}flatpakbackup${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}output_file${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}flatpakbackup${RESET} ${BRIGHT_BLUE}# Output to terminal${RESET}"
echo -e " ${BRIGHT_CYAN}flatpakbackup${RESET} ${BRIGHT_YELLOW}restore.sh${RESET} ${BRIGHT_BLUE}# Save to executable file${RESET}"
return 0
;;
"")
# Output to terminal
;;
*)
# Cleanup orphaned app data
echo -e "${BRIGHT_CYAN}Clean up data from orphaned Flatpak apps? (${BRIGHT_GREEN}y${BRIGHT_CYAN}/${BRIGHT_RED}N${BRIGHT_CYAN}):${RESET} \c"
read -r cleanup_confirm
if [[ ${cleanup_confirm} =~ ^[yY]$ ]]; then
installed="$(flatpak list --app --columns=application 2>/dev/null | LC_ALL=C sort -fu)"
for dir in "${HOME}/.var/app"/*/; do
[[ -d "${dir}" ]] || continue
app_id="$(basename "${dir}")"
if ! grep -qxF "${app_id}" <<< "${installed}"; then
echo -e "\n${BRIGHT_MAGENTA}Orphaned:${RESET} ${BRIGHT_WHITE}${app_id}${RESET}"
echo -e "${BRIGHT_CYAN}Remove ${BRIGHT_YELLOW}${dir}${BRIGHT_CYAN}? (${BRIGHT_GREEN}y${BRIGHT_CYAN}/${BRIGHT_RED}N${BRIGHT_CYAN}):${RESET} \c"
read -r confirm
if [[ ${confirm} =~ ^[yY]$ ]]; then
rm -rf -- "${dir}"
echo -e "${BRIGHT_GREEN}✓ Removed${RESET}"
else
echo -e "${BRIGHT_RED}X Skipped${RESET}"
fi
fi
done
fi
# Backup app data (use strongest widely-available compressor)
echo -e "\n${BRIGHT_CYAN}Create compressed backup of ${BRIGHT_YELLOW}~/.var/app/${BRIGHT_CYAN} (all Flatpak app data)? (${BRIGHT_GREEN}y${BRIGHT_CYAN}/${BRIGHT_RED}N${BRIGHT_CYAN}):${RESET} \c"
read -r backup_confirm
if [[ ${backup_confirm} =~ ^[yY]$ ]]; then
# Build a relative tar (no absolute paths) and choose best available compression.
if command -v xz >/dev/null 2>&1; then
outfile="flatpak-app-data-$(date +%Y%m%d-%H%M%S).tar.xz"
tar -C "${HOME}/.var" -I "xz -T0 -9e" -cf "${outfile}" app
elif command -v zstd >/dev/null 2>&1; then
outfile="flatpak-app-data-$(date +%Y%m%d-%H%M%S).tar.zst"
tar -C "${HOME}/.var" -I "zstd -T0 -19" -cf "${outfile}" app
else
outfile="flatpak-app-data-$(date +%Y%m%d-%H%M%S).tar.gz"
tar -C "${HOME}/.var" -I "gzip -9" -cf "${outfile}" app
fi
echo -e "${BRIGHT_GREEN}✓ Backup created:${RESET} ${BRIGHT_YELLOW}${outfile}${RESET}"
fi
# Save to file - redirect output and make executable
flatpakbackup "" > "${1}"
chmod ug+x "${1}"
echo -e "${BRIGHT_GREEN}✓ Flatpak backup saved to:${RESET} ${BRIGHT_YELLOW}${1}${RESET}"
return 0
;;
esac
# Helper function to parse individual override lines into flatpak commands
_parse_override_line() {
local LINE="$1"
local COMMAND_PREFIX="$2" # e.g., "sudo flatpak override --system"
local COMMAND_SUFFIX="$3" # e.g., ' "com.example.App"' or ""
# Skip section headers like [Context], [Environment], etc.
if [[ "${LINE}" =~ ^\[.*\]$ ]]; then
return 0
fi
case "${LINE}" in
# Explicit env unsets as reported by --show
"unset-env="*)
local VALUES="${LINE#*=}"
IFS=';' read -ra ITEMS <<< "${VALUES}"
for ITEM in "${ITEMS[@]}"; do
[[ -n "${ITEM}" ]] && echo "${COMMAND_PREFIX} --unset-env=${ITEM}${COMMAND_SUFFIX}"
done
;;
# Regular permissions (positive/negative items separated by ;)
"filesystems="*|"sockets="*|"devices="*|"shared="*|"own-name="*|"talk-name="*|"persist="*|"features="*)
local KEY="${LINE%%=*}"
local VALUES="${LINE#*=}"
local OPTION_MAP=""
case "${KEY}" in
"filesystems") OPTION_MAP="--filesystem" ;;
"sockets") OPTION_MAP="--socket" ;;
"devices") OPTION_MAP="--device" ;;
"shared") OPTION_MAP="--share" ;;
"own-name") OPTION_MAP="--own-name" ;;
"talk-name") OPTION_MAP="--talk-name" ;;
"persist") OPTION_MAP="--persist" ;;
"features") OPTION_MAP="--feature" ;;
esac
IFS=';' read -ra ITEMS <<< "${VALUES}"
for ITEM in "${ITEMS[@]}"; do
[[ -z "${ITEM}" ]] && continue
if [[ "${ITEM}" == "!"* ]]; then
# Negations map to specific --no*/--unshare flags
local VALUE="${ITEM#!}"
case "${KEY}" in
"sockets") echo "${COMMAND_PREFIX} --nosocket=${VALUE}${COMMAND_SUFFIX}" ;;
"filesystems") echo "${COMMAND_PREFIX} --nofilesystem=${VALUE}${COMMAND_SUFFIX}" ;;
"devices") echo "${COMMAND_PREFIX} --nodevice=${VALUE}${COMMAND_SUFFIX}" ;;
"shared") echo "${COMMAND_PREFIX} --unshare=${VALUE}${COMMAND_SUFFIX}" ;;
"own-name") echo "${COMMAND_PREFIX} --no-own-name=${VALUE}${COMMAND_SUFFIX}" ;;
"talk-name") echo "${COMMAND_PREFIX} --no-talk-name=${VALUE}${COMMAND_SUFFIX}" ;;
"persist") echo "${COMMAND_PREFIX} --no-persist=${VALUE}${COMMAND_SUFFIX}" ;;
"features") echo "${COMMAND_PREFIX} --no-feature=${VALUE}${COMMAND_SUFFIX}" ;;
*) echo "# TODO: unknown negation for key=${KEY} item=${VALUE}" ;;
esac
else
echo "${COMMAND_PREFIX} ${OPTION_MAP}=${ITEM}${COMMAND_SUFFIX}"
fi
done
;;
# Fallback: treat bare KEY=VALUE from [Environment] as --env
*"="*)
# Avoid re-catching known keys above
if [[ "${LINE}" != filesystems=* && "${LINE}" != sockets=* && "${LINE}" != devices=* && \
"${LINE}" != shared=* && "${LINE}" != own-name=* && "${LINE}" != talk-name=* && \
"${LINE}" != persist=* && "${LINE}" != features=* && "${LINE}" != unset-env=* ]]
then
echo "${COMMAND_PREFIX} --env=${LINE}${COMMAND_SUFFIX}"
fi
;;
esac
}
# Generate the restore script header
echo "#!/usr/bin/env bash"
echo "set -euo pipefail # fail fast on errors / unset vars / pipeline failures"
echo "# Flatpak permissions restore script generated on $(date '+%B %d, %Y at %I:%M %p')"
echo "# This script will recreate all your Flatpak app installations and permission overrides"
echo ""
# === SYSTEM GLOBAL OVERRIDES ===
echo "# System global overrides (applies to all apps for all users)"
echo "# Uncomment the reset line below to clear existing overrides first:"
echo "# sudo flatpak override --system --reset"
local system_output
system_output="$(flatpak override --system --show 2>/dev/null)"
if [[ -n "${system_output}" ]]; then
echo "${system_output}" | while IFS= read -r line; do
_parse_override_line "${line}" "sudo flatpak override --system" ""
done | LC_ALL=C sort -fu
else
echo "# No system global overrides found"
fi
echo ""
# === USER GLOBAL OVERRIDES ===
echo "# User global overrides (applies to all apps for current user)"
echo "# Uncomment the reset line below to clear existing overrides first:"
echo "# flatpak override --user --reset"
local user_output
user_output="$(flatpak override --user --show 2>/dev/null)"
if [[ -n "${user_output}" ]]; then
echo "${user_output}" | while IFS= read -r line; do
_parse_override_line "${line}" "flatpak override --user" ""
done | LC_ALL=C sort -fu
else
echo "# No user global overrides found"
fi
echo ""
# === APP-SPECIFIC OVERRIDES ===
echo "# App-specific overrides (sorted by app name for easy reference)"
echo "# Each app section includes installation command and custom permissions"
echo
# Get all apps sorted by display name, then extract app IDs
# Use process substitution (< <(...)) so the while-loop runs in the
# current shell — this makes `local` work correctly inside the loop
while read -r app_id; do
# Skip header line and empty lines
if [[ -n "${app_id}" && "${app_id}" != "Application" ]]; then
# Get override settings for this app (both system and user)
local system_app_output user_app_output
system_app_output="$(flatpak override --system --show "${app_id}" 2>/dev/null)"
user_app_output="$(flatpak override --user --show "${app_id}" 2>/dev/null)"
# Process all apps (even those without custom overrides)
# Get app description for the header comment
local app_description
app_description="$(printf '%s\n' "$(flatpak info "${app_id}" 2>/dev/null)" | awk 'NF { print; exit }')"
# Create app section header
if [[ -n "${app_description}" ]]; then
echo "# $(printf '%.0s=' {1..76})"
echo "# ${app_description}"
echo "# $(printf '%.0s=' {1..76})"
fi
# App installation and reset commands
# Detect current installation scope so restores match original location
local install_scope
install_scope="$(flatpak list --app --columns=installation,application 2>/dev/null \
| awk -v id="${app_id}" 'BEGIN{FS="\t"} $2==id{print $1; exit}')"
if [[ "${install_scope}" == "user" ]]; then
echo "flatpak install -y --or-update --noninteractive --user \"${app_id}\""
else
echo "flatpak install -y --or-update --noninteractive --system \"${app_id}\""
fi
# Only add override sections if there are actual overrides
if [[ -n "${system_app_output}" || -n "${user_app_output}" ]]; then
echo "# Uncomment the reset lines below to clear existing app overrides first:"
echo "# sudo flatpak override --system --reset \"${app_id}\""
echo "# flatpak override --user --reset \"${app_id}\""
# System-level app overrides
if [[ -n "${system_app_output}" ]]; then
echo "${system_app_output}" | while IFS= read -r line; do
_parse_override_line "${line}" "sudo flatpak override --system" " \"${app_id}\""
done | LC_ALL=C sort -fu
fi
# User-level app overrides
if [[ -n "${user_app_output}" ]]; then
echo "${user_app_output}" | while IFS= read -r line; do
_parse_override_line "${line}" "flatpak override --user" " \"${app_id}\""
done | LC_ALL=C sort -fu
fi
fi
echo ""
fi
done < <(flatpak list --app --columns=name,application 2>/dev/null \
| LC_ALL=C sort -fu \
| cut -f2)
}
# Make sure the Flatpak paths are present once (for icons/desktop files)
# Only append Flatpak paths if not already present (prevents duplicates on re-source)
if [[ ":${XDG_DATA_DIRS}:" != *":/var/lib/flatpak/exports/share:"* ]]; then
export XDG_DATA_DIRS="${XDG_DATA_DIRS:+${XDG_DATA_DIRS}:}/var/lib/flatpak/exports/share:${HOME}/.local/share/flatpak/exports/share"
fi
fi
# Aliases for snap packages if installed
if hascommand --strict snap; then
alias snaphas='snap info'
alias snapcheck='snap refresh --list'
alias snapupdate='sudo snap refresh'
alias snapinstall='sudo snap install'
alias snapremove='sudo snap remove'
alias snapclean='LANG=C snap list --all | while read snapname ver rev trk pub notes; do if [[ $notes = *disabled* ]]; then sudo snap remove "$snapname" --revision="$rev"; fi; done'
alias snapsearch='snap find'
alias snaplist='snap list'
alias snapsize='echo "Snap package sizes:" && du -hcs /var/lib/snapd/snaps/* 2>/dev/null || echo "No snap packages found"'
fi
#######################################################
# Alias for sudo replacements on machines with one user
#######################################################
if [[ $_SKIP_SUDO_ALTERNATIVE = false ]]; then
# A very slim alternative to both sudo and doas
# Link: https://codeberg.org/sw1tchbl4d3/rdo
# Config: sudoedit /etc/rdo.conf
# username=yourusername
# wrong_pw_sleep=1000
# session_ttl=5
if hascommand --strict rdo; then
alias sudo='rdo'
# If sudoedit is not avaliable, alias it
if ! hascommand --strict sudoedit; then
# The edit alias might not exist in root but the
# edit function handles sudo editing when needed
alias sudoedit='edit'
fi
# A port of OpenBSD's doas offers two benefits over sudo:
# 1) Its configuration file has a simple syntax and
# 2) It is smaller, requiring less effort to audit the code
# This makes it harder for both admins and coders to make mistakes that potentially open security holes in the system
# Link: https://github.com/Duncaen/OpenDoas or https://github.com/slicer69/doas
# Link: https://youtu.be/eamEZCj-CuQ
# Config: Add "permit <user> as root" in /etc/doas.conf or /usr/local/etc/doas.conf
elif hascommand --strict doas; then
alias sudo='doas'
# Replace sudoedit only if doasedit exists
# Link: https://github.com/AN3223/scripts/blob/master/doasedit
if hascommand --strict doasedit; then
alias sudoedit='doasedit'
fi
fi
fi
#######################################################
# General Function "Aliases"
#######################################################
# Run this function upon exit of the shell
function _exit() {
# Show who logged out
local COLOR="${BRIGHT_RED}" # Light Red
local HIGHLIGHT="${BRIGHT_BLUE}" # Light Blue
local NOCOLOR="${RESET}"
echo -e "${COLOR}User ${HIGHLIGHT}$(echo $USER)${COLOR} has logged out of ${HIGHLIGHT}$(echo $HOSTNAME)${COLOR}.${NOCOLOR}"
}
trap _exit EXIT
# Calculator that uses bc or Bash's built-in arithmetic
# Example: = 5*5+2
if hascommand bc; then
# Start calculator with math support
# echo 'if (scale == 0) scale=4' > ~/.config/bcrc
# Link: https://www.gnu.org/software/bc/manual/html_mono/bc.html
alias bc='bc --mathlib'
if [[ -f ~/.bcrc ]]; then
export BC_ENV_ARGS=~/.bcrc
elif [[ -f ~/.config/bcrc ]]; then
export BC_ENV_ARGS=~/.config/bcrc
fi
export BC_LINE_LENGTH=0
function =() {
bc <<< "${@}"
}
else
function =() {
local IFS=' '
local _CALC="${*//p/+}"
_CALC="${_CALC//x/*}"
echo "$(($_CALC))"
}
fi
# Cross-platform realpath equivalent for resolving symlinks to an absolute path
# Uses readlink -f on Linux and an alternative approach on macOS which lacks -f
function resolvesymlink() {
# Show help if no argument provided or help requested
if [[ -z "${1}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}resolvesymlink${RESET}: Resolve a symlink to its absolute target path"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}resolvesymlink${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}symlink_path${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}resolvesymlink${RESET} ${BRIGHT_YELLOW}/usr/bin/python${RESET}"
return 2
fi
# Check if the system is running macOS (Darwin)
if [[ "$(uname)" == "Darwin" ]]; then
# Initialize the TARGET_FILE variable to the input file path
local TARGET_FILE="$1"
local FULL_PATH
# Use a subshell to prevent changing the current working directory
(
# Change to the directory containing the target file
cd "$(dirname "${TARGET_FILE}")" || return 1
# Get the base name of the target file (strip directory path)
TARGET_FILE=$(basename "${TARGET_FILE}")
# Resolve any symlinks by following them iteratively
while [[ -L "${TARGET_FILE}" ]]; do
# Update TARGET_FILE with the link's actual destination
TARGET_FILE=$(readlink "${TARGET_FILE}")
# Change directory to where the symlink points
cd "$(dirname "${TARGET_FILE}")" || return 1
# Update TARGET_FILE to just the file name again
TARGET_FILE=$(basename "${TARGET_FILE}")
done
# Get the absolute path of the final resolved file
FULL_PATH="$(pwd -P)/${TARGET_FILE}"
# Output the resolved absolute path
echo "${FULL_PATH}"
)
else
# Use readlink -f for Linux systems
readlink -f "$1"
fi
}
# Confirm/Ask a question - See 'killps' for example of use
# General-purpose function to ask Yes/No questions in Bash,
# either with or without a default answer.
# It keeps repeating the question until it gets a valid answer.
# Link: https://gist.github.com/davejamesmiller/1965569
# Example Usage:
# if ask "Do you want to do such-and-such?"; then
# Default to Yes if the user presses enter without giving an answer:
# if ask "Do you want to do such-and-such?" Y; then
# Default to No if the user presses enter without giving an answer:
# if ask "Do you want to do such-and-such?" N; then
# Or if you prefer the shorter version:
# ask "Do you want to do such-and-such?" && said_yes
# ask "Do you want to do such-and-such?" || said_no
function ask() {
# Initialize local variables
local prompt default reply
# Determine the prompt and default based on the second parameter
if [[ "${2:-}" = "Y" ]]; then
prompt='Y/n'
default='Y'
elif [[ "${2:-}" = "N" ]]; then
prompt='y/N'
default='N'
else
prompt='y/n'
default=''
fi
# Loop until a valid answer is given
while true; do
# Ask the question (not using "read -p" as it uses stderr not stdout)
echo -ne "${1} [$prompt] "
# Read the answer (use /dev/tty in case stdin is redirected from somewhere else)
read -r reply </dev/tty
# Set reply to default if it is empty
[[ -z "${reply}" ]] && reply="${default}"
# Check if the reply is valid
case "${reply}" in
Y*|y*) return 0 ;;
N*|n*) return 1 ;;
esac
done
}
# Creates a menu for selecting an item from a list from either piped in
# multi-line text or command line arguments. Use --picker=app to force a picker
# Example: ls -1 ~ | createmenu
# Example: echo -e "Jen\nTom\nJoe Bob\nAmy\nPat" | sort | createmenu
# Example: cat "menuitems.txt" | createmenu
# Example: _TMUX_SESSION="$(tmux ls -F "#{session_name}" 2> /dev/null | createmenu)"
# Example: createmenu 'Option 1' 'Option 2' 'Option 3'
function createmenu() {
# Valid pickers to detect and automatically used in order
local _VALID_PICKERS="${_PREFERRED_PICKER} fzy sk fzf peco percol pick icepick selecta sentaku zf dmenu rofi wofi"
# Check if command line arguments are provided and if input is piped in
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]] || { [[ "$#" -eq 0 ]] && [[ -t 0 ]]; }; then
echo -e "${BRIGHT_CYAN}createmenu${RESET}: Create a menu for selecting an item from a list"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}createmenu${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}--picker=name${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}items...${BRIGHT_MAGENTA}]${RESET}"
echo -e " command | ${BRIGHT_CYAN}createmenu${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}--picker=name${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Pickers:${RESET} fzy, sk, fzf, peco, percol, pick, icepick, selecta, sentaku, zf, dmenu, rofi, wofi"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}createmenu${RESET} ${BRIGHT_YELLOW}'Option 1' 'Option 2' 'Option 3'${RESET}"
echo -e " ${BRIGHT_YELLOW}ls -1 ~${RESET} | ${BRIGHT_CYAN}createmenu${RESET}"
echo -e " ${BRIGHT_YELLOW}cat 'items.txt'${RESET} | ${BRIGHT_CYAN}createmenu${RESET}"
echo -e " ${BRIGHT_CYAN}createmenu${RESET} ${BRIGHT_GREEN}--picker=rofi${RESET} ${BRIGHT_YELLOW}'A' 'B' 'C'${RESET}"
return 1
fi
# Check for --picker parameter and remove it from arguments
local _PICKER
local _FOUND_PICKER=false
local NEW_ARGS=()
for ARG in "$@"; do
if [[ "$ARG" == --picker=* ]]; then
_PICKER="${ARG#*=}"
if type "${_PICKER}" &>/dev/null; then
_FOUND_PICKER=true
else
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}The picker ${BRIGHT_YELLOW}${_PICKER}${BRIGHT_CYAN} is not available or installed${RESET}"
return 1
fi
else
NEW_ARGS+=("$ARG")
fi
done
set -- "${NEW_ARGS[@]}"
# If no specific picker is provided or the picker is not valid...
if [ "$_FOUND_PICKER" == false ]; then
# Loop through the list and see if one of them is installed
for _PICKER in $_VALID_PICKERS; do
if type $_PICKER &>/dev/null; then
_FOUND_PICKER=true
break
fi
done
fi
# Check if command line arguments are provided
if [ "$#" -gt 0 ]; then
local _INPUT=""
local _COUNT=0
for arg in "$@"; do
# Increase count for each argument
((_COUNT++))
# Add newline after each argument except the last
if [ $_COUNT -lt $# ]; then
_INPUT+="${arg}"$'\n'
else
_INPUT+="${arg}"
fi
done
else
# Get the piped in multiple lines of text
local _INPUT="$(</dev/stdin)"
# Count the lines of text
local _COUNT=$(echo "${_INPUT}" | wc -l)
fi
# If there is no input, just exit with an error
if [ -z "${_INPUT}" ]; then
return 1
# If there is only one line (or one argument), no choice is needed
elif [ ${_COUNT} -eq 1 ]; then
echo "${_INPUT}"
return 0
fi
# If we found a picker, use it
if [ "$_FOUND_PICKER" == true ]; then
# echo -e "${BRIGHT_MAGENTA}The picker is: ${BRIGHT_GREEN}$_PICKER${RESET}"
case $_PICKER in
dmenu)
echo "${_INPUT}" | dmenu -l 10
;;
rofi)
echo "${_INPUT}" | rofi -dmenu -i -no-custom -no-fixed-num-lines -p "Choose:"
;;
wofi)
echo "${_INPUT}" | wofi --show dmenu --insensitive --prompt "Choose:"
;;
*)
echo "${_INPUT}" | $_PICKER
;;
esac
# Use Bash's built in select option
else
# Parse only on new lines
local _IFS_OLD="${IFS}"
IFS=$'\n'
# Turn off globbing filename generation
set -f
# Show a list to pick an item from
select RESULT in ${_INPUT}; do
if [ -n "${RESULT}" ]; then
echo "${RESULT}"
break
fi
done < /dev/tty
# Restore settings
IFS="${_IFS_OLD}"
set +f
fi
}
# This function automates the process of executing a command and providing visual feedback
# It displays an hourglass symbol next to the provided description while the command is running
# Upon successful execution, the hourglass is replaced with a green checkmark
# If the command fails, a red cross symbol is displayed instead
# Parameters:
# $1: Text description to display while the command is running
# $2: The command to execute
function runwithfeedback() {
# Check if both parameters are provided
if [[ -z "${1}" ]] || [[ -z "${2}" ]]; then
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}runwithfeedback${RESET} ${BRIGHT_GREEN}[description] [command]${RESET}"
return 1
fi
# Local variables for special characters with color codes
local HOURGLASS="${BRIGHT_YELLOW}${RESET}" # Yellow Hourglass
local CHECKMARK="\r${BRIGHT_GREEN}${RESET}" # Green Checkmark
local CROSS="\r${BRIGHT_RED}X${RESET}" # Red Error Cross
# Display the hourglass and message
echo -ne "${HOURGLASS} ${1}"
# Execute the command
if eval "${2}"; then
# If successful, display a green checkmark
echo -e "${CHECKMARK} ${1} "
else
# If failed, display a red cross
echo -e "${CROSS} ${1} "
fi
}
# Finds the current Linux distribution, name, version, and kernel version
function ver() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}ver${RESET}: Display system and kernel version information"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}ver${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Shows uname, /proc/version, lsb_release, and hostnamectl info${RESET}"
return 0
fi
if hascommand --strict uname; then
# Get information about the system kernel, release, and machine hardware
uname --kernel-name --kernel-release --machine
echo
fi
if [[ -e /proc/version ]]; then
# File that contains version information about the operating kernel
cat /proc/version
echo
fi
if hascommand --strict lsb_release; then
# Provides LSB (Linux Standard Base) and distribution-specific information
lsb_release -a
echo
fi
if hascommand --strict hostnamectl; then
# Control the Linux system hostname, also shows various system details
hostnamectl
echo
else
# Various files that contain text relating to the system identification
cat /etc/*-release 2> /dev/null
fi
}
# Aliases crontab with safety warning for -r option, confirming removal
alias crontab='_crontab_safe'
function _crontab_safe() {
# Check if the parameters contain the "-r" option
if [[ "$*" == *"-r"* ]]; then
# Display a warning message in bright red with a warning icon
echo -e "${BRIGHT_RED}WARNING: ${RESET}${BRIGHT_CYAN}You are attempting to remove your crontab. This action cannot be undone! ${BRIGHT_YELLOW}⚠️${RESET}"
# Ask for user confirmation before proceeding
read -p "Are you sure you want to continue? (y/N) " confirm
# Check if the user confirmed the operation
if [[ "$confirm" == [yY] || "$confirm" == [yY][eE][sS] ]]; then
# If confirmed, execute the actual crontab command with the provided parameters
command crontab "$@"
else
# If not confirmed, display a cancellation message
echo "Operation canceled."
fi
else
# If the "-r" option is not present, execute the regular crontab command with the provided parameters
command crontab "$@"
fi
}
# Search process names to kill
# https://unix.stackexchange.com/questions/443472/alias-for-killing-all-processes-of-a-grep-hit
function smash() {
# Check if a process name is specified; if not, show help text
if [[ -z "${1}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}smash${RESET}: Find and optionally kill processes by name"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}smash${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}process_name${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}smash${RESET} ${BRIGHT_YELLOW}vim${RESET}"
return 1
fi
local T_PROC=$1
local T_PIDS=($(pgrep -i "$T_PROC"))
if [[ "${#T_PIDS[@]}" -ge 1 ]]; then
echo "Found the following processes:"
for pid in "${T_PIDS[@]}"; do
echo "$pid" "$(command ps -p "$pid" -o comm= | awk -F'/' '{print $NF}')" | column -t
done
if ask "Kill them?" N; then
for pid in "${T_PIDS[@]}"; do
echo "Killing ${pid}..."
# Try SIGTERM first, then escalate if process survives
builtin kill -15 "$pid" 2>/dev/null
sleep 2
if builtin kill -0 "$pid" 2>/dev/null; then
builtin kill -2 "$pid" 2>/dev/null
sleep 2
if builtin kill -0 "$pid" 2>/dev/null; then
builtin kill -1 "$pid" 2>/dev/null
sleep 2
if builtin kill -0 "$pid" 2>/dev/null; then
echo "Cannot terminate ${pid}" >&2
continue
fi
fi
fi
done
else
echo "Exiting..."
return 0
fi
else
echo "No processes found for: $1" >&2 && return 1
fi
}
# Automatically downloads based on URL by dynamically choosing the appropriate command
# Detects the domain from the URL and chooses the appropriate download command
# ---
# IMPORTANT: Always ensure that you have the legal right and ethical justification
# to download media from various services. Respect copyright laws and terms of service
# agreements. Use of this script should comply with all applicable regulations.
alias d='download'
function download() {
local URL="${1}"
local DOWNLOAD_PATH="${2:-.}"
# Check if we are in a graphical environment and Desktop exists
if [[ "${DOWNLOAD_PATH}" == "." ]] && ([[ -n "$DISPLAY" ]] || [[ -n "$WAYLAND_DISPLAY" ]]) && [[ -n "$XDG_CURRENT_DESKTOP" ]] && hascommand xdg-user-dir; then
DOWNLOAD_PATH="$(xdg-user-dir DOWNLOAD)"
fi
# Display help if no URL provided or help requested
if [[ -z "${URL}" ]] || [[ "${URL}" == "--help" ]] || [[ "${URL}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}d${RESET}: Download from URL using the best available tool"
echo -en "${BRIGHT_WHITE}Tools:${RESET} ${BRIGHT_CYAN}axel${RESET}, ${BRIGHT_CYAN}aria2c${RESET}, ${BRIGHT_CYAN}wget${RESET}, ${BRIGHT_CYAN}curl${RESET},"
echo -e " ${BRIGHT_MAGENTA}yt-dlp${RESET}, ${BRIGHT_MAGENTA}spotdl${RESET}, ${BRIGHT_MAGENTA}tidal-dl${RESET}, ${BRIGHT_MAGENTA}scdl${RESET}"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}d${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}URL${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}download_path${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}d${RESET} ${BRIGHT_YELLOW}https://example.com/file.zip${RESET} ${BRIGHT_BLUE}# Direct download${RESET}"
echo -e " ${BRIGHT_CYAN}d${RESET} ${BRIGHT_YELLOW}https://youtube.com/watch?v=...${RESET} ${BRIGHT_BLUE}# YouTube video${RESET}"
echo -e " ${BRIGHT_CYAN}d${RESET} ${BRIGHT_YELLOW}https://open.spotify.com/track/...${RESET} ${BRIGHT_BLUE}# Spotify track${RESET}"
echo -e " ${BRIGHT_CYAN}d${RESET} ${BRIGHT_YELLOW}https://soundcloud.com/...${RESET} ${BRIGHT_BLUE}# SoundCloud${RESET}"
echo -e " ${BRIGHT_CYAN}d${RESET} ${BRIGHT_YELLOW}https://example.com/file.zip${RESET} ${BRIGHT_GREEN}~/Downloads${RESET} ${BRIGHT_BLUE}# Custom path${RESET}"
return 1
# Spotify URLs
elif [[ "${URL}" =~ ^https://open.spotify.com/ ]]; then
# spotDL finds songs from Spotify playlists on YouTube and downloads
# them along with album art, lyrics, and metadata
# Link: https://github.com/spotDL/spotify-downloader
if hascommand spotdl; then
echo -e "${BRIGHT_YELLOW}Using spotdl for Spotify URL...${RESET}"
(cd "${DOWNLOAD_PATH}" && spotdl --bitrate 320k "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
else
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}No suitable Spotify download tool found${RESET}"
return 1
fi
# Tidal URLs
elif [[ "${URL}" =~ ^https://tidal.com/ ]]; then
# TIDAL Downloader Next Generation downloads songs and videos from TIDAL
# https://github.com/exislow/tidal-dl-ng
if hascommand tidal-dl-ng; then
echo -e "${BRIGHT_YELLOW}Using tidal-dl-ng for Tidal URL...${RESET}"
(cd "${DOWNLOAD_PATH}" && tidal-dl-ng dl "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
# Tidal-Media-Downloader» lets you download videos and tracks from Tidal
# https://github.com/yaronzz/Tidal-Media-Downloader
elif hascommand tidal-dl; then
echo -e "${BRIGHT_YELLOW}Using tidal-dl for Tidal URL (ensure logged in)...${RESET}"
(cd "${DOWNLOAD_PATH}" && tidal-dl "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
else
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}No suitable Tidal download tool found${RESET}"
return 1
fi
# Soundcloud + scdl is a script is able to download music from SoundCloud
# and can also set the id3tag to the downloaded music file
# Link: https://github.com/scdl-org/scdl
elif [[ "${URL}" =~ ^https?://(www\.)?soundcloud.com/ ]] && hascommand scdl; then
# Download using the scdl script
echo -e "${BRIGHT_YELLOW}Using scdl for SoundCloud URL...${RESET}"
(scdl -l "${URL}" --path "${DOWNLOAD_PATH}" --onlymp3) || echo -e "${BRIGHT_RED}Download failed.${RESET}"
# SoundCloud, Youtube Music, Bandcamp and other audio-focused platforms
elif [[ "${URL}" =~ ^https?://(www\.)?(soundcloud\.com|bandcamp\.com|mixcloud\.com|audiomack\.com|audius\.co|hearthis\.at|play\.fm|clyp\.it|indieshuffle\.com|music\.youtube\.com)/ ]]; then
# yt-dlp is a feature-rich command-line audio/video downloader
# with support for thousands of sites
# Link: https://github.com/yt-dlp/yt-dlp
if hascommand yt-dlp; then
echo -e "${BRIGHT_YELLOW}Using yt-dlp for the audio URL...${RESET}"
if ask "Do you want to download the audio in MP3 format (choosing No will download the highest quality original format)?" Y; then
(cd "${DOWNLOAD_PATH}" && yt-dlp -f 'bestaudio/best' --extract-audio --audio-format mp3 --audio-quality 0 "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
else
(cd "${DOWNLOAD_PATH}" && yt-dlp -f 'bestaudio/best' --extract-audio --audio-quality 0 "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
fi
# youtube-dl downloads videos from youtube.com or other video platforms
# Link: https://github.com/ytdl-org/youtube-dl
elif hascommand youtube-dl; then
echo -e "${BRIGHT_YELLOW}Using youtube-dl for the audio URL...${RESET}"
if ask "Do you want to download the audio in MP3 format (choosing No will download the highest quality original format)?" Y; then
(cd "${DOWNLOAD_PATH}" && youtube-dl -f 'bestaudio' --extract-audio --audio-format mp3 --audio-quality 0 "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
else
(cd "${DOWNLOAD_PATH}" && youtube-dl -f 'bestaudio' --extract-audio --audio-format best --audio-quality 0 "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
fi
else
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}No suitable audio download tool found${RESET}"
return 1
fi
# Simple video platforms (Odysee, Rumble, Bitchute, etc) - basic format selection
elif [[ "${URL}" =~ ^https?://(www\.)?(bitchute\.com|lbry\.tv|metacafe\.com|odysee\.com|peertube\.[a-zA-Z]+|rumble\.com|streamable\.com|ted\.com|viddler\.com|wistia\.com)/ ]]; then
# Found yt-dlp command-line audio/video downloader
if hascommand yt-dlp; then
echo -e "${BRIGHT_YELLOW}Using yt-dlp for the video URL...${RESET}"
(cd "${DOWNLOAD_PATH}" && yt-dlp --format "best" --progress --no-warnings --geo-bypass "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
# Found youtube-dl command-line audio/video downloader
elif hascommand youtube-dl; then
echo -e "${BRIGHT_YELLOW}Using youtube-dl for the video URL...${RESET}"
(cd "${DOWNLOAD_PATH}" && youtube-dl --format "best" --progress --no-warnings --geo-bypass "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
else
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}No suitable video download tool found${RESET}"
return 1
fi
# Major streaming platforms (YouTube, Twitch, etc) - advanced format handling
elif [[ "${URL}" =~ ^https?://(www\.)?(youtube\.com|youtu\.be|dailymotion\.com|facebook\.com|instagram\.com|linkedin\.com|ok\.ru|reddit\.com|tiktok\.com|tumblr\.com|twitch\.tv|twitter\.com|vevo\.com|vimeo\.com|vk\.com)/ ]]; then
# Found yt-dlp command-line audio/video downloader
if hascommand yt-dlp; then
echo -e "${BRIGHT_YELLOW}Using yt-dlp for the video URL...${RESET}"
if ask "Do you want to download the highest available video quality (choosing No will limit to 1080p)?" N; then
(cd "${DOWNLOAD_PATH}" && yt-dlp -o "%(title)s.%(ext)s" -f 'bestvideo[ext=mp4]+bestaudio' --merge-output-format mp4 "${URL}" || yt-dlp -o "video.%(ext)s" -f 'bestvideo[ext=mp4]+bestaudio' --merge-output-format mp4 "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
else
(cd "${DOWNLOAD_PATH}" && yt-dlp -o "%(title)s.%(ext)s" -f 'bestvideo[ext=mp4][height<=1080]+bestaudio' --merge-output-format mp4 "${URL}" || yt-dlp -o "video.%(ext)s" -f 'bestvideo[ext=mp4][height<=1080]+bestaudio' --merge-output-format mp4 "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
fi
# Found youtube-dl command-line audio/video downloader
elif hascommand youtube-dl; then
echo -e "${BRIGHT_YELLOW}Using youtube-dl for the video URL...${RESET}"
if ask "Do you want to download the highest available video quality (choosing No will limit to 1080p)?" N; then
(cd "${DOWNLOAD_PATH}" && youtube-dl --format 'best[vcodec*=avc]+bestaudio' "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
else
(cd "${DOWNLOAD_PATH}" && youtube-dl --format 'best[vcodec*=avc][height<=1080]+bestaudio' "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
fi
else
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}No suitable video download tool found${RESET}"
return 1
fi
# Download using the detected tool
else
if hascommand axel; then
# Axel accelerates the download process by using multiple connections
# per file, and can also balance the load between different servers
# Link: https://github.com/axel-download-accelerator/axel
echo -e "${BRIGHT_YELLOW}Downloading using axel...${RESET}"
(cd "${DOWNLOAD_PATH}" && command axel -a -n 10 "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
elif hascommand aria2c; then
# aria2c is a lightweight multi-protocol & multi-source command-line
# download utility that supports HTTP/HTTPS, FTP, SFTP, BitTorrent, and
# Metalink with multiple connections and enhanced control over connections
# Link: https://aria2.github.io/
echo -e "${BRIGHT_YELLOW}Downloading using aria2c...${RESET}"
command aria2c --max-connection-per-server=5 --continue=true --async-dns=false --dir="${DOWNLOAD_PATH}" "${URL}" || echo -e "${BRIGHT_RED}Download failed.${RESET}"
elif hascommand curl; then
# curl supports data transfer from or to a server using multiple protocols
# like HTTP, HTTPS, and FTP, and features resuming and redirect following
# Link: https://curl.se/
echo -e "${BRIGHT_YELLOW}Downloading using curl...${RESET}"
(cd "${DOWNLOAD_PATH}" && command curl -C - -L -O "${URL}") || echo -e "${BRIGHT_RED}Download failed.${RESET}"
elif hascommand wget; then
# wget is a non-interactive command-line file downloader for HTTP, HTTPS,
# and FTP that supports resuming downloads on more unstable connections
# Link: https://www.gnu.org/software/wget/
echo -e "${BRIGHT_YELLOW}Downloading using wget...${RESET}"
command wget --continue -P "${DOWNLOAD_PATH}" "${URL}" || echo -e "${BRIGHT_RED}Download failed.${RESET}"
else
echo -e "${BRIGHT_RED}ERROR: No suitable download tool found${RESET}"
return 1
fi
fi
}
# Extracts any archive(s)
function extract() {
# If no archive is specified or --help or -h is passed, show help text
if [[ -z "${1}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}extract${RESET}: Extract one or multiple archive files"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}extract${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}archive${BRIGHT_MAGENTA}>${RESET}..."
echo -e "${BRIGHT_WHITE}Supports:${RESET} .tar.gz .tar.bz2 .tar .tgz .tbz2 .gz .bz2 .zip .rar .7z .Z"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}extract${RESET} ${BRIGHT_YELLOW}backup.tar.gz${RESET} ${BRIGHT_BLUE}# Extract tarball${RESET}"
echo -e " ${BRIGHT_CYAN}extract${RESET} ${BRIGHT_YELLOW}archive.zip${RESET} ${BRIGHT_BLUE}# Extract zip${RESET}"
echo -e " ${BRIGHT_CYAN}extract${RESET} ${BRIGHT_YELLOW}data.7z${RESET} ${BRIGHT_BLUE}# Extract 7-zip${RESET}"
echo -e " ${BRIGHT_CYAN}extract${RESET} ${BRIGHT_YELLOW}file1.tar.gz file2.zip${RESET} ${BRIGHT_BLUE}# Multiple archives${RESET}"
return 1
fi
# Loop through each archive provided as an argument
for archive in "$@"; do
# Check if the file exists
if [ -f "${archive}" ] ; then
# Switch case to handle various archive types
case "${archive}" in
*.tar.bz2) tar xvjf "${archive}" ;;
*.tar.gz) tar xvzf "${archive}" ;;
*.bz2) bunzip2 "${archive}" ;;
*.rar) rar x "${archive}" ;;
*.gz) gunzip "${archive}" ;;
*.tar) tar xvf "${archive}" ;;
*.tbz2) tar xvjf "${archive}" ;;
*.tgz) tar xvzf "${archive}" ;;
*.zip) unzip "${archive}" ;;
*.Z) uncompress "${archive}" ;;
*.7z) 7z x "${archive}" ;;
*) echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Unknown archive type ${BRIGHT_YELLOW}${archive##*.}${RESET}" ;;
esac
else
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}The file ${BRIGHT_YELLOW}${archive}${BRIGHT_CYAN} is not valid or does not exist${RESET}"
fi
done
}
# Generate a random password
# Pass the number of characters for the password on the command line
# Add the parameter +s to include symbols and -s to exclude them
# Example: genpw
# Example: genpw +s 24
# Example: genpw 8
# Example: genpw 12 -s
# HINT: Check passwords with cracklib: echo "1234abc" | cracklib-check
# Link: https://www.cyberciti.biz/security/linux-password-strength-checker/
function genpw() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}genpw${RESET}: Generate secure random passwords"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}genpw${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}length${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}-s|+s${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Options:${RESET}"
echo -e " ${BRIGHT_GREEN}+s${RESET} Include symbols"
echo -e " ${BRIGHT_GREEN}-s${RESET} Exclude symbols"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}genpw${RESET} ${BRIGHT_BLUE}# Interactive (default 16 chars)${RESET}"
echo -e " ${BRIGHT_CYAN}genpw${RESET} ${BRIGHT_GREEN}24${RESET} ${BRIGHT_BLUE}# 24 character passwords${RESET}"
echo -e " ${BRIGHT_CYAN}genpw${RESET} ${BRIGHT_GREEN}32 +s${RESET} ${BRIGHT_BLUE}# 32 chars with symbols${RESET}"
echo -e " ${BRIGHT_CYAN}genpw${RESET} ${BRIGHT_GREEN}16 -s${RESET} ${BRIGHT_BLUE}# 16 chars without symbols${RESET}"
return 0
fi
local _PASSWORD_LENGTH
local _PASSWORD_SYMBOLS
if [[ ${1} -gt 0 ]]; then
_PASSWORD_LENGTH=${1}
elif [[ ${2} -gt 0 ]]; then
_PASSWORD_LENGTH=${2}
else
read -e -i "16" -p "How many characters? " _PASSWORD_LENGTH
if [[ ! ${_PASSWORD_LENGTH} -gt 0 ]]; then
_PASSWORD_LENGTH=16
fi
fi
if [[ ${1} == '-s' ]] || [[ ${2} == '-s' ]]; then
_PASSWORD_SYMBOLS=false
elif [[ ${1} == '+s' ]] || [[ ${2} == '+s' ]]; then
_PASSWORD_SYMBOLS=true
else
if ask "Do you want to include symbols?" Y; then
_PASSWORD_SYMBOLS=true
fi
fi
if hascommand --strict apg; then
if [[ ${_PASSWORD_SYMBOLS} == true ]]; then
apg -a 1 -n 10 -m ${_PASSWORD_LENGTH} -l
else
apg -a 0 -n 10 -m ${_PASSWORD_LENGTH} -l
fi
elif hascommand --strict pwgen; then
if [[ ${_PASSWORD_SYMBOLS} == true ]]; then
pwgen --capitalize --numerals --symbols --secure -C ${_PASSWORD_LENGTH} 40
else
pwgen --capitalize --numerals --secure -C ${_PASSWORD_LENGTH} 40
fi
elif hascommand --strict gpg; then
for ((n=0;n<10;n++)); do
if [[ ${_PASSWORD_SYMBOLS} == true ]]; then
gpg --gen-random 1 1024 | tr -dc a-zA-Z0-9'`~!@#$%^&*-_=+()[]{}|;:",.?<>/\\'"'" | head -c${_PASSWORD_LENGTH}; echo
else
gpg --gen-random 1 1024 | tr -dc a-zA-Z0-9 | head -c${_PASSWORD_LENGTH}; echo
fi
done
elif hascommand --strict openssl; then
for ((n=0;n<10;n++)); do
if [[ ${_PASSWORD_SYMBOLS} == true ]]; then
openssl rand 1024 | tr -dc a-zA-Z0-9'`~!@#$%^&*-_=+()[]{}|;:",.?<>/\\'"'" | head -c${_PASSWORD_LENGTH}; echo
else
openssl rand 1024 | tr -dc a-zA-Z0-9 | head -c${_PASSWORD_LENGTH}; echo
fi
done
else
for ((n=0;n<10;n++)); do
if [[ ${_PASSWORD_SYMBOLS} == true ]]; then
cat /dev/urandom | tr -dc a-zA-Z0-9'`~!@#$%^&*-_=+()[]{}|;:",.?<>/\\'"'" | head -c${_PASSWORD_LENGTH}; echo
else
cat /dev/urandom | tr -dc a-zA-Z0-9 | head -c${_PASSWORD_LENGTH}; echo
fi
done
fi
}
# Schedule the computer to auto reboot (defaults to 4:00 AM)
function rebootlater() {
# Show help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}rebootlater${RESET}: Schedule the computer to auto reboot at a specified time"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}rebootlater${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}time${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}rebootlater${RESET} ${BRIGHT_BLUE}# Defaults to 4:00 AM${RESET}"
echo -e " ${BRIGHT_CYAN}rebootlater${RESET} ${BRIGHT_YELLOW}23:30${RESET} ${BRIGHT_BLUE}# Reboot at 11:30 PM${RESET}"
echo -e " ${BRIGHT_CYAN}sudo shutdown -c${RESET} ${BRIGHT_BLUE}# Cancel a scheduled reboot${RESET}"
return 0
fi
sudo shutdown -r "${1:-04:00}"
}
# See what command you are using the most (this parses the history command)
# Usage: mostused [num_items]
function mostused() {
local NUM_ITEMS="${1:-10}" # Default to 10 if NUM_ITEMS is not specified
history \
| awk ' { a[$4]++ } END { for ( i in a ) print a[i], i | "sort -rn | head -n'"${NUM_ITEMS}"'"}' \
| awk '$1 > max{ max=$1} { bar=""; i=s=10*$1/max;while(i-->0)bar=bar"#"; printf "%25s %15d %s %s", $2, $1,bar, "\n"; }'
}
# Start a program but immediately disown it and detach it from the terminal
function runfree() {
"$@" > /dev/null 2>&1 & disown
}
# Sends a desktop notification when a command finishes its execution
# Examples: sleep 1; alert # Notifies when the sleep command completes
# make && alert 'Success' || alert 'Error' # Notify result status
# tar -czvf file.tar.gz && alert # Notifies if the command succeeds
# false || alert "There was an error" # Notifies if the command fails
# Link: https://askubuntu.com/questions/423646/use-of-default-alias-alert
function alert() {
# Determine the icon based on the exit status of the last command
# If exit status is zero (success), use 'terminal' as icon; otherwise use 'error'
local ICON="$([[ $? == 0 ]] && echo terminal || echo error)"
# Extract the last executed command from the history
# Remove leading numbers and trailing 'alert' command if exists
local LAST_COMMAND="$(history | tail -n1 | sed -e 's/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//')"
# Send the desktop notification with highest urgency level ("critical")
notify-send --urgency=critical -i "${ICON}" "${LAST_COMMAND}"
}
# Format a string that is safe to be used in regular expressions
function regexformat() {
# Escape special regex characters for use in patterns
# Note: In POSIX bracket expressions, backslash is literal, not an escape char.
# Place backslash last to make intent explicit; parens/space don't need escaping.
echo -n "$(printf '%s' "${1}" | sed 's/[.[() *^$+?{|\\]/\\&/g')"
}
# Trim leading and trailing characters on the clipboard
function trimcb() {
# Fetch the current clipboard content and declare it as a local variable
local CONTENT_TO_TRIM=$(clipboard)
# If clipboard content is not empty, trim it and send it back to the clipboard
if [[ -n "${CONTENT_TO_TRIM}" ]]; then
echo -e "${CONTENT_TO_TRIM}" | sed 's/^[ \t]*//;s/[ \t]*$//' | clipboard
fi
}
# Long format directory listing with color columns (only requires gawk)
function llcolor {
if hascommand --strict gawk; then
# Show long directory listings with color columns
command ls -l --all --classify --group-directories-first --human-readable --color=always "$@" | awk '
BEGIN {
FPAT = "([[:space:]]*[^[:space:]]+)";
OFS = "";
}
{
$1 = "\033[0;37m" $1 "\033[0m";
$2 = "\033[0;34m" $2 "\033[0m";
$3 = "\033[0;35m" $3 "\033[0m";
$4 = "\033[0;31m" $4 "\033[0m";
$5 = "\033[0;33m" $5 "\033[0m";
$6 = "\033[0;32m" $6 "\033[0m";
$7 = "\033[0;32m" $7 "\033[0m";
$8 = "\033[0;36m" $8 "\033[0m";
print
}
'
else # Gawk not installed...
# Show long directory listings with highest compatibility
command ls -Fls "$@"
fi
}
# Commands pushd and popd now output the directory stack after modification
# and also prevents duplicate directories being added to the directory stack
function pushd() {
builtin pushd "${@}" > /dev/null
dirsdedup
echo "Directory Stack:"
dirs -v
}
function popd() {
builtin popd "${@}" > /dev/null
echo "Directory Stack:"
dirs -v
}
# Remove duplicate directories in the directory stack for pushd
function dirsdedup() {
# Declare an array to hold the new directory stack without duplicates
declare -a NEW_STACK=()
# Declare a copy of the current directory stack, excluding the first element
declare -a CURRENT_STACK_COPY=("${DIRSTACK[@]:1}")
# Declare an associative array to keep track of directories seen
declare -A SEEN_DIRECTORIES
# Local variables for loop iteration
local DIRECTORY INDEX
# Mark the current working directory as seen
SEEN_DIRECTORIES[$PWD]=1
# Iterate through the copied directory stack
for DIRECTORY in "${CURRENT_STACK_COPY[@]}"
do
# If the directory has not been seen before, add it to the new stack
if [ -z "${SEEN_DIRECTORIES[$DIRECTORY]}" ]; then
NEW_STACK+=("$DIRECTORY")
SEEN_DIRECTORIES[$DIRECTORY]=1
fi
done
# Rebuild the directory stack from the new stack, in reverse order
builtin dirs -c
for ((INDEX=${#NEW_STACK[@]}-1; INDEX>=0; INDEX--))
do
builtin pushd -n "${NEW_STACK[INDEX]}" >/dev/null
done
}
# View a comma delimited (.CSV) file
function csvview() {
# Check for the presence of arguments
if [[ $# -eq 0 ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}csvview${RESET}: View comma-delimited (.CSV) files"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}csvview${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}file.csv${BRIGHT_MAGENTA}>${RESET}..."
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}csvview${RESET} ${BRIGHT_YELLOW}data.csv${RESET}"
echo -e " ${BRIGHT_CYAN}csvview${RESET} ${BRIGHT_YELLOW}file1.csv file2.csv${RESET} ${BRIGHT_BLUE}# Multiple files${RESET}"
return 1
fi
# Loop through all the arguments
for FILE in "${@}"; do
# Check if file exists
if [[ ! -f "${FILE}" ]]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}The file ${BRIGHT_YELLOW}${FILE}${BRIGHT_CYAN} does not exist${RESET}"
continue
# Check if file is readable
elif [[ ! -r "${FILE}" ]]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}The file ${BRIGHT_YELLOW}${FILE}${BRIGHT_CYAN} is not readable${RESET}"
continue
fi
# Display the formatted CSV file
command cat "${FILE}" | command sed 's/,/ ,/g' | column -t -s, | command less -S
done
}
# Send file(s) to the trash
# Link: https://www.tecmint.com/trash-cli-manage-linux-trash-from-command-line/
function trash() {
# Check for the presence of arguments or help requested
if [[ $# -eq 0 ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}trash${RESET}: Send files to the trash"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}trash${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}file${BRIGHT_MAGENTA}>${RESET}..."
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}trash${RESET} ${BRIGHT_YELLOW}oldfile.txt${RESET}"
echo -e " ${BRIGHT_CYAN}trash${RESET} ${BRIGHT_YELLOW}file1.txt file2.txt${RESET} ${BRIGHT_BLUE}# Multiple files${RESET}"
return 1
fi
# Check if trash-cli exists...
# https://github.com/andreafrancia/trash-cli
if hascommand trash-put; then
trash-put "${@}" && return 0
# Check if rem exists...
# Link: https://github.com/quackduck/rem
elif hascommand rem; then
rem "${@}" && return 0
# Check if gio trash exists (glib2)...
# Link: https://wiki.archlinux.org/title/Trash-cli#gio_trash
elif hascommand gio; then
gio trash "${@}" && return 0
# Check if kioclient5 exists (kde-cli-tools)...
# Link: https://wiki.archlinux.org/title/Trash-cli#kioclient5
elif hascommand kioclient5; then
kioclient5 move "${@}" trash:/ && return 0
# Check for various trash directories
elif [[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/Trash/files" ]]; then
command mv -i "${@}" "${XDG_DATA_HOME:-${HOME}/.local/share}/Trash/files/" && return 0
elif [[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/trash/files" ]]; then
command mv -i "${@}" "${XDG_DATA_HOME:-${HOME}/.local/share}/trash/files/" && return 0
elif [[ -d "${HOME}/.Trash" ]]; then
command mv -i "${@}" "${HOME}/.Trash/" && return 0
elif [[ -d "${HOME}/.trash" ]]; then
command mv -i "${@}" "${HOME}/.trash/" && return 0
# Create the trash directory per the XDG specification if none exists
else
command mkdir -p "${XDG_DATA_HOME:-${HOME}/.local/share}/Trash/files"
command mv -i "${@}" "${XDG_DATA_HOME:-${HOME}/.local/share}/Trash/files/" && return 0
fi
# If none of the methods succeeded, return an error
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Failed to send files to the trash${RESET}"
return 1
}
# Display the contents of the trash
function trashlist() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}trashlist${RESET}: List files in the trash"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}trashlist${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Auto-detects trash-cli, rem, gio, or kioclient5${RESET}"
return 0
fi
# Check if trash-cli exists...
# https://github.com/andreafrancia/trash-cli
if hascommand --strict trash-list; then
trash-list
# Check if rem exists...
# Link: https://github.com/quackduck/rem
elif hascommand --strict rem; then
rem -l
# Check if gio trash exists (glib2)...
# Link: https://wiki.archlinux.org/title/Trash-cli#gio_trash
elif hascommand --strict gio; then
gio list trash:///
# Check if kioclient5 exists (kde-cli-tools)...
# Link: https://wiki.archlinux.org/title/Trash-cli#kioclient5
elif hascommand --strict kioclient5; then
kioclient5 ls trash:/
# Check for alternative trash directories and list files
elif [[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/Trash/files" ]]; then
ls -l "${XDG_DATA_HOME:-${HOME}/.local/share}/Trash/files/"
elif [[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/trash/files" ]]; then
ls -l "${XDG_DATA_HOME:-${HOME}/.local/share}/trash/files/"
elif [[ -d "${HOME}/.Trash" ]]; then
ls -l "${HOME}/.Trash/"
elif [[ -d "${HOME}/.trash" ]]; then
ls -l "${HOME}/.trash/"
else
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}No trash directory found${RESET}"
fi
}
# Empty and permanently delete all the files in the trash
function trashempty() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}trashempty${RESET}: Permanently delete all files in the trash"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}trashempty${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Prompts for confirmation before deleting${RESET}"
echo -e " ${BRIGHT_BLUE}Auto-detects trash-cli, rem, gio, or kioclient5${RESET}"
return 0
fi
# Ask for user confirmation before deleting trash
if ask "${BRIGHT_WHITE}Are you sure you want to ${BRIGHT_MAGENTA}permanently delete${BRIGHT_WHITE} all the files in the trash? ${BRIGHT_RED}This action cannot be undone.${RESET}" "N"; then
# Check if trash-cli exists...
# https://github.com/andreafrancia/trash-cli
if hascommand --strict trash-empty; then
trash-empty
# Check if rem exists...
# Link: https://github.com/quackduck/rem
elif hascommand --strict rem; then
rem --empty
# Check if gio trash exists (glib2)...
# Link: https://wiki.archlinux.org/title/Trash-cli#gio_trash
elif hascommand --strict gio; then
gio trash --empty
# Check if kioclient5 exists (kde-cli-tools)...
# Link: https://wiki.archlinux.org/title/Trash-cli#kioclient5
elif hascommand --strict kioclient5; then
kioclient5 empty trash:/
# Check for alternative trash directories and delete files
elif [[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/Trash/files" ]]; then
local _TRASH="${XDG_DATA_HOME:-${HOME}/.local/share}/Trash/files"
rm -rf "${_TRASH}"/{..?*,.[!.]*,*} 2>/dev/null
elif [[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/trash/files" ]]; then
local _TRASH="${XDG_DATA_HOME:-${HOME}/.local/share}/trash/files"
rm -rf "${_TRASH}"/{..?*,.[!.]*,*} 2>/dev/null
elif [[ -d "${HOME}/.Trash" ]]; then
rm -rf "${HOME}/.Trash"/{..?*,.[!.]*,*} 2>/dev/null
elif [[ -d "${HOME}/.trash" ]]; then
rm -rf "${HOME}/.trash"/{..?*,.[!.]*,*} 2>/dev/null
else
# No supported method found for emptying trash
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}No trash directory or supported application found${RESET}"
fi
else
# Operation was cancelled by the user
echo -e "${BRIGHT_RED}Operation cancelled.${RESET}"
fi
}
# Restore the trash only is trash-cli is installed
# trash-cli - Command Line Interface to FreeDesktop.org Trash
# Link: https://github.com/andreafrancia/trash-cli
if hascommand --strict restore-trash; then
alias trashrestore='restore-trash'
elif hascommand --strict trash-restore; then
alias trashrestore='trash-restore'
fi
# Recursively remove all empty directories from a given path
# Syntax: rmempty [directory]
function rmempty() {
# Show help if --help or -h is passed
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}rmempty${RESET}: Recursively remove all empty directories"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}rmempty${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}directory${BRIGHT_MAGENTA}]${RESET}"
echo
echo -e "${BRIGHT_WHITE}Options:${RESET}"
echo -e " ${BRIGHT_YELLOW}-h${RESET}, ${BRIGHT_YELLOW}--help${RESET} Show this help message"
echo
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " Finds and removes all empty directories within the specified path."
echo -e " If no directory is specified, uses the current working directory."
echo -e " You will be prompted for confirmation before any directories are removed."
echo
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}rmempty${RESET} ${BRIGHT_BLUE}# Current directory${RESET}"
echo -e " ${BRIGHT_CYAN}rmempty${RESET} ${BRIGHT_YELLOW}/path/to/dir${RESET} ${BRIGHT_BLUE}# Specified path${RESET}"
return 0
fi
# Use the provided directory or default to current directory
local TARGET_DIR="${1:-.}"
# Resolve to absolute path
local ABSOLUTE_PATH
ABSOLUTE_PATH=$(realpath "${TARGET_DIR}" 2>/dev/null)
# Check if realpath succeeded
if [[ -z "${ABSOLUTE_PATH}" ]]; then
echo -e "${BRIGHT_RED}Error:${RESET} ${BRIGHT_CYAN}Could not resolve path:${RESET} ${BRIGHT_YELLOW}${TARGET_DIR}${RESET}"
return 1
fi
# Check if the target directory exists
if [[ ! -d "${ABSOLUTE_PATH}" ]]; then
echo -e "${BRIGHT_RED}Error:${RESET} ${BRIGHT_CYAN}Directory does not exist:${RESET} ${BRIGHT_YELLOW}${ABSOLUTE_PATH}${RESET}"
return 1
fi
# Check if the target directory is readable
if [[ ! -r "${ABSOLUTE_PATH}" ]]; then
echo -e "${BRIGHT_RED}Error:${RESET} ${BRIGHT_CYAN}Directory is not readable:${RESET} ${BRIGHT_YELLOW}${ABSOLUTE_PATH}${RESET}"
return 1
fi
# Find all empty directories (depth-first order so nested empties are found)
local EMPTY_DIRS
EMPTY_DIRS=$(find "${ABSOLUTE_PATH}" -type d -empty 2>/dev/null)
# Count the empty directories
local COUNT=0
if [[ -n "${EMPTY_DIRS}" ]]; then
COUNT=$(echo "${EMPTY_DIRS}" | wc -l)
fi
# If no empty directories found, inform the user and exit
if [[ ${COUNT} -eq 0 ]]; then
echo -e "${BRIGHT_GREEN}No empty directories found in:${RESET} ${BRIGHT_MAGENTA}${ABSOLUTE_PATH}${RESET}"
return 0
fi
# Display the empty directories that will be removed
echo -e "${BRIGHT_WHITE}Found ${BRIGHT_YELLOW}${COUNT}${BRIGHT_WHITE} empty director$([[ ${COUNT} -eq 1 ]] && echo "y" || echo "ies") in:${RESET} ${BRIGHT_MAGENTA}${ABSOLUTE_PATH}${RESET}"
echo -e "${BRIGHT_BLACK}────────────────────────────────────────${RESET}"
# Show the list of directories
while IFS= read -r DIR; do
echo -e " ${BRIGHT_CYAN}${DIR}${RESET}"
done <<< "${EMPTY_DIRS}"
echo -e "${BRIGHT_BLACK}────────────────────────────────────────${RESET}"
# Ask for confirmation before deleting
if ask "${BRIGHT_YELLOW}Remove all ${COUNT} empty director$([[ ${COUNT} -eq 1 ]] && echo "y" || echo "ies")?${RESET}" N; then
# Remove empty directories (depth-first ensures nested empties are removed)
local REMOVED=0
local FAILED=0
while IFS= read -r DIR; do
if rmdir "${DIR}" 2>/dev/null; then
echo -e "${BRIGHT_GREEN}Removed:${RESET} ${BRIGHT_CYAN}${DIR}${RESET}"
((REMOVED++))
else
echo -e "${BRIGHT_RED}Failed:${RESET} ${BRIGHT_CYAN}${DIR}${RESET}"
((FAILED++))
fi
done <<< "$(echo "${EMPTY_DIRS}" | sort -r)"
# Summary
echo -e "${BRIGHT_BLACK}────────────────────────────────────────${RESET}"
if [[ ${FAILED} -eq 0 ]]; then
echo -e "${BRIGHT_GREEN}Successfully removed ${REMOVED} empty director$([[ ${REMOVED} -eq 1 ]] && echo "y" || echo "ies").${RESET}"
else
echo -e "${BRIGHT_YELLOW}Removed ${REMOVED}, failed ${FAILED}.${RESET}"
fi
else
echo -e "${BRIGHT_RED}Operation cancelled.${RESET}"
return 0
fi
}
# Check the sha256 checksum of a file using a checksum file parameter like sha256sum.txt
function checksha256() {
if [[ "$#" -lt 2 ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}checksha256${RESET}: Verify a file's SHA256 checksum against a checksum file"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}checksha256${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}file${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}checksum_file${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}checksha256${RESET} ${BRIGHT_YELLOW}download.iso sha256sum.txt${RESET}"
return 1
fi
if hascommand --strict sha256sum; then
if [[ $(sha256sum "${1}" | cut -d' ' -f 1) == $(grep -Pom1 '\b[a-fA-F0-9]{64}\b' "${2}") ]]; then
echo -e "${BRIGHT_GREEN}Good"
else
echo -e "${BRIGHT_RED}Bad"
fi
else
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}The application sha256sum is not installed${RESET}"
fi
}
# If we are in a graphical desktop environment...
if [[ -n "${DISPLAY}" ]] || [[ -n "${WAYLAND_DISPLAY}" ]]; then
# Search Desktop applications
function findapps() {
local SEARCH_TERM="$1" # The keyword to search for
# Ensure a search term is provided
if [[ -z "${SEARCH_TERM}" ]]; then
echo -e "\e[1;31mPlease provide a search term.\e[0m"
return 1
fi
# Search in both system-wide and user-specific application directories
local DIRECTORIES=("/usr/share/applications" "${XDG_DATA_HOME:-${HOME}/.local/share}/applications")
echo -e "\e[1;36mApplications matching\e[0m '\e[33m${SEARCH_TERM}\e[0m':"
echo -e "\e[1;90m------------------------------------\e[0m"
# Fields to search within the .desktop file
local FIELDS="Name|GenericName|Comment|Keywords"
# Process each directory and collect results
while read -r LINE; do
echo "${LINE}"
done < <(
for DIR in "${DIRECTORIES[@]}"; do
if [[ -d "${DIR}" ]]; then
grep -i -l -E "(${FIELDS})=.*${SEARCH_TERM}" "${DIR}"/*.desktop | \
while read -r DESKTOP_FILE; do
local APP_NAME FIELD_MATCH
APP_NAME=$(grep -m 1 '^Name=' "${DESKTOP_FILE}" | sed 's/Name=//')
FIELD_MATCH=$(grep -i -m 1 -E "(${FIELDS})=.*${SEARCH_TERM}" "${DESKTOP_FILE}")
echo -e "\e[1;32m✓\e[0m \e[1;33m${APP_NAME}\e[0m \e[1;35m${DESKTOP_FILE}\e[0m"
# Debug only: echo -e " \e[1;90mMatch found in: ${FIELD_MATCH}\e[0m"
done
fi
done | sort
)
}
fi
if hascommand --strict crontab; then
# Interactively search for cron jobs matching specified time criteria
function findcronjob() {
echo -e "${BRIGHT_CYAN}Cron Job Finder${RESET}"
echo -e "${BRIGHT_WHITE}Press Enter to match any value for a field${RESET}\n"
# Helper function to expand ranges and build regex
build_pattern() {
local INPUT="$1"
# Empty, "any", or "*" means match anything
if [[ -z "${INPUT}" || "${INPUT}" == "any" || "${INPUT}" == "*" ]]; then
echo '\S+'
return
fi
local RESULT=()
IFS=',' read -ra PARTS <<< "${INPUT}"
for PART in "${PARTS[@]}"; do
# Check if it's a range (e.g., 8-10)
if [[ "${PART}" =~ ^([0-9]+)-([0-9]+)$ ]]; then
local START="${BASH_REMATCH[1]}"
local END="${BASH_REMATCH[2]}"
for ((i = START; i <= END; i++)); do
RESULT+=("${i}")
done
else
RESULT+=("${PART}")
fi
done
# Join with | for regex alternation
local IFS='|'
echo "${RESULT[*]}"
}
# 1. Hour selection
local HOUR_INPUT HOUR_PATTERN
echo -e "${BRIGHT_YELLOW}Hour${RESET} (0-23, ranges/comma-separated, e.g., 8-10 or 9,15,20):"
echo -ne "${BRIGHT_CYAN}>${RESET} "
read HOUR_INPUT
HOUR_PATTERN=$(build_pattern "${HOUR_INPUT}")
# 2. Minute selection
local MINUTE_INPUT MINUTE_PATTERN
echo -e "${BRIGHT_YELLOW}Minute${RESET} (0-59, ranges/comma-separated):"
echo -ne "${BRIGHT_CYAN}>${RESET} "
read MINUTE_INPUT
MINUTE_PATTERN=$(build_pattern "${MINUTE_INPUT}")
# 3. Day of month selection
local DAY_INPUT DAY_PATTERN
echo -e "${BRIGHT_YELLOW}Day of month${RESET} (1-31, ranges/comma-separated):"
echo -ne "${BRIGHT_CYAN}>${RESET} "
read DAY_INPUT
DAY_PATTERN=$(build_pattern "${DAY_INPUT}")
# 4. Month selection
local MONTH_INPUT MONTH_PATTERN
echo -e "${BRIGHT_YELLOW}Month${RESET} (1-12, ranges/comma-separated):"
echo -ne "${BRIGHT_CYAN}>${RESET} "
read MONTH_INPUT
MONTH_PATTERN=$(build_pattern "${MONTH_INPUT}")
# 5. Day of week selection
local WEEKDAY_INPUT WEEKDAY_PATTERN
echo -e "${BRIGHT_YELLOW}Day of week${RESET} (0-7, where 0/7=Sunday, ranges/comma-separated):"
echo -ne "${BRIGHT_CYAN}>${RESET} "
read WEEKDAY_INPUT
WEEKDAY_PATTERN=$(build_pattern "${WEEKDAY_INPUT}")
# 6. User selection
local SUDO_CHOICE
echo -e "\n${BRIGHT_YELLOW}Search root crontab with sudo?${RESET} (Y/n):"
echo -ne "${BRIGHT_CYAN}>${RESET} "
read SUDO_CHOICE
local CRONTAB_COMMAND
if [[ "${SUDO_CHOICE}" =~ ^[Nn] ]]; then
CRONTAB_COMMAND="crontab -l"
else
CRONTAB_COMMAND="sudo crontab -l"
sudo true # Cache sudo
fi
# Build the full regex pattern
local PATTERN="^\s*(${MINUTE_PATTERN})\s+(${HOUR_PATTERN})\s+(${DAY_PATTERN})\s+(${MONTH_PATTERN})\s+(${WEEKDAY_PATTERN})\s+"
# Execute search
echo -e "\n${BRIGHT_GREEN}Matching cron jobs:${RESET}"
${CRONTAB_COMMAND} 2>/dev/null | grep -E "${PATTERN}" --color=always || echo -e "${BRIGHT_YELLOW}No matching jobs found${RESET}"
}
fi
# Searches for filenames (can use wildcards)
alias f="findfile"
function findfile() {
# Initialize the sudo prefix for running commands with elevated permissions
local SUDO_PREFIX=""
# Check for --sudo flag and remove it from arguments if present
if [[ "$1" == "--sudo" ]]; then
SUDO_PREFIX="sudo "
shift
fi
# Check if any filename or pattern is specified; if not, show help text
if [[ -z "$1" ]] || [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}findfile${RESET}: Search for filenames recursively"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}findfile${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}--sudo${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}pattern${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Options:${RESET}"
echo -e " ${BRIGHT_GREEN}--sudo${RESET} Run with elevated permissions"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}findfile${RESET} ${BRIGHT_YELLOW}'file.txt'${RESET}"
echo -e " ${BRIGHT_CYAN}findfile${RESET} ${BRIGHT_GREEN}--sudo${RESET} ${BRIGHT_YELLOW}'config'${RESET} ${BRIGHT_BLUE}# Search system dirs${RESET}"
return 1
fi
# Use fdfind if installed, else use fd or find as fallback
if hascommand --strict fdfind; then
# fdfind command options
# --type 'file' : Only search for files (not directories)
# --ignore-case : Perform a case-insensitive search
# --no-ignore : Do not respect .gitignore and .ignore files
# --hidden : Include hidden files in the search results
# --follow : Follow symlinks (WARNING: can get into an endless loop)
echo "${SUDO_PREFIX}fdfind --type 'file' --ignore-case --no-ignore --hidden '$1' ."
${SUDO_PREFIX}fdfind --type 'file' --ignore-case --no-ignore --hidden "$1" .
elif hascommand --strict fd; then
echo "${SUDO_PREFIX}fd --type 'file' --ignore-case --no-ignore --hidden '$1' ."
${SUDO_PREFIX}fd --type 'file' --ignore-case --no-ignore --hidden "$1" .
else # Use find command as a last resort
# find command options
# -type f : Search for files only, not directories
# -iname : Perform a case-insensitive search
# -follow : Dereference symlinks (follow them to their targets)
echo "${SUDO_PREFIX}find . -type f -iname '$1'"
${SUDO_PREFIX}find . -type f -iname "$1"
fi
}
# Searches for text in source code files located in the current path
# Supported languages: Ada, Assembly, AWK, Batch, C, COBOL, Config, C++,
# C#(CS), CSS, Dart, Emacs Lisp, Erlang, Elixir, Fortran(90, 95, older), Go,
# Groovy, Header(C, C++), HTML, Haxe, Include, INI, Arduino, Java, JavaScript,
# JSON, Kotlin, Library, Lua, M4, Objective-C, MATLAB, Makefiles, Nim, Nix,
# Pascal, PHP, Perl, Python, R, Ruby, Racket, Rust, Scala, SASS/SCSS, Shell,
# Standard ML, SQL, Swift, TCL, Templates, LaTeX, TypeScript, VB, VBA, VBS,
# VHDL, Wren, XML, YAML, Zig
function findcode() {
# Local constant for maximum line length cut-off
# NOTE: This is necessary for certain files like minified javascript
local -r LINE_LENGTH_CUTOFF=1000
# The prefix to prepend to search commands for elevated permissions
local SUDO_PREFIX=""
# Check for --sudo flag and remove it from arguments if present
if [[ "$1" == "--sudo" ]]; then
SUDO_PREFIX="sudo "
shift
fi
# If no parameter is specified, show help text
if [[ -z "$1" ]] || [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}findcode${RESET}: Search for text in source code files recursively"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}findcode${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}--sudo${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}pattern${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Options:${RESET}"
echo -e " ${BRIGHT_GREEN}--sudo${RESET} Run with elevated permissions"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}findcode${RESET} ${BRIGHT_YELLOW}'function_name'${RESET}"
echo -e " ${BRIGHT_CYAN}findcode${RESET} ${BRIGHT_YELLOW}'console\\.log\\('${RESET} ${BRIGHT_BLUE}# Regex search${RESET}"
echo -e " ${BRIGHT_CYAN}findcode${RESET} ${BRIGHT_GREEN}--sudo${RESET} ${BRIGHT_YELLOW}'todo'${RESET} ${BRIGHT_BLUE}# Search system dirs${RESET}"
return 1
fi
# If ripgrep is installed, use that
# Link: https://github.com/BurntSushi/ripgrep
if hascommand --strict rg; then
echo -e "${BRIGHT_CYAN}Search using ${BRIGHT_YELLOW}ripgrep${BRIGHT_CYAN}:${RESET}"
echo "${SUDO_PREFIX}rg --smart-case --no-ignore --hidden --pretty '$@' -g '!*.min.*' ."
${SUDO_PREFIX}rg --smart-case --no-ignore --hidden --pretty "${@}" -g '!*.min.*' \
-g '*.{ada,asm,awk,bat,c,cbl,cfg,conf,config,cpp,cpy,cs,css,dart,el,erl,ex,f,f90,f95,for,go,gradle,groovy,h,hpp,hrl,htm,html,hx,hxsl,inc,ini,ino,java,js,json,jsx,kt,lib,lua,m,m4,mat,mk,nim,nix,pascal,php,pl,plx,ps1,py,r,rb,rkt,rlib,rs,sc,scala,scss,sh,sml,sql,swift,tcl,template,tex,tpl,ts,tsx,vb,vba,vbs,vhd,vhdl,wren,xml,yaml,yml,zig}' | \
awk -v len=$LINE_LENGTH_CUTOFF '{ $0=substr($0, 1, len); print $0 }'
# If The Silver Searcher is installed, use that
# Link: https://github.com/ggreer/the_silver_searcher
# Hint: You can use --ignore "dir/or/file"
elif hascommand --strict ag; then
echo -e "${BRIGHT_CYAN}Search using ${BRIGHT_YELLOW}The Silver Searcher${BRIGHT_CYAN}:${RESET}"
echo "${SUDO_PREFIX}ag --color --smart-case --hidden --literal --ignore '*.min.*' '$@'"
${SUDO_PREFIX}ag --color \
--smart-case \
--hidden \
--literal \
--ignore "*.min.*" \
--file-search-regex ".*\.(ada|asm|awk|bat|c|cbl|cfg|conf|config|cpp|cpy|cs|css|dart|el|erl|ex|f|f90|f95|for|go|gradle|groovy|h|hpp|hrl|htm|html|hx|hxsl|inc|ini|ino|java|js|json|jsx|kt|lib|lua|m|m4|mat|mk|nim|nix|pascal|php|pl|plx|ps1|py|r|rb|rkt|rlib|rs|sc|scala|scss|sh|sml|sql|swift|tcl|template|tex|tpl|ts|tsx|vb|vba|vbs|vhd|vhdl|wren|xml|yaml|yml|zig)" \
"${@}" \
2> /dev/null \
| awk -v len=$LINE_LENGTH_CUTOFF '{ $0=substr($0, 1, len); print $0 }'
# Use grep (see findtext function for options explanation)
# Hint: You can use --exclude='/dir/or/file'
else
echo -e "${BRIGHT_CYAN}Search using ${BRIGHT_YELLOW}grep${BRIGHT_CYAN}:${RESET}"
echo "${SUDO_PREFIX}grep --color=always --recursive --ignore-case --binary-files=without-match --with-filename --line-number '$@'"
${SUDO_PREFIX}grep --color=always --recursive --ignore-case --binary-files=without-match --with-filename --line-number \
--include=*.ada \
--include=*.asm \
--include=*.awk \
--include=*.bat \
--include=*.c \
--include=*.cbl \
--include=*.cfg \
--include=*.conf \
--include=*.config \
--include=*.cpp \
--include=*.cpy \
--include=*.cs \
--include=*.css \
--include=*.dart \
--include=*.el \
--include=*.erl \
--include=*.ex \
--include=*.f90 \
--include=*.f95 \
--include=*.f \
--include=*.for \
--include=*.go \
--include=*.gradle \
--include=*.groovy \
--include=*.h \
--include=*.hpp \
--include=*.hrl \
--include=*.htm \
--include=*.html \
--include=*.hx \
--include=*.hxsl \
--include=*.inc \
--include=*.ini \
--include=*.ino \
--include=*.java \
--include=*.js \
--include=*.json \
--include=*.jsx \
--include=*.kt \
--include=*.lib \
--include=*.lua \
--include=*.m \
--include=*.m4 \
--include=*.mat \
--include=*.mk \
--include=*.nim \
--include=*.nix \
--include=*.pascal \
--include=*.php \
--include=*.pl \
--include=*.plx \
--include=*.ps1 \
--include=*.py \
--include=*.r \
--include=*.rb \
--include=*.rkt \
--include=*.rlib \
--include=*.rs \
--include=*.sc \
--include=*.scala \
--include=*.scss \
--include=*.sh \
--include=*.sml \
--include=*.sql \
--include=*.swift \
--include=*.tcl \
--include=*.template \
--include=*.tpl \
--include=*.tex \
--include=*.ts \
--include=*.tsx \
--include=*.vb \
--include=*.vba \
--include=*.vbs \
--include=*.vhd \
--include=*.vhdl \
--include=*.wren \
--include=*.xml \
--include=*.yaml \
--include=*.yml \
--include=*.zig \
--exclude='*.min.*' \
"${@}" \
| awk -v len=$LINE_LENGTH_CUTOFF '{ $0=substr($0, 1, len); print $0 }'
fi
}
# Searches for text in all files in the current folder
function findtext() {
# Local constant for maximum line length cut-off
# NOTE: This is necessary for certain files like binaries
local -r LINE_LENGTH_CUTOFF=1000
# The prefix to prepend to search commands for elevated permissions
local SUDO_PREFIX=""
# Check for --sudo flag and remove it from arguments if present
if [[ "$1" == "--sudo" ]]; then
SUDO_PREFIX="sudo "
shift
fi
# If no search text is specified, show help text
if [[ -z "$1" ]] || [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}findtext${RESET}: Search for text in all files recursively"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}findtext${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}--sudo${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}pattern${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Options:${RESET}"
echo -e " ${BRIGHT_GREEN}--sudo${RESET} Run with elevated permissions"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}findtext${RESET} ${BRIGHT_YELLOW}'example text'${RESET}"
echo -e " ${BRIGHT_CYAN}findtext${RESET} ${BRIGHT_YELLOW}'Hello\\s+world\\.'${RESET} ${BRIGHT_BLUE}# Regex search${RESET}"
echo -e " ${BRIGHT_CYAN}findtext${RESET} ${BRIGHT_GREEN}--sudo${RESET} ${BRIGHT_YELLOW}'todo'${RESET} ${BRIGHT_BLUE}# Search system dirs${RESET}"
return 1
fi
# If ripgrep is installed, use that
# Link: https://github.com/BurntSushi/ripgrep
if hascommand --strict rg; then
echo -e "${BRIGHT_CYAN}Search using ${BRIGHT_YELLOW}ripgrep${BRIGHT_CYAN}:${RESET}"
echo "${SUDO_PREFIX}rg --smart-case --no-ignore --hidden --pretty '$@' ."
${SUDO_PREFIX}rg --smart-case --no-ignore --hidden --pretty "$@" . | \
awk -v len=$LINE_LENGTH_CUTOFF '{ $0=substr($0, 1, len); print $0 }'
# If The Silver Searcher is installed, use that
# Link: https://github.com/ggreer/the_silver_searcher
# Hint: You can use --ignore "dir/or/file"
elif hascommand --strict ag; then
echo -e "${BRIGHT_CYAN}Search using ${BRIGHT_YELLOW}The Silver Searcher${BRIGHT_CYAN}:${RESET}"
echo "${SUDO_PREFIX}ag --color --smart-case --hidden --literal '$@'"
${SUDO_PREFIX}ag --color --smart-case --hidden --literal "$@" 2> /dev/null | \
awk -v len=$LINE_LENGTH_CUTOFF '{ $0=substr($0, 1, len); print $0 }'
else # Use grep
# Link: https://www.howtogeek.com/496056/how-to-use-the-grep-command-on-linux/
# Hint: You can use --exclude='/dir/or/file'
# --ignore-case (-i) : Makes the search case-insensitive
# --binary-files=without-match (-I) : Ignores binary files
# --with-filename (-H) : Displays the filename along with the matching line
# --recursive (-r) : Searches through all subdirectories recursively
# --line-number (-n) : Adds the line number to the output
# Optional:
# --fixed-strings (-F) : Treats the search term as a fixed string (not a regular expression)
# --files-with-matches (-l) : Only outputs the filenames that contain a match (e.g., grep -irl "$@" *)
echo -e "${BRIGHT_CYAN}Search using ${BRIGHT_YELLOW}grep${BRIGHT_CYAN}:${RESET}"
echo "${SUDO_PREFIX}grep --color=always --recursive --ignore-case --binary-files=without-match --with-filename --line-number '$@'"
${SUDO_PREFIX}grep \
--color=always \
--recursive \
--ignore-case \
--binary-files=without-match \
--with-filename \
--line-number \
"${@}" \
| awk -v len=${LINE_LENGTH_CUTOFF} '{ $0=substr($0, 1, len); print $0 }'
fi
}
# Performs case-insensitive text replacement in a file or directory recursively
function replacetext() {
# Check for --sudo flag and remove it from arguments if present
local SUDO_PREFIX=""
if [[ "$1" == "--sudo" ]]; then
SUDO_PREFIX="sudo "
shift
fi
# Handle the optional [file_path] parameter
local FILE_PATH="$(command pwd)"
if [[ $# -eq 3 ]]; then
FILE_PATH=$3
fi
# If not enough arguments, show help
if [[ $# -lt 2 ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}replacetext${RESET}: Perform case-insensitive text replacement in files recursively"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}replacetext${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}--sudo${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}find${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}replace${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}path${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Options:${RESET}"
echo -e " ${BRIGHT_GREEN}--sudo${RESET} Run with elevated permissions"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}replacetext${RESET} ${BRIGHT_YELLOW}'foo' 'bar'${RESET} ${BRIGHT_BLUE}# Replace in current dir${RESET}"
echo -e " ${BRIGHT_CYAN}replacetext${RESET} ${BRIGHT_YELLOW}'foo' 'bar' '/path'${RESET} ${BRIGHT_BLUE}# Replace in specific path${RESET}"
echo -e " ${BRIGHT_CYAN}replacetext${RESET} ${BRIGHT_GREEN}--sudo${RESET} ${BRIGHT_YELLOW}'foo' 'bar' '/etc'${RESET} ${BRIGHT_BLUE}# With sudo${RESET}"
return 1
fi
# Escape special regex characters and the delimiter for the search pattern
# SC2155: split declaration so sed failure isn't masked by local's exit code
local FIND_TEXT
FIND_TEXT=$(printf '%s' "${1}" | sed 's/[][\\.() *^$+?{|}/-]/\\&/g')
# Escape only characters special in sed replacement text: & \ and the delimiter
local REPLACE_TEXT
REPLACE_TEXT=$(printf '%s' "${2}" | sed 's/[&\\/]/\\&/g')
# Show safety check and ask for confirmation only for directory operations
if [[ -d "${FILE_PATH}" ]]; then
echo -e "${BRIGHT_GREEN} Find: ${BRIGHT_CYAN}${1}${RESET}"
echo -e "${BRIGHT_GREEN}Replace: ${BRIGHT_CYAN}${2}${RESET}"
echo -e "${BRIGHT_RED}Warning:${BRIGHT_YELLOW} You are about to recursively operate on the directory ${BRIGHT_CYAN}${FILE_PATH}${RESET}"
if ! ask "${BRIGHT_MAGENTA}Are you sure?" "N"; then
echo -e "${BRIGHT_RED}Aborted${RESET}"
return 1
fi
else
echo -e "${BRIGHT_RED}ERROR: Path not found ${FILE_PATH}${RESET}"
return 1
fi
# Execute the find-and-replace operation
echo -e "${BRIGHT_GREEN}Replace: ${BRIGHT_CYAN}find \"${FILE_PATH}\" -type f -exec ${SUDO_PREFIX}sed -i \"s/${FIND_TEXT}/${REPLACE_TEXT}/gi\" {} +${RESET}"
find "${FILE_PATH}" -type f -exec ${SUDO_PREFIX}sed -i "s/${FIND_TEXT}/${REPLACE_TEXT}/gi" {} +
}
# Enhanced lines function with uppercase variables and improved error messaging
function lines() {
# Check for minimum required arguments
if [[ $# -lt 2 ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}lines${RESET}: Display specific lines or line ranges from a file"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}lines${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}file${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}line_number${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}more_lines...${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}lines${RESET} ${BRIGHT_YELLOW}file.txt 123${RESET} ${BRIGHT_BLUE}# Single line${RESET}"
echo -e " ${BRIGHT_CYAN}lines${RESET} ${BRIGHT_YELLOW}file.txt 123 456 78${RESET} ${BRIGHT_BLUE}# Multiple lines${RESET}"
echo -e " ${BRIGHT_CYAN}lines${RESET} ${BRIGHT_YELLOW}file.txt 50-100${RESET} ${BRIGHT_BLUE}# Line range${RESET}"
echo -e " ${BRIGHT_CYAN}lines${RESET} ${BRIGHT_YELLOW}file.txt 10 20 30-40${RESET} ${BRIGHT_BLUE}# Mixed${RESET}"
return 1
fi
# Assign first argument to FILENAME and shift arguments
local FILENAME="$1"
shift
# Check if the specified file exists
if [ ! -f "$FILENAME" ]; then
echo -e "${BRIGHT_RED}Error:${RESET} File not found: ${BRIGHT_CYAN}$FILENAME${RESET}"
return 1
fi
# Process each argument
while [ $# -gt 0 ]; do
local ARG="$1"
# Handle line range
if [[ "$ARG" == *-* ]]; then
# Extract start and end lines from the range
local START_LINE=$(echo "$ARG" | cut -d'-' -f1)
local END_LINE=$(echo "$ARG" | cut -d'-' -f2)
# Display lines within the specified range using 'sed'
sed -n "${START_LINE},${END_LINE}p" "$FILENAME"
else # Handle individual line number
# Display the specified line using 'sed'
sed -n "${ARG}p" "$FILENAME"
fi
# Shift to the next argument
shift
done
}
# Analyzes a given code file to provide metrics and statistics
function analyzecode() {
# Validate input
if [[ $# -eq 0 ]]; then
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}analyzecode${RESET} ${BRIGHT_GREEN}[filename]${RESET}"
return 1
fi
# Check if the file exists and is readable
local FILE="$1"
if [[ ! -f ${FILE} ]] || [[ ! -r ${FILE} ]]; then
echo "Error: File '${FILE}' does not exist or is not readable."
return 1
fi
# Initialize counters and variables
local TOTAL_LINES=$(wc -l < "${FILE}")
local NON_COMMENT_LINES=0
local COMMENT_LINES=0
local BLANK_LINES=0
local LONGEST_LINE=0
local SHORTEST_LINE=999999
local FILE_SIZE
if [[ "$(uname)" == "Darwin" ]]; then
FILE_SIZE=$(stat -f%z "${FILE}")
else
FILE_SIZE=$(stat -c%s "${FILE}")
fi
local TOTAL_CHARS=0
# Process each line
while IFS= read -r LINE; do
local LINE_LENGTH=${#LINE}
[[ $LINE_LENGTH -gt $LONGEST_LINE ]] && LONGEST_LINE=$LINE_LENGTH
# Exclude blank lines for shortest line and character count
if [[ "$LINE" =~ [^[:space:]] ]]; then
[[ $LINE_LENGTH -lt $SHORTEST_LINE ]] && SHORTEST_LINE=$LINE_LENGTH
((TOTAL_CHARS+=LINE_LENGTH))
fi
# Count blank lines
if [[ "$LINE" =~ ^[[:space:]]*$ ]]; then
((BLANK_LINES++))
elif [[ "$LINE" =~ ^[[:space:]]*([#]|\/\/) || "$LINE" =~ \/\* || "$LINE" =~ \*\/ ]]; then
((COMMENT_LINES++))
else
((NON_COMMENT_LINES++))
fi
done < "$FILE"
# Handle edge case: if no non-blank lines were found, reset SHORTEST_LINE
[[ ${SHORTEST_LINE} -eq 999999 ]] && SHORTEST_LINE=0
# Calculate percentages based on non-blank lines
local NON_BLANK_TOTAL_LINES=$((TOTAL_LINES - BLANK_LINES))
local AVG_LINE_LENGTH=$((NON_BLANK_TOTAL_LINES ? TOTAL_CHARS / NON_BLANK_TOTAL_LINES : 0))
local NON_COMMENT_LINE_PERCENT=$((TOTAL_LINES ? NON_COMMENT_LINES * 100 / TOTAL_LINES : 0))
local COMMENT_LINE_PERCENT=$((TOTAL_LINES ? COMMENT_LINES * 100 / TOTAL_LINES : 0))
local BLANK_LINE_PERCENT=$((TOTAL_LINES ? BLANK_LINES * 100 / TOTAL_LINES : 0))
# Calculate indentation using awk
local METRICS=$(awk '
BEGIN {
indentTabs = 0;
indentSpaces = 0;
}
{
if (match($0, /^[ \t]+/)) {
indent = substr($0, RSTART, RLENGTH);
indentTabs += gsub(/\t/, "", indent);
indentSpaces += gsub(/ /, "", indent);
}
}
END {
print indentTabs, indentSpaces;
}' "${FILE}")
local INDENT_TABS=$(echo ${METRICS} | cut -d' ' -f1)
local INDENT_SPACES=$(echo ${METRICS} | cut -d' ' -f2)
local INDENT_TYPE="Unknown"
[[ ${INDENT_TABS} -gt ${INDENT_SPACES} ]] && INDENT_TYPE="Tabs"
[[ ${INDENT_SPACES} -gt ${INDENT_TABS} ]] && INDENT_TYPE="Spaces"
# Display results
echo "File Size: ${FILE_SIZE} bytes"
echo "Total Lines: ${TOTAL_LINES}"
echo "Longest Line: ${LONGEST_LINE} characters"
echo "Shortest Line: ${SHORTEST_LINE} characters"
echo "Average Line Length: ${AVG_LINE_LENGTH} characters"
echo "Indentation: ${INDENT_TYPE}"
echo "Non-Comment Lines: ${NON_COMMENT_LINES} (${NON_COMMENT_LINE_PERCENT}%)"
echo "Comment Lines: ${COMMENT_LINES} (${COMMENT_LINE_PERCENT}%)"
echo "Blank Lines: ${BLANK_LINES} (${BLANK_LINE_PERCENT}%)"
}
# List and sort all function names from code files (with line numbers)
function showfunctions() {
# Check if a filename is provided
if [[ -z "${1}" ]]; then
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}showfunctions${RESET} ${BRIGHT_GREEN}[filename]${RESET}"
return 1
fi
# Perform a grep to find function names and include line numbers
# Use awk for formatting, filtering comments, and cleaning up
# Further filter with awk to exclude names that start with '_'
# Remove leading spaces and sort by function name and then by line number, while also removing duplicates
grep -n -E '(function[[:space:]]+\w+)|(def[[:space:]]+\w+)|(public[[:space:]]+static[[:space:]]+void[[:space:]]+\w+)|(public[[:space:]]+function[[:space:]]+\w+)' "${1}" | \
awk -F: '$2 !~ /^[[:space:]]*(#|\/\/)/ { gsub(/^[[:space:]]+/, "", $2); print $1 ":\t" $2 }' | \
sed -E 's/(function|def|public static void|public function)[[:space:]]+//' | \
awk -F ':\t' '$2 !~ /^_/ {print $1 ":\t" $2}' | \
sort -k2,2 -k1,1n | \
uniq -f 1
}
# Swap indentations between tabs and spaces
function swapindent() {
# This is the default number of spaces that a tab will be converted to
local DEFAULT_TAB_SPACING=4
# If no arguments are provided and no text piped in, or help requested, display help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]] || { [[ $# -eq 0 ]] && [[ -t 0 ]]; }; then
echo -e "${BRIGHT_CYAN}swapindent${RESET}: Swap indentation between tabs and spaces"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}swapindent${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}file${BRIGHT_MAGENTA}]${RESET}"
echo -e " command | ${BRIGHT_CYAN}swapindent${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}- If a file is provided, it modifies the file in-place${RESET}"
echo -e " ${BRIGHT_BLUE}- If no file is provided, reads from stdin, writes to stdout${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}swapindent${RESET} ${BRIGHT_YELLOW}script.sh${RESET} ${BRIGHT_BLUE}# Modify file in-place${RESET}"
echo -e " ${BRIGHT_YELLOW}cat script.sh${RESET} | ${BRIGHT_CYAN}swapindent${RESET} ${BRIGHT_BLUE}# Output to stdout${RESET}"
echo -e " ${BRIGHT_YELLOW}cat script.sh${RESET} | ${BRIGHT_CYAN}swapindent${RESET} | ${BRIGHT_YELLOW}less${RESET} ${BRIGHT_BLUE}# Preview changes${RESET}"
return 0
fi
# Declare an array to hold the lines from the input
local LINES=()
# Declare an array to hold the modified lines
local MODIFIED_LINES=()
# Variables to hold state for the type of first indent (tab or space)
# and the smallest count of leading spaces in lines
local FIRST_INDENT_TYPE=""
local SMALLEST_SPACE_COUNT=1000 # Initialize to a high value to find the minimum easily
# Read lines from either a file or standard input into the 'LINES' array
while IFS= read -r LINE; do
LINES+=("${LINE}") # Append line to LINES array
# Check if the line starts with any kind of whitespace
if [[ "${LINE}" =~ ^[[:space:]] ]]; then
# If the first type of indentation has not yet been determined
if [[ -z "${FIRST_INDENT_TYPE}" ]]; then
# Determine if the first indent in the file is a tab or space
if [[ "${LINE}" =~ ^$'\t' ]]; then
FIRST_INDENT_TYPE="tab"
elif [[ "${LINE}" =~ ^' ' ]]; then
FIRST_INDENT_TYPE="space"
fi
fi
# If the first indent is a space, count the leading spaces
if [[ "${FIRST_INDENT_TYPE}" == "space" ]]; then
local SPACE_COUNT=$(echo "${LINE}" | sed -E 's/[^ ].*//g' | wc -c)
(( SPACE_COUNT-- )) # Account for the newline character from wc
# Update the SMALLEST_SPACE_COUNT if this line has fewer leading spaces
[[ ${SPACE_COUNT} -lt ${SMALLEST_SPACE_COUNT} ]] && SMALLEST_SPACE_COUNT=${SPACE_COUNT}
fi
fi
done < <(cat "${1:-/dev/stdin}" && echo) # Append newline so read captures the last line
# Loop through 'LINES' array to swap and output the indentations
local MODIFIED_LINE
for LINE in "${LINES[@]}"; do
# If the first indent is a tab, convert tabs to spaces
if [[ "${FIRST_INDENT_TYPE}" == "tab" ]]; then
MODIFIED_LINE=$(echo "${LINE}" | sed "s/\t/$(printf "%${DEFAULT_TAB_SPACING}s")/g")
else
# If the first indent is a space, convert spaces to tabs
# Create a string of 'SMALLEST_SPACE_COUNT' number of spaces
local TAB_TO_SPACE_STRING=$(printf "%${SMALLEST_SPACE_COUNT}s")
MODIFIED_LINE=$(echo "${LINE}" | sed "s/${TAB_TO_SPACE_STRING}/\t/g")
fi
# Append the modified line to MODIFIED_LINES array
MODIFIED_LINES+=("${MODIFIED_LINE}")
done
# If a filename is provided, write the modified lines back into the file
if [[ -n "$1" ]]; then
printf "%s\n" "${MODIFIED_LINES[@]}" > "$1"
else
# If no filename, print the modified lines to stdout
printf "%s\n" "${MODIFIED_LINES[@]}"
fi
}
# Copy a file or directory with a progress bar
function cpp() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}cpp${RESET}: Copy files with progress bar (uses rsync)"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}cpp${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}source${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}destination${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}cpp${RESET} ${BRIGHT_YELLOW}largefile.iso${RESET} ${BRIGHT_YELLOW}/mnt/usb/${RESET}"
echo -e " ${BRIGHT_CYAN}cpp${RESET} ${BRIGHT_YELLOW}~/Downloads/video.mp4${RESET} ${BRIGHT_YELLOW}/backup/${RESET}"
return 0
fi
# Check the parameters
if [[ -z "${1}" ]]; then
echo "Source is not specified."
return 1
fi
if [[ -z "${2}" ]]; then
echo "Destination path is not specified."
return 1
fi
if [[ ! -d "${2}" ]]; then
echo "Destination is not a directory."
return 1
fi
# Check if rsync is available
if hascommand --strict rsync; then
# Use rsync with archive mode and overall progress bar
rsync -ah --info=progress2 "${1}" "${2}"
else
# If rsync is not available, use strace with cp to show progress
strace -q -ewrite cp -- "${1}" "${2}" 2>&1 \
| awk '{
count += $NF
if (count % 10 == 0) {
percent = int(count / total_size * 100)
printf "%3d%% [", percent
for (i = 0; i <= percent; i++)
printf "="
printf ">"
for (i = percent; i < 100; i++)
printf " "
printf "]\r"
}
}
END { print "" }' total_size=$([[ "$(uname)" == "Darwin" ]] && stat -f%z "${1}" || stat -c '%s' "${1}") count=0
fi
}
# Copy a file and optionally go to the directory
function cpg() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}cpg${RESET}: Copy file and cd to destination directory"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}cpg${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}source${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}destination${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}cpg${RESET} ${BRIGHT_YELLOW}file.txt${RESET} ${BRIGHT_YELLOW}/tmp/${RESET} ${BRIGHT_BLUE}# Copy and cd to /tmp${RESET}"
echo -e " ${BRIGHT_CYAN}cpg${RESET} ${BRIGHT_YELLOW}script.sh${RESET} ${BRIGHT_YELLOW}backup.sh${RESET} ${BRIGHT_BLUE}# Copy with new name${RESET}"
return 0
fi
# Check the parameters
if [[ -z "${1}" ]]; then
echo "Source is not specified."
return 1
fi
if [[ -z "${2}" ]]; then
echo "Destination is not specified."
return 1
fi
if [[ -d "${2}" ]]; then
# Destination is a directory, copy the file and go to the directory
cp "${1}" "${2}" && cd "${2}"
else
# Destination is not a directory, just copy the file
cp "${1}" "${2}"
fi
}
# Move a file or directory and optionally go to the directory
function mvg() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}mvg${RESET}: Move file and cd to destination directory"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}mvg${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}source${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}destination${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}mvg${RESET} ${BRIGHT_YELLOW}file.txt${RESET} ${BRIGHT_YELLOW}/tmp/${RESET} ${BRIGHT_BLUE}# Move and cd to /tmp${RESET}"
echo -e " ${BRIGHT_CYAN}mvg${RESET} ${BRIGHT_YELLOW}old.txt${RESET} ${BRIGHT_YELLOW}new.txt${RESET} ${BRIGHT_BLUE}# Rename file${RESET}"
return 0
fi
# Check the parameters
if [[ -z "${1}" ]]; then
echo "Source is not specified."
return 1
fi
if [[ -z "${2}" ]]; then
echo "Destination path is not specified."
return 1
fi
if [[ -d "${2}" ]]; then
# Destination is a directory, move the file and go to the directory
mv "${1}" "${2}" && cd "${2}"
else
# Destination is not a directory, just move the file
mv "${1}" "${2}"
fi
}
# Create and go to the directory
alias md='mkdirg'
function mkdirg() {
# Check if any arguments are provided
if [[ $# -eq 0 ]]; then
# Display usage message and return error code 1
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}mkdirg${RESET} ${BRIGHT_GREEN}[directory]${RESET}"
return 1
fi
# Verify if the directory already exists
if [ -d "$1" ]; then
# Directory exists, change into it
cd "$1"
else
# Directory doesn't exist, create it and change into it
command mkdir -p "$1"
cd "$1"
fi
}
# Repeats a given command a specified number of times
function repeat() {
# Check that at least two arguments are provided (number and command)
if [[ $# -lt 2 ]]; then
# Display usage message and return error code 1
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}repeat${RESET} ${BRIGHT_GREEN}[count] [command]${RESET}"
return 1
fi
# Check that the first argument is a non-negative integer
if ! [[ $1 =~ ^[0-9]+$ ]]; then
# Display error message for invalid count
echo "Error: The count must be a non-negative integer."
return 1
fi
# Assign the provided count to maxCount and shift to remove it from arguments
local count maxCount
maxCount=$1; shift
for ((count = 1; count <= maxCount; count++)); do
# Execute the provided command using eval
eval "$@"
done
}
# Goes up a specified number of directories (i.e. up 4)
# If no argument is provided, it goes up by one directory
function up() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}up${RESET}: Navigate up multiple directory levels"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}up${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}levels${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}up${RESET} ${BRIGHT_BLUE}# Same as cd ..${RESET}"
echo -e " ${BRIGHT_CYAN}up${RESET} ${BRIGHT_GREEN}2${RESET} ${BRIGHT_BLUE}# Same as cd ../..${RESET}"
echo -e " ${BRIGHT_CYAN}up${RESET} ${BRIGHT_GREEN}5${RESET} ${BRIGHT_BLUE}# Go up 5 directories${RESET}"
return 0
fi
# Declare a variable to build the directory path
local DIRECTORY_PATH=""
# Set the number of levels to go up - default to 1 if not provided
local LEVELS_TO_GO_UP=${1:-1}
# Loop from 1 to the number of levels to go up
for ((i = 1; i <= LEVELS_TO_GO_UP; i++)); do
# Append "../" to the directory path for each iteration
DIRECTORY_PATH="../${DIRECTORY_PATH}"
done
# Change to the built directory path
cd "${DIRECTORY_PATH}"
}
# Returns the last 2 fields of the working directory
function pwdtail() {
pwd | awk -F/ '{nlast = NF -1;print $nlast"/"$NF}'
}
# Encryption has been changed to gpg from SSL due to security
# Link: https://stackoverflow.com/questions/28247821/openssl-vs-gpg-for-encrypting-off-site-backups
# Install: pkginstall gnupg # gpg2 on SUSE
if hascommand --strict gpg; then
# Set an alias to clear the GPG cache
alias gpgclear='echo RELOADAGENT | gpg-connect-agent'
## Uses OpenSSL AES 256bit Cipher Block Chaining Encryption to encrypt a file
function encrypt() {
if [[ -p /dev/stdin ]]; then
# If there is data being piped from stdin
if [[ "${#}" -lt 1 ]]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Output file not specified${RESET}"
return 1
fi
echo "Encrypting data..."
# equivalents in the gpg.conf file
# s2k-mode 3
# s2k-count 65011712
# s2k-digest-algo SHA512
# s2k-cipher-algo AES256
gpg --s2k-mode 3 --s2k-count 65011712 --s2k-digest-algo SHA512 --s2k-cipher-algo AES256 --symmetric --output "${1}"
if [[ $? -eq 0 ]]; then
echo -e "${BRIGHT_GREEN}Data successfully encrypted to ${1}${RESET}"
else
echo -e "${BRIGHT_RED}Encryption failed${RESET}"
return 1
fi
else
# If no data from stdin, expect a file as argument
if [[ "${#}" -lt 1 ]] || [[ "${1}" = "--help" ]] || [[ "${1}" = "-h" ]]; then
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}encrypt${RESET} ${BRIGHT_GREEN}[input_file]${RESET} ${BRIGHT_MAGENTA}(the output file will be named [input_file].gpg)${RESET}"
echo -e "${BRIGHT_WHITE} or: ${BRIGHT_YELLOW}command${RESET} | ${BRIGHT_CYAN}encrypt${RESET} ${BRIGHT_GREEN}[output_file]${RESET} ${BRIGHT_MAGENTA}(encrypt data piped from another command)${RESET}"
return
elif [[ ! -r "${1}" ]]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}File ${BRIGHT_YELLOW}${1}${BRIGHT_CYAN} not found${RESET}"
return 1
fi
echo "Encrypting ${1}..."
if gpg --s2k-mode 3 --s2k-count 65011712 --s2k-digest-algo SHA512 --s2k-cipher-algo AES256 --symmetric "${1}"; then
local _encrypted_file="${1}.gpg"
echo -e "${BRIGHT_GREEN}File ${_encrypted_file} successfully encrypted${RESET}"
else
echo -e "${BRIGHT_RED}Encryption failed${RESET}"
return 1
fi
fi
}
## Uses OpenSSL AES 256bit Cipher Block Chaining Encryption to decrypt a file
function decrypt() {
if [[ "${#}" -lt 1 ]] || [[ "${1}" = "--help" ]] || [[ "${1}" = "-h" ]]; then
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}decrypt${RESET} ${BRIGHT_GREEN}[encrypted_file.gpg]${RESET}"
return
elif [[ ! -r "${1}" ]]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}File ${BRIGHT_YELLOW}${1}${BRIGHT_CYAN} not found${RESET}"
return 1
fi
echo "Decrypting ${1}..."
local _DECRYPTED_FILE="${1%.gpg}" # Remove '.gpg' extension from the input file name
if gpg --output "${_DECRYPTED_FILE}" --decrypt "${1}"; then
echo -e "${BRIGHT_GREEN}File successfully decrypted to ${_DECRYPTED_FILE}${RESET}"
else
echo -e "${BRIGHT_RED}Error decrypting ${BRIGHT_YELLOW}${1}${RESET}"
return 1
fi
}
fi
# Print a list of colors
function colors() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}colors${RESET}: Display basic 8-color terminal palette"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}colors${RESET}"
echo -e "${BRIGHT_WHITE}See also:${RESET} ${BRIGHT_GREEN}colors256${RESET}, ${BRIGHT_GREEN}colors24bit${RESET}"
return 0
fi
# Define variables for foreground, background, values, and sequence
local foreground background values sequence
# Print introductory information about color escapes and styles
printf "Color escapes are %s\n" '\033[${value};...;${value}m'
printf "Values 30..37 are \033[33mforeground colors\033[m\n"
printf "Values 40..47 are \033[43mbackground colors\033[m\n"
printf "Value 1 gives a \033[1mbold-faced look\033[m\n\n"
# Iterate through foreground colors (values 30 to 37)
for foreground in {30..37}; do
# Iterate through background colors (values 40 to 47)
for background in {40..47}; do
# Extract color names from numerical values
# Use separate variables to avoid clobbering the loop iterators
local FG_VAL=${foreground#37} # white
local BG_VAL=${background#40} # black
# Construct values string for color escape sequences
values="${FG_VAL:+$FG_VAL;}${BG_VAL}"
values=${values%%;}
# Construct escape sequence for color styling
sequence="${values:+\033[${values}m}"
# Print formatted color examples
printf " %-9s" "${sequence:-(default)}"
printf " ${sequence}TEXT\033[m"
printf " \033[${values:+${values+$values;}}1mBOLD\033[m"
done
echo; echo
done
}
# Print a list of 256 colors
function colors256() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}colors256${RESET}: Display 256-color terminal palette"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}colors256${RESET}"
echo -e "${BRIGHT_WHITE}See also:${RESET} ${BRIGHT_GREEN}colors${RESET}, ${BRIGHT_GREEN}colors24bit${RESET}"
return 0
fi
# Define the number of colors per line
colors_per_line=8
for i in {0..255}; do
printf "\x1b[38;5;${i}mcolor%-5d" "$i"
# Check if the current color is the last in the line
if (( (i + 1) % colors_per_line == 0 )); then
echo # Move to the next line
fi
done
# Add a final newline if needed
if ((255 % colors_per_line != 0)); then
echo
fi
}
# Test for 24bit true color in the terminal
function colors24bit() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}colors24bit${RESET}: Test terminal 24-bit true color support"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}colors24bit${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Displays color gradients - smooth = true color supported${RESET}"
echo -e "${BRIGHT_WHITE}See also:${RESET} ${BRIGHT_GREEN}colors${RESET}, ${BRIGHT_GREEN}colors256${RESET}"
return 0
fi
echo 'If the gradients are smooth, you are displaying 24bit true color.'
awk 'BEGIN{
# Generate a long string of characters for testing
s = "1234567890";
s = s s s s s s s s s s s s s s s s s s s s s s s s; # Extended string
len = length(s); # Length of the string
# Generate and display color gradients
for (colnum = 0; colnum < 256; colnum++) {
r = 255 - (colnum * 255 / 255);
g = (colnum * 510 / 255);
b = (colnum * 255 / 255);
if (g > 255) g = 510 - g;
# Set background and foreground colors using ANSI escape sequences
printf "\033[48;2;%d;%d;%dm", r, g, b; # Background color
printf "\033[38;2;%d;%d;%dm", 255 - r, 255 - g, 255 - b; # Foreground color
# Display a character with the defined colors and reset formatting
printf "%s\033[0m", substr(s, (colnum % len) + 1, 1);
}
printf "\n"; # Move to the next line after printing colors
}'
}
# Prints random height bars across the width of the screen
# (great with lolcat application on new terminal windows)
function sparkbars() {
columns=$(tput cols)
chars=▁▂▃▄▅▆▇█
for ((i = 1; i <= $columns; i++))
do
echo -n "${chars:RANDOM%${#chars}:1}"
done
echo
}
# View Apache logs
function apachelog() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}apachelog${RESET}: View Apache web server logs"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}apachelog${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Auto-detects /var/log/httpd or /var/log/apache2${RESET}"
return 0
fi
if [ -d /var/log/httpd ]; then
sudo \ls -Ah /var/log/httpd && logview "/var/log/httpd/*_log"
else
sudo \ls -Ah /var/log/apache2 && logview "/var/log/apache2/*.log"
fi
}
# Auto-find and edit the Apache configuration
function apacheconfig() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}apacheconfig${RESET}: Edit Apache configuration file"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}apacheconfig${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}config_path${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Auto-detects httpd.conf or apache2.conf location${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}apacheconfig${RESET} ${BRIGHT_BLUE}# Auto-find config${RESET}"
echo -e " ${BRIGHT_CYAN}apacheconfig${RESET} ${BRIGHT_GREEN}/etc/httpd/conf/httpd.conf${RESET} ${BRIGHT_BLUE}# Specific path${RESET}"
return 0
fi
if hascommand --strict httpd || hascommand --strict apache2 || hascommand --strict apachectl; then
# Define an array with common paths
declare -a paths=(
"/etc/httpd/conf/httpd.conf"
"/etc/httpd/httpd.conf"
"/etc/apache2/apache2.conf"
"/usr/local/apache2/apache2.conf"
"/usr/local/etc/httpd/httpd.conf"
)
# Check if a custom path is provided
[[ -n "$1" ]] && paths=("$1")
for path in "${paths[@]}"; do
if [[ -f $path ]]; then
edit "$path"
return 0
fi
done
echo "Error: Apache config file could not be found."
echo "Searching for possible locations:"
httpd -V 2> /dev/null || apachectl -V 2> /dev/null || apache2 -V
else
echo "Apache is not installed."
fi
}
# Find the Apache service and restart/start it
function apacherestart() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}apacherestart${RESET}: Test config and restart Apache service"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}apacherestart${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Tests configuration before restarting${RESET}"
echo -e " ${BRIGHT_BLUE}Auto-detects apache2/httpd service name${RESET}"
return 0
fi
# Determine Apache config test command
local apache_cmd
if hascommand --strict apache2ctl; then
apache_cmd="apache2ctl"
elif hascommand --strict httpd; then
apache_cmd="httpd"
else
echo -e "${BRIGHT_RED}Error: Could not find Apache executable (apache2ctl or httpd)${RESET}"
return 1
fi
# Test configuration FIRST
echo -e "${BRIGHT_CYAN}Testing Apache configuration...${RESET}"
if ! sudo $apache_cmd -t; then
echo -e "${BRIGHT_RED}✗ Configuration test failed - cannot restart Apache with invalid config${RESET}"
echo -e "${BRIGHT_CYAN}Fix the configuration errors and try again${RESET}"
return 1
fi
echo -e "${BRIGHT_GREEN}✓ Configuration is valid${RESET}"
# Determine service name by trying common names
local apache_service
if hascommand --strict systemctl; then
for service in apache2 httpd apache; do
if systemctl list-unit-files | grep -q "^$service.service"; then
apache_service="$service"
break
fi
done
if [[ -z "$apache_service" ]]; then
echo -e "${BRIGHT_RED}Error: Could not find Apache service (tried apache2, httpd, apache)${RESET}"
return 1
fi
echo -e "${BRIGHT_CYAN}Restarting Apache ($apache_service) via systemctl...${RESET}"
if sudo systemctl restart $apache_service; then
echo -e "${BRIGHT_GREEN}✓ Apache restarted successfully${RESET}"
if sudo systemctl is-active $apache_service &>/dev/null; then
echo -e "${BRIGHT_GREEN}✓ Apache is running${RESET}"
else
echo -e "${BRIGHT_RED}✗ Apache failed to start - check logs: sudo journalctl -u $apache_service${RESET}"
return 1
fi
else
echo -e "${BRIGHT_RED}✗ Failed to restart Apache${RESET}"
return 1
fi
# [rest of init.d and service logic similar to before, but with service name detection]
fi
}
# Auto-find and edit the Nginx configuration
function ngconfig() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}ngconfig${RESET}: Edit Nginx configuration file"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}ngconfig${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}config_path${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Auto-detects nginx.conf location${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}ngconfig${RESET} ${BRIGHT_BLUE}# Auto-find config${RESET}"
echo -e " ${BRIGHT_CYAN}ngconfig${RESET} ${BRIGHT_GREEN}/etc/nginx/nginx.conf${RESET} ${BRIGHT_BLUE}# Specific path${RESET}"
return 0
fi
if hascommand --strict nginx; then
# Define an array with common paths
declare -a paths=(
"/etc/nginx/nginx.conf"
"/usr/local/nginx/conf/nginx.conf"
"/usr/local/etc/nginx/nginx.conf"
)
# Check if a custom path is provided
[[ -n "$1" ]] && paths=("$1")
for path in "${paths[@]}"; do
if [[ -f $path ]]; then
edit "$path"
return 0
fi
done
echo "Error: Nginx config file could not be found."
echo "Please specify the location manually, or check your Nginx installation."
else
echo "Nginx is not installed."
fi
}
# Find the Nginx service and restart/start it
function ngrestart() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}ngrestart${RESET}: Test config and restart Nginx service"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}ngrestart${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Tests configuration before restarting${RESET}"
return 0
fi
# Test configuration FIRST before attempting restart
echo -e "${BRIGHT_CYAN}Testing nginx configuration...${RESET}"
if ! sudo nginx -t; then
echo -e "${BRIGHT_RED}✗ Configuration test failed - cannot restart nginx with invalid config${RESET}"
echo -e "${BRIGHT_CYAN}Fix the configuration errors and try again${RESET}"
return 1
fi
echo -e "${BRIGHT_GREEN}✓ Configuration is valid${RESET}"
# If we are using Systemd...
if hascommand --strict systemctl; then
# Check if Nginx executable is available
if hascommand --strict nginx; then
echo -e "${BRIGHT_CYAN}Restarting nginx via systemctl...${RESET}"
if sudo systemctl restart nginx; then
echo -e "${BRIGHT_GREEN}✓ Nginx restarted successfully${RESET}"
# Check if service is actually running
if sudo systemctl is-active nginx &>/dev/null; then
echo -e "${BRIGHT_GREEN}✓ Nginx is running${RESET}"
else
echo -e "${BRIGHT_RED}✗ Nginx failed to start - check logs: sudo journalctl -u nginx${RESET}"
return 1
fi
else
echo -e "${BRIGHT_RED}✗ Failed to restart nginx${RESET}"
return 1
fi
else
echo -e "${BRIGHT_RED}Error: Could not find nginx executable${RESET}"
return 1
fi
# If using init.d...
elif [[ -d /etc/init.d ]] && [[ -f /etc/init.d/nginx ]]; then
echo -e "${BRIGHT_CYAN}Restarting nginx via init.d...${RESET}"
if sudo /etc/init.d/nginx restart; then
echo -e "${BRIGHT_GREEN}✓ Nginx restarted successfully${RESET}"
else
echo -e "${BRIGHT_RED}✗ Failed to restart nginx${RESET}"
return 1
fi
# Other systems including OpenRC...
elif hascommand --strict service; then
# Check if Nginx executable is available
if hascommand --strict nginx; then
echo -e "${BRIGHT_CYAN}Restarting nginx via service command...${RESET}"
if sudo service nginx restart; then
echo -e "${BRIGHT_GREEN}✓ Nginx restarted successfully${RESET}"
else
echo -e "${BRIGHT_RED}✗ Failed to restart nginx${RESET}"
return 1
fi
else
echo -e "${BRIGHT_RED}Error: Could not find nginx executable${RESET}"
return 1
fi
else # Unknown
echo -e "${BRIGHT_RED}Error: Could not find service controller (systemctl, service, or init.d)${RESET}"
return 1
fi
}
# Check the syntax of a PHP file for errors
function phpcheck() {
if [[ $# -eq 0 ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}phpcheck${RESET}: Validate PHP syntax without executing the file"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}phpcheck${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}file.php${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}phpcheck${RESET} ${BRIGHT_YELLOW}index.php${RESET}"
return 2
fi
echo -e "${BRIGHT_RED}[${BRIGHT_CYAN}PHP Check${BRIGHT_RED}]${BRIGHT_YELLOW}->${BRIGHT_GREEN}${1}${RESET}"
php -l "${1}"
}
# Auto-find and edit the PHP configuration file
function phpconfig() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}phpconfig${RESET}: Edit PHP configuration file (php.ini)"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}phpconfig${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Auto-detects php.ini location via PHP itself${RESET}"
return 0
fi
if hascommand --strict php; then
local _php_ini_file=$(php -r 'echo php_ini_loaded_file();')
if [[ -f "${_php_ini_file}" ]]; then
echo "Found: ${_php_ini_file}"
edit "${_php_ini_file}"
elif [[ -f /etc/php.ini ]]; then
echo "Found: /etc/php.ini"
edit /etc/php.ini
elif [[ -f /etc/php/php.ini ]]; then
echo "Found: /etc/php/php.ini"
edit /etc/php/php.ini
else
echo "Error: php.ini file could not be found automatically."
echo "Searching for possible locations:"
# sudo updatedb && locate php.ini
php --ini
fi
else
echo "PHP is not installed."
fi
}
# Auto-find and edit the MySQL configuration file
function mysqlconfig() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}mysqlconfig${RESET}: Edit MySQL configuration file (my.cnf)"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}mysqlconfig${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Auto-detects my.cnf location${RESET}"
return 0
fi
if hascommand --strict mysqld; then
if [[ -f /etc/my.cnf ]]; then
edit /etc/my.cnf
elif [[ -f /etc/mysql/my.cnf ]]; then
edit /etc/mysql/my.cnf
elif [[ -f /usr/local/etc/my.cnf ]]; then
edit /usr/local/etc/my.cnf
elif [[ -f /usr/bin/mysql/my.cnf ]]; then
edit /usr/bin/mysql/my.cnf
elif [[ -f "${HOME}/my.cnf" ]]; then
edit "${HOME}/my.cnf"
else
echo "Error: my.cnf file could not be found automatically."
echo "Searching for possible locations:"
# sudo updatedb && locate my.cnf
mysqld --verbose --help | grep -A 1 "Default options"
fi
else
echo "MySQL is not installed."
fi
}
# Determine if a system reboot is required
function checkreboot() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}checkreboot${RESET}: Check if a system reboot is required"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}checkreboot${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Detects kernel updates on Arch, RHEL, Debian, SUSE${RESET}"
return 0
fi
# Initialize a variable to indicate if a reboot is required
local REBOOT_REQUIRED=0
# Check if pacman exists (indicative of an Arch-based system)
if command -v pacman &> /dev/null; then
# Determine the running kernel's package name from uname -r
# uname -r returns e.g. "6.18.8-zen2-1-zen" with a trailing flavor suffix
local RUNNING_UNAME
RUNNING_UNAME=$(uname -r)
local KERNEL_PKG="linux"
case "${RUNNING_UNAME}" in
*-lts*) KERNEL_PKG="linux-lts" ;;
*-zen*) KERNEL_PKG="linux-zen" ;;
*-hardened*) KERNEL_PKG="linux-hardened" ;;
esac
# Get the installed version of that specific kernel package
local INSTALLED_VER
INSTALLED_VER=$(pacman -Q "${KERNEL_PKG}" 2>/dev/null | awk '{print $2}')
if [[ -n "${INSTALLED_VER}" ]]; then
# Normalize both: replace all dashes with dots for comparison
local ACTIVE_KERNEL=${RUNNING_UNAME//-/.}
local CURRENT_KERNEL=${INSTALLED_VER//-/.}
# uname -r has a trailing flavor suffix (e.g. ".zen", ".lts")
# that pacman's version string doesn't include.
# Check if active starts with installed version to handle this.
if [[ "${ACTIVE_KERNEL}" != "${CURRENT_KERNEL}"* ]]; then
echo -e "${BRIGHT_RED}->${RESET} ${BRIGHT_CYAN}Arch Linux${RESET}: ${BRIGHT_YELLOW}Running kernel (${ACTIVE_KERNEL}) does not match installed ${KERNEL_PKG} (${CURRENT_KERNEL}).${RESET}"
REBOOT_REQUIRED=1
fi
fi
# Use hascommand to check if rpm exists (indicative of a RedHat-based system like CentOS)
elif hascommand --strict rpm; then
if ! rpm -q kernel | grep -q $(uname -r); then
echo -e "${BRIGHT_RED}->${RESET} ${BRIGHT_CYAN}Red Hat Linux${RESET}: ${BRIGHT_YELLOW}Kernel update detected. A reboot is required.${RESET}"
REBOOT_REQUIRED=1
fi
# Check for the existence of /var/run/reboot-required (indicative of Ubuntu/Debian)
elif [[ -f /var/run/reboot-required ]]; then
# If the file exists, a reboot is required
echo -e "${BRIGHT_RED}->${RESET} ${BRIGHT_CYAN}Ubuntu/Debian${RESET}: ${BRIGHT_YELLOW}A reboot is required.${RESET}"
REBOOT_REQUIRED=1
# Check if zypper exists (indicative of a SUSE-based system)
elif command -v zypper &> /dev/null; then
if zypper ps -s | grep -q 'yes'; then
echo -e "${BRIGHT_RED}->${RESET} ${BRIGHT_CYAN}SUSE${RESET}: ${BRIGHT_YELLOW}Kernel or service update detected. A reboot is required.${RESET}"
REBOOT_REQUIRED=1
fi
fi
# General: Check for deleted libraries still in use
local LIBRARIES=$(lsof -n +c 0 2> /dev/null | grep 'DEL.*lib' | awk '{print $1 ": " $NF}' | sort -u)
if [[ -n ${LIBRARIES} ]]; then
echo -e "${BRIGHT_RED}->${RESET} ${BRIGHT_CYAN}General${RESET}: ${BRIGHT_YELLOW}The following libraries require a reboot:${RESET}"
echo "${LIBRARIES}"
REBOOT_REQUIRED=1
fi
# Final message and exit code
if [[ ${REBOOT_REQUIRED} -eq 0 ]]; then
echo -e "${BRIGHT_RED}->${RESET} ${BRIGHT_CYAN}Results${RESET}: ${BRIGHT_GREEN}No reboot is required.${RESET}"
return 0
else
echo -e "${BRIGHT_RED}->${RESET} ${BRIGHT_CYAN}Results${RESET}: ${BRIGHT_YELLOW}A reboot is required.${RESET}"
return 1
fi
}
# Interactively create, configure, and test a new Linux user
function createuser() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}createuser${RESET}: Create a new user account interactively"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}createuser${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}username${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Creates home directory, sets password, optionally grants sudo${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}createuser${RESET} ${BRIGHT_BLUE}# Interactive prompts${RESET}"
echo -e " ${BRIGHT_CYAN}createuser${RESET} ${BRIGHT_GREEN}newuser${RESET} ${BRIGHT_BLUE}# Specify username${RESET}"
return 0
fi
local username
# Check if the user can execute sudo commands
if ! sudo -v; then
echo -e "${BRIGHT_RED}Error:${RESET} You do not have sufficient permissions to run this script with necessary privileges"
return 1
fi
# Check if a username was passed as a parameter
if [ "$#" -eq 1 ]; then
username="$1"
else
read -r -p $'Enter the username for the new user: ' username
fi
# Check if the user already exists
if [[ -z "${username}" ]]; then
echo -e "${BRIGHT_RED}User cannot be blank. Aborting.${RESET}"
return 1
elif id "${username}" &>/dev/null; then
echo -e "${BRIGHT_RED}User ${username} already exists. Aborting.${RESET}"
return 1
fi
# Confirm if the user should be created with a home directory
if ask "${BRIGHT_GREEN}Create a new user with a home folder?${RESET}" N; then
sudo useradd -m "${username}"
else
echo -e "${BRIGHT_RED}User creation aborted${RESET}"
return 1
fi
# Set the user's password
echo -e "${BRIGHT_YELLOW}\nSet the user's password:${RESET}"
sudo passwd "${username}"
# Ask if the user should change their password upon next login
if ask "${BRIGHT_GREEN}Force user to change password on next login?${RESET}" N; then
sudo passwd -e "${username}"
else
echo -e "${BRIGHT_YELLOW}No change password enforced${RESET}"
fi
# Ask if the user should have root (sudo) access
if ask "${BRIGHT_MAGENTA}⚠️ Give user root access? ⚠️${RESET}" N; then
sudo usermod -a -G sudo "${username}"
else
echo -e "${BRIGHT_YELLOW}No root access granted${RESET}"
fi
# Change the user's login shell to bash
echo -e "${BRIGHT_CYAN}\nChange users login shell to bash${RESET}"
sudo usermod --shell /bin/bash "${username}"
# Verify the user's settings
echo -e "${BRIGHT_YELLOW}\nVerifying user settings:${RESET}"
sudo grep "${username}" /etc/passwd
# Ask if you should copy over the local .bashrc to the new user
if ask "${BRIGHT_GREEN}Copy over your local .bashrc?${RESET}" N; then
sudo cp ~/.bashrc /home/"${username}"/
sudo chown "${username}":"${username}" /home/"${username}"/.bashrc
sudo chmod 644 /home/"${username}"/.bashrc
else
echo -e "${BRIGHT_YELLOW}No .bashrc copy${RESET}"
fi
# Test login with the new user
if ask "${BRIGHT_GREEN}⚠️ Test a login as this user? ⚠️${RESET}" N; then
echo -e "${BRIGHT_CYAN}\nTesting: Logging in as ${username}${RESET}"
sudo su - "${username}"
fi
}
# Remove a user from the system
alias deleteuser='sudo userdel'
function wipeuser() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}wipeuser${RESET}: Completely remove a user and their home directory"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}wipeuser${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}username${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Kills processes, removes home dir, removes from groups${RESET}"
echo -e " ${BRIGHT_BLUE}If no username given, shows interactive menu${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}wipeuser${RESET} ${BRIGHT_BLUE}# Select from menu${RESET}"
echo -e " ${BRIGHT_CYAN}wipeuser${RESET} ${BRIGHT_GREEN}olduser${RESET} ${BRIGHT_BLUE}# Remove specific user${RESET}"
return 0
fi
local USERNAME="$1"
# Check if the user can execute sudo commands
if ! sudo -v; then
echo -e "${BRIGHT_RED}Error:${RESET} You do not have sufficient permissions to run this script with necessary privileges"
return 1
fi
# If username is not provided, get the list of users and use createmenu
if [[ -z "${USERNAME}" ]]; then
echo -e "${BRIGHT_CYAN}Select a user to delete:${RESET}"
USERNAME=$(sudo awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd | createmenu)
fi
# If username is still empty (e.g. if the user cancels the menu selection), exit
if [[ -z "${USERNAME}" ]]; then
echo -e "${BRIGHT_RED}No user selected. Aborting.${RESET}"
return 1
# Check against this being the current user
elif [[ "${USERNAME}" == "${USER}" ]]; then
echo -e "${BRIGHT_RED}You cannot remove the currently logged-in user. Aborting.${RESET}"
return 1
fi
# Check if the user exists
if id "${USERNAME}" &>/dev/null; then
# Confirm deletion
if ask "${BRIGHT_RED}⚠️ Are you sure you want to delete user ${USERNAME} and all their data? ⚠️ This action cannot be undone! ⚠️${RESET}" N; then
# Kill all processes by the user
sudo pkill -U "${USERNAME}"
# Remove the user and their home directory
sudo userdel -rf "${USERNAME}"
# Remove the user from any additional groups
sudo delgroup "${USERNAME}" &>/dev/null
echo -e "${BRIGHT_GREEN}User ${USERNAME} and their home directory have been deleted.${RESET}"
else
echo -e "${BRIGHT_YELLOW}User deletion aborted.${RESET}"
fi
else
echo -e "${BRIGHT_RED}User ${USERNAME} does not exist.${RESET}"
fi
}
# A full chmod calculator on command line (type chmodcalc for usage and examples)
function chmodcalc() {
# Show help if requested
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}chmodcalc${RESET}: Calculate and display chmod permissions"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}chmodcalc${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}octal${BRIGHT_MAGENTA}>${RESET}"
echo -e " ${BRIGHT_CYAN}chmodcalc${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}owner${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}group${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}other${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}chmodcalc${RESET} ${BRIGHT_YELLOW}755${RESET} ${BRIGHT_BLUE}# Show rwx for 755${RESET}"
echo -e " ${BRIGHT_CYAN}chmodcalc${RESET} ${BRIGHT_YELLOW}rwx rw r${RESET} ${BRIGHT_BLUE}# Convert to octal (754)${RESET}"
echo -e "${BRIGHT_WHITE}Octal values:${RESET} read=${BRIGHT_CYAN}4${RESET}, write=${BRIGHT_CYAN}2${RESET}, execute=${BRIGHT_CYAN}1${RESET}"
return 0
fi
# Validate the number of arguments
if [[ "$#" -eq 1 ]]; then
# Validate the length of the argument (accept 1-4 digit octals)
if [[ "${#1}" -gt 4 ]] || [[ "${#1}" -lt 1 ]]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Invalid octal (use 1-4 digits).${RESET}"
return 128
fi
# Handle 4-digit octals: extract the special bit prefix
local SPECIAL_BIT=""
local TEXT="$1"
if [[ "${#TEXT}" -eq 4 ]]; then
SPECIAL_BIT="${TEXT:0:1}"
TEXT="${TEXT:1}"
fi
local -a PART=()
local EXAMPLE
local INDEX=0
while (( INDEX++ < ${#TEXT} )); do
# Extract individual octal digit
local CHAR="${TEXT:INDEX-1:1}"
# Map octal digit to permissions
case ${CHAR} in
0) PART[${INDEX}]="---" ;;
1) PART[${INDEX}]="--x" ;;
2) PART[${INDEX}]="-w-" ;;
3) PART[${INDEX}]="-wx" ;;
4) PART[${INDEX}]="r--" ;;
5) PART[${INDEX}]="r-x" ;;
6) PART[${INDEX}]="rw-" ;;
7) PART[${INDEX}]="rwx" ;;
*)
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Invalid octal digit at position ${BRIGHT_YELLOW}${INDEX}${RESET}"
return 128
;;
esac
# Create example representation
EXAMPLE[${INDEX}]="${PART[${INDEX}]//-}"
done
# Display formatted output and examples
echo -e "${BRIGHT_GREEN}${PART[1]}${RESET} ${BRIGHT_YELLOW}${PART[2]}${RESET} ${BRIGHT_RED}${PART[3]}${RESET}"
# Display special bit info if a 4-digit octal was provided
if [[ -n "${SPECIAL_BIT}" ]] && [[ "${SPECIAL_BIT}" -ne 0 ]]; then
local SPECIAL_DESC=""
(( SPECIAL_BIT & 4 )) && SPECIAL_DESC+="setuid "
(( SPECIAL_BIT & 2 )) && SPECIAL_DESC+="setgid "
(( SPECIAL_BIT & 1 )) && SPECIAL_DESC+="sticky "
echo -e "${BRIGHT_MAGENTA}Special bit (${SPECIAL_BIT}):${RESET} ${SPECIAL_DESC}"
fi
echo -e "Examples:"
local FULL_OCTAL="${SPECIAL_BIT}${TEXT}"
echo -e "${BRIGHT_CYAN}chmod${RESET} ${BRIGHT_CYAN}-R${RESET} ${BRIGHT_MAGENTA}${FULL_OCTAL}${RESET} ${BRIGHT_BLUE}./*${RESET}"
echo -e "${BRIGHT_CYAN}chmod${RESET} ${BRIGHT_CYAN}-R${RESET} ${BRIGHT_CYAN}u=${BRIGHT_GREEN}${EXAMPLE[1]}${RESET}${BRIGHT_CYAN},g=${BRIGHT_YELLOW}${EXAMPLE[2]}${RESET}${BRIGHT_CYAN},o=${BRIGHT_RED}${EXAMPLE[3]}${RESET} ${BRIGHT_BLUE}./*${RESET}"
elif [[ "$#" -eq 3 ]]; then
local FORMATTED=""
local OCTAL_VALUE
for PERMISSION in "$@"; do
OCTAL_VALUE=0
[[ ${PERMISSION} =~ .*r.* ]] && (( OCTAL_VALUE+=4 ))
[[ ${PERMISSION} =~ .*w.* ]] && (( OCTAL_VALUE+=2 ))
[[ ${PERMISSION} =~ .*x.* ]] && (( OCTAL_VALUE+=1 ))
FORMATTED="${FORMATTED}${OCTAL_VALUE}"
done
echo -e "${BRIGHT_CYAN}${FORMATTED}${RESET}"
chmodcalc "${FORMATTED}"
else
echo -e "${BRIGHT_CYAN}chmodcalc${RESET}: Calculate and display chmod permissions"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}chmodcalc${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}octal${BRIGHT_MAGENTA}>${RESET}"
echo -e " ${BRIGHT_CYAN}chmodcalc${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}owner${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}group${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}other${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}chmodcalc${RESET} ${BRIGHT_YELLOW}755${RESET} ${BRIGHT_BLUE}# Show rwx for 755${RESET}"
echo -e " ${BRIGHT_CYAN}chmodcalc${RESET} ${BRIGHT_YELLOW}rwx rw r${RESET} ${BRIGHT_BLUE}# Convert to octal (754)${RESET}"
echo -e "${BRIGHT_WHITE}Octal values:${RESET} read=${BRIGHT_CYAN}4${RESET}, write=${BRIGHT_CYAN}2${RESET}, execute=${BRIGHT_CYAN}1${RESET}"
fi
}
# Recursively set permissions for only files
function chmodfiles() {
# Initialize local variables
local _DIRECTORY="${2:-${PWD}}" # Default to current directory if no directory is provided
local _PERMISSION="$1" # The permission mode to be set
# Check for missing permission parameter
if [[ -z "${_PERMISSION}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}chmodfiles${RESET}: Set permissions for files recursively"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}chmodfiles${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}mode${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}directory${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}chmodfiles${RESET} ${BRIGHT_YELLOW}664${RESET} ${BRIGHT_BLUE}# Current directory${RESET}"
echo -e " ${BRIGHT_CYAN}chmodfiles${RESET} ${BRIGHT_YELLOW}+x${RESET} ${BRIGHT_GREEN}/some/path${RESET} ${BRIGHT_BLUE}# Specific directory${RESET}"
return 1
fi
# Check if the specified directory exists
if [ ! -d "${_DIRECTORY}" ]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Directory ${BRIGHT_YELLOW}${_DIRECTORY}${BRIGHT_CYAN} not found${RESET}"
return 2
fi
# Ask the user if sudo should be used for the operation
local SUDO_PREFIX=''
if ask "${BRIGHT_CYAN}Use administrator priveledges for this operation?${RESET}" N; then
SUDO_PREFIX='sudo '
fi
# Ask for confirmation
if ask "${BRIGHT_CYAN}Are you sure you want to change file permissions in ${BRIGHT_YELLOW}${_DIRECTORY}${BRIGHT_CYAN}?${RESET}" N; then
# Change permissions for files recursively
${SUDO_PREFIX}find "${_DIRECTORY}" -type f -exec chmod "${_PERMISSION}" {} \;
echo -e "${BRIGHT_GREEN}Permissions set for files in ${BRIGHT_YELLOW}${_DIRECTORY}${RESET}"
else
echo -e "${BRIGHT_RED}Operation canceled${RESET}"
fi
}
# Recursively set permissions for only directories
alias chmoddirectories='chmoddirs'
alias chmodfolders='chmoddirs'
function chmoddirs() {
# Initialize local variables
local _DIRECTORY="${2:-${PWD}}" # Default to current directory if no directory is provided
local _PERMISSION="$1" # The permission mode to be set
# Check for missing permission parameter
if [[ -z "${_PERMISSION}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}chmoddirs${RESET}: Set permissions for directories recursively"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}chmoddirs${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}mode${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}directory${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}chmoddirs${RESET} ${BRIGHT_YELLOW}775${RESET} ${BRIGHT_BLUE}# Current directory${RESET}"
echo -e " ${BRIGHT_CYAN}chmoddirs${RESET} ${BRIGHT_YELLOW}+x${RESET} ${BRIGHT_GREEN}/some/path${RESET} ${BRIGHT_BLUE}# Specific directory${RESET}"
return 1
fi
# Check if the specified directory exists
if [ ! -d "${_DIRECTORY}" ]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Directory ${BRIGHT_YELLOW}${_DIRECTORY}${BRIGHT_CYAN} not found${RESET}"
return 2
fi
# Ask the user if sudo should be used for the operation
local SUDO_PREFIX=''
if ask "${BRIGHT_CYAN}Use administrator priveledges for this operation?${RESET}" N; then
SUDO_PREFIX='sudo '
fi
# Confirm the action before proceeding
if ask "${BRIGHT_CYAN}Are you sure you want to change directory permissions in ${BRIGHT_YELLOW}${_DIRECTORY}${BRIGHT_CYAN}?${RESET}" N; then
# Change permissions for directories recursively
${SUDO_PREFIX}find "${_DIRECTORY}" -type d -name \* -exec chmod "${_PERMISSION}" {} \;
echo -e "${BRIGHT_GREEN}Permissions set for directories in ${BRIGHT_YELLOW}${_DIRECTORY}${RESET}"
else
echo -e "${BRIGHT_RED}Operation canceled${RESET}"
fi
}
# Recursively set permissions of code files and directories
# WARNING: Will add execute permissions to .sh files!
function chfix() {
# Check if -h or --help is provided as the first argument
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}chfix${RESET}: Set permissions and ownership for files and directories recursively"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}chfix${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}file_mode${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}dir_mode${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}owner${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}directory${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Defaults:${RESET} file_mode=664, dir_mode=775, directory=current"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}chfix${RESET} ${BRIGHT_BLUE}# Use defaults${RESET}"
echo -e " ${BRIGHT_CYAN}chfix${RESET} ${BRIGHT_YELLOW}644 755${RESET} ${BRIGHT_BLUE}# Custom modes${RESET}"
echo -e " ${BRIGHT_CYAN}chfix${RESET} ${BRIGHT_YELLOW}644 755${RESET} ${BRIGHT_GREEN}www-data${RESET} ${BRIGHT_BLUE}# With owner${RESET}"
echo -e " ${BRIGHT_CYAN}chfix${RESET} ${BRIGHT_YELLOW}664 775${RESET} ${BRIGHT_GREEN}www-data:dev /var/www${RESET} ${BRIGHT_BLUE}# Full usage${RESET}"
return 1
fi
# Initialize variables with default values or passed arguments
# Write access is also given to the group (e.g. for a group of developers)
local FILE_PERMISSIONS="${1:-0664}" # Default to User:rw Group:rw Other:r
local DIR_PERMISSIONS="${2:-0775}" # Default to User:rwx Group:rwx Other:rx
local OWNER="${3:-}" # Optional owner
local DIRECTORY="${4:-${PWD}}" # Default to current directory
# Check if the specified directory exists
if [ ! -d "${DIRECTORY}" ]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Directory ${BRIGHT_YELLOW}${DIRECTORY}${BRIGHT_CYAN} not found${RESET}"
return 2
fi
# Ask the user if sudo should be used for the operation
local SUDO_PREFIX=''
if ask "${BRIGHT_CYAN}Use administrator privileges for this operation?${RESET}" N; then
SUDO_PREFIX='sudo '
fi
# Confirm the action
if ask "${BRIGHT_CYAN}This will change all permissions and optionally ownership for directories and files in ${BRIGHT_YELLOW}${DIRECTORY}${RESET}. Are you sure?${RESET}" N; then
# Change permissions
${SUDO_PREFIX}find "${DIRECTORY}" -type f -exec chmod "${FILE_PERMISSIONS}" {} \;
${SUDO_PREFIX}find "${DIRECTORY}" -type d -exec chmod "${DIR_PERMISSIONS}" {} \;
# Add execute permissions to .sh files
${SUDO_PREFIX}find "${DIRECTORY}" -type f -name "*.sh" -exec chmod +x {} \;
# Change ownership if OWNER is specified
[ -n "${OWNER}" ] && ${SUDO_PREFIX}chown -R "${OWNER}" "${DIRECTORY}"
echo -e "${BRIGHT_GREEN}Permissions and ownership set for ${BRIGHT_YELLOW}${DIRECTORY}${RESET}"
fi
}
# Copy/clone file permissions
function chmodcopy() {
# Check if at least two arguments are provided (source and destination)
if [ $# -lt 2 ]; then
# Display usage information if the required arguments are not provided
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}chmodcopy${RESET} ${BRIGHT_GREEN}[source_file]${RESET} ${BRIGHT_YELLOW}[destination_file]${RESET} ${BRIGHT_CYAN}[additional_optional_chmod_parameters]${RESET}"
return 1
fi
# Prompt the user to confirm if they want tu use administrator privileges
local SUDO_PREFIX=''
if ask "${BRIGHT_CYAN}Use administrator privileges for this operation?${RESET}" N; then
SUDO_PREFIX='sudo '
fi
# Execute the chmod command with --reference option
${SUDO_PREFIX}chmod --reference="${@}"
echo -e "${BRIGHT_GREEN}Permissions copied${RESET}"
}
# Improved terminal clipboard management for viewing, setting, and
# clearing content, with support for piping input and output.
function clipboard() {
# Help message
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}clipboard${RESET}: Terminal clipboard management with piping support"
echo -e "${BRIGHT_WHITE}Usage:${RESET}"
echo -e " ${BRIGHT_CYAN}clipboard${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}text${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_BLUE}# Copy text to clipboard${RESET}"
echo -e " command | ${BRIGHT_CYAN}clipboard${RESET} ${BRIGHT_BLUE}# Copy command output to clipboard${RESET}"
echo -e " ${BRIGHT_CYAN}clipboard${RESET} | command ${BRIGHT_BLUE}# Paste clipboard to command${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}clipboard${RESET} ${BRIGHT_YELLOW}'Hello World'${RESET} ${BRIGHT_BLUE}# Copy text${RESET}"
echo -e " ${BRIGHT_YELLOW}cat file.txt${RESET} | ${BRIGHT_CYAN}clipboard${RESET} ${BRIGHT_BLUE}# Copy file contents${RESET}"
echo -e " ${BRIGHT_CYAN}clipboard${RESET} | ${BRIGHT_YELLOW}less${RESET} ${BRIGHT_BLUE}# View clipboard in less${RESET}"
echo -e " ${BRIGHT_YELLOW}pwd${RESET} | ${BRIGHT_CYAN}clipboard${RESET} ${BRIGHT_BLUE}# Copy current directory${RESET}"
return
fi
# Local variables
local CLIPBOARD_CONTENT="" # Content for clipboard
local SEND_TO_CLIPBOARD=false # Flag if content needs to be sent to clipboard
# If we are logged in via SSH...
if [[ -n "${SSH_CLIENT}" ]] || [[ -n "${SSH_TTY}" ]]; then
# No local clipboard access in SSH
return 1
# Determine if text is passed as a parameter
elif [[ -n "${1}" ]]; then
CLIPBOARD_CONTENT="${1}"
SEND_TO_CLIPBOARD=true
# Determine if content is piped to clipboard
elif [[ -p /dev/stdin ]]; then
CLIPBOARD_CONTENT=$(cat -)
SEND_TO_CLIPBOARD=true
fi
# Handle content that needs to be sent to clipboard
if ${SEND_TO_CLIPBOARD}; then
if [[ "${OSTYPE}" == "darwin"* ]] && hascommand --strict pbcopy; then
# macOS
echo -n "${CLIPBOARD_CONTENT}" | pbcopy
elif hascommand --strict wl-copy && [[ "${XDG_SESSION_TYPE}" == "wayland" ]]; then
# Wayland
echo -n "${CLIPBOARD_CONTENT}" | wl-copy &>/dev/null
elif hascommand --strict xclip; then
# X11 - xclip
echo -n "${CLIPBOARD_CONTENT}" | xclip -selection clipboard &>/dev/null
elif hascommand --strict xsel; then
# X11 - xsel
echo -n "${CLIPBOARD_CONTENT}" | xsel -ib &>/dev/null
fi
else
# Handle content that needs to be pasted from clipboard
if [[ "${OSTYPE}" == "darwin"* ]] && hascommand --strict pbpaste; then
# macOS
pbpaste
elif hascommand --strict wl-paste && [[ "${XDG_SESSION_TYPE}" == "wayland" ]]; then
# Wayland
wl-paste
elif hascommand --strict xclip; then
# X11 - xclip
xclip -o -selection clipboard
elif hascommand --strict xsel; then
# X11 - xsel
xsel -ob
fi
fi
# If Tmux is running and there is content for the clipboard...
if [[ -n "${TMUX}" ]] && [[ -n "${CLIPBOARD_CONTENT}" ]]; then
echo -n "${CLIPBOARD_CONTENT}" | tmux loadb -
fi
}
# Copy a file's contents to the clipboard
function file2cb() {
# Check for parameters...
if [[ $# -eq 0 ]]; then
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}file2cb ${BRIGHT_YELLOW}[filename]${BRIGHT_WHITE}"
echo -e "${BRIGHT_WHITE}Copy a file's contents to the clipboard.${RESET}"
return 1
# Check if the file exists...
elif [[ ! -f "$1" ]]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}The file ${BRIGHT_YELLOW}$1${BRIGHT_CYAN} does not exist.${RESET}"
return 1
fi
command cat "$1" | clipboard
}
# Save the clipboard contents to a file
function cb2file() {
if [[ $# -eq 0 ]]; then
echo -e "${BRIGHT_WHITE}cb2file:${RESET} Save the clipboard contents to a file"
echo -e "${BRIGHT_WHITE}Usage:${BRIGHT_CYAN} cb2file${RESET} ${BRIGHT_YELLOW}<filename>${RESET}"
return 1
fi
clipboard > "$1"
}
# Dump the clipboard contents to the console
function cbshow() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}cbshow${RESET}: Display current clipboard contents"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}cbshow${RESET}"
return 0
fi
clipboard
}
# When using the which command, copy the output to the clipboard or Tmux buffer
alias which='_which_to_clipboard'
# Call the 'which' command and copy its output to clipboard
function _which_to_clipboard() {
# Get the path of the requested which command
local WHICH_OUTPUT=$(command which "$@" 2>&1)
# Capture the exit status of the 'which' command
local EXIT_STATUS=$?
# Check if 'which' output is non-empty and not "/usr/bin/grc"
if [[ ${EXIT_STATUS} -eq 0 ]] && [[ "${WHICH_OUTPUT}" != "/usr/bin/grc" ]]; then
# Copy the output to clipboard and display in terminal
echo "${WHICH_OUTPUT}" | clipboard
fi
# Display the standard output of 'which'
echo "${WHICH_OUTPUT}"
# Return the captured exit status
return ${EXIT_STATUS}
}
# When using the pwd command, copy the directory to the clipboard or Tmux buffer
alias pwd='_pwd_to_clipboard'
function _pwd_to_clipboard() {
# Run the real pwd command and capture its output
local _PWD_OUTPUT="$(command pwd "$@")"
echo "${_PWD_OUTPUT}"
# If we are not in the enhancd program folder...
if [[ "${PWD}" != *"/enhancd" ]]; then
# Use the new clipboard command to copy the output
clipboard "${_PWD_OUTPUT}"
fi
}
# Compress a file (even binary files) to the clipboard as base64 text
function file2asc() {
# Check if a parameter was passed and if the file exists
if [[ ${#} -eq 0 ]] || [[ ! -f "${1}" ]]; then
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}file2asc${RESET} ${BRIGHT_GREEN}[filename]${RESET}"
return 1
fi
# Check if gzip is installed
if ! hascommand --strict gzip; then
>&2 echo "Requires gzip/gunzip"
return 1
fi
# Create the base64-encoded gzipped content
# SC2155: split declaration so gzip/base64 failure isn't masked
local ENCODED_CONTENT
ENCODED_CONTENT=$(command cat "${1}" | gzip -9 | base64)
# Send the content to clipboard
if clipboard "${ENCODED_CONTENT}"; then
echo "The clipboard now contains the file: ${1}"
else
# If clipboard function couldn't handle the content, fallback
# We might have a headless environment with no clipboard
if [[ -z "${PAGER}" ]]; then
printf '=%.0s' {1..80}; echo
echo "${ENCODED_CONTENT}"
printf '=%.0s' {1..80}; echo
else # Use the pager
# To save text as a file from less, type s then type the file name
echo "${ENCODED_CONTENT}" | "${PAGER}"
fi
fi
# Done!
echo "Use asc2file to convert the base64 ASCII text to a file."
}
# Convert compressed base64 clipboard back to a file
function asc2file() {
# If no parameters...
if [[ ${#} -eq 0 ]]; then
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_CYAN}asc2file${RESET} ${BRIGHT_GREEN}[filename]${RESET}"
return 1
fi
# Check for gunzip
if ! hascommand --strict gunzip; then
>&2 echo "Requires gzip/gunzip"
return 1
fi
# Fetch the current clipboard content
local CLIPBOARD_CONTENT=$(clipboard)
if [[ -z "${CLIPBOARD_CONTENT}" ]]; then
echo -e "${BRIGHT_YELLOW}Paste the text and press ${BRIGHT_CYAN}CTRL+d${BRIGHT_YELLOW} when done:${RESET} "
CLIPBOARD_CONTENT=$(</dev/stdin)
fi
# Decode and gunzip the clipboard content, then save to file
echo "${CLIPBOARD_CONTENT}" | base64 -di | gunzip > "${1}"
# Display the saved file path
if hascommand --strict realpath; then
echo "The file was saved: $(command realpath "${1}")"
else
echo "The file was saved: ${1}"
fi
}
# Convert an image to compressed JPG format
alias {png2jpg,image2jpg}='compressimage'
function compressimage() {
# Show help message if no parameters are provided
if [[ -z "$1" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}compressimage${RESET}: Convert an image to compressed JPG format"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}compressimage${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}image${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}quality${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Defaults:${RESET} quality=85 (range 1-100)"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}compressimage${RESET} ${BRIGHT_YELLOW}screenshot.png${RESET} ${BRIGHT_BLUE}# Use default quality${RESET}"
echo -e " ${BRIGHT_CYAN}compressimage${RESET} ${BRIGHT_YELLOW}photo.png${RESET} ${BRIGHT_GREEN}90${RESET} ${BRIGHT_BLUE}# Higher quality${RESET}"
echo -e " ${BRIGHT_CYAN}compressimage${RESET} ${BRIGHT_YELLOW}large.png${RESET} ${BRIGHT_GREEN}60${RESET} ${BRIGHT_BLUE}# More compression${RESET}"
return 1
fi
# Check if ImageMagick's 'convert' and 'identify' commands are available
if ! hascommand --strict convert || ! hascommand --strict identify; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}ImageMagick is not installed. Please install it first.${RESET}"
return 1
fi
# Check if the input image file exists
if [ ! -f "$1" ]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}The input image file ${BRIGHT_YELLOW}$1${BRIGHT_CYAN} does not exist.${RESET}"
return 1
fi
# Default quality setting
local QUALITY=85
# Validate optional quality argument
if [ ! -z "$2" ]; then
if [[ ! "$2" =~ ^[0-9]+$ ]] || [ "$2" -le 0 ] || [ "$2" -gt 100 ]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Invalid quality percentage. It should be a positive integer between 1 and 100.${RESET}"
return 1
fi
QUALITY=$2
fi
# Extract file name and output to the same directory as the source file
local INPUT_FILENAME
INPUT_FILENAME=$(basename -- "$1")
local INPUT_DIR
INPUT_DIR=$(dirname -- "$1")
local OUTPUT_FILENAME="${INPUT_DIR}/${INPUT_FILENAME%.*}.jpg"
# Perform the conversion
convert "$1" -quality ${QUALITY} "${OUTPUT_FILENAME}"
echo -e "${BRIGHT_GREEN}Success: ${BRIGHT_WHITE}Converted ${BRIGHT_YELLOW}$1${BRIGHT_WHITE} to ${BRIGHT_YELLOW}${OUTPUT_FILENAME}${BRIGHT_WHITE} with quality ${BRIGHT_YELLOW}${QUALITY}%${RESET}."
}
# Converts a markdown title string into a markdown tag
alias md2tag='convert2mdtag'
function convert2mdtag() {
# Help text
if [[ -z "${1}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}convert2mdtag${RESET}: Convert a heading to a markdown anchor link"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}convert2mdtag${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}heading${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Creates [Title](#anchor) format, copies to clipboard${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}convert2mdtag${RESET} ${BRIGHT_YELLOW}'## My Section'${RESET} ${BRIGHT_BLUE}# [My Section](#my-section)${RESET}"
echo -e " ${BRIGHT_CYAN}convert2mdtag${RESET} ${BRIGHT_YELLOW}'Hello World'${RESET} ${BRIGHT_BLUE}# [Hello World](#hello-world)${RESET}"
return 0
fi
# Trim leading '#' or spaces and trailing spaces
local TRIMMED_INPUT=$(echo "${1}" | sed -e 's/^[\# ]*//' -e 's/[ ]*$//')
# Convert to lowercase, replace spaces with hyphens,
# remove unwanted characters, and fix double dashes
local TAG=$(echo "${TRIMMED_INPUT,,}" | sed -e 's/ /-/g' | tr -dc 'a-z0-9-' | sed 's/--*/-/g')
# Output the result in the desired markdown link format
echo "[${TRIMMED_INPUT}](#${TAG})" | clipboard
echo "[${TRIMMED_INPUT}](#${TAG})"
}
# Remove unneeded execute permissions from strictly non-executable file types
# It is good practice to remove execute permissions from data files to enhance
# security, as files masquerading as these types could potentially be malicious
function fixinvalidexecutepermissions() {
# Parse arguments to check for help flag
local TARGET_DIRECTORY
local SHOW_HELP=false
# Process parameters
while [[ $# -gt 0 ]]; do
case "${1}" in
-h|--help)
SHOW_HELP=true
shift
;;
*)
# First non-flag argument is treated as target directory
if [[ -z "${TARGET_DIRECTORY}" ]]; then
TARGET_DIRECTORY="${1}"
fi
shift
;;
esac
done
# Show help if requested
if ${SHOW_HELP}; then
echo -e "${BRIGHT_CYAN}fixinvalidexecutepermissions${RESET}: Remove execute permissions from non-executable file types"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}fixinvalidexecutepermissions${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}directory${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Removes +x from docs, images, fonts, archives, etc.${RESET}"
echo -e " ${BRIGHT_BLUE}Enhances security by preventing accidental execution${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}fixinvalidexecutepermissions${RESET} ${BRIGHT_BLUE}# Current directory${RESET}"
echo -e " ${BRIGHT_CYAN}fixinvalidexecutepermissions${RESET} ${BRIGHT_GREEN}~/Documents${RESET} ${BRIGHT_BLUE}# Specific directory${RESET}"
return 0
fi
# Use current directory as default if no directory specified
TARGET_DIRECTORY="${TARGET_DIRECTORY:-.}"
# Check if the user can execute sudo commands
if ! sudo -v; then
echo -e "${BRIGHT_RED}Error:${RESET} You do not have sufficient permissions to run this script with necessary privileges"
return 1
fi
# Confirmation prompt using the provided ask function
if ask "${BRIGHT_MAGENTA}Do you want to remove invalid execute permissions in directory: ${BRIGHT_CYAN}${TARGET_DIRECTORY}${RESET}" Y; then
# Ask about including Git directories
local INCLUDE_GIT=false
if ask "${BRIGHT_MAGENTA}Include Git directories for execute permissions cleanup?${RESET} ${BRIGHT_YELLOW}(Not recommended)${RESET}" N; then
INCLUDE_GIT=true
fi
# Document and Text Files
local -a doc_extensions=(
"ans" "asc" "azw" "azw3" "azw4" "cfg" "chm" "clf" "cnf" "conf" "csv"
"doc" "docx" "dot" "dotx" "ebook" "eml" "epub" "fdf" "hlp" "htm"
"html" "htmlz" "ics" "inf" "ini" "lng" "log" "lst" "mcw" "mht"
"mobi" "mpp" "msg" "nfo" "odp" "ods" "odt" "opf" "org" "ott" "pdf"
"plist" "po" "pot" "pps" "ppt" "pptx" "prn" "properties" "rpt" "rst"
"rtf" "slk" "sxw" "template" "tex" "toc" "txt" "url" "wb1" "wk1"
"wk3" "wk4" "wp5" "wpd" "wps" "wri" "wtf" "xls" "xlsm" "xlsx"
"xlsxm" "xlt" "xlw" "xps" "xslt"
)
# Non-Executable Code and Markup Files (should never contain a shebang)
local -a code_extensions=(
"ascx" "c" "cc" "config" "cpp" "css" "ctl" "cxx" "dtd" "editorconfig"
"exp" "frx" "h" "handlebars" "hpp" "htc" "inc" "jshintrc" "json"
"jsonl" "kwinrule" "less" "mak" "manifest" "md" "mm" "mo" "proj"
"project" "pxd" "qml" "rc" "res" "resx" "sass" "scc" "scss" "sql"
"strings" "tpl" "vsixmanifest" "xhtml" "xlf" "xml" "xsd" "yaml" "yml"
)
# Font Files
local -a font_extensions=(
"afm" "eot" "fnt" "fon" "otf" "ttc" "ttf" "ufm" "woff" "woff2"
)
# Image Files
local -a image_extensions=(
"afdesign" "afphoto" "ai" "ani" "blp" "bmp" "bpg" "bw" "cbz" "cbr"
"cdr" "cgm" "cpt" "cur" "dcx" "dib" "drw" "emf" "eps" "exif" "gif"
"hdr" "heic" "heif" "ico" "idml" "iff" "ilbm" "img" "indd" "indl"
"indt" "jfif" "jif" "jpeg" "jpg" "kdc" "lbm" "pbm" "pcd" "pcx" "pdn"
"pgm" "pic" "pix" "png" "pnm" "ppm" "ps" "psd" "psp" "pspimage"
"raw" "rgb" "rgba" "rle" "sgi" "spp" "svg" "svgz" "tga" "tif" "tiff"
"vml" "webp" "wmf" "xcf" "xisf" "xmp"
)
# Audio Files
local -a audio_extensions=(
"8svx" "aa" "aac" "aax" "acm" "act" "aip" "amr" "as" "au" "awb"
"cda" "dct" "dsm" "dss" "dvf" "dwd" "flac" "gsm" "iklax" "it" "ivs"
"m3u" "m4a" "m4b" "m4p" "mid" "midi" "mmf" "mod" "mogg" "mp1" "mp2"
"mp3" "mpa" "mpc" "msv" "mtm" "nmf" "nsf" "oga" "ogg" "opus" "pcm"
"pls" "ra" "rmi" "s3m" "sam" "sln" "smp" "snd" "stm" "svx" "vba"
"vce" "voc" "vox" "wav" "wma" "wv" "xm"
)
# Video Files
local -a video_extensions=(
"3g2" "3gp" "asf" "asx" "avi" "divx" "fla" "flv" "m1v" "m4v" "mkv"
"mov" "mp4" "mpe" "mpeg" "mpg" "mpv" "qt" "qtif" "ram" "rm" "rmd"
"rmm" "rp" "rt" "smi" "smil" "swf" "ts" "viv" "vob" "webm" "wmv"
)
# Subtitle Files
local -a subtitle_extensions=(
"srt" "sub" "vtt"
)
# Gaming and Emulation Files
local -a gaming_emulation_extensions=(
"chd" "gba" "gcm" "nds" "nes" "rom" "sav" "sfc" "state" "wad"
)
# Archive Files
local -a archive_extensions=(
"7z" "ace" "arj" "bz2" "cab" "csv.gz" "cue" "deb" "dmg" "ex_" "gz"
"iso" "isz" "lha" "log.gz" "lzh" "mdf" "mds" "nrg" "pak" "pkg" "rar"
"rar5" "rpm" "sitx" "tar" "tar.bz2" "tar.gz" "tgz" "xpi" "z" "zip"
"zipx"
)
# Backup and Temporary Files
local -a backup_temp_extensions=(
"~" "bac" "backup" "bak" "bkp" "old" "orig" "original" "snapshot"
"swp" "temp" "tmp"
)
# Checksum and Security Files
local -a checksum_security_extensions=(
"crc" "md5" "md5sum" "par" "par2" "sha1" "sha1sum" "sha256"
"sha256sum" "sha512"
)
# Key and Certificate Files
local -a key_certs_extensions=(
"cer" "cert" "crt" "csr" "der" "jks" "key" "p12" "pfx" "pem" "ppk"
"pub" "pubkey" "sig"
)
# Database Files
local -a database_extensions=(
"accdb" "db" "db3" "dbf" "idx" "mdb" "sqlite" "sqlite3"
)
# Miscellaneous Utilities
local -a misc_utilities_extensions=(
"cat" "chk" "cpl" "dat" "dll" "dmp" "drv" "dylib" "icl" "lib" "ocb"
"ocx" "opml" "ost" "part" "pdb" "prefs" "pst" "reg" "rnd" "rng"
"sys" "tlb" "torrent" "vxd" "wasm"
)
# Configuration Files
local -a config_files=(
".aspell.*.prepl" ".aspell.*.pws" ".bash_history" ".bash_logout"
".bash_profile" ".bashrc" ".cshrc" ".csslintrc" ".cvsignore"
".dircolors" ".dmrc" ".eslintrc" ".env" ".gitconfig" ".gitignore"
".gitmodules" ".gtkrc-2.0" ".htaccess" ".htpasswd" ".ICEauthority"
".inputrc" "Jenkinsfile" ".kshrc" ".lesshst" ".nanorc"
".node_repl_history" ".npmignore" ".nvmrc" ".rnd" ".screenrc"
".steampath" ".steampid" ".tmux.conf" ".vimrc" ".vuescanrc"
".wget-hsts" ".Xauthority" ".Xclients" ".xinitrc" ".Xmodmap"
".xprofile" ".Xresources" ".xscreensaver" ".yarnrc" ".zshrc"
)
# Combine all extensions into one array
local -a all_extensions=(
"${doc_extensions[@]}"
"${code_extensions[@]}"
"${font_extensions[@]}"
"${image_extensions[@]}"
"${audio_extensions[@]}"
"${video_extensions[@]}"
"${subtitle_extensions[@]}"
"${gaming_emulation_extensions[@]}"
"${archive_extensions[@]}"
"${backup_temp_extensions[@]}"
"${checksum_security_extensions[@]}"
"${key_certs_extensions[@]}"
"${database_extensions[@]}"
"${misc_utilities_extensions[@]}"
)
# Build the find command arguments for file extensions and config files
local -a FIND_ARGS=()
local EXTENSION
for EXTENSION in "${all_extensions[@]}"; do
FIND_ARGS+=( -iname "*.${EXTENSION}" -o )
done
# Add patterns for configuration files (exact filenames)
local FILENAME
for FILENAME in "${config_files[@]}"; do
FIND_ARGS+=( -iname "${FILENAME}" -o )
done
# Remove the last '-o' (logical OR) to prevent syntax error
unset 'FIND_ARGS[${#FIND_ARGS[@]}-1]'
# Construct find command based on whether to include Git directories
if ${INCLUDE_GIT}; then
# Include Git directories in the search
sudo find "${TARGET_DIRECTORY}" \
-type f \( "${FIND_ARGS[@]}" \) \
-exec chmod a-x {} +
echo -e "${BRIGHT_GREEN}${RESET} Execute permissions have been removed from non-executable files ${BRIGHT_YELLOW}(including Git directories)${RESET}"
else
# Exclude Git directories from the search using prune
sudo find "${TARGET_DIRECTORY}" \
-type d -name ".git" -prune -o \
-type f \( "${FIND_ARGS[@]}" \) \
-exec chmod a-x {} +
echo -e "${BRIGHT_GREEN}${RESET} Execute permissions have been removed from non-executable files ${BRIGHT_BLUE}(Git directories excluded)${RESET}"
fi
else
return 1
fi
}
# Fixes permissions in a user's home folder
# If no user is specified, the current user is selected
function fixuserhome() {
# Validate input parameters
local _username
if [[ -z "${1}" ]]; then
_username="$(whoami)"
else
_username="${1}"
fi
# Attempt to retrieve the home directory
# SC2155: split declaration so getent failure isn't masked
local _home_dir
_home_dir=$(getent passwd "${_username}" 2>/dev/null | cut -d: -f6)
# Check if the retrieved directory is valid and exists
if [[ -z "${_home_dir}" ]] || [[ ! -d "${_home_dir}" ]]; then
# Default to /home/username if no valid directory is found
_home_dir="/home/${_username}"
fi
# Verify if the home folder exists
if [ -d "${_home_dir}" ]; then
echo -e "Home directory found: ${BRIGHT_YELLOW}${_home_dir}${RESET}"
else
echo -e "${BRIGHT_RED}Error:${RESET} User ${BRIGHT_CYAN}${_username}${RESET} does not have a home directory"
return 1
fi
# Check if the user can execute sudo commands
if ! sudo -v; then
echo -e "${BRIGHT_RED}Error:${RESET} You do not have sufficient permissions to run this script with necessary privileges"
return 1
fi
# Confirm with the user
if ask "${BRIGHT_RED}WARNING:${RESET} Change all permissions for user ${BRIGHT_CYAN}${_username}${RESET}'s home folder?" N; then
if ask "Reset group ownership permissions to ${BRIGHT_CYAN}${_username}${RESET}?" Y; then
runwithfeedback \
"Set the owner and group as ${_username}" \
"sudo chown -R '${_username}':'${_username}' '${_home_dir}'"
else
runwithfeedback \
"Set the owner and group as ${_username}" \
"sudo chown -R '${_username}' '${_home_dir}'"
fi
# Remove unneeded execute permissions from strictly non-executable file types
fixinvalidexecutepermissions "${_home_dir}"
runwithfeedback \
"Make sure we have read and write access" \
"chmod -R u+rw '${_home_dir}'"
runwithfeedback \
"Remove write access from group" \
"chmod -R g-w '${_home_dir}'"
runwithfeedback \
"Remove all access from others" \
"chmod -R o-rwx '${_home_dir}'"
runwithfeedback \
"Make .sh shell script files executable" \
"find '${_home_dir}' -type f \( -name \"*.sh\" -o -name \".*.sh\" \) -exec chmod ug+x {} \;"
runwithfeedback \
"Make sure all directories have execute permissions" \
"chmod -R ug+X '${_home_dir}'"
# Remove group permissions for directories without group read
runwithfeedback \
"Remove group permissions for directories without group read" \
"find '${_home_dir}' -type d ! -perm -g+r -execdir chmod g-wx {} \;"
if [[ -d "${_home_dir}/.local/share/kwalletd" ]]; then
runwithfeedback \
"User only access to KDE Wallet keyring" \
"chmod -R go-rwx '${_home_dir}/.local/share/kwalletd'"
fi
# If there is a ~/.local/share/keyrings directory...
if [[ -d "${_home_dir}/.local/share/keyrings" ]]; then
runwithfeedback \
"User only access to GNOME keyring" \
"chmod -R go-rwx '${_home_dir}/.local/share/keyrings'"
fi
# If there is an .ssh directory...
if [[ -d "${_home_dir}/.ssh" ]]; then
# Setting ownership for .ssh directory and files
runwithfeedback \
"Setting ownership for .ssh directory and files" \
"chown -R '${_username}':'${_username}' '${_home_dir}/.ssh'"
# Setting strict permissions for .ssh and private keys
runwithfeedback \
"User only access to .ssh and private keys" \
"chmod -R go-rwx '${_home_dir}/.ssh'"
fi
# If there is a .putty directory...
if [[ -d "${_home_dir}/.putty" ]]; then
runwithfeedback \
"User only access to .putty and ssh keys" \
"chmod -R go-rwx '${_home_dir}/.putty'"
fi
# If there is a .pki directory...
if [[ -d "${_home_dir}/.pki" ]]; then
runwithfeedback \
"User only access to .pki keys and certificates" \
"chmod -R go-rwx '${_home_dir}/.pki'"
fi
# If there is a .gnupg directory...
if [[ -d "${_home_dir}/.gnupg" ]]; then
runwithfeedback \
"User only access to .gnupg and private keys" \
"chmod -R go-rwx '${_home_dir}/.gnupg'"
fi
# If KeePassXC/KeePass/KeeWeb is installed...
if hascommand --strict keepassxc || hascommand --strict keepass || hascommand --strict keeweb; then
runwithfeedback \
"User only access to KeePassXC/KeePass/KeeWeb .kdbx files" \
"find '${_home_dir}' -type f \( -name '*.kdbx' -o -name '.*.kdbx' \) -exec chmod go-rwx {} \;"
fi
# If there is a pass directory...
if [[ -d "${_home_dir}/.password-store" ]]; then
runwithfeedback \
"User only access to pass data" \
"chmod -R go-rwx '${_home_dir}/.password-store'"
fi
# If there is a Bitwarden directory...
if [[ -d "${_home_dir}/.config/Bitwarden" ]]; then
runwithfeedback \
"User only access to Bitwarden data" \
"chmod -R go-rwx '${_home_dir}/.config/Bitwarden'"
fi
if [[ -d "${_home_dir}/.var/app/com.bitwarden.desktop" ]]; then
runwithfeedback \
"User only access to Bitwarden data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/com.bitwarden.desktop'"
fi
# Check for Tor settings
if [[ -d "${_home_dir}/.local/share/torbrowser" ]]; then
runwithfeedback \
"User only access to Tor browser data" \
"chmod -R go-rwx '${_home_dir}/.local/share/torbrowser'"
fi
if [[ -d "${_home_dir}/.var/app/com.github.micahflee.torbrowser-launcher" ]]; then
runwithfeedback \
"User only access to Tor browser data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/com.github.micahflee.torbrowser-launcher'"
fi
# Check for Brave settings
if [[ -d "${_home_dir}/.config/BraveSoftware" ]]; then
runwithfeedback \
"User only access to Brave browser data" \
"chmod -R go-rwx '${_home_dir}/.config/BraveSoftware'"
fi
if [[ -d "${_home_dir}/.var/app/com.brave.Browser" ]]; then
runwithfeedback \
"User only access to Brave browser data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/com.brave.Browser'"
fi
# Check for Chrome settings
if [[ -d "${_home_dir}/.config/google-chrome" ]]; then
runwithfeedback \
"User only access to Chrome browser data" \
"chmod -R go-rwx '${_home_dir}/.config/google-chrome'"
fi
if [[ -d "${_home_dir}/.var/app/com.google.Chrome" ]]; then
runwithfeedback \
"User only access to Chrome browser data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/com.google.Chrome'"
fi
# Check for Chromium settings
if [[ -d "${_home_dir}/.config/chromium" ]]; then
runwithfeedback \
"User only access to Chromium browser data" \
"chmod -R go-rwx '${_home_dir}/.config/chromium'"
fi
if [[ -d "${_home_dir}/.var/app/org.chromium.Chromium" ]]; then
runwithfeedback \
"User only access to Chromium browser data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/org.chromium.Chromium'"
fi
if [[ -d "${_home_dir}/.var/app/net.sourceforge.chromium-bsu" ]]; then
runwithfeedback \
"User only access to Ungoogled Chromium browser data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/net.sourceforge.chromium-bsu'"
fi
# Check for Firefox settings
if [[ -d "${_home_dir}/.mozilla" ]]; then
runwithfeedback \
"User only access to Firefox browser data" \
"chmod -R go-rwx '${_home_dir}/.mozilla'"
fi
if [[ -d "${_home_dir}/.var/app/org.mozilla.firefox" ]]; then
runwithfeedback \
"User only access to Firefox browser data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/org.mozilla.firefox'"
fi
# Check for LibreWolf settings
if [[ -d "${_home_dir}/.librewolf" ]]; then
runwithfeedback \
"User only access to LibreWolf browser data" \
"chmod -R go-rwx '${_home_dir}/.librewolf'"
fi
if [[ -d "${_home_dir}/.var/app/io.gitlab.librewolf-community" ]]; then
runwithfeedback \
"User only access to LibreWolf browser data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/io.gitlab.librewolf-community'"
fi
# Check for Opera settings
if [[ -d "${_home_dir}/.config/opera" ]]; then
runwithfeedback \
"User only access to Opera browser data" \
"chmod -R go-rwx '${_home_dir}/.config/opera'"
fi
# Check for Vivaldi settings
if [[ -d "${_home_dir}/.config/vivaldi" ]]; then
runwithfeedback \
"User only access to Vivaldi browser data" \
"chmod -R go-rwx '${_home_dir}/.config/vivaldi'"
fi
# Check for Microsoft Edge settings
if [[ -d "${_home_dir}/.config/microsoft-edge" ]]; then
runwithfeedback \
"User only access to Microsoft Edge browser data" \
"chmod -R go-rwx '${_home_dir}/.config/microsoft-edge'"
fi
if [[ -d "${_home_dir}/.config/microsoft-edge-beta" ]]; then
runwithfeedback \
"User only access to Microsoft Edge browser data" \
"chmod -R go-rwx '${_home_dir}/.config/microsoft-edge-beta'"
fi
# Check for Evolution settings
if [[ -d "${_home_dir}/.config/evolution" ]]; then
runwithfeedback \
"User only access to Evolution email data" \
"chmod -R go-rwx '${_home_dir}/.config/evolution'"
fi
if [[ -d "${_home_dir}/.var/app/org.gnome.Evolution" ]]; then
runwithfeedback \
"User only access to Evolution email data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/org.gnome.Evolution'"
fi
# Check for Geary settings
if [[ -d "${_home_dir}/.local/share/geary" ]]; then
runwithfeedback \
"User only access to Geary email data" \
"chmod -R go-rwx '${_home_dir}/.local/share/geary'"
fi
if [[ -d "${_home_dir}/.var/app/org.gnome.Geary" ]]; then
runwithfeedback \
"User only access to Geary email data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/org.gnome.Geary'"
fi
# Check for Thunderbird settings
if [[ -d "${_home_dir}/.thunderbird" ]]; then
runwithfeedback \
"User only access to Thunderbird email data" \
"chmod -R go-rwx '${_home_dir}/.thunderbird'"
fi
if [[ -d "${_home_dir}/.var/app/org.mozilla.Thunderbird" ]]; then
runwithfeedback \
"User only access to Thunderbird email data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/org.mozilla.Thunderbird'"
fi
# Check for Element settings
if [[ -d "${_home_dir}/.config/Element" ]]; then
runwithfeedback \
"User only access to Element chat data" \
"chmod -R go-rwx '${_home_dir}/.config/Element'"
fi
if [[ -d "${_home_dir}/.var/app/im.riot.Riot" ]]; then
runwithfeedback \
"User only access to Element chat data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/im.riot.Riot'"
fi
# Check for Signal settings
if [[ -d "${_home_dir}/.config/Signal" ]]; then
runwithfeedback \
"User only access to Signal chat data" \
"chmod -R go-rwx '${_home_dir}/.config/Signal'"
fi
if [[ -d "${_home_dir}/.var/app/org.signal.Signal" ]]; then
runwithfeedback \
"User only access to Signal chat data (Flatpak)" \
"chmod -R go-rwx '${_home_dir}/.var/app/org.signal.Signal'"
fi
if [[ -f "${_home_dir}/.config/birthdays.csv" ]]; then
runwithfeedback \
"User only access to birthday/anniversary reminder data" \
"chmod 600 '${_home_dir}/.config/birthdays.csv'"
elif [[ -f "${_BDAY_FILE}" ]]; then
runwithfeedback \
"User only access to birthday/anniversary reminder data (from variable)" \
"chmod 600 \"${_BDAY_FILE}\""
fi
# [OPTIONAL] Copy group permissions to other
#runwithfeedback \
# "Copy group permissions to other" \
# "chmod -R o=g ${_home_dir}"
# [OPTIONAL] Copy user permissions to group
#runwithfeedback \
# "Copy user permissions to group" \
# "chmod -R g=u ${_home_dir}"
# Set the setgid bit, so that files/folder under the directory
# will be created with the same group as <directory>
runwithfeedback \
"Set the setgid bit to inherit folder permissions" \
"chmod g+s '${_home_dir}'"
# If Access Control Lists (ACL) is installed...
# To "activate" ACL, you have to remount the drive with the "acl" option
# NOTE: Btrfs and Xfs filesystem use the ACL mount option by default
# Example /etc/fstab Entry:
# UUID=abc123def456 / ext4 defaults,acl 0 1
# Type "man acl" for more information
if hascommand --strict setfacl; then
# Use getfacl [directory] to check ACL for these directories
runwithfeedback \
"Set user default ACL entries" \
"setfacl -d -m u::rwx '${_home_dir}'"
runwithfeedback \
"Set group default ACL entries" \
"setfacl -d -m g::rx '${_home_dir}'"
runwithfeedback \
"Set others default ACL entries" \
"setfacl -d -m o::--- '${_home_dir}'"
fi
echo "Done!"
else
return 0
fi
}
# Copy over configuration settings from one account to root/default/another
# It also handles special cases for 'root' and 'default' (skel) directories
# Syntax:
# configcopy [from_user] [to_user]
# Parameters:
# from_user - The user from whom to copy the configuration files
# to_user - The user to whom to copy the configuration files
# Examples:
# configcopy alice jimbob # Copies files from user alice's home to bob's home
# configcopy default root # Copies files from /etc/skel to /root
alias copyconfig='configcopy'
function configcopy() {
# Config files to copy over
declare -a files=(
".bash_logout"
".bash_profile"
".bashrc"
".bashrc_help"
".commacd.sh"
".config/alacritty/alacritty.toml"
".config/alacritty/alacritty.yml"
".config/bat/config"
".config/btop/btop.conf"
".config/dolphinrc"
".config/fresh/config.json"
".config/git/config"
".config/git/ignore"
".config/gtkrc"
".config/helix/config.toml"
".config/katerc"
".config/kitty/kitty.conf"
".config/konsolerc"
".config/konsolesshconfig"
".config/kwriterc"
".config/lazygit/config.yml"
".config/micro/bindings.json"
".config/micro/settings.json"
".config/Notepadqq/Notepadqq.ini"
".config/nvim/init.lua"
".config/nvim/init.vim"
".config/picom/picom.conf"
".config/starship.toml"
".config/Typora/conf/conf.user.json"
".config/Typora/themes/base.user.css"
".config/Typora/themes/jeff.css"
".config/Typora/themes/night.user.css"
".config/VSCodium/User/settings.json"
".config/yakuakerc"
".config/yt-dlp/config"
".curlrc"
".dircolors"
".editorconfig"
".gitconfig"
".gitignore_global"
".gtkrc-2.0"
".inputrc"
".nanorc"
".p10k.zsh"
".profile"
".ripgreprc"
".screenrc"
".selected_editor"
".tmux.conf"
".vimrc"
".wgetrc"
".Xmodmap"
".xprofile"
".Xresources"
".zshrc"
"git-completion.bash"
"git-prompt.sh"
"gitalias.txt"
)
# Config directories to copy over
declare -a dirs=(
".config/alacritty"
".config/bashrc"
".config/bashrc/.qfc"
".config/bashrc/bashmarks"
".config/bashrc/enhancd"
".config/bashrc/fzf-tab-completion"
".config/bashrc/hstr"
".config/bat"
".config/btop"
".config/devilspie2"
".config/fish"
".config/flameshot"
".config/fontconfig"
".config/geany"
".config/ghostty"
".config/git"
".config/gtk-3.0"
".config/gtk-4.0"
".config/htop"
".config/kitty"
".config/Kvantum"
".config/lazygit"
".config/lxterminal"
".config/mc"
".config/micro/colorschemes"
".config/micro/plug"
".config/mpv"
".config/nvim"
".config/qterminal.org"
".config/ranger"
".config/rofi"
".config/sxhkd"
".config/swhkd"
".config/terminator"
".config/wezterm"
".config/yt-dlp"
".local/share/blesh/src/ble.sh"
".local/share/dolphin"
".local/share/konsole"
".local/share/kxmlgui5/dolphin"
".local/share/kxmlgui5/konsole"
".local/share/qtermwidget6/color-schemes"
".qfc"
".vim"
"bashmarks"
"ble.sh"
"enhancd"
"fzf-tab-completion"
"hstr"
)
# Declare routing variables as local to avoid polluting global scope
local dirfrom dirto owner
if [[ -z "${1}" ]] || [[ -z "${2}" ]] || [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}configcopy${RESET}: Copy config files between users"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}configcopy${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}from_user${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}to_user${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Special users:${RESET} ${BRIGHT_CYAN}root${RESET}, ${BRIGHT_CYAN}default${RESET} (/etc/skel)"
echo -e "${BRIGHT_WHITE}Available users:${RESET} ${BRIGHT_CYAN}$(command awk -F: '$3 ~ /^[0-9]{4}$/' /etc/passwd | command cut -d: -f1 | command tr '\n' ' ' | command sed 's/[[:space:]]*$//')${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}configcopy${RESET} ${BRIGHT_YELLOW}alice bob${RESET} ${BRIGHT_BLUE}# Copy from alice to bob${RESET}"
echo -e " ${BRIGHT_CYAN}configcopy${RESET} ${BRIGHT_YELLOW}default root${RESET} ${BRIGHT_BLUE}# Copy /etc/skel to /root${RESET}"
echo -e " ${BRIGHT_CYAN}configcopy${RESET} ${BRIGHT_YELLOW}jeff default${RESET} ${BRIGHT_BLUE}# Update /etc/skel${RESET}"
return
elif [ "${1}" == "${2}" ]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}The from and to user parameters cannot be the same${RESET}"
return 2
elif [ ! -d "/home/${1}" ] && [ "${1}" != "root" ] && [ "${1}" != "default" ]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}User ${BRIGHT_YELLOW}${1}${BRIGHT_CYAN} does not exist${RESET}"
return 1
elif [ ! -d "/home/${2}" ] && [ "${2}" != "root" ] && [ "${2}" != "default" ]; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}User ${BRIGHT_YELLOW}${2}${BRIGHT_CYAN} does not exist${RESET}"
return 1
elif [ "${1}" == "default" ] && [ "${2}" == "root" ]; then
dirfrom=/etc/skel/
dirto=/root/
owner=root
elif [ "${1}" == "default" ]; then
dirfrom=/etc/skel/
dirto=/home/"${2}"/
owner="${2}"
elif [ "${2}" == "default" ] && [ "${1}" == "root" ]; then
dirfrom=/root/
dirto=/etc/skel/
owner=root
elif [ "${2}" == "default" ]; then
dirfrom=/home/"${1}"/
dirto=/etc/skel/
owner=root
elif [ "${1}" == "root" ]; then
dirfrom=/root/
dirto=/home/"${2}"/
owner="${2}"
elif [ "${2}" == "root" ]; then
dirfrom=/home/"${1}"/
dirto=/root/
owner=root
else
dirfrom=/home/"${1}"/
dirto=/home/"${2}"/
owner="${2}"
fi
# Ask for confirmation
if ! ask "${BRIGHT_RED}Are you sure? ${BRIGHT_CYAN}This will overwrite configuration files in ${BRIGHT_YELLOW}${dirto%/}${RESET}" N; then
return
fi
# Check if the user can execute sudo commands (only needed if copying to another user)
if [[ "${owner}" != "${USER}" ]] && ! sudo -v 2>/dev/null; then
echo -e "${BRIGHT_RED}Error:${RESET} You do not have sufficient permissions to run this command with sudo"
return 1
fi
# Loop through and copy the files over
for file in "${files[@]}"; do
src="${dirfrom}${file}"
dest="${dirto}${file}"
dest_dir=$(dirname "${dest}")
# Check if the source file exists
if [[ -f "${src}" ]]; then
# Ensure destination directory exists
[[ ! -d "${dest_dir}" ]] && \
sudo mkdir -p "${dest_dir}" && \
sudo chown "${owner}":"${owner}" "${dest_dir}"
# Copy and change permissions
sudo cp "${src}" "${dest_dir}" && \
sudo chown "${owner}":"${owner}" "${dest}" && \
echo -e "${BRIGHT_GREEN}${RESET} Copied file: ${BRIGHT_CYAN}${file}${RESET}"
fi
done
# Loop through and copy directories over
for dir in "${dirs[@]}"; do
src="${dirfrom}${dir}"
dest="${dirto}${dir}"
dest_dir=$(dirname "${dest}")
# Check if the source directory exists
if [[ -d "${src}" ]]; then
# Ensure destination directory exists
[[ ! -d "${dest_dir}" ]] && \
sudo mkdir -p "${dest_dir}" && \
sudo chown "${owner}":"${owner}" "${dest_dir}"
# Copy directory and change ownership recursively
sudo cp -R "${src}" "${dest_dir}" && \
sudo chown -R "${owner}":"${owner}" "${dest}" && \
echo -e "${BRIGHT_GREEN}${RESET} Copied directory ${BRIGHT_CYAN}${dir}${RESET}"
fi
done
# We are done
echo -e "Owner set to: ${BRIGHT_MAGENTA}${owner}${RESET}"
echo -e "${BRIGHT_GREEN}Finished copying configuration files from ${BRIGHT_YELLOW}${dirfrom%/}${BRIGHT_GREEN} to ${BRIGHT_YELLOW}${dirto%/}${RESET}"
return
}
# Synchronize files using rsync over SSH
function sync2ssh() {
# Check the number of arguments
if [[ $# -lt 3 ]]; then
echo -e "${BRIGHT_WHITE}sync2ssh:${RESET} Synchronize files using rsync over SSH"
echo -e "${BRIGHT_WHITE}Usage:${RESET}"
echo -e " ${BRIGHT_CYAN}sync2ssh${RESET} ${BRIGHT_YELLOW}[LOCAL_DIR] ${BRIGHT_BLUE}[USER@REMOTE_HOST${BRIGHT_CYAN}[:PORT]${BRIGHT_BLUE}] ${BRIGHT_YELLOW}[REMOTE_DIR]${RESET} ${BRIGHT_GREEN}[OPTIONAL_SSH_PASSWORD]${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}sync2ssh${RESET} ${BRIGHT_YELLOW}~/local/folder/${RESET} ${BRIGHT_BLUE}user@example.com${RESET} ${BRIGHT_YELLOW}/path/on/remote${RESET}"
echo -e " ${BRIGHT_CYAN}sync2ssh${RESET} ${BRIGHT_YELLOW}~/local/folder/${RESET} ${BRIGHT_BLUE}user@example.com${RESET} ${BRIGHT_YELLOW}/path/on/remote${RESET} ${BRIGHT_GREEN}password123${RESET}"
echo -e " ${BRIGHT_CYAN}sync2ssh${RESET} ${BRIGHT_YELLOW}~/local/folder/${RESET} ${BRIGHT_BLUE}user@example.com${BRIGHT_WHITE}:${BRIGHT_CYAN}22${RESET} ${BRIGHT_YELLOW}/path/on/remote${RESET}"
return 1
fi
# Append trailing slash to directories if it's not present
local LOCAL_DIR
[[ "${1: -1}" != "/" ]] && LOCAL_DIR="${1}/" || LOCAL_DIR="$1"
local SSH_USER_HOST_PORT="$2"
local REMOTE_DIR="$3"
# Extract the port (if present)
local SSH_PORT SSH_USER_HOST
SSH_PORT="${SSH_USER_HOST_PORT##*:}"
if [[ "${SSH_PORT}" == "${SSH_USER_HOST_PORT}" ]]; then
SSH_PORT=22 # default SSH port
SSH_USER_HOST="${SSH_USER_HOST_PORT}"
else
SSH_USER_HOST="${SSH_USER_HOST_PORT%%:*}"
fi
# Build the rsync command as an array to avoid eval
# Rsync options:
# --recursive: Transfer files and directories recursively
# --links: Treat symbolic links as references to their target files/directories
# --compress: Compress files during transfer for efficiency
# --verbose: Display a detailed log of files being transferred
# --delete: Delete files on the destination that aren't present at the source
# -e: Use SSH with a 10-second connection timeout for transfers
local RSYNC_CMD=()
# Check if SSH password is provided
if [[ -n "$4" ]]; then
# Ensure sshpass is installed
if ! hascommand --strict sshpass; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}Install sshpass or use SSH keys instead${RESET}"
return 1
fi
# Use SSHPASS env var instead of -p flag (avoids password in ps output)
RSYNC_CMD=(env "SSHPASS=$4" sshpass -e)
fi
RSYNC_CMD+=(rsync --recursive --links --compress --verbose --delete
-e "ssh -o ConnectTimeout=10 -p ${SSH_PORT}"
"${LOCAL_DIR}" "${SSH_USER_HOST}:${REMOTE_DIR}")
# Execute the rsync command directly (no eval needed)
if ! "${RSYNC_CMD[@]}"; then
echo -e "${BRIGHT_RED}Error: ${BRIGHT_CYAN}rsync command failed${RESET}"
return 1
fi
echo -e "${BRIGHT_GREEN}Files synchronized successfully${RESET}"
}
# When executed, will toggle the hosts file on and off
function hoststoggle() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}hoststoggle${RESET}: Toggle hosts file between full and minimal"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}hoststoggle${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Backs up current hosts file and replaces with minimal version${RESET}"
echo -e " ${BRIGHT_BLUE}Run again to restore the backup${RESET}"
echo -e " ${BRIGHT_BLUE}Useful for testing if hosts blocking causes issues${RESET}"
return 0
fi
# Confirm we want to do this...
if ask "Are you sure you wish to modify the hosts file?" N; then
# Check if the user can execute sudo commands
if ! sudo -v; then
echo -e "${BRIGHT_RED}Error:${RESET} You do not have sufficient permissions to run this script with necessary privileges"
return 1
fi
# If the hosts backup file exists, restore it
if [ -f /etc/hosts.backup_temporary ]; then
sudo \rm -f /etc/hosts
sudo \mv /etc/hosts.backup_temporary /etc/hosts
sudo chmod 644 /etc/hosts
echo -e "${BRIGHT_BLUE}Hosts file ${BRIGHT_GREEN}restored${BRIGHT_BLUE}.${RESET}"
echo -e "(File is $(sudo cat /etc/hosts | wc -l) lines long.)"
sudo head -16 /etc/hosts
# Make a backup of the hosts file
elif [ -f /etc/hosts ]; then
sudo \mv /etc/hosts /etc/hosts.backup_temporary
# Replaces the hosts file with a generic empty hosts file
sudo bash -c 'printf "#\n# /etc/hosts: static lookup table for host names\n#\n\n127.0.0.1 localhost\n255.255.255.255 broadcasthost\n::1 localhost\n::1 ip6-localhost ip6-loopback\nfe00::0 ip6-localnet\nff00::0 ip6-mcastprefix\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\nff02::3 ip6-allhosts\n" > /etc/hosts'
sudo chmod 644 /etc/hosts
echo -e "${BRIGHT_BLUE}Hosts file ${BRIGHT_RED}disabled${BRIGHT_BLUE}.${RESET}"
# Supports hBlock - A POSIX-compliant shell script that gets a list of domains
# that serve ads, tracking scripts and malware from multiple sources and creates
# a hosts file, among other formats, that prevents your system from connecting
# to them. See https://github.com/hectorm/hblock
elif hascommand --strict hblock; then
if [ -f /etc/hosts.personal ]; then
sudo /usr/bin/hblock --header /etc/hosts.personal
else
sudo /usr/bin/hblock
fi
sudo chmod 644 /etc/hosts
echo -e "${BRIGHT_BLUE}Hosts file ${BRIGHT_YELLOW}recreated${BRIGHT_BLUE}.${RESET}"
# No hosts file was found
else
echo -e "${BRIGHT_RED}ERROR: ${BRIGHT_CYAN}Hosts file and backup not found.${RESET}"
fi
fi
}
function timeelapsed() {
# Check if at least one parameter is provided
if [[ -z "${1}" ]]; then
echo -e "Please provide a date in ${BRIGHT_CYAN}YYYY-MM-DD${RESET} format."
return 1
fi
# Parameters
local DATE_GIVEN="${1}"
local MESSAGE="${2}"
# Check if the first parameter is a valid date...
if ! date -d "${DATE_GIVEN}" >/dev/null 2>&1; then
echo -e "Invalid date format. Please provide a date in ${BRIGHT_CYAN}YYYY-MM-DD${RESET} format."
return 1
fi
# Default message if the second parameter is empty...
if [[ -z "${MESSAGE}" ]]; then
MESSAGE="${BRIGHT_CYAN}The date${RESET} ${BRIGHT_GREEN}${DATE_GIVEN}${RESET} ${BRIGHT_CYAN}was"
else
MESSAGE="${BRIGHT_CYAN}${MESSAGE}${RESET}"
fi
# Calculate total days elapsed
local TOTAL_DAYS=$(( ( $(date '+%s') - $(date -d "${DATE_GIVEN}" '+%s') ) / 86400 ))
# Use date arithmetic for accurate year/month/day breakdown
# (accounts for leap years and varying month lengths)
local GIVEN_Y GIVEN_M GIVEN_D NOW_Y NOW_M NOW_D
GIVEN_Y=$(date -d "${DATE_GIVEN}" '+%Y')
GIVEN_M=$(date -d "${DATE_GIVEN}" '+%-m')
GIVEN_D=$(date -d "${DATE_GIVEN}" '+%-d')
NOW_Y=$(date '+%Y')
NOW_M=$(date '+%-m')
NOW_D=$(date '+%-d')
local YEARS=$((NOW_Y - GIVEN_Y))
local MONTHS=$((NOW_M - GIVEN_M))
local DAYS=$((NOW_D - GIVEN_D))
# Borrow from months if days went negative
if [[ ${DAYS} -lt 0 ]]; then
((MONTHS--))
# Days in previous month (use date to get it right)
DAYS=$((DAYS + $(date -d "${NOW_Y}-${NOW_M}-01 -1 day" '+%-d')))
fi
# Borrow from years if months went negative
if [[ ${MONTHS} -lt 0 ]]; then
((YEARS--))
MONTHS=$((MONTHS + 12))
fi
# Display message with colors
echo -e "${MESSAGE} ${BRIGHT_MAGENTA}${TOTAL_DAYS} total days${BRIGHT_CYAN} ago which is ${BRIGHT_YELLOW}${YEARS} years${BRIGHT_CYAN}, ${BRIGHT_YELLOW}${MONTHS} months${BRIGHT_CYAN}, and ${BRIGHT_YELLOW}${DAYS} days${BRIGHT_CYAN}!${RESET}"
}
# ROT13 (rotate by 13 positions) is a simple letter substitution cipher that
# replaces each letter with the letter 13 positions after it in the alphabet
# NOTE: Since the English alphabet has 26 letters, ROT13 acts as its own inverse
# applying it twice gets you back to the original text.
function rot13() {
if [[ $# -eq 0 ]]; then
tr '[a-m][n-z][A-M][N-Z]' '[n-z][a-m][N-Z][A-M]'
else
echo "$*" | tr '[a-m][n-z][A-M][N-Z]' '[n-z][a-m][N-Z][A-M]'
fi
}
# Command to spell check command line input
# Example: spellcheck "definately"
# Example: spellcheck "I sincerly reccomend programing"
spellcheck() {
# Check if any spell checker is installed
if type -P aspell >/dev/null; then
local SPELLCHECKER="aspell -a"
elif type -P hunspell >/dev/null; then
local SPELLCHECKER="hunspell"
else
echo -e "${BRIGHT_RED}No spell checker found: ${BRIGHT_CYAN}Please install aspell or hunspell${RESET}"
return 1
fi
# Check if any arguments were provided
if [[ $# -eq 0 ]]; then
echo -e "${BRIGHT_WHITE}Usage: ${BRIGHT_GREEN}spellcheck ${BRIGHT_YELLOW}[text to check]${RESET}"
return 1
fi
# Combine all arguments into a single string
local TEXT="$*"
# Run spell checker and process the output
local LINE
local WORD
local SUGGESTIONS
echo "${TEXT}" | ${SPELLCHECKER} | while read -r LINE; do
# Skip the version header line
if [[ ${LINE} == @\(#\)* || ${LINE} == Hunspell* ]]; then
continue
# Skip lines that just show '*' (correctly spelled words)
elif [[ ${LINE} == \** && ${#LINE} -eq 1 ]]; then
continue
# Process spell check suggestions
elif [[ ${LINE} == \&* ]]; then
# Extract the misspelled word and suggestions
WORD=$(echo "${LINE}" | cut -d' ' -f2)
# Get everything after the ':' or last number for suggestions
if [[ ${SPELLCHECKER} == "hunspell" ]]; then
# Hunspell format: "& word num offset: suggestion1, suggestion2"
SUGGESTIONS=$(echo "${LINE}" | cut -d':' -f2- | sed 's/^[ ]*//')
# If no colon, get everything after the last number
if [[ -z "${SUGGESTIONS}" ]]; then
SUGGESTIONS=$(echo "${LINE}" | sed 's/^& [^ ]* [0-9]* [0-9]* //')
fi
else
# Aspell format
SUGGESTIONS=$(echo "${LINE}" | cut -d':' -f2- | sed 's/^[ ]*//')
fi
echo -e "${BRIGHT_RED}X Misspelled: ${BRIGHT_CYAN}${WORD}${RESET}"
echo -e "${BRIGHT_GREEN}✓ Suggestions:${RESET} ${BRIGHT_YELLOW}${SUGGESTIONS}${RESET}"
echo
fi
done
}
# Transform text using common string formatting and case operations
function formattext() {
local TEXT=""
local PARAMS=()
# Internal function to show help message
# Named with function prefix to avoid polluting global scope
# (bash doesn't scope nested functions — they become global)
function _formattext_show_help() {
echo -e "${BRIGHT_CYAN}formattext${RESET}: Format text with various transformations"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}formattext${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}options${BRIGHT_MAGENTA}]${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}text${BRIGHT_MAGENTA}>${RESET}"
echo -e " command | ${BRIGHT_CYAN}formattext${RESET} ${BRIGHT_MAGENTA}[${BRIGHT_GREEN}options${BRIGHT_MAGENTA}]${RESET}"
echo -e "${BRIGHT_WHITE}Note:${RESET} Text is processed in order of options specified"
echo -e "${BRIGHT_WHITE}Options:${RESET}"
echo -e " ${BRIGHT_GREEN}-c${RESET}, ${BRIGHT_GREEN}--capitalize${RESET} Capitalize every word"
echo -e " ${BRIGHT_GREEN}-C${RESET}, ${BRIGHT_GREEN}--titlecase${RESET} Convert text to title case"
echo -e " ${BRIGHT_GREEN}-d${RESET}, ${BRIGHT_GREEN}--dashes-to-spaces${RESET} Replace dashes with spaces"
echo -e " ${BRIGHT_GREEN}-f${RESET}, ${BRIGHT_GREEN}--filename-friendly${RESET} Replace characters difficult for filenames"
echo -e " ${BRIGHT_GREEN}-i${RESET}, ${BRIGHT_GREEN}--input${RESET} Specify input text (-i=text or -i text)"
echo -e " ${BRIGHT_GREEN}-l${RESET}, ${BRIGHT_GREEN}--lowercase${RESET} Convert text to lowercase"
echo -e " ${BRIGHT_GREEN}-q${RESET}, ${BRIGHT_GREEN}--smart-quotes${RESET} Convert quotes to smart quotes"
echo -e " ${BRIGHT_GREEN}-Q${RESET}, ${BRIGHT_GREEN}--unsmart-quotes${RESET} Smart quotes to regular quotes"
echo -e " ${BRIGHT_GREEN}-r${RESET}, ${BRIGHT_GREEN}--remove-duplicate-spaces${RESET} Remove duplicate spaces"
echo -e " ${BRIGHT_GREEN}-s${RESET}, ${BRIGHT_GREEN}--spaces-to-dashes${RESET} Replace spaces with dashes"
echo -e " ${BRIGHT_GREEN}-S${RESET}, ${BRIGHT_GREEN}--spaces-to-underscores${RESET} Replace spaces with underscores"
echo -e " ${BRIGHT_GREEN}-t${RESET}, ${BRIGHT_GREEN}--trim${RESET} Trim leading and trailing whitespace"
echo -e " ${BRIGHT_GREEN}-T${RESET}, ${BRIGHT_GREEN}--tabs-to-spaces${RESET} Replace tabs with spaces"
echo -e " ${BRIGHT_GREEN}-u${RESET}, ${BRIGHT_GREEN}--uppercase${RESET} Convert text to uppercase"
echo -e " ${BRIGHT_GREEN}-U${RESET}, ${BRIGHT_GREEN}--underscores-to-spaces${RESET} Replace underscores with spaces"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}formattext${RESET} ${BRIGHT_GREEN}--titlecase${RESET} ${BRIGHT_YELLOW}'hello world'${RESET} ${BRIGHT_BLUE}# Returns: Hello World${RESET}"
echo -e " ${BRIGHT_CYAN}formattext${RESET} ${BRIGHT_GREEN}-r -C${RESET} ${BRIGHT_YELLOW}'hello world'${RESET} ${BRIGHT_BLUE}# Combine options${RESET}"
echo -e " ${BRIGHT_YELLOW}echo 'text'${RESET} | ${BRIGHT_CYAN}formattext${RESET} ${BRIGHT_GREEN}-u${RESET} ${BRIGHT_BLUE}# Uppercase from pipe${RESET}"
return 0
}
# Check for piped input
local LINE
if [[ ! -t 0 ]]; then
# Read from stdin (pipe) line by line
while IFS= read -r LINE; do
if [[ -z "${TEXT}" ]]; then
TEXT="${LINE}"
else
TEXT="${TEXT}"$'\n'"${LINE}"
fi
done
# Else check to make sure we have parameters
elif [[ $# -eq 0 ]]; then
# Show help if no parameters provided
_formattext_show_help
return 0
fi
# Parse command line options
while [[ $# -gt 0 ]]; do
case "${1}" in
-h|--help)
_formattext_show_help
return 0
;;
-i=*|--input=*)
if [[ -z "${TEXT}" ]]; then
TEXT="${1#*=}" # If text is empty, set it
else
TEXT+=" ${1#*=}" # Otherwise append with space
fi
shift
;;
-i|--input)
if [[ -n "${2}" ]] && [[ "${2}" != -* ]]; then
if [[ -z "${TEXT}" ]]; then
TEXT="${2}" # If text is empty, set it
else
TEXT+=" ${2}" # Otherwise append with space
fi
shift 2
else
echo "Error: --input requires an argument" >&2
return 1
fi
;;
-*)
PARAMS+=("${1}")
shift
;;
*)
if [[ -z "${TEXT}" ]]; then
TEXT="${1}" # First word without space
else
TEXT+=" ${1}" # Subsequent words with space
fi
shift
;;
esac
done
# Check if no text is provided
if [[ -z "${TEXT}" ]]; then
return 1
fi
# Apply transformations in the specified order
local PARAM
local RESULT
local SMALL_WORDS
local CAPITALIZE_NEXT
local WORD
local INDEX
local CHAR
local LEFT_DOUBLE_QUOTE
local RIGHT_DOUBLE_QUOTE
local LEFT_SINGLE_QUOTE
local RIGHT_SINGLE_QUOTE
for PARAM in "${PARAMS[@]}"; do
case "${PARAM}" in
-c|--capitalize)
TEXT=$(IFS=' ' read -ra words <<< "${TEXT}" && (IFS=' '; echo "${words[@]^}"))
;;
-C|--titlecase)
RESULT=""
SMALL_WORDS="a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|vs?\\.?|via"
CAPITALIZE_NEXT=true
for WORD in ${TEXT}; do
if ${CAPITALIZE_NEXT} || ! [[ ${WORD} =~ ^(${SMALL_WORDS})$ ]]; then
RESULT+="${WORD^} "
CAPITALIZE_NEXT=false
else
RESULT+="${WORD,,} "
fi
# Check if we should capitalize the next word
if [[ ${WORD: -1} =~ [:.?!-] ]]; then
CAPITALIZE_NEXT=true
fi
done
TEXT="${RESULT% }"
;;
-d|--dashes-to-spaces)
TEXT="${TEXT//-/ }"
;;
-f|--filename-friendly)
RESULT=""
for ((INDEX=0; INDEX<${#TEXT}; INDEX++)); do
CHAR="${TEXT:INDEX:1}"
case "${CHAR}" in
"<") RESULT+="" ;;
">") RESULT+="" ;;
":") RESULT+="" ;;
"\"") RESULT+="" ;;
"/") RESULT+="" ;;
"\\") RESULT+="" ;;
"|") RESULT+="" ;;
"?") RESULT+="" ;;
"*") RESULT+="" ;;
[[:cntrl:]]) RESULT+=" " ;;
*) RESULT+="${CHAR}" ;;
esac
done
TEXT="${RESULT}"
;;
-l|--lowercase)
TEXT="${TEXT,,}"
;;
-q|--smart-quotes)
if ! hascommand sed; then
echo -e "${BRIGHT_RED}Error:${RESET} ${BRIGHT_CYAN}Smart quotes requires ${BRIGHT_YELLOW}sed${RESET}"
return 1
fi
# Get the actual Unicode characters for smart quotes
LEFT_DOUBLE_QUOTE=$(echo -ne '\u201C') # Left double quote "
RIGHT_DOUBLE_QUOTE=$(echo -ne '\u201D') # Right double quote "
LEFT_SINGLE_QUOTE=$(echo -ne '\u2018') # Left single quote '
RIGHT_SINGLE_QUOTE=$(echo -ne '\u2019') # Right single quote '
# First convert doubles
TEXT=$(echo "${TEXT}" | sed -e "s/\"/${LEFT_DOUBLE_QUOTE}/g") # Replace all " with opening "
TEXT=$(echo "${TEXT}" | sed -e "s/${LEFT_DOUBLE_QUOTE}\([^${RIGHT_DOUBLE_QUOTE}]*\)$/${LEFT_DOUBLE_QUOTE}\1/g") # Fix lone opening quotes at end
TEXT=$(echo "${TEXT}" | sed -e "s/${LEFT_DOUBLE_QUOTE}\([^${RIGHT_DOUBLE_QUOTE}]*\)${LEFT_DOUBLE_QUOTE}/${LEFT_DOUBLE_QUOTE}\1${RIGHT_DOUBLE_QUOTE}/g") # Fix pairs
# Then convert singles
TEXT=$(echo "${TEXT}" | sed -e "s/'/${LEFT_SINGLE_QUOTE}/g") # Replace all ' with opening '
TEXT=$(echo "${TEXT}" | sed -e "s/${LEFT_SINGLE_QUOTE}\([^${RIGHT_SINGLE_QUOTE}]*\)$/${LEFT_SINGLE_QUOTE}\1/g") # Fix lone opening quotes at end
TEXT=$(echo "${TEXT}" | sed -e "s/${LEFT_SINGLE_QUOTE}\([^${RIGHT_SINGLE_QUOTE}]*\)${LEFT_SINGLE_QUOTE}/${LEFT_SINGLE_QUOTE}\1${RIGHT_SINGLE_QUOTE}/g") # Fix pairs
;;
-Q|--unsmart-quotes)
# Convert from smart quotes back to regular
TEXT="${TEXT//'/\'}"
TEXT="${TEXT//'/\'}"
TEXT="${TEXT//\"/\"}"
TEXT="${TEXT//\"/\"}"
;;
-r|--remove-duplicate-spaces)
while [[ "${TEXT}" =~ " " ]]; do
TEXT="${TEXT// / }"
done
;;
-s|--spaces-to-dashes)
# First remove duplicate spaces, then convert to dashes
while [[ "${TEXT}" =~ " " ]]; do
TEXT="${TEXT// / }"
done
TEXT="${TEXT// /-}"
;;
-S|--spaces-to-underscores)
TEXT="${TEXT// /_}"
;;
-t|--trim)
TEXT="${TEXT#"${TEXT%%[![:space:]]*}"}" # trim leading
TEXT="${TEXT%"${TEXT##*[![:space:]]}"}" # trim trailing
;;
-T|--tabs-to-spaces)
TEXT="${TEXT// / }"
;;
-u|--uppercase)
TEXT="${TEXT^^}"
;;
-U|--underscores-to-spaces)
TEXT="${TEXT//_/ }"
;;
esac
done
echo "${TEXT}"
}
# Convenience aliases for common text formatting operations
# These support both direct input: lowercase "HELLO"
# and piped input: echo "HELLO" | lowercase
# NOTE: Aliases only work in interactive shells, not in scripts
alias trim='formattext --trim'
alias fixspaces='formattext --tabs-to-spaces --underscores-to-spaces --remove-duplicate-spaces'
alias lowercase='formattext --lowercase'
alias uppercase='formattext --uppercase'
alias capitalize='formattext --capitalize'
alias titlecase='formattext --titlecase'
alias smartquotes='formattext --smart-quotes'
alias unsmartquotes='formattext --unsmart-quotes'
# Reverse the text in a string
function reversetext() {
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]] || { [[ $# -eq 0 ]] && [[ -t 0 ]]; }; then
echo -e "${BRIGHT_CYAN}reversetext${RESET}: Reverse the characters in text"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}reversetext${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}text${BRIGHT_MAGENTA}>${RESET}"
echo -e " command | ${BRIGHT_CYAN}reversetext${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}reversetext${RESET} ${BRIGHT_YELLOW}\"hello world\"${RESET} ${BRIGHT_BLUE}# Returns: dlrow olleh${RESET}"
echo -e " ${BRIGHT_YELLOW}echo \"racecar\"${RESET} | ${BRIGHT_CYAN}reversetext${RESET} ${BRIGHT_BLUE}# Returns: racecar${RESET}"
echo -e " ${BRIGHT_YELLOW}cat file.txt${RESET} | ${BRIGHT_CYAN}reversetext${RESET} ${BRIGHT_BLUE}# Reverse file contents${RESET}"
return 2
fi
local TEXT=""
local REVERSED=""
local LINE
local INDEX
# Check for piped input
if [[ ! -t 0 ]]; then
# Read from stdin (pipe) line by line
while IFS= read -r LINE; do
if [[ -z "${TEXT}" ]]; then
TEXT="${LINE}"
else
TEXT="${TEXT}"$'\n'"${LINE}"
fi
done
else
TEXT="$*"
fi
# Iterate over each character in the string in reverse order
for (( INDEX=${#TEXT}-1; INDEX>=0; INDEX-- )); do
REVERSED+="${TEXT:INDEX:1}"
done
echo "${REVERSED}"
}
# Count the number of characters in a string
function countchars() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}countchars${RESET}: Count characters in a string"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}countchars${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}text${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}countchars${RESET} ${BRIGHT_YELLOW}'hello world'${RESET} ${BRIGHT_BLUE}# Returns: 11${RESET}"
return 0
fi
# Use wc -m to count the number of characters (-n prevents trailing newline)
echo -n "$1" | wc -m
}
# Count the number of words in a string
function countwords() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}countwords${RESET}: Count words in a string"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}countwords${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}text${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}countwords${RESET} ${BRIGHT_YELLOW}'hello world'${RESET} ${BRIGHT_BLUE}# Returns: 2${RESET}"
return 0
fi
# Use wc -w to count the number of words (use $* to count all arguments)
echo "$*" | wc -w
}
# Check if a string starts with a specific substring
function startswith() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}startswith${RESET}: Check if string starts with a prefix"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}startswith${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}string${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}prefix${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Returns:${RESET} 0 if true, 1 if false"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}startswith${RESET} ${BRIGHT_YELLOW}'hello' 'he'${RESET} && echo yes ${BRIGHT_BLUE}# Yes${RESET}"
echo -e " ${BRIGHT_CYAN}startswith${RESET} ${BRIGHT_YELLOW}'hello' 'lo'${RESET} || echo no ${BRIGHT_BLUE}# No${RESET}"
return 0
fi
# Use parameter expansion to check if the string starts with the substring
if [[ "$1" == "$2"* ]]; then
return 0
else
return 1
fi
}
# Check if a string ends with a specific substring
function endswith() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}endswith${RESET}: Check if string ends with a suffix"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}endswith${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}string${BRIGHT_MAGENTA}>${RESET} ${BRIGHT_MAGENTA}<${BRIGHT_YELLOW}suffix${BRIGHT_MAGENTA}>${RESET}"
echo -e "${BRIGHT_WHITE}Returns:${RESET} 0 if true, 1 if false"
echo -e "${BRIGHT_WHITE}Examples:${RESET}"
echo -e " ${BRIGHT_CYAN}endswith${RESET} ${BRIGHT_YELLOW}'hello' 'lo'${RESET} && echo yes ${BRIGHT_BLUE}# Yes${RESET}"
echo -e " ${BRIGHT_CYAN}endswith${RESET} ${BRIGHT_YELLOW}'hello' 'he'${RESET} || echo no ${BRIGHT_BLUE}# No${RESET}"
return 0
fi
# Use parameter expansion to check if the string ends with the substring
if [[ "$1" == *"$2" ]]; then
return 0
else
return 1
fi
}
# Fix specified filenames by trimming whitespace, replacing multiple spaces,
# tabs, and underscores with a single space, and converting to title case
function fixfilename() {
local FILE
local DIRECTORY
local FILENAME
local NEW_FILENAME
local NEW_FILEPATH
for FILE in "$@"; do
if [[ -f "${FILE}" ]]; then
DIRECTORY=$(dirname "${FILE}")
FILENAME=$(basename "${FILE}")
# Use formattext with multiple transformations:
NEW_FILENAME=$(formattext \
--tabs-to-spaces \
--underscores-to-spaces \
--titlecase \
--filename-friendly \
--remove-duplicate-spaces \
--trim \
--input="${FILENAME##-}")
if [[ "${FILENAME}" != "${NEW_FILENAME}" ]]; then
NEW_FILEPATH="${DIRECTORY}/${NEW_FILENAME}"
echo -e "${BRIGHT_CYAN}Rename: ${BRIGHT_YELLOW}${FILENAME}${RESET}"
echo -e "${BRIGHT_CYAN} To: ${BRIGHT_YELLOW}${NEW_FILENAME}${RESET}"
if ask "${BRIGHT_WHITE}Continue?${RESET}" Y; then
mv -i "${FILE}" "${NEW_FILEPATH}"
echo -e "${BRIGHT_GREEN}Renamed to ${BRIGHT_YELLOW}${NEW_FILENAME}${RESET}"
else
echo -e "${BRIGHT_CYAN}Skipped renaming ${BRIGHT_YELLOW}${FILENAME}${RESET}"
fi
else
echo -e "${BRIGHT_MAGENTA}No changes needed for ${BRIGHT_YELLOW}${FILENAME}${RESET}"
fi
else
echo -e "${BRIGHT_RED}File not found: ${BRIGHT_YELLOW}${FILE}${RESET}"
fi
done
}
#######################################################
# Show the initial information HUD on initial Bash load
#######################################################
# If we are NOT root or in a virtual terminal console or TMUX or Git Bash...
if [[ $EUID -ne 0 ]] && \
[[ ! "$(tty)" =~ /dev/tty ]] && \
[[ ! "${TERM}" =~ screen ]] && \
[[ -z "${TMUX}" ]] && \
[[ "${_KERNEL_NAME}" != "MINGW" ]] && \
[[ "${_KERNEL_NAME}" != "CYGWI" ]] && \
[[ -z "${INSIDE_EMACS}" ]] && \
[[ -z "${VIM_TERMINAL}" ]] && \
[[ ! "${TERM}" =~ vim ]] && \
[[ ! "${TERM}" =~ nvim ]] && \
[[ "${TERM_PROGRAM}" != "vscode" ]] && \
[[ "${TERMINAL_EMULATOR}" != "JetBrains-JediTerm" ]] && \
[[ "${TERM_PROGRAM}" != "Sublime_Terminal" ]] && \
[[ "${TERM_PROGRAM}" != "atom" ]] && \
[[ $_SKIP_SYSTEM_INFO = false ]]; then
if hascommand --strict espifetch; then
espifetch
elif hascommand --strict hyfetch; then
hyfetch
# Link: https://github.com/LinusDierheimer/fastfetch
elif hascommand --strict fastfetch; then
fastfetch
# Link: https://ostechnix.com/neofetch-display-linux-systems-information/
elif hascommand --strict neofetch; then
neofetch
printf '\033[A\033[K' # Move the cursor up one line
# Link: https://github.com/KittyKatt/screenFetch
elif hascommand --strict screenfetch; then
screenfetch
# Link: https://github.com/deater/linux_logo
elif hascommand --strict linuxlogo; then
linuxlogo
# Link: https://lclarkmichalek.github.io/archey3/
elif hascommand --strict archey; then
archey
# Link: https://github.com/dylanaraps/pfetch
elif [[ -f "${HOME}/pfetch.sh" ]]; then
"${HOME}/pfetch.sh"
elif hascommand --strict pfetch.sh; then
pfetch.sh
elif hascommand --strict pfetch; then
pfetch
fi
fi
#######################################################
# Show text or ASCII on initial Bash load
# Create ASCII: jp2a --color ~/input_image.jpg > ~/.bash_motd_shown
# Test: cat ~/.bash_motd_shown
#######################################################
# If the file exists and we are NOT root...
if [[ -f "${HOME}/.bash_motd_shown" ]] && [[ $EUID -ne 0 ]]; then
# Show the ASCII text or image
cat "${HOME}/.bash_motd_shown"
fi
#######################################################
# Show upgrade information on new terminal windows
# Note: This is disabled by default because it takes several seconds to run
#######################################################
if [[ $_SKIP_UPGRADE_NOTIFY = false ]]; then
# If this is an Arch based distrobution...
# Add to your crontab (this runs every 3 hours): 0 */3 * * * /usr/bin/pacman -Sy
if hascommand --strict pacman && hascommand --strict paccache; then
# Show if there are updates available
_PACKAGE_UPDATE_COUNT=$(pacman -Q --upgrades | wc -l)
if [[ "$_PACKAGE_UPDATE_COUNT" -gt "0" ]]; then
echo -e "${BRIGHT_YELLOW}*${RESET} ${BRIGHT_GREEN}There are${RESET} ${BRIGHT_WHITE}"$_PACKAGE_UPDATE_COUNT"${RESET} ${BRIGHT_GREEN}avaliable program updates${RESET}"
echo -e "${BRIGHT_BLACK}*${RESET} ${BRIGHT_CYAN}Type${RESET} ${BRIGHT_MAGENTA}pacman -Qu${RESET} ${BRIGHT_CYAN}for more information${RESET}"
echo
fi
# If this is an Ubuntu based distro...
# Add to your crontab (this runs every 3 hours): 0 */3 * * * /usr/bin/apt update
elif [[ -x "/usr/lib/update-notifier/apt-check" ]]; then
# Show if there are updates available
IFS=';' read _PACKAGE_UPDATE_COUNT _PACKAGE_SECURITY_UPDATE_COUNT < <(/usr/lib/update-notifier/apt-check 2>&1)
if [[ "$_PACKAGE_UPDATE_COUNT" -gt "0" ]]; then
echo -e "${BRIGHT_YELLOW}*${RESET} ${BRIGHT_GREEN}There are${RESET} ${BRIGHT_WHITE}"$_PACKAGE_UPDATE_COUNT"${RESET} ${BRIGHT_GREEN}avaliable program updates${RESET}"
fi
if [[ "$_PACKAGE_SECURITY_UPDATE_COUNT" -gt "0" ]]; then
echo -e "${BRIGHT_BLACK}*${RESET} ${BRIGHT_CYAN}There are${RESET} ${BRIGHT_MAGENTA}"$_PACKAGE_SECURITY_UPDATE_COUNT"${RESET} ${BRIGHT_CYAN}security updates.${RESET}"
fi
if [[ "$_PACKAGE_UPDATE_COUNT" -gt "0" ]] || [[ "$_PACKAGE_SECURITY_UPDATE_COUNT" -gt "0" ]]; then
echo
fi
fi
fi
################################################################################
# Birthday/anniversary reminder that shows a message in your teminal
# Reads the birthday CSV file: ~/.config/birthdays.csv
# The format is (year is optional and can be left blank):
# Month,Day,Year,"Message"
# Jan,1,2000,"This is a message!"
#
# Toilet application is a required dependency
# Arch/Manjaro: sudo pacman -S toilet
# Ubuntu/Debian: sudo apt-get install toilet
################################################################################
# Make an alias to edit the birthday csv file
if [[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/birthdays.csv" ]]; then
alias ebd="edit ~/.config/birthdays.csv"
elif [[ -f "${_BDAY_FILE}" ]]; then
alias ebd="edit ${_BDAY_FILE}"
fi
# Show a birthday or anniversary reminder
function birthday() {
# Help text
if [[ "${1}" == "--help" ]] || [[ "${1}" == "-h" ]]; then
echo -e "${BRIGHT_CYAN}birthday${RESET}: Display birthday reminders from CSV file"
echo -e "${BRIGHT_WHITE}Usage:${RESET} ${BRIGHT_CYAN}birthday${RESET}"
echo -e "${BRIGHT_WHITE}Description:${RESET}"
echo -e " ${BRIGHT_BLUE}Reads from ~/.config/birthdays.csv${RESET}"
echo -e " ${BRIGHT_BLUE}Format: Month,Day,Year,\"Message\"${RESET}"
echo -e " ${BRIGHT_BLUE}Example: Jan,15,1990,\"Happy Birthday John!\"${RESET}"
return 0
fi
# Use extended globbing for more advanced pattern matching
# This is also necessary for programmable completion
shopt -s extglob
# If the birthday CSV file exists and toilet is installed...
if [[ $_SKIP_BDAY_REMINDER = false ]] && [[ -f "${_BDAY_FILE}" ]]; then
# Loop through the birthday CSV file: ~/.config/birthdays.csv
# The first line is ignored (header) and the format is:
# Month,Day,Year,"Message"
# Jan,1,1985,"This is a message!"
while IFS=, read -r _BDAY_MONTH _BDAY_DAY _BDAY_YEAR _BDAY_MESSAGE; do
# Compare the month (case insensitive) and the day (remove leading zeros)
if [[ "$(date +%^b)" = "${_BDAY_MONTH^^}" ]] && [[ "$(date +%-d)" = "${_BDAY_DAY##+(0)}" ]]; then
# Remove the surrounding quotes from the message
_BDAY_MESSAGE="${_BDAY_MESSAGE%\"}"
_BDAY_MESSAGE="${_BDAY_MESSAGE#\"}"
# Show the message using toilet and the future font
if hascommand --strict toilet; then
toilet --font="${_BDAY_FONT}" --termwidth --gay "${_BDAY_MESSAGE}"
elif hascommand --strict lolcat; then
echo "${_BDAY_MESSAGE}" | lolcat --spread=0.4
else
echo -e "${BRIGHT_CYAN}${_BDAY_MESSAGE}${RESET}"
fi
# If the year is a number and not blank, show the age
# Use date subtraction for accuracy (accounts for leap years)
if [ -n "${_BDAY_YEAR}" ] && [ "${_BDAY_YEAR}" -eq "${_BDAY_YEAR}" ] 2>/dev/null; then
echo -e "${BRIGHT_YELLOW}$(( $(date +%Y) - _BDAY_YEAR ))${RESET} Years"
fi
#echo ""
fi
# Strip off the first line in the CSV file
done < <(tail -n +2 "${_BDAY_FILE}")
fi
}
# If we aren't supposed to skip this...
if [[ $_SKIP_BDAY_REMINDER = false ]]; then
# Call the birthday function
birthday
fi
#######################################################
# Show the calendar when Bash starts
#######################################################
if [[ $_SHOW_BASH_CALENDAR = true ]]; then
if hascommand --strict gcal; then
gcal -H '\033[34m:\033[0m:\033[32m:\033[0m' -q "${_GCAL_COUNTRY_CODE}"
else
cal -3
fi
fi
#######################################################
# bashmarks Directory Bookmarks
# Link: https://github.com/huyng/bashmarks
# Install: git clone git://github.com/huyng/bashmarks.git
# s <bookmark_name> - Saves the current directory as "bookmark_name"
# g <bookmark_name> - Goes (cd) to the directory associated with "bookmark_name"
# p <bookmark_name> - Prints the directory associated with "bookmark_name"
# d <bookmark_name> - Deletes the bookmark
# l - Lists all available bookmarks
#######################################################
# If bashmarks is installed, load it
if [[ -f "${HOME}/bashmarks/bashmarks.sh" ]]; then
builtin source "${HOME}/bashmarks/bashmarks.sh"
elif [[ -f "${BASHRC_INSTALL_DIR}/bashmarks/bashmarks.sh" ]]; then
builtin source "${BASHRC_INSTALL_DIR}/bashmarks/bashmarks.sh"
elif [[ -f "${HOME}/.local/bin/bashmarks.sh" ]]; then
builtin source "${HOME}/.local/bin/bashmarks.sh"
elif [[ -f /usr/share/bashmarks/bashmarks.sh ]]; then
builtin source /usr/share/bashmarks/bashmarks.sh
fi
#######################################################
# Zoxide is a smarter cd command (inspired by z and autojump)
# Link: https://github.com/ajeetdsouza/zoxide
# Install: curl -sS https://webinstall.dev/zoxide | bash
#######################################################
# If Zoxide is installed, run it's initialization
if hascommand --strict zoxide; then
eval "$(zoxide init bash)"
fi
#######################################################
# commacd Improved cd
# Link: https://github.com/shyiko/commacd
# Install: curl -sSL https://github.com/shyiko/commacd/raw/v1.0.0/commacd.sh -o ~/.commacd.sh
#######################################################
# If commacd is installed
if [[ -f "${HOME}/.commacd.sh" ]]; then
builtin source "${HOME}/.commacd.sh"
elif [[ -f /usr/share/commacd/commacd.bash ]]; then
builtin source /usr/share/commacd/commacd.bash
elif [[ -f /usr/share/commacd/commacd.sh ]]; then
builtin source /usr/share/commacd/commacd.sh
fi
#######################################################
# Improve navigation and searching your command history
#######################################################
# Search command line history
alias h='history | grep'
# Enable incremental history search with up/down arrows and other Readline features
# Learn more about this here: http://codeinthehole.com/writing/the-most-important-command-line-tip-incremental-history-searching-with-inputrc/
bind '"\033[A": history-search-backward'
bind '"\033[B": history-search-forward'
# Atuin - Magical shell history stored in a SQLite database
# (records command, directory, exit code, duration, hostname, and timestamp with full-text, fuzzy, or prefix search)
# Link: https://github.com/atuinsh/atuin
# Docs: https://docs.atuin.sh
# Install: sudo pacman -S atuin bash-preexec
if hascommand --strict atuin; then
# Alias hh for atuin interactive search
alias hh='atuin search -i'
# Initialize atuin (keep up/down arrows for history-search-backward/forward)
eval "$(atuin init bash --disable-up-arrow)"
# HSTR Easily navigate and search your command history and favorites
# (has favorites, syncs across shells, does not require an extra database, but does not show the time since a command)
# Link: https://github.com/dvorka/hstr
# Manual: man hstr
elif hascommand --strict hstr; then
# Alias hh for hstr
alias hh='hstr'
# Get more colors
export HSTR_CONFIG=hicolor
# Bind hstr to CTRL+r (for Vi mode check doc)
bind '"\C-r": "\C-a hstr -- \C-j"'
# Bind 'kill last command' to CTRL+x k
bind '"\C-xk": "\C-a hstr -k \C-j"'
# McFly - fly through your shell history using a small neural network
# (shows the time since the command, but does not have favorites, and has issues syncing history across multiple shells)
# NOTE: You can type % to match any number of characters when searching
# Link: https://github.com/cantino/mcfly
# Install: curl -LSfs https://raw.githubusercontent.com/cantino/mcfly/master/ci/install.sh | sh -s -- --git cantino/mcfly
elif hascommand --strict mcfly; then
# Initialize McFly
eval "$(mcfly init bash)"
# Alias hh for McFly
alias hh='mcfly search'
# Enable fuzzy searching
export MCFLY_FUZZY=2
# Change the maximum number of results shown (default: 10)
export MCFLY_RESULTS=60
# To swap the color scheme for use in a light terminal, change this
export MCFLY_LIGHT=FALSE
# Rich Enhanced Shell History (resh) Context-based replacement/enhancement for zsh and bash shell history
# (shows more information, has raw mode, but uses own database, does not have favorites, can't delete history)
# Link: https://github.com/curusarn/resh
# Install: (git clone https://github.com/curusarn/resh.git && cd resh && scripts/rawinstall.sh)
# Update: reshctl update
# WARNING: Install automatically adds lines to the end of the ~/.bashrc file
elif [[ -f ~/.resh/shellrc ]]; then
# Source the scripts
builtin source ~/.resh/shellrc
[[ -f ~/.bash-preexec.sh ]] && builtin source ~/.bash-preexec.sh
# Bind 'kill last command' to CTRL+x k
bind '"\C-xk": "\C-a hstr -k \C-j"'
# Alias hh for resh
alias hh='resh'
fi
#######################################################
# qfc Command Line File Completion (CTRL+F to list files)
# Link: https://github.com/pindexis/qfc
# Install: git clone https://github.com/pindexis/qfc ${HOME}/.qfc
#######################################################
# If qfc is installed, run it's initiation script
# CTRL+f will pop up to select directories or files
# CTRL+/ to cd into directory using qfc
if [[ -f "${HOME}/.qfc/bin/qfc.sh" ]]; then
builtin source "${HOME}/.qfc/bin/qfc.sh"
qfc_quick_command 'cd' '\C-_' 'cd "$0"'
qfc_quick_command 'edit' '\C-e' 'edit $0'
elif [[ -f "${BASHRC_INSTALL_DIR}/.qfc/bin/qfc.sh" ]]; then
builtin source "${BASHRC_INSTALL_DIR}/.qfc/bin/qfc.sh"
qfc_quick_command 'cd' '\C-_' 'cd "$0"'
qfc_quick_command 'edit' '\C-e' 'edit $0'
elif [[ -f /usr/share/qfc/qfc.sh ]]; then
builtin source /usr/share/qfc/qfc.sh
qfc_quick_command 'cd' '\C-_' 'cd "$0"'
qfc_quick_command 'edit' '\C-e' 'edit $0'
fi
#######################################################
# Settings and Exports
#######################################################
# Make sure 256 color terminals are enabled
# export TERM=xterm-256color
# Linux tries very hard to set it to a sane value depending on things
# like which terminal you are actually using and how you are connected
# You can override a particular value which the login process
# often chooses but which is not to your liking
case $TERM in "") TERM=xterm-256color;; esac
# For use with LS_COLORS
export use_color=true
# Tell NCURSES to use UTF-8 encoding
export NCURSES_NO_UTF8_ACS=1
if hascommand --strict moar; then
# Use moar
# Link: https://github.com/walles/moar
export PAGER='moar'
export MANPAGER='moar'
alias less='moar'
alias les='moar -no-linenumbers'
elif [[ -n "$LESSOPEN" ]]; then
# Use less over most if it has syntax highlighting
export PAGER='less'
export MANPAGER='less'
alias les='less -n'
elif hascommand --strict most; then
# Use most
# Link: https://www.jedsoft.org/most/
export PAGER='most'
export MANPAGER='most'
alias less='most'
alias les='command less -n'
else
export PAGER='less'
export MANPAGER='less'
alias les='less -n'
fi
# If bat is installed...
# https://github.com/sharkdp/bat
if hascommand --strict batcat; then
export PAGER='less' # Bat expects and uses less
export MANPAGER='batcat --style=plain'
alias bat='batcat --force-colorization'
elif hascommand --strict bat; then
export PAGER='less' # Bat expects and uses less
export MANPAGER='bat --style=plain'
alias bat='bat --force-colorization'
fi
# If bat-extras is installed with the extra commands:
# batgrep, batman, batpipe, batwatch, batdiff, prettybat
# Link: https://github.com/eth-p/bat-extras
if hascommand --strict batman; then
alias man='batman'
fi
# ccat is the colorizing cat
# Link: https://github.com/owenthereal/ccat
if hascommand --strict ccat; then
alias cat='ccat'
fi
# Color for manpages in less makes manpages a little easier to read
if [[ -f /usr/share/source-highlight/src-hilite-lesspipe.sh ]]; then
export LESSOPEN="| /usr/share/source-highlight/src-hilite-lesspipe.sh %s"
elif hascommand --strict src-hilite-lesspipe.sh; then
export LESSOPEN="| src-hilite-lesspipe.sh %s"
fi
# NOTE: Use \less with the back-slash to remove line numbers
# or you can use -n or --line-numbers if moar/most are not installed
export LESS='-N -x4 --force --ignore-case --quit-if-one-screen --no-init --RAW-CONTROL-CHARS --LONG-PROMPT --prompt=%t?f%f :stdin .?pb%pb\%:?lbLine %lb:?bbByte %bb:-...'
export LESS_TERMCAP_mb=$'\E[01;31m'
export LESS_TERMCAP_md=$'\E[01;31m'
export LESS_TERMCAP_me=$'\E[0m'
export LESS_TERMCAP_se=$'\E[0m'
export LESS_TERMCAP_so=$'\E[01;44;33m'
export LESS_TERMCAP_ue=$'\E[0m'
export LESS_TERMCAP_us=$'\E[01;32m'
# Make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
#######################################################
# Vivid LS_COLORS Generator
# Link: https://github.com/sharkdp/vivid
# Themes: https://github.com/sharkdp/vivid/tree/master/themes
#######################################################
# LS_COLORS (lscolors-git in Arch AUR repository)
# Link: https://github.com/trapd00r/LS_COLORS
# Install:
# mkdir /tmp/LS_COLORS && curl -L https://api.github.com/repos/trapd00r/LS_COLORS/tarball/master | tar xzf - --directory=/tmp/LS_COLORS --strip=1
# ( cd /tmp/LS_COLORS && sh install.sh )
#######################################################
# Colors for ls
export CLICOLOR=1
if hascommand --strict vivid; then
# Vivid is installed
export LS_COLORS="$(vivid generate snazzy)"
_LS_COLORS_SOURCE="Vivid"
elif [[ -f "${XDG_DATA_HOME:-${HOME}/.local/share}/lscolors.sh" ]]; then
# LS_COLORS is installed locally
builtin source "${XDG_DATA_HOME:-${HOME}/.local/share}/lscolors.sh"
_LS_COLORS_SOURCE="LS_COLORS Local"
elif [[ -f /usr/share/LS_COLORS/dircolors.sh ]]; then
# LS_COLORS is installed system wide
builtin source /usr/share/LS_COLORS/dircolors.sh
_LS_COLORS_SOURCE="LS_COLORS System Wide"
elif hascommand --strict dircolors; then
# dircolors is used by ls to set LS_COLORS for colorized directory output
# Check if a custom .dircolors file exists in the user's home directory
if test -r ~/.dircolors; then
# If the file exists and is readable, use it to set LS_COLORS instead
eval "$(dircolors -b ~/.dircolors)"
else
# If no custom .dircolors file exists then use the default settings
eval "$(dircolors -b)"
fi
_LS_COLORS_SOURCE="dircolors"
else
# Otherwise, export a full featured custom ls color profile
LS_COLORS='bd=38;5;68:ca=38;5;17:cd=38;5;113;1:di=38;5;30:do=38;5;127:ex=38;5;208;1:pi=38;5;126:fi=0:ln=target:mh=38;5;222;1:no=0:or=48;5;196;38;5;232;1:ow=38;5;220;1:sg=48;5;3;38;5;0:su=38;5;220;1;3;100;1:so=38;5;197:st=38;5;86;48;5;234:tw=48;5;235;38;5;139;3:*LS_COLORS=48;5;89;38;5;197;1;3;4;7:*.txt=38;5;253:*README=38;5;220;1:*README.rst=38;5;220;1:*README.md=38;5;220;1:*LICENSE=38;5;220;1:*COPYING=38;5;220;1:*INSTALL=38;5;220;1:*COPYRIGHT=38;5;220;1:*AUTHORS=38;5;220;1:*HISTORY=38;5;220;1:*CONTRIBUTORS=38;5;220;1:*PATENTS=38;5;220;1:*VERSION=38;5;220;1:*NOTICE=38;5;220;1:*CHANGES=38;5;220;1:*.log=38;5;190:*.adoc=38;5;184:*.asciidoc=38;5;184:*.etx=38;5;184:*.info=38;5;184:*.markdown=38;5;184:*.md=38;5;184:*.mkd=38;5;184:*.nfo=38;5;184:*.org=38;5;184:*.pod=38;5;184:*.rst=38;5;184:*.tex=38;5;184:*.textile=38;5;184:*.bib=38;5;178:*.json=38;5;178:*.jsonl=38;5;178:*.jsonnet=38;5;178:*.libsonnet=38;5;142:*.ndjson=38;5;178:*.msg=38;5;178:*.pgn=38;5;178:*.rss=38;5;178:*.xml=38;5;178:*.fxml=38;5;178:*.toml=38;5;178:*.yaml=38;5;178:*.yml=38;5;178:*.RData=38;5;178:*.rdata=38;5;178:*.xsd=38;5;178:*.dtd=38;5;178:*.sgml=38;5;178:*.rng=38;5;178:*.rnc=38;5;178:*.accdb=38;5;60:*.accde=38;5;60:*.accdr=38;5;60:*.accdt=38;5;60:*.db=38;5;60:*.fmp12=38;5;60:*.fp7=38;5;60:*.localstorage=38;5;60:*.mdb=38;5;60:*.mde=38;5;60:*.sqlite=38;5;60:*.typelib=38;5;60:*.nc=38;5;60:*.cbr=38;5;141:*.cbz=38;5;141:*.chm=38;5;141:*.djvu=38;5;141:*.pdf=38;5;141:*.PDF=38;5;141:*.mobi=38;5;141:*.epub=38;5;141:*.docm=38;5;111;4:*.doc=38;5;111:*.docx=38;5;111:*.odb=38;5;111:*.odt=38;5;111:*.rtf=38;5;111:*.pages=38;5;111:*.odp=38;5;166:*.pps=38;5;166:*.ppt=38;5;166:*.pptx=38;5;166:*.ppts=38;5;166:*.pptxm=38;5;166;4:*.pptsm=38;5;166;4:*.csv=38;5;78:*.tsv=38;5;78:*.numbers=38;5;112:*.ods=38;5;112:*.xla=38;5;76:*.xls=38;5;112:*.xlsx=38;5;112:*.xlsxm=38;5;112;4:*.xltm=38;5;73;4:*.xltx=38;5;73:*.key=38;5;166:*config=1:*cfg=1:*conf=1:*rc=1:*authorized_keys=1:*known_hosts=1:*.ini=1:*.plist=1:*.profile=1:*.bash_profile=1:*.bash_login=1:*.bash_logout=1:*.zshenv=1:*.zprofile=1:*.zlogin=1:*.zlogout=1:*.viminfo=1:*.pcf=1:*.psf=1:*.hidden-color-scheme=1:*.hidden-tmTheme=1:*.last-run=1:*.merged-ca-bundle=1:*.sublime-build=1:*.sublime-commands=1:*.sublime-keymap=1:*.sublime-settings=1:*.sublime-snippet=1:*.sublime-project=1:*.sublime-workspace=1:*.tmTheme=1:*.user-ca-bundle=1:*.rstheme=1:*.epf=1:*.git=38;5;197:*.gitignore=38;5;240:*.gitattributes=38;5;240:*.gitmodules=38;5;240:*.awk=38;5;172:*.bash=38;5;172:*.bat=38;5;172:*.BAT=38;5;172:*.sed=38;5;172:*.sh=38;5;172:*.zsh=38;5;172:*.vim=38;5;172:*.kak=38;5;172:*.ahk=38;5;41:*.py=38;5;41:*.ipynb=38;5;41:*.xsh=38;5;41:*.rb=38;5;41:*.gemspec=38;5;41:*.pl=38;5;208:*.PL=38;5;160:*.pm=38;5;203:*.t=38;5;114:*.msql=38;5;222:*.mysql=38;5;222:*.pgsql=38;5;222:*.sql=38;5;222:*.tcl=38;5;64;1:*.r=38;5;49:*.R=38;5;49:*.gs=38;5;81:*.clj=38;5;41:*.cljs=38;5;41:*.cljc=38;5;41:*.cljw=38;5;41:*.scala=38;5;41:*.sc=38;5;41:*.dart=38;5;51:*.asm=38;5;81:*.cl=38;5;81:*.lisp=38;5;81:*.rkt=38;5;81:*.el=38;5;81:*.elc=38;5;241:*.eln=38;5;241:*.lua=38;5;81:*.moon=38;5;81:*.c=38;5;81:*.C=38;5;81:*.h=38;5;110:*.H=38;5;110:*.tcc=38;5;110:*.c++=38;5;81:*.h++=38;5;110:*.hpp=38;5;110:*.hx=38;5;110:*.hxx=38;5;110:*.hxsl=38;5;110:*.ii=38;5;110:*.M=38;5;110:*.m=38;5;110:*.cc=38;5;81:*.cs=38;5;81:*.cp=38;5;81:*.cpp=38;5;81:*.cxx=38;5;81:*.cr=38;5;81:*.go=38;5;81:*.f=38;5;81:*.F=38;5;81:*.for=38;5;81:*.ftn=38;5;81:*.f90=38;5;81:*.F90=38;5;81:*.f95=38;5;81:*.F95=38;5;81:*.f03=38;5;81:*.F03=38;5;81:*.f08=38;5;81:*.F08=38;5;81:*.nim=38;5;81:*.nimble=38;5;81:*.s=38;5;110:*.S=38;5;110:*.rs=38;5;81:*.scpt=38;5;219:*.swift=38;5;219:*.sx=38;5;81:*.vala=38;5;81:*.vapi=38;5;81:*.hi=38;5;110:*.hs=38;5;81:*.lhs=38;5;81:*.agda=38;5;81:*.lagda=38;5;81:*.lagda.tex=38;5;81:*.lagda.rst=38;5;81:*.lagda.md=38;5;81:*.agdai=38;5;110:*.zig=38;5;81:*.v=38;5;81:*.pyc=38;5;240:*.tf=38;5;168:*.tfstate=38;5;168:*.tfvars=38;5;168:*.css=38;5;125;1:*.less=38;5;125;1:*.sass=38;5;125;1:*.scss=38;5;125;1:*.htm=38;5;125;1:*.html=38;5;125;1:*.jhtm=38;5;125;1:*.mht=38;5;125;1:*.eml=38;5;125;1:*.mustache=38;5;125;1:*.coffee=38;5;074;1:*.java=38;5;074;1:*.js=38;5;074;1:*.mjs=38;5;074;1:*.jsm=38;5;074;1:*.jsp=38;5;074;1:*.ada=38;5;81:*.cbl=38;5;81:*.conf=38;5;81:*.cpy=38;5;81:*.ctp=38;5;81:*.erl=38;5;81:*.groovy=38;5;81:*.hrl=38;5;81:*.inc=38;5;81:*.ino=38;5;81:*.kt=38;5;81:*.lib=38;5;81:*.mat=38;5;81:*.mk=38;5;81:*.pascal=38;5;81:*.php=38;5;81:*.plx=38;5;81:*.sml=38;5;81:*.template=38;5;81:*.tpl=38;5;81:*.twig=38;5;81:*.vb=38;5;81:*.vba=38;5;81:*.vbs=38;5;81:*.wren=38;5;81:*Dockerfile=38;5;155:*.dockerignore=38;5;240:*Makefile=38;5;155:*MANIFEST=38;5;243:*pm_to_blib=38;5;240:*.nix=38;5;155:*.dhall=38;5;178:*.rake=38;5;155:*.am=38;5;242:*.in=38;5;242:*.hin=38;5;242:*.scan=38;5;242:*.m4=38;5;242:*.old=38;5;242:*.out=38;5;242:*.SKIP=38;5;244:*.diff=48;5;197;38;5;232:*.patch=48;5;197;38;5;232;1:*.bmp=38;5;97:*.dicom=38;5;97:*.tiff=38;5;97:*.tif=38;5;97:*.TIFF=38;5;97:*.cdr=38;5;97:*.flif=38;5;97:*.gif=38;5;97:*.icns=38;5;97:*.ico=38;5;97:*.jpeg=38;5;97:*.JPG=38;5;97:*.jpg=38;5;97:*.nth=38;5;97:*.png=38;5;97:*.psd=38;5;97:*.pxd=38;5;97:*.pxm=38;5;97:*.xpm=38;5;97:*.webp=38;5;97:*.ai=38;5;99:*.eps=38;5;99:*.epsf=38;5;99:*.drw=38;5;99:*.ps=38;5;99:*.svg=38;5;99:*.avi=38;5;114:*.divx=38;5;114:*.IFO=38;5;114:*.m2v=38;5;114:*.m4v=38;5;114:*.mkv=38;5;114:*.MOV=38;5;114:*.mov=38;5;114:*.mp4=38;5;114:*.mpeg=38;5;114:*.mpg=38;5;114:*.ogm=38;5;114:*.rmvb=38;5;114:*.sample=38;5;114:*.wmv=38;5;114:*.3g2=38;5;115:*.3gp=38;5;115:*.gp3=38;5;115:*.webm=38;5;115:*.gp4=38;5;115:*.asf=38;5;115:*.flv=38;5;115:*.ts=38;5;115:*.ogv=38;5;115:*.f4v=38;5;115:*.VOB=38;5;115;1:*.vob=38;5;115;1:*.ass=38;5;117:*.srt=38;5;117:*.ssa=38;5;117:*.sub=38;5;117:*.sup=38;5;117:*.vtt=38;5;117:*.3ga=38;5;137;1:*.S3M=38;5;137;1:*.aac=38;5;137;1:*.amr=38;5;137;1:*.au=38;5;137;1:*.caf=38;5;137;1:*.dat=38;5;137;1:*.dts=38;5;137;1:*.fcm=38;5;137;1:*.m4a=38;5;137;1:*.mod=38;5;137;1:*.mp3=38;5;137;1:*.mp4a=38;5;137;1:*.oga=38;5;137;1:*.ogg=38;5;137;1:*.opus=38;5;137;1:*.s3m=38;5;137;1:*.sid=38;5;137;1:*.wma=38;5;137;1:*.ape=38;5;136;1:*.aiff=38;5;136;1:*.cda=38;5;136;1:*.flac=38;5;136;1:*.alac=38;5;136;1:*.mid=38;5;136;1:*.midi=38;5;136;1:*.pcm=38;5;136;1:*.wav=38;5;136;1:*.wv=38;5;136;1:*.wvc=38;5;136;1:*.afm=38;5;66:*.fon=38;5;66:*.fnt=38;5;66:*.pfb=38;5;66:*.pfm=38;5;66:*.ttf=38;5;66:*.otf=38;5;66:*.woff=38;5;66:*.woff2=38;5;66:*.PFA=38;5;66:*.pfa=38;5;66:*.7z=38;5;40:*.a=38;5;40:*.arj=38;5;40:*.bz2=38;5;40:*.cpio=38;5;40:*.gz=38;5;40:*.lrz=38;5;40:*.lz=38;5;40:*.lzma=38;5;40:*.lzo=38;5;40:*.rar=38;5;40:*.s7z=38;5;40:*.sz=38;5;40:*.tar=38;5;40:*.tbz=38;5;40:*.tgz=38;5;40:*.warc=38;5;40:*.WARC=38;5;40:*.xz=38;5;40:*.z=38;5;40:*.zip=38;5;40:*.zipx=38;5;40:*.zoo=38;5;40:*.zpaq=38;5;40:*.zst=38;5;40:*.zstd=38;5;40:*.zz=38;5;40:*.apk=38;5;215:*.ipa=38;5;215:*.deb=38;5;215:*.rpm=38;5;215:*.jad=38;5;215:*.jar=38;5;215:*.ear=38;5;215:*.war=38;5;215:*.cab=38;5;215:*.pak=38;5;215:*.pk3=38;5;215:*.vdf=38;5;215:*.vpk=38;5;215:*.bsp=38;5;215:*.dmg=38;5;215:*.crx=38;5;215:*.xpi=38;5;215:*.iso=38;5;124:*.img=38;5;124:*.bin=38;5;124:*.nrg=38;5;124:*.qcow=38;5;124:*.fvd=38;5;124:*.sparseimage=38;5;124:*.toast=38;5;124:*.vcd=38;5;124:*.vdi=38;5;124:*.vhd=38;5;124:*.vhdl=38;5;124:*.vhdx=38;5;124:*.vfd=38;5;124:*.vmdk=38;5;124:*.swp=38;5;244:*.swo=38;5;244:*.tmp=38;5;244:*.sassc=38;5;244:*.pacnew=38;5;33:*.un~=38;5;241:*.orig=38;5;241:*.BUP=38;5;241:*.bak=38;5;241:*.o=38;5;241:*core=38;5;241:*.mdump=38;5;241:*.rlib=38;5;241:*.dll=38;5;241:*.aria2=38;5;241:*.dump=38;5;241:*.stackdump=38;5;241:*.zcompdump=38;5;241:*.zwc=38;5;241:*.part=38;5;239:*.r[0-9]{0,2}=38;5;239:*.zx[0-9]{0,2}=38;5;239:*.z[0-9]{0,2}=38;5;239:*.pid=38;5;248:*.state=38;5;248:*lockfile=38;5;248:*lock=38;5;248:*.err=38;5;160;1:*.error=38;5;160;1:*.stderr=38;5;160;1:*.pcap=38;5;29:*.cap=38;5;29:*.dmp=38;5;29:*.allow=38;5;112:*.deny=38;5;196:*.service=38;5;45:*@.service=38;5;45:*.socket=38;5;45:*.swap=38;5;45:*.device=38;5;45:*.mount=38;5;45:*.automount=38;5;45:*.target=38;5;45:*.path=38;5;45:*.timer=38;5;45:*.snapshot=38;5;45:*.lnk=38;5;39:*.application=38;5;116:*.cue=38;5;116:*.description=38;5;116:*.directory=38;5;116:*.m3u=38;5;116:*.m3u8=38;5;116:*.md5=38;5;116:*.properties=38;5;116:*.sfv=38;5;116:*.theme=38;5;116:*.torrent=38;5;116:*.urlview=38;5;116:*.webloc=38;5;116:*.asc=38;5;192;3:*.bfe=38;5;192;3:*.enc=38;5;192;3:*.gpg=38;5;192;3:*.signature=38;5;192;3:*.sig=38;5;192;3:*.p12=38;5;192;3:*.pem=38;5;192;3:*.pgp=38;5;192;3:*.p7s=38;5;192;3:*id_dsa=38;5;192;3:*id_rsa=38;5;192;3:*id_ecdsa=38;5;192;3:*id_ed25519=38;5;192;3:*.32x=38;5;213:*.cdi=38;5;213:*.fm2=38;5;213:*.rom=38;5;213:*.sav=38;5;213:*.st=38;5;213:*.a00=38;5;213:*.a52=38;5;213:*.A64=38;5;213:*.a64=38;5;213:*.a78=38;5;213:*.adf=38;5;213:*.atr=38;5;213:*.gb=38;5;213:*.gba=38;5;213:*.gbc=38;5;213:*.gel=38;5;213:*.gg=38;5;213:*.ggl=38;5;213:*.ipk=38;5;213:*.j64=38;5;213:*.nds=38;5;213:*.nes=38;5;213:*.sms=38;5;213:*.8xp=38;5;121:*.8eu=38;5;121:*.82p=38;5;121:*.83p=38;5;121:*.8xe=38;5;121:*.stl=38;5;216:*.dwg=38;5;216:*.ply=38;5;216:*.wrl=38;5;216:*.xib=38;5;208:*.iml=38;5;166:*.DS_Store=38;5;239:*.localized=38;5;239:*.CFUserTextEncoding=38;5;239:*CodeResources=38;5;239:*PkgInfo=38;5;239:*.nib=38;5;57:*.car=38;5;57:*.dylib=38;5;241:*.entitlements=1:*.pbxproj=1:*.strings=1:*.storyboard=38;5;196:*.xcconfig=1:*.xcsettings=1:*.xcuserstate=1:*.xcworkspacedata=1:*.pot=38;5;7:*.pcb=38;5;7:*.mm=38;5;7:*.gbr=38;5;7:*.scm=38;5;7:*.xcf=38;5;7:*.spl=38;5;7:*.Rproj=38;5;11:*.sis=38;5;7:*.1p=38;5;7:*.3p=38;5;7:*.cnc=38;5;7:*.def=38;5;7:*.ex=38;5;7:*.example=38;5;7:*.feature=38;5;7:*.ger=38;5;7:*.ics=38;5;7:*.map=38;5;7:*.mf=38;5;7:*.mfasl=38;5;7:*.mi=38;5;7:*.mtx=38;5;7:*.pc=38;5;7:*.pi=38;5;7:*.plt=38;5;7:*.rdf=38;5;7:*.ru=38;5;7:*.sch=38;5;7:*.sty=38;5;7:*.sug=38;5;7:*.tdy=38;5;7:*.tfm=38;5;7:*.tfnt=38;5;7:*.tg=38;5;7:*.vcard=38;5;7:*.vcf=38;5;7:*.xln=38;5;7:'
_LS_COLORS_SOURCE="Custom Colors"
fi
#######################################################
# grc Generic Colouriser
# Link: https://github.com/garabik/grc
#######################################################
if [[ "${_SKIP_GRC}" = false ]] && hascommand --strict grc; then
GRC_ALIASES=true
# If we are not defaulting to manually set values and using includes...
if [[ "${_GRC_USE_BASHRC_BUILTIN}" != true ]]; then
# Source the first include found
if [[ -f "${HOME}/.local/bin/grc.sh" ]]; then
builtin source "${HOME}/.local/bin/grc.sh"
elif [[ -f "/etc/profile.d/grc.sh" ]]; then
builtin source "/etc/profile.d/grc.sh"
elif [[ -f "/etc/grc.sh" ]]; then
builtin source "/etc/grc.sh"
else
# No includes found, we will have to use the built-in aliases
_GRC_USE_BASHRC_BUILTIN=true
fi
fi
# If we need to use the manual built-in aliases...
if [[ "${_GRC_USE_BASHRC_BUILTIN}" == true ]]; then
# Find the grc command
GRC="$(command -v grc)"
# If the terminal is interactive and GRC command is found...
if tty -s && [ -n "${TERM}" ] && [ "${TERM}" != "dumb" ] && [ -n "${GRC}" ]; then
alias colourify="${GRC} -es"
alias as='colourify as'
alias blkid='colourify blkid'
alias configure='colourify ./configure'
[[ "$(type -t df)" != 'alias' ]] && alias df='colourify df --human-readable --print-type --exclude-type=squashfs'
[[ "$(type -t diff)" != 'alias' ]] && alias diff='colourify diff'
alias dig='colourify dig'
alias docker='colourify docker'
alias docker-compose='colourify docker-compose'
alias docker-machine='colourify docker-machine'
alias du='colourify du'
# alias env='colourify env'
alias fdisk='colourify fdisk'
alias findmnt='colourify findmnt'
alias free='colourify free -m'
alias g++='colourify g++'
alias gas='colourify gas'
alias gcc='colourify gcc'
alias getsebool='colourify getsebool'
alias head='colourify head'
alias id='colourify id'
alias ifconfig='colourify ifconfig'
alias ip='colourify ip'
alias iptables='colourify iptables'
alias journalctl='colourify journalctl'
alias kubectl='colourify kubectl'
alias ld='colourify ld'
alias lsof='colourify lsof'
alias lspci='colourify lspci'
alias m='colourify mount'
alias make='colourify make'
alias mount='colourify mount'
alias mtr='colourify mtr'
alias netstat='colourify netstat'
alias ping='colourify ping -c 5'
# alias ps='colourify ps auxf'
alias semanage='colourify semanage'
alias sockstat='colourify sockstat'
alias ss='colourify ss'
alias tail='colourify tail'
alias traceroute='colourify traceroute'
alias traceroute6='colourify traceroute6'
fi
fi
# Aliasing ps causes issues with some scripts
alias ps &>/dev/null && unalias ps
# Create another alias for ps color instead
alias pss='colourify ps auxf'
# Override lsblk to enhance the color output
#alias lsblk='colourify lsblk --exclude 1,7 --output NAME,MAJ:MIN,RM,SIZE,RO,TYPE,MOUNTPOINTS'
alias lsblk='colourify lsblk --exclude 1,7 --output NAME,MAJ:MIN,TYPE,FSTYPE,RM,MOUNTPOINTS,LABEL,SIZE,FSUSE%,RO,UUID 2> /dev/null || colourify lsblk --exclude 1,7'
else # grc is not installed...
# List block devices but show more info including the files systems and permissions
# and removes all mem and loopback devices (like snap packages) from the list
#alias lsblk='command lsblk --exclude 1,7 --output NAME,MAJ:MIN,RM,SIZE,RO,TYPE,MOUNTPOINTS'
alias lsblk='command lsblk --exclude 1,7 --output NAME,MAJ:MIN,TYPE,FSTYPE,RM,MOUNTPOINTS,LABEL,SIZE,FSUSE%,RO,UUID 2> /dev/null || command lsblk --exclude 1,7'
# Make df look better if not already aliased at this point
[[ "$(type -t df)" != 'alias' ]] && alias df='command df --human-readable --print-type --exclude-type=squashfs'
fi
#######################################################
# mysql-colorize
# Link: https://github.com/zpm-zsh/mysql-colorize
# Install: git clone https://github.com/horosgrisa/mysql-colorize.bash ~/.bash/mysql-colorize
#######################################################
# If qfc is installed
if [[ -f "${HOME}/.bash/mysql-colorize/mysql-colorize.bash" ]]; then
builtin source "${HOME}/.bash/mysql-colorize/mysql-colorize.bash"
fi
#######################################################
# Nethogs shows which processes are using network bandwidth
# Link: https://github.com/raboof/nethogs
#######################################################
# lsof command stands for List Of Open File
# Link: https://www.geeksforgeeks.org/lsof-command-in-linux-with-examples/
#######################################################
# iftop is a network analyzing tool used to view the bandwidth related stats
# Link: https://www.geeksforgeeks.org/iftop-command-in-linux-with-examples/
#######################################################
if hascommand --strict nethogs; then
# Watch real time network activity by process
alias netwatch='sudo nethogs -d 0.5 -C'
elif hascommand --strict iftop; then
# Use iftop
alias netwatch='sudo iftop'
else
# Watch real time network activity using lsof instead
alias netwatch='lsof -i -r 10'
fi
#######################################################
# Diff Enhancement
#######################################################
if hascommand --strict delta; then
if hascommand --strict batdiff; then
# bat-extras diff uses delta
# Link: https://github.com/eth-p/bat-extras
alias diff='batdiff --delta'
else
# delta - Beautiful side by side colored diff with Git support and syntax highlighting
# Link: https://github.com/dandavison/delta
# Info: Add listed settings to your ~/.gitconfig
alias diff='delta --side-by-side --line-numbers'
fi
export DIFFPROG='delta --side-by-side --line-numbers'
elif hascommand --strict difft; then
# Difftastic is a structural diff tool that compares files based on their syntax
# Supports over 30 programming languages
# Link: https://github.com/Wilfred/difftastic
alias diff='difft'
export DIFFPROG='difft'
elif hascommand --strict icdiff; then
# Icdiff - Improved (side by side) colored diff
# Link: https://github.com/jeffkaufman/icdiff
alias diff='icdiff --line-numbers --strip-trailing-cr'
export DIFFPROG="icdiff --line-numbers --strip-trailing-cr"
elif hascommand --strict diff-so-fancy; then
# diff-so-fancy strives to make your diffs human readable instead of machine readable
# Link: https://github.com/so-fancy/diff-so-fancy
alias diff='_diff_f() { command diff -u "$@" | diff-so-fancy; }; _diff_f'
export DIFFPROG='diff-so-fancy'
elif hascommand --strict colordiff; then
# Colorize diff output if colordiff is installed
alias diff='colordiff'
export DIFFPROG="colordiff"
else
if [[ ${EDITOR} = 'nvim' ]]; then
export DIFFPROG="nvim -d"
elif [[ ${EDITOR} = 'vim' ]]; then
export DIFFPROG="vim -d"
else
export DIFFPROG="command diff --side-by-side --suppress-common-lines --ignore-all-space --ignore-blank-lines --strip-trailing-cr --report-identical-files"
fi
fi
#######################################################
# Desktop Environment
#######################################################
# If we are inside a desktop environment (and not TTY or SSH)
if ([[ -n "$DISPLAY" ]] || [[ -n "$WAYLAND_DISPLAY" ]]) && [[ -n "$XDG_CURRENT_DESKTOP" ]]; then
# Alias to log out the currect user
alias logout="sudo pkill -u ${USER}"
# Check if the user's session type is X11
if [[ "${XDG_SESSION_TYPE}" == "x11" ]]; then
# Ensure the ffmpeg command is available
if hascommand --strict ffmpeg; then
# Create an alias to capture video on X11 using ffmpeg
# Documentation for ffmpeg: https://ffmpeg.org/documentation.html
alias grabvideo='ffmpeg -f x11grab -s wxga -r 25 -i :0.0 -qscale 0'
fi
# Fix QT_QPA_PLATFORM if wrongly set to offscreen (makes Qt/KDE apps invisible)
if [[ $QT_QPA_PLATFORM == "offscreen" ]]; then
export QT_QPA_PLATFORM=xcb
fi
# Check if the user's session type is Wayland
elif [[ "${XDG_SESSION_TYPE}" == "wayland" ]]; then
# Ensure the wf-recorder command is available (requires wl-roots compositor)
if hascommand wf-recorder; then
# Create an alias to capture video on Wayland using wf-recorder
# Documentation for wf-recorder: https://github.com/ammen99/wf-recorder
alias grabvideo='wf-recorder -f output.mp4'
fi
# Fix QT_QPA_PLATFORM if wrongly set to offscreen (makes Qt/KDE apps invisible)
if [[ $QT_QPA_PLATFORM == "offscreen" ]]; then
export QT_QPA_PLATFORM=wayland
fi
fi
# If a gui diff/merge application is installed, use that instead
for _DIFF_APP_GUI in \
meld \
kompare \
kdiff3 \
xxdiff
do
if hascommand --strict ${_DIFF_APP_GUI}; then
# Create a function capturing the diff tool value at define time
# (eval-based closure so the function doesn't depend on the loop variable)
eval "gdiff() { \"${_DIFF_APP_GUI}\" \"\$@\" > /dev/null 2>&1 & disown; }"
# Optionally, set DIFFPROG
# export DIFFPROG="${_DIFF_APP_GUI}"
break
fi
done
# Loop through potential file managers and set alias for the first found
for _FILE_MANAGER in \
kde-open \
gnome-open \
xdg-open \
exo-open \
krusader \
doublecmd \
dolphin \
thunar \
pcmanfm \
nautilus \
nemo \
caja \
konqueror \
ranger \
nnn \
mc
do
if hascommand --strict ${_FILE_MANAGER}; then
if [[ "${_FILE_MANAGER}" == "kde-open" ]]; then
# Only use kde-open on KDE desktops
[[ "$XDG_CURRENT_DESKTOP" == *KDE* ]] || continue
alias ui="kde-open \"\${PWD}\""
elif [[ "${_FILE_MANAGER}" == "gnome-open" ]]; then
# Only use gnome-open on GNOME desktops
[[ "$XDG_CURRENT_DESKTOP" == *GNOME* ]] || continue
alias ui="gnome-open \"\${PWD}\""
elif [[ "${_FILE_MANAGER}" == "exo-open" ]]; then
# Only use exo-open on Xfce desktops
[[ "$XDG_CURRENT_DESKTOP" == *XFCE* ]] || continue
alias ui="exo-open --launch FileManager \"\${PWD}\" > /dev/null 2>&1 & disown"
elif [[ "${_FILE_MANAGER}" =~ ^(ranger|nnn|mc)$ ]]; then
# These commands do not need backgrounding or output suppression
alias ui="${_FILE_MANAGER} \"\${PWD}\""
else
# General case for GUI-based file managers (xdg-open, dolphin, etc.)
alias ui="${_FILE_MANAGER} \"\${PWD}\" > /dev/null 2>&1 & disown"
fi
break # Stop the loop once the first available file manager is found
fi
done
fi
#######################################################
# Bash Completion
# Link: https://github.com/scop/bash-completion
#######################################################
# Use extended globbing for more advanced pattern matching
# This is necessary for programmable completion
shopt -s extglob
if [[ ! ${_SKIP_BASH_COMPLETION} = true ]]; then
# Use bash-completion, if available
if [[ -f "/usr/share/bash-completion/bash_completion" ]]; then
builtin source "/usr/share/bash-completion/bash_completion"
elif [[ -f "/etc/bash_completion" ]]; then
builtin source "/etc/bash_completion"
elif [[ -f "${HOME}/bash_completion" ]]; then
builtin source "${HOME}/bash_completion"
elif [[ -f "${XDG_DATA_HOME:-${HOME}/.local/share}/bash_completion" ]]; then
builtin source "${XDG_DATA_HOME:-${HOME}/.local/share}/bash_completion"
elif [[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/bash_completion" ]]; then
builtin source "${XDG_CONFIG_HOME:-${HOME}/.config}/bash_completion"
fi
fi
#######################################################
# Cod is a completion daemon for bash, fish, and zsh
# Link: https://github.com/dim-an/cod
#######################################################
if hascommand --strict cod; then
builtin source <(cod init $$ bash)
fi
###########################################################################
# fzf-tab-completion tab completion using fzf in bash, GNU readline apps
# Link: https://github.com/lincheney/fzf-tab-completion
# Install: git clone https://github.com/lincheney/fzf-tab-completion
###########################################################################
# Check if the fzf-bash-completion.sh script exists in known locations
if [[ -f "/usr/share/fzf-tab-completion/bash/fzf-bash-completion.sh" ]]; then
# Source the fzf bash completion script
builtin source "/usr/share/fzf-tab-completion/bash/fzf-bash-completion.sh"
# Bind the tab key to the fzf_bash_completion function
bind -x '"\t": fzf_bash_completion'
# Bind Shift+Tab to the default completion function as a backup
bind '"\033[Z": complete'
# If nodejs is installed, enable fzf-tab-completion for nodejs repl
if hascommand node; then
alias node='node -r /usr/share/fzf-tab-completion/node/fzf-node-completion.js'
fi
elif [[ -f "${HOME}/fzf-tab-completion/bash/fzf-bash-completion.sh" ]]; then
# Source the fzf bash completion script if git cloned
builtin source "${HOME}/fzf-tab-completion/bash/fzf-bash-completion.sh"
# Bind the tab key to the fzf_bash_completion function
bind -x '"\t": fzf_bash_completion'
# Bind Shift+Tab to the default completion function as a backup
bind '"\033[Z": complete'
# If nodejs is installed, enable fzf-tab-completion for nodejs repl
if hascommand node; then
alias node="node -r ${HOME}/fzf-tab-completion/node/fzf-node-completion.js"
fi
elif [[ -f "${BASHRC_INSTALL_DIR}/fzf-tab-completion/bash/fzf-bash-completion.sh" ]]; then
# Source the fzf bash completion script if git cloned
builtin source "${BASHRC_INSTALL_DIR}/fzf-tab-completion/bash/fzf-bash-completion.sh"
# Bind the tab key to the fzf_bash_completion function
bind -x '"\t": fzf_bash_completion'
# Bind Shift+Tab to the default completion function as a backup
bind '"\033[Z": complete'
# If nodejs is installed, enable fzf-tab-completion for nodejs repl
if hascommand node; then
alias node="node -r ${BASHRC_INSTALL_DIR}/fzf-tab-completion/node/fzf-node-completion.js"
fi
fi
#######################################################
# Enable the "Command not found" hook
# Link: https://github.com/falconindy/pkgfile
# NOTE: pkgfile is targetted at Arch Linux users
# Install: pacman -S pkgfile
#######################################################
if [[ -r /usr/share/doc/pkgfile/command-not-found.bash ]]; then
builtin source /usr/share/doc/pkgfile/command-not-found.bash
fi
#######################################################
# Better Bash Defaults
# Link: http://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
#######################################################
# Bind the Right arrow key to forward-char (move cursor forward)
bind '"\033[C": forward-char'
# Bind the Left arrow key to backward-char (move cursor backward)
bind '"\033[D": backward-char'
# CTRL+u will now undo (CTRL+z may or may not work)
bind '"\C-u": undo'
# bind '"\C-z": undo'
# CRTL+q will now clear the entire line in the terminal (remember quit command)
bind '"\C-q": kill-whole-line'
# CTRL+d will disconnect even if there is text on the line
bind '"\C-d":"\C-k\C-u\C-d"'
# Disable the bell sound but make it visible
bind 'set bell-style visible'
# Update window size after each command and, if necessary, update the values of LINES and COLUMNS
# shopt -s checkwinsize
shopt -s checkwinsize
# Automatically trim long paths in the prompt (requires Bash 4.x)
PROMPT_DIRTRIM=3
# Turn on recursive globbing (enables ** to recurse all directories)
# For example, ls **/*.txt will list all text files in the current directory hierarchy
shopt -s globstar 2> /dev/null
# Case-insensitive globbing (used in pathname expansion)
shopt -s nocaseglob;
# Report the status of terminated background jobs immediately rather than before the next primary prompt
set -o notify
# Bash checks that a command found in the hash table exists before trying to execute it
shopt -s checkhash
# Bash includes filenames beginning with a "." in the results of filename expansion
shopt -s dotglob
# Disable core dumps because of the following reasons:
# 1. Security: Core dumps can contain sensitive data, such as passwords or
# encryption keys which are a potential security risk!
# 2. Disk Space: Core dumps can be large and consume significant disk space
# 3. Performance: Writing core dumps to disk can impact system performance
# particularly if crashes are frequent or if the disk is slow
# You can also edit the /etc/security/limits.conf file and add the following:
# * soft core 0 <- without the # in the beginning
# * hard core 0 <- without the # in the beginning
# For more information on disabling core dumps in systemd, visit the following:
# https://www.cyberciti.biz/faq/disable-core-dumps-in-linux-with-systemd-sysctl/
ulimit -S -c 0
# Don't let my shell warn me of incoming mail
shopt -u mailwarn
unset MAILCHECK
# Prevent overwriting an existing file with the >, >&, and <> redirection operators
# Use `>|` to force redirection to an existing file
#set -o noclobber
# Turns off CTRL+D to log out
#set -o ignoreeof
# Fix the HOME and END keys in PuTTY
if [[ "$COLORTERM" ]]; then # rxvt
bind '"\033[7~": beginning-of-line'
bind '"\033[8~": end-of-line'
else # xterm
bind '"\033[1~": beginning-of-line'
bind '"\033[4~": end-of-line'
fi
#######################################################
### SMARTER TAB-COMPLETION (Readline bindings)
#######################################################
# Perform file completion in a case insensitive fashion
bind 'set completion-ignore-case on'
# Treat hyphens and underscores as equivalent
bind 'set completion-map-case on'
# Display matches for ambiguous patterns at first tab press instead of bell
bind 'set show-all-if-ambiguous on'
# This line sets the completions to be listed immediately instead of ringing
# the bell when the completing word has more than one possible completion but
# no partial completion can be made
bind 'set show-all-if-unmodified on'
# Immediately add a trailing slash when autocompleting symlinks to directories
bind 'set mark-symlinked-directories on'
# Add a trailing slash when completing a directory name
bind 'set mark-directories on'
# Set autoexpansion of the '~' when TAB is pressed
bind 'set expand-tilde off'
# This line sets readline to display possible completions using different
# colors to indicate their file types. The colors are determined by the
# environmental variable LS_COLORS, which can be nicely configured
bind 'set colored-stats on'
# This lines sets completions to be appended by characters that indicate their
# file types reported by the stat system call
bind 'set visible-stats on'
#######################################################
### BETTER DIRECTORY NAVIGATION
#######################################################
# Prepend cd to directory names automatically
shopt -s autocd 2> /dev/null
# Correct spelling errors during tab-completion
shopt -s dirspell 2> /dev/null
shopt -s direxpand 2> /dev/null
# Correct spelling errors in arguments supplied to cd
shopt -s cdspell 2> /dev/null
# This defines where cd looks for targets
# Add the directories you want to have fast access to, separated by colon
# Ex: CDPATH=".:~:~/projects" will look for targets in the current working directory, in home and in the ~/projects folder
CDPATH="."
# The source builtin uses the value of PATH to find the directory containing the file supplied as an argument
shopt -s sourcepath
# If Readline is being used, Bash will not attempt to search the PATH for possible completions when completion is attempted on an empty line
shopt -s no_empty_cmd_completion
#######################################################
# User Specific Aliases
# This runs towards the end of the script in order to
# have supporting aliases and features and also so
# these can be over-written or modified (see unalias)
#######################################################
if [[ -f "${HOME}/.bash_aliases" ]]; then
builtin source "${HOME}/.bash_aliases"
elif [[ -f "${BASHRC_INSTALL_DIR}/aliases" ]]; then
builtin source "${BASHRC_INSTALL_DIR}/aliases"
fi
#######################################################
# Enhancd next-generation cd command with an interactive filter
# Link: https://github.com/b4b4r07/enhancd
# Install: cd ~ && git clone https://github.com/b4b4r07/enhancd
# NOTE: Breaks/replaces Bash shell option "shopt -s cdable_vars"
#######################################################
# If enhancd is installed, initialize it
if [[ -f "/usr/share/enhancd/init.sh" ]]; then
ENHANCD_FILTER=fzy:sk:fzf:peco:percol:pick:icepick:selecta:sentaku:zf
export ENHANCD_FILTER
builtin source "/usr/share/enhancd/init.sh"
elif [[ -f "${HOME}/enhancd/init.sh" ]]; then
ENHANCD_FILTER=fzy:sk:fzf:peco:percol:pick:icepick:selecta:sentaku:zf
export ENHANCD_FILTER
builtin source "${HOME}/enhancd/init.sh"
elif [[ -f "${BASHRC_INSTALL_DIR}/enhancd/init.sh" ]]; then
ENHANCD_FILTER=fzy:sk:fzf:peco:percol:pick:icepick:selecta:sentaku:zf
export ENHANCD_FILTER
builtin source "${BASHRC_INSTALL_DIR}/enhancd/init.sh"
fi
#######################################################
# Fasd keeps track of files and directories you have
# accessed so that you can quickly reference them
# a - any (directory or file)
# s - show / search / select
# d - directory
# f - file
# sd - interactive directory selection
# sf - interactive file selection
# z - cd, same functionality as j in autojump
# zz - cd with interactive selection
# Link: https://github.com/clvv/fasd
#######################################################
# If fasd is installed, initialize it
if hascommand --strict fasd; then
eval "$(fasd --init auto)"
fi
#######################################################
# Automatically source node.js and npm
#######################################################
# Check if npm command exists
if hascommand --strict npm; then
# Define an array to store possible locations of NVM initialization files
# Order of the locations matters as this will use the first file it finds
_NVM_FILE_LOCATIONS=(
"${HOME}/.nvm/init-nvm.sh"
"${HOME}/.nvm/nvm.sh"
"${XDG_DATA_HOME:-${HOME}/.local/share}/nvm/init-nvm.sh"
"${XDG_DATA_HOME:-${HOME}/.local/share}/nvm/nvm.sh"
"${XDG_CONFIG_HOME:-${HOME}/.config}/nvm/init-nvm.sh"
"${XDG_CONFIG_HOME:-${HOME}/.config}/nvm/nvm.sh"
"/usr/share/nvm/init-nvm.sh"
"/opt/nvm/init-nvm.sh"
"/opt/nvm/nvm.sh"
"/usr/share/nvm/nvm.sh"
)
# Iterate over the locations
for _NVM_FILE in "${_NVM_FILE_LOCATIONS[@]}"; do
# Check if the current file exists
if [[ -L "${_NVM_FILE}" || -f "${_NVM_FILE}" ]]; then
# If the file exists, source it to initialize NVM and set the
# NVM_DIR based on the directory of the found initialization file
if _RESOLVED_PATH="$(resolvesymlink "${_NVM_FILE}" 2>/dev/null)" && \
[[ -n "${_RESOLVED_PATH}" ]] && [[ -f "${_RESOLVED_PATH}" ]] && \
builtin source "${_RESOLVED_PATH}" 2>/dev/null && \
NVM_DIR="$(builtin cd "$(command dirname "${_RESOLVED_PATH}")" && command pwd 2>/dev/null)"; then
export NVM_DIR
break # Break out of the loop
fi
fi
done
# Define an array to store possible locations of the bash_completion file
_NVM_FILE_LOCATIONS=(
"${NVM_DIR}/bash_completion"
"${HOME}/.nvm/bash_completion"
"${XDG_DATA_HOME:-${HOME}/.local/share}/nvm/bash_completion"
"${XDG_CONFIG_HOME:-${HOME}/.config}/nvm/bash_completion"
"/usr/share/nvm/bash_completion"
"/opt/nvm/bash_completion"
)
# Iterate over the locations
for _NVM_FILE in "${_NVM_FILE_LOCATIONS[@]}"; do
# Check if the current bash_completion file exists
if [[ -f "${_NVM_FILE}" ]]; then
# If the file exists, source it to load NVM bash completion
builtin source "${_NVM_FILE}" 2>/dev/null
break # Break out of the loop
fi
done
# Clean up
unset _RESOLVED_PATH _NVM_FILE_LOCATIONS _NVM_FILE
fi
#######################################################
# Automatically source all files/links in the directory:
# ~/.config/bashrc/bashrc.d
#######################################################
# If the auto-source folder exists and has files in it...
if [[ -d "${BASHRC_INSTALL_DIR}/bashrc.d" ]]; then
# If the directory is not empty...
if [[ "$(command ls -A "${BASHRC_INSTALL_DIR}/bashrc.d")" ]]; then
# Loop through files (in alphabetical order) and source them
# To specify a load order, files can be prefixed with numbers
# (e.g. 00--filename, 50--filename, 95--filename)
for file in "${BASHRC_INSTALL_DIR}"/bashrc.d/*; do
builtin source "${file}"
done
fi
fi
#######################################################
# Custom Bash Prompt Configuration
# This script configures a personalized, attractive, and informative Bash prompt
# that displays useful information such as the current user, date, time, working
# directory, and Git repository status organized by an attractive color scheme
# NOTE This default prompt will be used if another prompt is not installed
# Link: https://ezprompt.net/ (some modifications below)
#######################################################
# Git Code Reference:
# > Renamed
# * Ahead
# + New File
# ? Untracked
# x Deleted
# ! Dirty
#######################################################
# Get current branch in Git repo
function _prompt_git_branch() {
if hascommand --strict git; then
if [[ $_GIT_IS_SLOW = false ]]; then
BRANCH=$(git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
if [[ -n "${BRANCH}" ]]; then
STAT=$(_prompt_git_status)
echo " [${BRANCH}${STAT}]"
else
echo ""
fi
else
_prompt_git_fast "${@}"
fi
else
# Git is not installed, return empty string
echo ""
fi
}
# Get current status of Git repo
function _prompt_git_status() {
# Check if we are in a git directory
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo ""
return
fi
# Cache the git status
local STATUS=$(git status --porcelain 2>&1)
# Check for conditions using pattern matching on porcelain status
# Porcelain format: XY where X=index status, Y=working-tree status
local DIRTY=$(echo "${STATUS}" | grep -c '^[ MADRCU][MD]')
local UNTRACKED=$(echo "${STATUS}" | grep -c '^??')
local AHEAD=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo 0)
local NEW_FILE=$(echo "${STATUS}" | grep -c '^A')
local RENAMED=$(echo "${STATUS}" | grep -c '^R')
# Match deletions in both index (D.) and working-tree (.D)
local DELETED=$(echo "${STATUS}" | grep -c '^\(.D\|D.\)')
# Staged changes: anything with a non-space in the index column
# Exclude new/renamed/deleted since they have their own symbols
local STAGED=$(echo "${STATUS}" | grep -c '^[MC] ')
# Build the status string
local BITS=''
[[ ${RENAMED:-0} -ne 0 ]] && BITS=">${BITS}"
[[ ${AHEAD:-0} -ne 0 ]] && BITS="*${BITS}"
[[ ${NEW_FILE:-0} -ne 0 ]] && BITS="+${BITS}"
[[ ${UNTRACKED:-0} -ne 0 ]] && BITS="?${BITS}"
[[ ${DELETED:-0} -ne 0 ]] && BITS="x${BITS}"
[[ ${DIRTY:-0} -ne 0 ]] && BITS="!${BITS}"
[[ ${STAGED:-0} -ne 0 ]] && BITS="~${BITS}"
# Output result
[[ -n "${BITS}" ]] && echo " ${BITS}" || echo ""
}
# Faster Git information for Git Bash and slow networks
# https://stackoverflow.com/questions/4485059/git-bash-is-extremely-slow-in-windows-7-x64/19500237#19500237
# https://stackoverflow.com/questions/4485059/git-bash-is-extremely-slow-in-windows-7-x64/13476961#13476961
# https://stackoverflow.com/questions/39518124/check-if-directory-is-git-repository-without-having-to-cd-into-it/39518382#39518382
function _prompt_git_fast() {
if hascommand --strict git; then
git -C . rev-parse 2>/dev/null >/dev/null && \
echo " [$(git symbolic-ref --short -q HEAD 2>/dev/null || \
git rev-parse -q --short HEAD 2>/dev/null)]"
else
# Git is not installed, return empty string
echo ""
fi
}
# Return the path for the multi-line prompt
function _prompt_pwd_full() {
# Returns the full path but still shows the home directory as ~
echo "${PWD}" | sed "s@${HOME}@~@"
}
# Clear out the prompt command before we begin
# (Some environments can set this and cause errors)
export PROMPT_COMMAND=''
# Set the prompt based on the environment
if [[ "$_KERNEL_NAME" = "MINGW" ]] || [[ "$_KERNEL_NAME" = "CYGWI" ]]; then
# If using Git Bash or Cygwin use faster and less intensive functions
export PS1='\[\033[0;35m\]\u \
\[\033[0;34m\]\D{%b %d} \
\[\033[0;36m\]\A \
\[\033[0;33m\]\w\
\[\033[0;31m\]`_prompt_git_fast`\
\[\033[0m\]\n\$ '
else # Standard default prompt
# Prompt color changes if logged on as root
if [[ ${EUID} -gt 0 ]]; then
_COLOR_USER="\033[0;32m"
else
_COLOR_USER="\033[1;31m"
fi
# Date formats can be found here (man strftime):
# https://manpages.ubuntu.com/manpages/xenial/man3/strftime.3.html
# https://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
# Ff connected SSH, show the host...
if [[ -n "$SSH_CLIENT" ]] || [[ -n "$SSH_TTY" ]]; then
if [[ $_PROMPT_BUILTIN_FULL_PATH = false ]]; then
# Remote SSH with short path (1 line)
export PS1="\[\033[0;35m\]\u\
@\[\033[1;35m\]\h \
\[\033[0;34m\]\D{%b %-d} \
\[\033[0;36m\]\D{%-H:%M} \
\[\033[0;33m\]\w\
\[\033[0;31m\]\$(_prompt_git_branch) \
\[${_COLOR_USER}\]>\[\033[0m\] "
else
# Remote SSH with full path (2 lines)
export PS1="\[\033[0;35m\]\u\
@\[\033[1;35m\]\h \
\[\033[0;34m\]\D{%b %-d} \
\[\033[0;36m\]\D{%-H:%M:%S} \
\[\033[0;33m\]\$(_prompt_pwd_full)\
\[\033[0;31m\]\$(_prompt_git_branch)\
\[\033[0m\]\n\[${_COLOR_USER}\]>\[\033[0m\] "
fi
else # Local prompt - only show the name
if [[ $_PROMPT_BUILTIN_FULL_PATH = false ]]; then
# Local with short path (1 line)
export PS1="\[\033[0;35m\]\u \
\[\033[0;34m\]\D{%b %-d} \
\[\033[0;36m\]\D{%-H:%M} \
\[\033[0;33m\]\w\
\[\033[0;31m\]\$(_prompt_git_branch) \
\[${_COLOR_USER}\]>\[\033[0m\] "
else
# Local with full path (2 lines)
export PS1="\[\033[0;35m\]\u \
\[\033[0;34m\]\D{%b %-d} \
\[\033[0;36m\]\D{%-H:%M:%S} \
\[\033[0;33m\]\$(_prompt_pwd_full)\
\[\033[0;31m\]\$(_prompt_git_branch)\
\[\033[0m\]\n\[${_COLOR_USER}\]>\[\033[0m\] "
fi
fi
fi
#######################################################
# Attempt to find and load some of the top installed prompts
# Only set and replace the custom prompt script if installed
# Otherwise, the standard prompt in a section above is already set
#######################################################
# The original older Extreme Ultimate .bashrc File prompt with added Git support
if [[ -f "${HOME}/.bashrc_prompt" ]] && [[ $_SKIP_PROMPT_ORIGINAL = false ]]; then
builtin source "${HOME}/.bashrc_prompt"
elif [[ -f "${BASHRC_INSTALL_DIR}/prompt" ]] && [[ $_SKIP_PROMPT_ORIGINAL = false ]]; then
builtin source "${BASHRC_INSTALL_DIR}/prompt"
# Trueline Bash (true 24-bit color and glyph support)
# This is the preferred prompt since it looks amazing,
# has so many features, is easily extended using functions,
# and is a single Bash script file that is easy to install.
# NOTE: You can place trueline.sh in the ~/.config/bashrc directory to keep it out of home
# Link: https://github.com/petobens/trueline
# Install: wget https://raw.githubusercontent.com/petobens/trueline/master/trueline.sh -P ~/
# Fonts: https://github.com/powerline/fonts
elif hascommand --strict trueline && [[ $_SKIP_PROMPT_TRUELINE = false ]]; then
builtin source "$(command which trueline)"
elif [[ -f /usr/bin/trueline ]] && [[ $_SKIP_PROMPT_TRUELINE = false ]]; then
builtin source /usr/bin/trueline
elif [[ -f "${BASHRC_INSTALL_DIR}/trueline.sh" ]] && [[ $_SKIP_PROMPT_TRUELINE = false ]]; then
builtin source "${BASHRC_INSTALL_DIR}/trueline.sh"
elif [[ -f "${HOME}/trueline/trueline.sh" ]] && [[ $_SKIP_PROMPT_TRUELINE = false ]]; then
builtin source "${HOME}/trueline/trueline.sh"
elif [[ -f "${HOME}/trueline.sh" ]] && [[ $_SKIP_PROMPT_TRUELINE = false ]]; then
builtin source "${HOME}/trueline.sh"
# Powerline-Go Global Install (this prompt uses no special glyphs)
# Link: https://github.com/justjanne/powerline-go
elif [[ -f "/usr/bin/powerline-go" ]] && [[ $_SKIP_PROMPT_POWERLINE_GO = false ]]; then
# Prompt Configuration for Powerline-Go
function _powerline_go_update_ps1() {
PS1="$(/usr/bin/powerline-go -error $? -jobs $(jobs -p | wc -l))"
# Automatically clear errors after showing them once (can cause problems)
#set "?"
}
PROMPT_COMMAND="_powerline_go_update_ps1; $PROMPT_COMMAND"
# Powerline-Go Home Folder Install (this prompt uses no special glyphs)
elif [[ -f "$GOPATH/bin/powerline-go" ]] && [[ $_SKIP_PROMPT_POWERLINE_GO = false ]]; then
# Prompt Configuration for Powerline-Go
function _powerline_go_update_ps1() {
PS1="$($GOPATH/bin/powerline-go -error $? -jobs $(jobs -p | wc -l))"
# Automatically clear errors after showing them once (can cause problems)
#set "?"
}
PROMPT_COMMAND="_powerline_go_update_ps1; $PROMPT_COMMAND"
# Powerline-Shell (details about git/svn/hg/fossil branch and Python virtualenv environment)
# Link: https://github.com/b-ryan/powerline-shell
elif hascommand --strict powerline-shell && [[ $_SKIP_PROMPT_POWERLINE_SHELL = false ]]; then
# Prompt Configuration for Powerline-Shell
function _powerline_shell_update_ps1() {
PS1=$(powerline-shell $?)
}
PROMPT_COMMAND="_powerline_shell_update_ps1; $PROMPT_COMMAND"
# Pureline (256 color written in bash script)
# Link: https://github.com/chris-marsh/pureline
# Install:
# git clone https://github.com/chris-marsh/pureline.git
# cp pureline/configs/powerline_full_256col.conf ~/.pureline.conf
elif [[ -f "${HOME}/pureline/pureline" ]] && [[ $_SKIP_PROMPT_PURELINE = false ]]; then
builtin source "${HOME}/pureline/pureline" "${HOME}/.pureline.conf"
# Starship Cross Shell Prompt (focus on compatibility and written in Rust)
# Link: https://starship.rs
# Install: sh -c "$(curl -fsSL https://starship.rs/install.sh)"
elif hascommand --strict starship && [[ $_SKIP_PROMPT_STARSHIP = false ]]; then
eval "$(starship init bash)"
# Oh-My-Git (only used for Git but has huge support for it, requires font)
# Link: https://github.com/arialdomartini/oh-my-git
# Install: git clone https://github.com/arialdomartini/oh-my-git.git ~/.oh-my-git
elif [[ -f "${HOME}/.oh-my-git/prompt.sh" ]] && [[ $_SKIP_PROMPT_OH_MY_GIT = false ]]; then
builtin source "${HOME}/.oh-my-git/prompt.sh"
# Bash Git Prompt (shows git repository, branch name, difference with remote branch, number of files staged, changed, etc)
# Link: https://github.com/magicmonty/bash-git-prompt
# Install: git clone https://github.com/magicmonty/bash-git-prompt.git ~/.bash-git-prompt --depth=1
elif [[ -f /usr/lib/bash-git-prompt/gitprompt.sh ]] && [[ $_SKIP_PROMPT_BASH_GIT_PROMPT = false ]]; then
# To only show the git prompt in or under a repository directory
GIT_PROMPT_ONLY_IN_REPO=1
# To use upstream's default theme
# GIT_PROMPT_THEME=Default
# To use upstream's default theme, modified by arch maintainer
GIT_PROMPT_THEME=Default_Arch
builtin source /usr/lib/bash-git-prompt/gitprompt.sh
elif [[ -f "${HOME}/.bash-git-prompt/gitprompt.sh" ]] && [[ $_SKIP_PROMPT_BASH_GIT_PROMPT = false ]]; then
# To only show the git prompt in or under a repository directory
GIT_PROMPT_ONLY_IN_REPO=1
# To use upstream's default theme
# GIT_PROMPT_THEME=Default
# To use upstream's default theme, modified by arch maintainer
GIT_PROMPT_THEME=Default_Arch
builtin source "${HOME}/.bash-git-prompt/gitprompt.sh"
# Bash Powerline (no need for patched fonts, supports git, previous command execution status, platform-dependent prompt symbols)
# Link: https://github.com/riobard/bash-powerline
# Install: curl https://raw.githubusercontent.com/riobard/bash-powerline/master/bash-powerline.sh > ~/.bash-powerline.sh
elif [[ -f "${HOME}/.bash-powerline.sh" ]] && [[ $_SKIP_PROMPT_BASH_POWERLINE = false ]]; then
builtin source "${HOME}/.bash-powerline.sh"
# Sexy Bash Prompt (supports git, 256 color)
# Link: https://github.com/twolfson/sexy-bash-prompt
# Install: (cd /tmp && ([[ -d sexy-bash-prompt ]] || git clone --depth 1 --config core.autocrlf=false https://github.com/twolfson/sexy-bash-prompt) && cd sexy-bash-prompt && make install)
elif [[ -f "${HOME}/.bash_prompt" ]] && [[ $_SKIP_PROMPT_SEXY_BASH_PROMPT = false ]]; then
builtin source "${HOME}/.bash_prompt"
# Liquid Prompt (adaptive prompt with low color and no glyphs)
# Link: https://github.com/nojhan/liquidprompt
# Install: git clone --branch stable https://github.com/nojhan/liquidprompt.git ~/liquidprompt
elif [[ -f "${HOME}/liquidprompt/liquidprompt" ]] && [[ $_SKIP_PROMPT_LIQUIDPROMPT = false ]]; then
builtin source "${HOME}/liquidprompt/liquidprompt"
# Original Powerline Status Line for Vim Bash Zsh fish tmux IPython Awesome i3 Qtile
# Link: https://github.com/powerline/powerline
# Install: https://medium.com/earlybyte/powerline-for-bash-6d3dd004f6fc
# NOTE: Requires Python and can be used with Trueline in Bash
# WARNING: This path may change or break in the future with new Python versions
elif [[ $_SKIP_PROMPT_POWERLINE = false ]]; then
_POWERLINE_PATH=$(find /usr/lib/python3* -type f -path "*/site-packages/powerline/bindings/bash/powerline.sh" 2>/dev/null | head -n 1)
if [[ -f "$_POWERLINE_PATH" ]]; then
builtin source "$_POWERLINE_PATH"
fi
fi
#######################################################
# Play nice with Midnight Commander subshell
# Link: https://midnight-commander.org/
# Link: https://superuser.com/questions/526201/how-to-change-the-prompt-of-mcs-subshell
#######################################################
if [[ -n "$(ps -p "${PPID}" -o comm= 2>/dev/null | grep -x mc)" ]]; then
# The Midnight Commander subshell doesn't like aliases for pwd
alias pwd &>/dev/null && unalias pwd
# Exit here
return
fi
#######################################################
# Blesh: Bash Line Editor replaces default GNU Readline (Do this step last)
# Link: https://github.com/akinomyoga/ble.sh
# Link for configuration: https://github.com/akinomyoga/ble.sh/blob/master/blerc
# WARNING: Can be buggy with certain prompts (like Trueline)
# To Update (in a ble.sh session): ble-update
# To Install:
# mkdir -P ~/.local/share/blesh/src && cd ~/.local/share/blesh/src
# git clone --recursive --depth 1 --shallow-submodules https://github.com/akinomyoga/ble.sh.git
# make -C ble.sh install PREFIX=~/.local
# To Run Without Installation:
# git clone --recursive --depth 1 --shallow-submodules https://github.com/akinomyoga/ble.sh.git
# make -C ble.sh
#######################################################
# Define an array of possible locations for ble.sh (checked in order)
_BLESH_PATHS=(
"${XDG_DATA_HOME:-${HOME}/.local/share}/blesh/ble.sh" # User-level installation
"${XDG_DATA_HOME:-${HOME}/.local/share}/doc/blesh/ble.sh" # User-level installation
"${HOME}/ble.sh/out/ble.sh" # Local Git installation
"/usr/share/blesh/ble.sh" # System-wide installation
)
# Loop through each potential path to find where ble.sh might be located
for _BLESH_PATH in "${_BLESH_PATHS[@]}"; do
# Check if ble.sh exists at the current path in the loop
if [[ -f $_BLESH_PATH ]]; then
# Check if Blesh should be skipped
if [[ $_SKIP_BLESH = false ]]; then
# If found, source ble.sh from the located path
builtin source "${_BLESH_PATH}"
# Set the prompt end-of-line mark to a specific character
bleopt prompt_eol_mark='⏎'
# Easier to read syntax highlighting for function names
ble-face -s syntax_function_name fg=171,bold
ble-face -s command_function fg=171
ble-face -s varname_expr fg=171,bold
# Bind 'C-d' to exit in ble.sh and suppress any output or error
ble-bind -x 'C-d' 'exit' > /dev/null 2>&1 # CTRL+d to exit
# Create an alias to load/reload ble.sh
alias blesh="ble-reload"
else
# Create an alias to load/reload ble.sh
alias blesh="builtin source ${_BLESH_PATH} && bleopt prompt_eol_mark='⏎' && ble-face -s syntax_function_name fg=171,bold && ble-face -s command_function fg=171 && ble-face -s varname_expr fg=171,bold && ble-bind -x 'C-d' 'exit' > /dev/null 2>&1"
fi
# Exit the loop as ble.sh has been found and sourced
break
fi
done
# Clean up
unset _BLESH_PATH
unset _BLESH_PATHS
#######################################################
### HISTORY DEFAULTS
#######################################################
# If missing, recreate a new empty history file so apps don't show errors
if [[ -z ${HISTFILE+x} ]]; then
[[ ! -f "${HOME}/.bash_history" ]] && touch "${HOME}/.bash_history"
else
[[ ! -f "$HISTFILE" ]] && touch "$HISTFILE"
fi
# Turn off bash history completely
# set +o history
# Enable history expansion with space
# E.g. typing !!<space> will replace the !! with your last command
bind Space:magic-space
# Huge history. Doesn't appear to slow things down, so why not?
export HISTFILESIZE=100000
export HISTSIZE=${HISTFILESIZE}
# Avoid duplicate lines in the history and do not add lines that start with a space
export HISTCONTROL=ignoreboth:erasedups
# Append to history instead of overwriting it so if you start a new terminal, you have old session history
shopt -s histappend histverify
# Save and reload the history after each command finishes
# WARNING: Some of these mess up the history counter and is slower as history grows larger
# WARNING: It can also break some more advanced installed prompts
# This has issues with McFly so only set if it's not installed
if ! hascommand --strict mcfly; then
## Append new history to history file, clear internal history list, and re-read the history file
export PROMPT_COMMAND="history -a; history -c; history -r; ${PROMPT_COMMAND}"
## -Or- just record each line as it gets issued but new history is not in other sessions (faster)
#export PROMPT_COMMAND="history -a; ${PROMPT_COMMAND}"
fi
# Save multi-line commands as one command
shopt -s cmdhist
# Consecutive duplicate commands, invocations of common commands like ls without parameters,
# plus calls to the bg, fg and exit built-ins will not be appended to the history list
export HISTIGNORE='&:[ ]*:ls:ll:[bf]g:history:clear:cls:exit'
# Use standard ISO 8601 timestamp
# %F equivalent to %Y-%m-%d
# %T equivalent to %H:%M:%S (24-hours format)
export HISTTIMEFORMAT='%F %T '
# Allow CTRL+S for history navigation (with CTRL+R)
stty -ixon
#######################################################
# Terminology is a graphical EFL terminal emulator that can run in TTY sessions
# To split the window horizontally press Ctrl+Shift+PgUp
# To split the window vertically press Ctrl+Shift+PgDn
# To create Tabs press Ctrl+Shift+T and cycle through using Ctrl+1-9
# Link: https://github.com/borisfaure/terminology
# Link: https://linoxide.com/terminology-terminal/
#######################################################
if [[ $_SKIP_TERMINOLOGY_TTY = false ]] && hascommand --strict terminology; then
# If we are in a TTY window , not in TMUX, and not logged in via SSH...
if [[ "$(tty)" =~ /dev/tty ]] && [[ ! "$TERM" =~ screen ]] && [[ -z "$SSH_CLIENT" ]] && [[ -z "$SSH_TTY" ]]; then
# If TMUX is installed and set to load at TTY
if [[ $_TMUX_LOAD_TTY = true ]] && hascommand --strict tmux; then
# Get the default session name
if [[ -z "${_TMUX_LOAD_SESSION_NAME}" ]]; then
if [[ "$(tmux list-sessions 2> /dev/null | wc -l)" -gt 0 ]]; then
_TMUX_LOAD_SESSION_NAME=""
else
_TMUX_LOAD_SESSION_NAME="$(whoami)"
fi
fi
# Create the TMUX session if it doesn't exist
TMUX='' tmux -u new-session -d -s "${_TMUX_LOAD_SESSION_NAME}" 2> /dev/null
terminology --fullscreen --borderless --256color --exec "tmux attach -t ${_TMUX_LOAD_SESSION_NAME}"
# No TMUX
else
terminology --fullscreen --borderless --256color && exit
fi
fi
#######################################################
# Automatically launch TMUX if this is a TTY Console or SSH session
# Most terminals can launch TMUX and automatically exit when TMUX is detached:
# konsole -e 'tmux new-session -A -s main'
# xfce4-terminal -e 'tmux new-session -A -s main'
# gnome-terminal -e 'tmux new-session -A -s main'
# kitty sh -c "tmux new-session -A -s main"
# terminology --exec "tmux new-session -A -s main"
# guake -e tmux
# Yakuake Profile -> Command -> /bin/bash -c "tmux new-session -A -s main"
# Alacritty: https://github.com/alacritty/alacritty/issues/2956
# ssh user@server -t tmux new-session -A -s main
#######################################################
# If TMUX is installed...
elif hascommand --strict tmux; then
# We're in a TTY terminal...
if [[ "$(tty)" =~ /dev/tty ]] && [[ ! "$TERM" =~ screen ]]; then
[[ $_TMUX_LOAD_TTY = true ]] && tm
# We're logged in via SSH...
elif [[ -n "$SSH_CLIENT" ]] || [[ -n "$SSH_TTY" ]]; then
[[ $_TMUX_LOAD_SSH = true ]] && tm
# We are local and not using SSH or TTY...
else
[[ $_TMUX_LOAD_LOCAL = true ]] && tm
fi
fi
#######################################################
# Tilix VTE Configuration
# Link: https://gnunn1.github.io/tilix-web/manual/vteconfig/
#######################################################
if [[ $TILIX_ID ]] || [[ $VTE_VERSION ]]; then
if [[ -f /etc/profile.d/vte.sh ]]; then
source /etc/profile.d/vte.sh
fi
fi