Files
infra/dotfiles/common/.bashrc
2026-04-03 22:46:44 +02:00

432 lines
8.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =========================
# Portable Bash config
# Target: Void Linux + FreeBSD
# =========================
# Exit if not interactive
[[ $- != *i* ]] && return
# --- environment detection
case "$(uname -s)" in
Linux) PLATFORM="linux" ;;
FreeBSD) PLATFORM="freebsd" ;;
*) PLATFORM="other" ;;
esac
export PLATFORM
# --- history
HISTSIZE=10000
HISTFILESIZE=20000
HISTCONTROL=ignoredups:erasedups
HISTIGNORE='ls:ll:la:l:pwd:exit:clear:history'
HISTTIMEFORMAT=
shopt -s histappend
shopt -s checkwinsize
__history_sync() {
history -a
history -c
history -r
}
# --- shell options
set -o emacs
shopt -s cmdhist
# --- PATH
[ -d "$HOME/.local/bin" ] && PATH="$HOME/.local/bin:$PATH"
[ -d "$HOME/bin" ] && PATH="$HOME/bin:$PATH"
export PATH
if [ -d "$HOME/.bashrc.d" ]; then
for bashrc_file in "$HOME"/.bashrc.d/*.sh; do
[ -r "$bashrc_file" ] || continue
. "$bashrc_file"
done
fi
# =========================
# Aliases (portable)
# =========================
if ls --color=auto >/dev/null 2>&1; then
alias ls='ls --color=auto'
else
alias ls='ls -G'
fi
alias l='ls'
alias ll='ls -lh'
alias la='ls -lah'
if grep --color=auto "" /dev/null >/dev/null 2>&1; then
alias grep='grep --color=auto'
fi
alias ..='cd ..'
alias ...='cd ../..'
alias cls='clear'
alias cp='cp -i'
alias mv='mv -i'
alias rm='rm -i'
# Git
alias g='git'
alias ga='git add'
alias gb='git branch'
alias gc='git commit'
alias gca='git commit -a'
alias gco='git checkout'
alias gd='git diff'
alias gl='git pull'
alias gp='git push'
alias gs='git status -sb'
alias glog='git log --oneline --decorate --graph -20'
# =========================
# Git prompt helpers
# =========================
__git_branch() {
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 1
command git symbolic-ref --quiet --short HEAD 2>/dev/null && return
command git rev-parse --short HEAD 2>/dev/null && return
return 1
}
__git_flags() {
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 1
local flags=""
git diff --no-ext-diff --quiet --exit-code 2>/dev/null || flags="${flags}*"
git diff --cached --no-ext-diff --quiet --exit-code 2>/dev/null || flags="${flags}+"
[ -n "$(git ls-files --others --exclude-standard 2>/dev/null)" ] && flags="${flags}?"
printf '%s' "$flags"
}
# =========================
# Colors
# =========================
if [ -t 1 ]; then
C_RESET='\[\033[0m\]'
C_USER='\[\033[1;97m\]'
C_HOST='\[\033[38;5;250m\]'
C_PATH='\[\033[1;96m\]'
C_GIT='\[\033[38;5;197m\]'
C_GIT_FLAG='\[\033[38;5;204m\]'
C_VENV='\[\033[38;5;177m\]'
C_CONT='\[\033[38;5;208m\]'
C_OK='\[\033[38;5;46m\]'
C_ERR='\[\033[38;5;196m\]'
C_DIM='\[\033[38;5;240m\]'
else
C_RESET=''
C_USER=''
C_HOST=''
C_PATH=''
C_GIT=''
C_GIT_FLAG=''
C_VENV=''
C_CONT=''
C_OK=''
C_ERR=''
C_DIM=''
fi
# =========================
# Prompt helpers
# =========================
__prompt_git() {
local branch flags
branch="$(__git_branch)" || return 0
flags="$(__git_flags)"
if [ -n "$flags" ]; then
printf ' %s<%s%s:%s%s>%s' \
"$C_GIT" "$branch" "$C_DIM" "$C_GIT_FLAG" "$flags" "$C_RESET"
else
printf ' %s<%s>%s' "$C_GIT" "$branch" "$C_RESET"
fi
}
__prompt_venv() {
[ -n "${VIRTUAL_ENV-}" ] || return 0
printf ' %s(%s)%s' "$C_VENV" "${VIRTUAL_ENV##*/}" "$C_RESET"
}
__prompt_container() {
local marker=""
if [ -f /run/.containerenv ]; then
marker="ctr"
elif [ -f /.dockerenv ]; then
marker="docker"
elif [ -n "${container-}" ]; then
marker="$container"
elif [ -n "${debian_chroot-}" ]; then
marker="$debian_chroot"
fi
[ -n "$marker" ] || return 0
printf ' %s[%s]%s' "$C_CONT" "$marker" "$C_RESET"
}
# =========================
# Prompt
# =========================
__set_prompt() {
local last_status="$1"
local host_part symbol status_color
if [ -n "${SSH_CONNECTION-}" ] || [ -n "${SSH_CLIENT-}" ]; then
host_part="${C_USER}\u${C_DIM}@${C_HOST}\h${C_RESET}"
else
host_part="${C_USER}\u${C_RESET}"
fi
if [ "${EUID:-$(id -u)}" -eq 0 ]; then
symbol="#"
else
symbol=""
fi
if [ "$last_status" -eq 0 ]; then
status_color="${C_OK}"
else
status_color="${C_ERR}"
fi
PS1="${host_part}${C_DIM}:${C_RESET}${C_PATH}\w${C_RESET}$(__prompt_venv)$(__prompt_container)$(__prompt_git) ${status_color}${symbol}${C_RESET} "
}
__prompt_command() {
local last_status=$?
__history_sync
__set_prompt "$last_status"
}
PROMPT_COMMAND="__prompt_command"
# =========================
# Readline / history search
# =========================
bind '"\e[A": history-search-backward'
bind '"\e[B": history-search-forward'
bind '"\e[1;5C": forward-word'
bind '"\e[1;5D": backward-word'
bind '"\e[5C": forward-word'
bind '"\e[5D": backward-word'
# =========================
# fzf history on Ctrl-r
# =========================
if command -v fzf >/dev/null 2>&1; then
__fzf_history() {
local selected history_data
if command -v tac >/dev/null 2>&1; then
history_data="$(
builtin history \
| sed 's/^[[:space:]]*[0-9][0-9]*[[:space:]]*//' \
| awk '!seen[$0]++' \
| awk 'NF' \
| tac
)"
else
history_data="$(
builtin history \
| sed 's/^[[:space:]]*[0-9][0-9]*[[:space:]]*//' \
| awk '!seen[$0]++' \
| awk 'NF' \
| tail -r
)"
fi
selected="$(printf '%s\n' "$history_data" | fzf --height 40% --layout=reverse --border --prompt='history> ')" || return
READLINE_LINE=$selected
READLINE_POINT=${#READLINE_LINE}
}
bind -x '"\C-r":__fzf_history'
fi
# =========================
# Completion
# =========================
for bc in \
/usr/share/bash-completion/bash_completion \
/usr/local/share/bash-completion/bash_completion \
/etc/bash_completion
do
if [ -r "$bc" ]; then
. "$bc"
break
fi
done
# =========================
# zoxide - smarter cd
# =========================
if command -v zoxide >/dev/null 2>&1; then
eval "$(zoxide init bash)"
fi
# =========================
# Portable helpers
# =========================
mkcd() {
[ $# -eq 1 ] || return 1
mkdir -p -- "$1" && cd -- "$1" || return
}
__cd_resolve_case_insensitive() {
local target="$1"
local base rest component entry name
local -a parts matches
if [[ "$target" == /* ]]; then
base="/"
rest=${target#/}
else
base="."
rest=$target
fi
IFS='/' read -r -a parts <<< "$rest"
for component in "${parts[@]}"; do
[ -n "$component" ] || continue
case "$component" in
.|..)
base="$base/$component"
continue
;;
esac
matches=()
if [[ "$component" == .* ]]; then
for entry in "$base"/.*; do
[ -d "$entry" ] || continue
name=${entry##*/}
case "$name" in
.|..) continue ;;
esac
[[ ${name,,} == ${component,,} ]] && matches+=("$entry")
done
else
for entry in "$base"/*; do
[ -d "$entry" ] || continue
name=${entry##*/}
[[ ${name,,} == ${component,,} ]] && matches+=("$entry")
done
fi
case ${#matches[@]} in
0)
return 1
;;
1)
base=${matches[0]}
;;
*)
printf 'cd: ambiguous case-insensitive match for %s\n' "$target" >&2
printf '%s\n' "${matches[@]}" >&2
return 2
;;
esac
done
printf '%s\n' "$base"
}
cd() {
local resolved status
if builtin cd "$@" 2>/dev/null; then
return 0
fi
status=$?
if [ $# -ne 1 ]; then
builtin cd "$@"
return $?
fi
case "$1" in
''|-|-*)
builtin cd "$@"
return $?
;;
esac
resolved=$(__cd_resolve_case_insensitive "$1")
status=$?
case $status in
0)
builtin cd -- "$resolved"
;;
2)
return 1
;;
*)
builtin cd "$@"
;;
esac
}
extract() {
[ $# -eq 1 ] || {
printf 'usage: extract <archive>\n' >&2
return 1
}
[ -f "$1" ] || {
printf 'extract: file not found: %s\n' "$1" >&2
return 1
}
case "$1" in
*.tar.bz2|*.tbz2) tar xjf "$1" ;;
*.tar.gz|*.tgz) tar xzf "$1" ;;
*.tar.xz|*.txz) tar xJf "$1" ;;
*.tar) tar xf "$1" ;;
*.bz2) bunzip2 "$1" ;;
*.gz) gunzip "$1" ;;
*.xz) unxz "$1" ;;
*.zip) unzip "$1" ;;
*.7z) 7z x "$1" ;;
*) printf 'extract: unsupported archive: %s\n' "$1" >&2; return 1 ;;
esac
}
# =========================
# OS-specific small touches
# =========================
if [ "$PLATFORM" = "freebsd" ]; then
alias df='df -h'
alias du='du -h'
elif [ "$PLATFORM" = "linux" ]; then
alias df='df -h'
alias du='du -h'
fi
[ -r "$HOME/.bashrc.aliases" ] && . "$HOME/.bashrc.aliases"