Last active
January 15, 2025 21:14
-
-
Save Raiu/579a993b9c5dcda726bf561ffc9df3f4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# | |
# Bash and Vim Environment Setup Script | |
# ===================================== | |
# | |
# Purpose: | |
# Automates the setup of a Bash environment with practical defaults, adhering | |
# to XDG Base Directory standards, ensuring clean, isolated, and maintainable configurations. | |
# | |
# Features: | |
# - Configures Bash with custom `.bashrc` and `.profile`. | |
# - Sets up Vim with vim-plug for modern plugin management. | |
# - Supports quiet mode (-q) for automated scripts. | |
# - Includes cleanup option to remove old configurations and history files. | |
# - Fully POSIX-compliant for compatibility with minimal environments. | |
# | |
# Usage: | |
# sh -c "$(curl -fsSL https://gist.githubusercontent.com/Raiu/579a993b9c5dcda726bf561ffc9df3f4/raw/setup_bash.sh)" | |
# sh -c "$(curl -fsSL https://gist.githubusercontent.com/Raiu/579a993b9c5dcda726bf561ffc9df3f4/raw/setup_bash.sh)" -- -q | |
# sh -c "$(curl -fsSL https://gist.githubusercontent.com/Raiu/579a993b9c5dcda726bf561ffc9df3f4/raw/setup_bash.sh)" -- -q --clean | |
# sh -c "$(curl -fsSL https://gist.githubusercontent.com/Raiu/579a993b9c5dcda726bf561ffc9df3f4/raw/setup_bash.sh)" -- --clean | |
# | |
# Other useful packages: | |
# bat, fzf, | |
# Eza: | |
# curl -sL https://github.com/eza-community/eza/releases/latest/download/eza_x86_64-unknown-linux-gnu.tar.gz | tar xz -C /tmp && sudo mv /tmp/eza /usr/local/bin/eza | |
# | |
set -e | |
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" | |
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" | |
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" | |
BASH_DIR="${XDG_CONFIG_HOME}/bash" | |
PROFILE="${BASH_DIR}/profile" | |
BASHRC="${BASH_DIR}/bashrc" | |
VIM_DIR="${XDG_CONFIG_HOME}/vim" | |
VIMRC="${VIM_DIR}/vimrc" | |
VIMRC_MIN="${VIM_DIR}/vimrc.minimal" | |
CLEANUP_FILES=".bash_history:.bashrc:.bash_profile:.inputrc:.vimrc:.viminfo:.wget-hsts:.lesshst:.sudo_as_admin_successful" | |
log() { [ "$QUIET" -ne 1 ] && echo "$@"; } | |
error() { echo "Error: $*" >&2; exit 1; } | |
exist() { command -v "$1" >/dev/null 2>&1; } | |
BASHRC_CONTENT=$( | |
cat <<'EOF' | |
# Helpers | |
_exist() { command -v "$1" >/dev/null 2>&1; } | |
# Blues: Light (BL1), Medium (BL2), Sky (BL3) | |
BL1="\[$(tput setaf 12)\]" BL2="\[$(tput setaf 45)\]" BL3="\[$(tput setaf 51)\]" | |
# Reds: Bright (RD1), Crimson (RD2), Intense (RD3) | |
RD1="\[$(tput setaf 9)\]" RD2="\[$(tput setaf 160)\]" RD3="\[$(tput setaf 196)\]" | |
# Greens: Light (GR1), Medium (GR2), Deep (GR3) | |
GR1="\[$(tput setaf 10)\]" GR2="\[$(tput setaf 40)\]" GR3="\[$(tput setaf 46)\]" | |
# Yellows/Oranges: Bright Yellow (YL1), Intense Yellow (YL2), Light Orange (OR1), Dark Orange (OR2) | |
YL1="\[$(tput setaf 11)\]" YL2="\[$(tput setaf 226)\]" OR1="\[$(tput setaf 208)\]" OR2="\[$(tput setaf 202)\]" | |
# Purples: Bright (PR1), Deep (PR2), Vibrant (PR3) | |
PR1="\[$(tput setaf 13)\]" PR2="\[$(tput setaf 165)\]" PR3="\[$(tput setaf 201)\]" | |
# White/Black: Bright White (WH1), Light White (WH2), Light Gray (GY1), Dark Gray (GY2), Black (BK1) | |
WH1="\[$(tput setaf 231)\]" WH2="\[$(tput setaf 15)\]" GY1="\[$(tput setaf 250)\]" GY2="\[$(tput setaf 240)\]" BK1="\[$(tput setaf 232)\]" | |
# Reset | |
RES="\[$(tput sgr0)\]" | |
# Set XDG Base Directories | |
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" | |
export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" | |
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" | |
export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" | |
mkdir -p "${XDG_CACHE_HOME}/bash" "${XDG_DATA_HOME}/bash" "${XDG_STATE_HOME}/bash" || { | |
echo "Failed to create directories" >&2 | |
} | |
# Other exports | |
export FZF_DEFAULT_OPTS="--height 40% --layout=reverse --border" | |
# General Environment Settings | |
umask 022 | |
shopt -s checkwinsize | |
shopt -s autocd | |
shopt -s globstar | |
# History Configuration | |
HISTFILE="${XDG_STATE_HOME}/bash/history" | |
[ ! -f "$HISTFILE" ] && touch "$HISTFILE" | |
HISTSIZE=1000 | |
HISTFILESIZE=2000 | |
shopt -s histappend | |
export HISTCONTROL=ignoredups:erasedups | |
# Enable Completion | |
if [ -f "$XDG_CONFIG_HOME/bash_completion" ]; then | |
. "$XDG_CONFIG_HOME/bash_completion" | |
elif [ -f "/usr/share/bash-completion/bash_completion" ]; then | |
. "/usr/share/bash-completion/bash_completion" | |
fi | |
if _exist fzf; then | |
fzf_kbpath=$(find /usr/share -type f -name "key-bindings.bash" 2>/dev/null | head -n 1) | |
[ -f "$fzf_kbpath" ] && source "$fzf_kbpath" | |
fi | |
# Binds | |
if [ "$PS1" ] ; then | |
bind '"\C-@": " \C-u$(fzf)\e\C-e\er"' # open fzf with ctrl-space | |
bind 'set show-all-if-ambiguous on' | |
bind 'TAB:menu-complete' | |
fi | |
# Prompt | |
LAST_DIR="" | |
chpwd() { [[ "$LAST_DIR" != "$PWD" ]] && LAST_DIR="$PWD" && ls; } | |
prompt() { | |
PS1="\n${OR1}\u${RES}@${BL1}\h${RES}: ${BL3}\w${RES}\n" | |
[[ $_ecode -eq 0 ]] && PS1+="${GR3}→${RES} " || PS1+="${RD3}→${RES} " | |
} | |
export PROMPT_COMMAND='_ecode=$?; history -a; history -c; history -r; chpwd; prompt' | |
# Functions | |
batman() { | |
_exist bat || { echo "Error: 'bat' is not installed." >&2; return 1; } | |
[[ -z "$1" ]] && { echo "Usage: batman <command>" >&2; return 1; } | |
man "$1" | col -bx | bat --paging=always --language=man | |
} | |
# Alias Definitions | |
_exist "batcat" && alias bat="batcat" | |
_exist "fdfind" && alias fd="fdfind" | |
if _exist "eza"; then | |
alias ls='eza --icons --group-directories-first' | |
alias lsa='eza -a --icons --group-directories-first' | |
alias lt='eza -T --group-directories-first --icons --git' | |
alias lta='eza -Ta --group-directories-first --icons --git' | |
alias ll='eza -lmh --group-directories-first --color-scale --icons' | |
alias la='eza -lamhg --group-directories-first --color-scale --icons --git' | |
alias laa='eza -lamhg@ --group-directories-first --color-scale --icons --git' | |
alias lx='eza -lbhHigUmuSa@ --group-directories-first --color-scale --icons --git --time-style=long-iso' | |
else | |
alias ls='ls -h --group-directories-first --color=auto' | |
alias lsa='ls -Ah --group-directories-first --color=auto' | |
alias ll='ls -lh --group-directories-first --color=auto' | |
alias la='ls -lAh --group-directories-first --color=auto' | |
fi | |
alias c='clear' | |
alias h='history' | |
alias ..='cd ..' | |
alias ...='cd ../..' | |
alias md='mkdir -p' | |
alias rd='rmdir' | |
alias egrep='grep -E --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn,.idea,.tox}' | |
alias fgrep='grep -F --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn,.idea,.tox}' | |
alias wget='wget --no-hsts' | |
alias bed='vim ${XDG_CONFIG_HOME}/bash/bashrc' | |
alias bre='source ${XDG_CONFIG_HOME}/bash/bashrc' | |
alias ved='vim ${XDG_CONFIG_HOME}/vim/vimrc' | |
alias vim.recover='vim -u ${XDG_CONFIG_HOME}/vim/vimrc.minimal -U NONE -i NONE ${XDG_CONFIG_HOME}/vim/vimrc' | |
alias minvim='vim -u ${XDG_CONFIG_HOME}/vim/vimrc.minimal -U NONE -i NONE' | |
EOF | |
) | |
PROFILE_CONTENT=$( | |
cat <<'EOF' | |
# XDG Base Directory Specification | |
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" | |
export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" | |
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" | |
export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" | |
# Ensure XDG directories exist | |
mkdir -p "${XDG_CONFIG_HOME}/bash" "${XDG_DATA_HOME}/bash"\ | |
"${XDG_CACHE_HOME}/bash" "${XDG_STATE_HOME}/bash" | |
# Ensure PATH includes common directories | |
old_path=$PATH:"$HOME/.local/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin" | |
new_path="" | |
for dir in $(echo "$old_path" | tr ':' '\n' | sed '1!G;h;$!d'); do | |
case ":$new_path:" in | |
*":$dir:"*) ;; | |
*) new_path="$dir${new_path:+:$new_path}" ;; | |
esac | |
done | |
export PATH="$new_path" | |
# Source interactive shell configurations only if the shell is Bash and interactive | |
if [ -n "$BASH" ] && [ "$-" != "${-#*i}" ]; then | |
if [ -f "$XDG_CONFIG_HOME/bash/bashrc" ]; then | |
. "$XDG_CONFIG_HOME/bash/bashrc" | |
elif [ -f "$HOME/.bashrc" ]; then | |
. "$HOME/.bashrc" | |
fi | |
fi | |
# VIM | |
export VIMINIT='let $MYVIMRC = $XDG_CONFIG_HOME . "/vim/vimrc" | source $MYVIMRC' | |
EOF | |
) | |
VIMRC_CONTENT=$( | |
cat <<'EOF' | |
" ====== XDG Compliance ====== | |
let $XDG_CONFIG_HOME = exists('$XDG_CONFIG_HOME') ? $XDG_CONFIG_HOME : expand('~/.config') | |
let $XDG_DATA_HOME = exists('$XDG_DATA_HOME') ? $XDG_DATA_HOME : expand('~/.local/share') | |
let $VIMDOTDIR = $XDG_CONFIG_HOME . '/vim' | |
let $VIMDATADIR = $XDG_DATA_HOME . '/vim' | |
" ====== General Settings ====== | |
syntax on " Enable syntax highlighting | |
filetype plugin indent on " Enable filetype detection and indentation | |
set clipboard=unnamedplus " Use system clipboard | |
set hlsearch " Highlight search results | |
set ignorecase smartcase " Case-insensitive search unless uppercase is used | |
set incsearch " Incremental search | |
set nocursorline " Highlight the current line | |
set number relativenumber " Show line numbers | |
set mouse=a " Enable mouse support | |
set scrolloff=5 " Keep 5 lines visible when scrolling | |
set showmode " Display mode in status line | |
set sidescrolloff=5 " Horizontal scrolling offset | |
set splitbelow splitright " Open splits to the right and below | |
set tabstop=4 shiftwidth=4 expandtab " Use spaces instead of tabs | |
set timeoutlen=500 " 500ms for mappings | |
set ttimeoutlen=10 " Faster key timeout | |
set updatetime=300 " Faster updates for a smoother experience | |
" ====== Directories and Files ====== | |
silent! call mkdir($VIMDATADIR . '/backup', 'p') | |
silent! call mkdir($VIMDATADIR . '/undo', 'p') | |
silent! call mkdir($VIMDATADIR . '/swap', 'p') | |
set backupdir=$VIMDATADIR/backup// | |
set undodir=$VIMDATADIR/undo// | |
set directory=$VIMDATADIR/swap// | |
set backup | |
set undofile | |
set viminfo+=n$VIMDATADIR/viminfo | |
" ====== Colorscheme ====== | |
colorscheme slate | |
set background=dark | |
" ====== Statusline ====== | |
set statusline=%<%f\ %h%m%r\ %y\ [%{&fileformat}]\ [%{&fileencoding}]\ %=%-14.(%l,%c%V%)\ %P | |
set laststatus=2 | |
" ====== Mode Settings ====== | |
" Insert mode cursorline | |
au InsertEnter * set cul | |
au InsertLeave * set nocul | |
" Insert mode lin shape | |
let &t_SI = "\e[6 q" | |
let &t_EI = "\e[2 q" | |
" ====== Key Mappings ====== | |
let mapleader = "," | |
" Edit and Source vimrc | |
nnoremap <leader>ev :e $MYVIMRC<CR> | |
nnoremap <leader>sv :source $MYVIMRC<CR> | |
" Toggle between Normal and Insert mode | |
nnoremap <C-@> a | |
inoremap <C-@> <Esc> | |
vnoremap <C-@> <Esc> | |
" Easy file saving and quitting | |
nnoremap <leader>w :w<CR> | |
nnoremap <leader>q :q<CR> | |
" Save all files and Quit all files | |
nnoremap <leader>W :wa<CR> | |
nnoremap <leader>Q :qa<CR> | |
" Clear search highlights | |
nnoremap <leader>h :nohlsearch<CR> | |
" Window navigation | |
nnoremap <C-h> <C-w>h | |
nnoremap <C-j> <C-w>j | |
nnoremap <C-k> <C-w>k | |
nnoremap <C-l> <C-w>l | |
" Split management | |
nnoremap <leader>v :vsplit<CR> | |
nnoremap <leader>s :split<CR> | |
" Quick search and replace | |
nnoremap <leader>r :%s//g<Left><Left> | |
" Yank selected text into register (z) | |
vnoremap <leader>y "zy | |
" Search-replace using text from register (z) | |
nnoremap <leader>c :<C-u>let @/ = escape(@z, '/\\')<CR>:%s/<C-r>=@z<CR>/<C-r>=@z<CR>/g<Left><Left> | |
vnoremap <leader>c :<C-u>let @/ = escape(@z, '/\\')<CR>:'<,'>s/<C-r>=@z<CR>/<C-r>=@z<CR>/g<Left><Left> | |
" Search-replace using selection as search pattern | |
vnoremap <leader>r "hy:let @/ = escape(@h, '/\\.*^$[]~')<CR>:%s/<C-r>=@/<CR>/<C-r>=@/<CR>/g<Left><Left> | |
" edit or source vimrc | |
nnoremap <Leader>e :edit $MYVIMRC<CR> | |
" vv for Visual Character mode | |
nnoremap vv v | |
vnoremap vv v | |
" vb for Visual Block mode | |
nnoremap vb <C-v> | |
vnoremap vb <C-v> | |
" Manual trimming for all file types | |
nnoremap <leader>t :silent! %s/\s\+$//e<CR>:echo "Trailing whitespace removed!"<CR> | |
" Toggle search hl | |
nnoremap <F2> :set hlsearch!<CR>:echo "Search hl " . (&hlsearch ? "enabled" : "disabled")<CR> | |
" Show list of open buffers | |
nnoremap <F3> :ls<CR>:b | |
" Toggle file explorer | |
nnoremap <F4> :Lexplore<CR> | |
" Cycle buffers (silent) | |
nnoremap <F5> :silent! bnext<CR>:echo "Next buffer: " . bufname('%')<CR> | |
nnoremap <F6> :silent! bprev<CR>:echo "Prev buffer: " . bufname('%')<CR> | |
" ====== Functions ====== | |
" ====== Autocommands ====== | |
if has("autocmd") | |
" Automatically source vimrc on save | |
autocmd! BufWritePost $MYVIMRC silent! source $MYVIMRC | redraw! | echo "vimrc sourced" | |
" Automatically reload files when changed externally | |
autocmd FocusGained,BufEnter * checktime | |
" Highlight yanked text | |
autocmd TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=300} | |
" Automatically create parent directories on save | |
autocmd BufWritePre * call mkdir(expand('<afile>:p:h'), 'p') | |
" Trim trailing whitespace on save for specific file types | |
autocmd BufWritePre *.c,*.cpp,*.h,*.py,*.js,*.ts,*.html,*.css,*.json,*.yml,*.yaml,*.sql,*.sh,*.go %s/\s\+$//e | |
endif | |
" ====== Miscellaneous ====== | |
EOF | |
) | |
VIMRC_MIN_CONTENT=$( | |
cat <<'EOF' | |
filetype plugin indent on | |
syntax on | |
set backspace=indent,eol,start | |
set hlsearch | |
set ignorecase smartcase | |
set nocompatible | |
set noerrorbells | |
set nowrap | |
set number | |
set ruler | |
set tabstop=4 shiftwidth=4 expandtab | |
EOF | |
) | |
symlink() { | |
[ ! -d "$(dirname "$2")" ] && mkdir -p "$(dirname "$2")" | |
[ -L "$2" ] && [ "$(readlink -f "$2")" = "$(readlink -f "$1")" ] && return 0 | |
[ -e "$2" ] && [ ! -L "$2" ] && mv "$2" "$2.bak" | |
ln -sf "$1" "$2" | |
} | |
download_file() { | |
if exist curl; then | |
curl -sSfLo "$2" "$1" || error "Failed to download $1 with curl." | |
elif exist wget; then | |
wget -qO "$2" "$1" || error "Failed to download $1 with wget." | |
else | |
error "curl or wget is required." | |
fi | |
} | |
create_xdg_dirs() { | |
mkdir -p "${BASH_DIR}" "${XDG_CACHE_HOME}/bash" "${XDG_DATA_HOME}/bash" \ | |
"${VIM_DIR}" "${XDG_CACHE_HOME}/vim" "${XDG_DATA_HOME}/vim" | |
} | |
deploy_bash() { | |
echo "${BASHRC_CONTENT}" >"${BASHRC}" | |
echo "${PROFILE_CONTENT}" >"${PROFILE}" | |
symlink "${PROFILE}" "$HOME/.profile" | |
} | |
deploy_vim() { | |
echo "${VIMRC_CONTENT}" >"${VIMRC}" | |
echo "${VIMRC_MIN_CONTENT}" >"${VIMRC_MIN}" | |
} | |
check_dependencies() { | |
exist bash || error "bash is required but not installed." | |
exist vim || error "vim is required but not installed." | |
exist curl || exist wget || error "curl or wget is required but neither is installed." | |
} | |
cleanup() { | |
log "Cleaning up old configurations and history files..." | |
echo "$CLEANUP_FILES" | tr ':;' '\n' | while IFS= read -r file; do | |
target="$HOME/$file" | |
if [ -e "$target" ]; then | |
rm -rf "$target" && log "Removed: $file" | |
fi | |
done | |
log "Cleanup complete." | |
} | |
prompt_cleanup() { | |
printf "Do you want to clean up old configurations and history files? [y/N]: " | |
read -r response | |
case "$response" in | |
[yY][eE][sS] | [yY]) cleanup ;; | |
*) log "Skipping cleanup." ;; | |
esac | |
} | |
main() { | |
QUIET=0 | |
CLEANUP=0 | |
for arg in "$@"; do | |
case "$arg" in | |
-q | --quiet) QUIET=1 ;; | |
-c | --clean) CLEANUP=1 ;; | |
*) error "Invalid argument: $arg" ;; | |
esac | |
done | |
log "Setting up..." | |
log "* Verifying dependencies..." | |
check_dependencies | |
log "* Creating XDG directories..." | |
create_xdg_dirs | |
log "* Deploying Bash configuration..." | |
deploy_bash | |
log "* Installing Vim..." | |
deploy_vim | |
if [ "$CLEANUP" -eq 1 ]; then | |
cleanup | |
elif [ "$QUIET" -eq 0 ]; then | |
prompt_cleanup | |
fi | |
log "Setup complete! Restart your shell or run: exec bash" | |
} | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment