#!/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 signature’s 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}${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}${RESET} - Path of the file in the Git repository" echo -e " ${BRIGHT_YELLOW}${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 don’t 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 # 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 " 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 file_name # Example: git difftool file_name # Example: git difftool 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 "$(' # 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 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/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/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 user’s 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}${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=$( "${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 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 - Saves the current directory as "bookmark_name" # g - Goes (cd) to the directory associated with "bookmark_name" # p - Prints the directory associated with "bookmark_name" # d - 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 !! 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