#!/bin/bash # Mike Crute's .bashrc File (mcrute@gmail.com) # vim:set et: function clean_unalias { if alias $1 &> /dev/null; then unalias $1 fi } function have_command { command -v $1 >/dev/null } function load_all_from { [[ ! -d "$1" ]] && return for file in $1/*; do [[ -r "$file" ]] && source "$file" done } # # Prefix or Suffix Value to Path-like Variable # # usage: add_to_path_var [suffix] # # This function will (by default) suffix a path-like variable with another # path, provided the path does not already exist in the variable. If it does no # action is taken. Passing a third argument of 1 will result in the path being # appended to the variable. # function add_to_path_var() { local var_name="$1" local add_value="$2" local prefix=${3:-0} case :${!var_name}: in *:${add_value}:*) ;; *) if [ -z "${!var_name}" ]; then eval "$var_name=$add_value" else if [ $prefix -eq 1 ]; then eval "$var_name=$add_value:${!var_name}" else eval "$var_name=${!var_name}:$add_value" fi fi ;; esac } # # Test if variable contains sub-expression # # usage: var_contains # # Returns true if the contents of var_name contain value, otherwise false. # function var_contains() { local var_name="$1" local value="$2" [ ! -z "${!var_name}" -a -z "${!var_name##*$value*}" ] } # # XDG SETUP # export XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache} # systemd or something else should set this but just in-case if [[ -z "$XDG_RUNTIME_DIR" ]]; then export XDG_RUNTIME_DIR=$(mktemp -d) fi # # SHELL INITIALIZATION # export HISTCONTROL="erasedups:ignoreboth" export HISTSIZE=500000 export HISTFILESIZE=100000 export HISTTIMEFORMAT='%F %T ' export HISTIGNORE="&:[ ]*:exit:ls:ll:la:bg:fg:history:clear:reset" export EDITOR="vim" export LSCOLORS="ExGxBxDxCxEgEdxbxgxcxd" export LANG=en_US.UTF-8 export KRB5CCNAME=FILE:/tmp/krb5cc_`id -ru` export PASSWORD_STORE_ENABLE_EXTENSIONS=true export PASSWORD_STORE_GENERATED_LENGTH=20 export PASSWORD_STORE_CHARACTER_SET="[:alnum:]" # Allow each user to override their timezone if [[ -f "$HOME/.timezone" ]]; then export TZ="$(cat $HOME/.timezone)" fi set bell-style none set completion-ignore-case on MY_SHELL=$(ps -o command -p $$ | awk '/^[^C]/ { print $1 }') # Prompt 3 runs through a host process that breaks normal shell detection # but this can be discovered by looking at the PPID. This might be a bug # in Prompt because it exposes the shell as just "-l". if [[ "$(ps -c -o command -p $PPID | tail -n1)" =~ "PromptLocalTerminal" ]]; then MY_SHELL="bash" fi if [[ "$MY_SHELL" =~ "bash" ]]; then shopt -s checkwinsize shopt -s cdspell shopt -s histappend shopt -s cmdhist function _prompt_command { # This effectively forcibly synchronizes history lists between windows # such that a command entered in a different window will be available # for ^R in this window after the next prompt generation. history -a # Append current window history to history file # This can be nice to have commands always synced but also # disorienting. So disabling it for now. #history -n # Read new history entries to current window history list } export PROMPT_COMMAND=_prompt_command # Cache user and host instead of using \h and \u to avoid the hostname and # username lookup on every line. This can be really slow on a slow # connection or on systems running SSSD. 2019-9-22: Removed username from # the cache so that it switches to roo correctly on sudo. Will re-evaluate. # Colorful version (colors are for my normal stterm scheme, not ASCII) _clear="\[\e[39;0;49m\]" _cyan="\[\e[36;1;49m\]" _green="\[\e[91;1;49m\]" _gold="\[\e[33;1;49m\]" _red="\[\e[92;1;49m\]" export PS1="\t ${_green}$(hostname -s) ${_cyan}\W ${_gold}\\$ ${_clear}" load_all_from "$HOME/.bash_completion.d" if shopt | grep globstar &>/dev/null; then shopt -s globstar fi if shopt | grep dirspell &>/dev/null; then shopt -s dirspell fi # TODO: ## Enable history expansion with space ## E.g. typing !! will replace the !! with your last command #bind Space:magic-space #bind "set completion-map-case on" #bind "set show-all-if-ambiguous on" #bind "set mark-symlinked-directories on" function func_export { export -f $1 } elif [[ "$MY_SHELL" =~ "zsh" ]]; then export PS1="%n@$(hostname -s) %1d %# " autoload -U compinit compinit set -o emacs export HISTFILE="$HOME/.history" export SAVEHIST=$HISTSIZE # Use C-x C-e to edit the current command line autoload -U edit-command-line zle -N edit-command-line bindkey '\C-x\C-e' edit-command-line # By default, zsh considers many characters part of a word (e.g., _ and -). # Narrow that down to allow easier skipping through words via M-f and M-b. export WORDCHARS='*?[]~&;!$%^<>' function func_export { typeset -U $1 } fi # The st terminal is close enough to XTerm that if the right terminfo doesn't # exist then just pretend to be xterm. if [[ "$TERM" == "stterm-256color" && ! -f "/usr/share/terminfo/s/stterm-256color" ]]; then export TERM=xterm fi if [[ "$TERM" == "stterm-256color" ]]; then export TERM=st-256color fi # Fuck you RedHat unset command_not_found_handle clean_unalias rm have_command lesspipe && eval "$(lesspipe)" have_command lesspipe.sh && eval "$(lesspipe.sh)" [[ -r /etc/bash_completion ]] && source /etc/bash_completion # Mac OS will START X any time you invoke an X command # which makes it impossible to test if X is running. # # Also this can be very slow if X is being forwarded over an SSH connection and # these settings aren't that useful in that case anyhow so avoid setting them. if [[ `uname` != "Darwin" && !( "$DISPLAY" =~ ^localhost:.* && -n "$SSH_TTY") ]]; then # Only do this stuff if X is running if xdpyinfo &>/dev/null; then have_command xset && xset b 0 0 0 # NO BEEPING! [[ -e ~/.Xdefaults ]] && xrdb -merge ~/.Xdefaults fi fi if [ "$(uname)" == "Darwin" ]; then # Export CA roots from the keychain for Python requests. This allows using # custom root certificates in addition to the normal system roots. EXPORTED_CA_ROOTS="$HOME/.mac-ca-roots" [ -f "$EXPORTED_CA_ROOTS" ] && export REQUESTS_CA_BUNDLE="$EXPORTED_CA_ROOTS" function refresh_ca_bundle { ( security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain && \ security find-certificate -a -p /Library/Keychains/System.keychain ) > "$EXPORTED_CA_ROOTS" } func_export refresh_ca_bundle # Keychain does Kerberos on Mac OS, get out of its way unset KRB5CCNAME fi # # Silence macOS Presumtive Warnings # export BASH_SILENCE_DEPRECATION_WARNING=1 # # Ruby and RVM Setup # [[ -s ~/.rvm/scripts/rvm ]] && source ~/.rvm/scripts/rvm export RSPEC_FORMAT="documentation" alias testify="watchr ~/.watchr.rb" # # Go Setup # # Support versions of go < 1.12 if [[ -d "$HOME/go" ]]; then export GOPATH="$HOME/go" else # This is still needed in go > 1.12 but is used only for the module cache # so put it somewhere reasonable. if [[ ! -d "$HOME/.cache/go-path" ]]; then mkdir -p "$HOME/.cache/go-path" fi export GOPATH="$HOME/.cache/go-path" fi if [[ -d /usr/local/go ]]; then add_to_path_var PATH "/usr/local/go/bin" fi # # SETUP SYSTEM PATHS # # Start clean with stuff we want to override the system path _OPATH="$PATH" PATH="/usr/local/bin:/usr/local/sbin" # Push on all the system default stuff _IFS="$IFS"; IFS=: for pc in $_OPATH; do case "$pc" in # Why do distros seem to like to put this crap on the path? /usr/games|/usr/local/games) ;; # Skip past these, we'll add them later if relevant and want to prefix # them to the path not have them de-duplicated in-place "$HOME"/*) ;; *) add_to_path_var PATH "$pc" ;; esac done IFS="$_IFS"; unset _IFS # Locally compiled stuff LOCAL_PATH="$HOME/.local" if [[ -e "$LOCAL_PATH" ]]; then _include="-I${LOCAL_PATH}/include" if ! var_contains CFLAGS "$_include"; then CFLAGS="$CFLAGS $_include" fi unset _include _ldflags="-L${LOCAL_PATH}/lib" if ! var_contains LDFLAGS "$_ldflags"; then LDFLAGS="$LDFLAGS $_ldflags" fi unset _ldflags add_to_path_var PATH "${LOCAL_PATH}/bin" 1 add_to_path_var PATH "${LOCAL_PATH}/sbin" 1 fi if [[ -e "/snap/bin" ]]; then add_to_path_var PATH /snap/bin fi # This should take precedence over the $HOME/.local stuff if [[ -e "$HOME/bin" ]]; then add_to_path_var PATH "$HOME/bin" 1 fi if [[ -d /Applications/Postgres.app ]]; then add_to_path_var PATH "/Applications/Postgres.app/Contents/Versions/9.3/bin" # Fix postgres linking issue on Mac OS X add_to_path_var DYLD_FALLBACK_LIBRARY_PATH "/Applications/Postgres.app/Contents/MacOS/lib" export DYLD_FALLBACK_LIBRARY_PATH fi if [[ -d /Applications/Xcode.app ]]; then add_to_path_var PATH "/Applications/Xcode.app/Contents/Developer/usr/bin" 1 fi export PATH CFLAGS LDFLAGS unset _OPATH # # COMMAND ALIASES # if have_command vim; then alias vi='vim' else alias vim='vi' fi # tmclip: tmux buffer -> system clipboard # cliptm: system clipboard -> tmux buffer if have_command xclip; then alias cliptm="tmux set-buffer -b buffer0000 \"\$(xclip -o -selection clipboard)\"" alias tmclip="tmux show-buffer 2>&1>/dev/null && tmux show-buffer | xclip -i -selection clipboard" alias xcopy="xclip -i -selection clipboard" elif have_command pbcopy; then alias cliptm="tmux set-buffer -b buffer0000 \"\$(pbpaste)\"" alias tmclip="tmux show-buffer 2>&1>/dev/null && tmux show-buffer | pbcopy" fi have_command mysql5 && alias mysql='mysql5' have_command otool && alias ldd='otool -L' have_command tidy && alias tidy_xml='tidy -xml -indent -wrap 0 2>/dev/null' have_command gitx && alias gitx="gitx --all" have_command play && alias noise='play -n synth brownnoise synth pinknoise mix vol 0.5' have_command socat && alias hproxy="socat TCP4-LISTEN:6600,fork SOCKS4:127.0.0.1:172.16.0.166:6600,socksport=9999" have_command docker && alias docker-interactive="docker run -ti --rm --detach-keys=ctrl-^ -v $HOME:$HOME " # Subversion aliases if have_command svn; then alias sup='svn update' alias sin='svn commit' alias sst='svn status' alias sadd='svn st | grep "^\?" | cut -d" " -f2- | xargs svn add' alias svngrep="grep --exclude='*.svn*' -r " function sclean { svn st | grep "\?" | awk '{ print $2 }' | xargs rm -rf } fi if have_command git; then # When git repos are across a tunnel from the current system function hgit { GIT_SSH_COMMAND="ssh -F ~/.ssh/home" git $@ } fi if have_command xrandr; then # This is a hack around a cheap implementation of screen management in # awesome. Eventually that should be improved and this removed. It disables # all display outputs know to xrandr and lets the display manager hook # re-enable only those currently connected. function reset_screens { xrandr $(xrandr -q | awk '/(disconnected|connected)/ { printf "--output " $1 " --off " }') } fi if have_command pass; then # By default pass copies the password or OTP credentials to the X # clipboard, which is typically correct. However, for pasting into a # terminal this doesn't work. This version copies the credentials to # the X primary selection instead. function pass_shell { env PASSWORD_STORE_X_SELECTION=primary pass $@ } fi if have_command systemctl; then alias usystemctl="systemctl --user" alias systemctl-failed-user-units="systemctl --user list-units --state=failed" alias failed-units="systemctl list-units --state=failed --all" alias ufailed-units="systemctl list-units --state=failed --user --all" fi # Spotify on high-resolution screens without Gnome if [[ -x "/snap/bin/spotify" ]]; then alias spotify4k="( /snap/bin/spotify --force-device-scale-factor=2 & )&" fi # Other Useful Aliases alias vihosts="sudo vim /etc/hosts" alias pyclean='find . \( -name __pycache__ -o -name \*.pyc \) -delete' alias chmox="chmod +x" alias less="less -FRSX" alias killfox="pkill -9 firefox" alias grep="grep --color=auto" alias hssh="ssh -F ~/.ssh/home" alias hscp="scp -F ~/.ssh/home" alias tmux-lighten="tmux set-environment -g TERM_BG_SHADE light" alias tmux-darken="tmux set-environment -g TERM_BG_SHADE dark" # ls Aliases # Color flags a different between BSD ls and GNU ls # If we have dircolors then we're probably linux (dircolors is in coreutils) if have_command dircolors; then eval "`dircolors -b`" alias ls='ls --color=auto' else alias ls='ls -G' fi if [[ ! -z "$NOCOLOR" ]]; then alias ls='ls -F' fi alias ll='ls -l' alias la='ls -A' function vimgrep { vim -O $(grep -rl $1 $2) } func_export vimgrep function lgrep { grep --color=always $@ | less } func_export lgrep # Start the correct version of mutt for the environment and revise the config # for whatever color-scheme is appropriate for the terminal background color function mutt { local muttrc="$(mktemp)" local shade="${TERM_BG_SHADE:-dark}" local mutt="" if have_command neomutt; then mutt="neomutt" elif [ -f "$HOME/.local/bin/mutt" ]; then mutt="$HOME/.local/bin/mutt" elif [ -f /usr/bin/mutt ]; then mutt="/usr/bin/mutt" else mutt="$(which mutt)" fi echo "source ~/.mutt/muttrc" > $muttrc echo "source ~/.mutt/colorscheme-${shade}" >> $muttrc # Run in a sub-shell so the config gets cleaned up ( trap "rm $muttrc" EXIT $mutt -F $muttrc "$@" ) } func_export mutt # # tmux Sessions # function tmux_session_home { tmux set-option -t "$1" status off tmux send-keys "hssh tun.home.crute.me" C-m tmux new-window -t "$1:1" tmux send-keys "hssh home.crute.me" C-m tmux set-option -t "$1" prefix C-@ } function tmux_session_newsboat { tmux send-keys -t "$1" "exec newsboat" C-m tmux set-option -t "$1" status off } # # USEFUL FUNCTIONS # # Get new tmux window or connect to a running tmux session function tm { local tmux="tmux -2" local do_exec=1 local OPTIND opt while getopts "nh" opt; do case $opt in n) # [n]o-exec do_exec=0 ;; h) # help echo "Usage: tm [nh]" echo " -n : Run but do not exec tmux" echo " -h : Show this message" return ;; esac done shift $((OPTIND-1)) $tmux start-server if [[ -z "$1" ]]; then echo "Enter screen name (or attach to one of these):" $tmux list-sessions -F "#{session_name}" return 1 fi if $tmux has-session -t "$1" &>/dev/null; then # Set status-line color to match terminal background shade # These are duplicated in ~/.tmux.conf if [[ "$TERM_BG_SHADE" == "light" ]]; then $tmux set-option -t "$1" -g status-bg colour255 elif [[ "$TERM_BG_SHADE" == "dark" ]]; then $tmux set-option -t "$1" -g status-bg colour234 fi if [[ $do_exec -eq 1 ]]; then exec $tmux attach -t "$1" else $tmux attach -t "$1" fi return fi if [[ $(type -t "tmux_session_$1") == "function" ]]; then $tmux new-session -d -s "$1" eval "tmux_session_$1" "$1" if [[ $do_exec -eq 1 ]]; then exec $tmux attach-session -t "$1" else $tmux attach-session -t "$1" fi return fi if (( $# == 2 )); then if [[ $do_exec -eq 1 ]]; then exec $tmux new -s "$1" -t "$2" else $tmux new -s "$1" -t "$2" fi else if [[ $do_exec -eq 1 ]]; then exec $tmux new -s "$1" else $tmux new -s "$1" fi fi } func_export tm # An ugly check to test for ssh agent liveness. Normally ssh-add should return # 2 to indicate that it can't talk to the agent but in older versions of Ubutu # the command returns 1 and some text on stderr about not being able to connect # to the agent (even though the man page says this should not happen). Thus # this ugly hack of a check function. function _ssh_agent_is_running { output=$(ssh-add -l 2>&1) [[ $? -ne 2 && ! "$output" =~ "communication with agent failed" ]] } # Start or resume an ssh agent and add keys if needed # The -d argument will add keys if needed function get_ssh_agent { local start_agent="" local load_keys="" local debug="" local OPTIND opt while getopts "dsklh" opt; do case $opt in d) # debug debug="1" ;; s) # start-agent start_agent="1" ;; k) # kill-all-agents killall ssh-agent &>/dev/null start_agent="1" ;; l) # load-keys load_keys="1" ;; h) # help echo "Usage: get_ssh_agent [dsklh]" echo " -d : Emit debug messages" echo " -s : Start an agent if one is not running" echo " -k : Kill all running agents and start new ones" echo " -l : Load all known keys into agent" echo " -h : Show this message" return ;; esac done # Prefer to use the running agent if there's one already running. # Otherwise try to figure out where a suitable agent is and connect. # to it. if ! _ssh_agent_is_running; then [[ -n "$debug" ]] && echo "No agent running, commencing search..." for path in "${TMPDIR:-/tmp}"/ssh-*/agent.*; do basepath=$(basename $path) sockpath=$(dirname $path) export SSH_AUTH_SOCK=${path} export SSH_AGENT_PID=${basepath##agent.} # When no files are present matching the pattern the pattern itself is # passed through. shopt -s nullglob would fix this but too much # fiddling to make it work without polluting the parent shell state. if [[ "$SSH_AGENT_PID" == "*" ]]; then [[ -n "$debug" ]] && echo "No agents found" break fi [[ -n "$debug" ]] && echo -n "Trying ${SSH_AUTH_SOCK} for ${SSH_AGENT_PID}... " if _ssh_agent_is_running; then [[ -n "$debug" ]] && echo "Success" break else [[ -n "$debug" ]] && echo "Failure... removing ${sockpath}" rm -rf "$sockpath" fi done else [[ -n "$debug" ]] && echo "Using already running agent..." fi # If the agent still isn't connected after sourcing our local agent # state file then it probably isn't running at all. Start a new # agent and update the state file. if ! _ssh_agent_is_running && [[ -n "$start_agent" ]]; then [[ -n "$debug" ]] && echo "Starting new agent..." eval $(ssh-agent) &>/dev/null fi if [[ -n "$load_keys" ]]; then [[ -n "$debug" ]] && echo "Adding SSH keys..." add_ssh_keys fi } func_export get_ssh_agent # Add keys to the agent SSH_TOKENS=() SSH_KEY_PATTERN=("id_?sa*" "id_ecdsa*" "id_ed25519*") if [ -e "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so" ]; then SSH_TOKENS+=("opensc-pkcs11.so") fi if [ -e "/Library/OpenSC/lib/opensc-pkcs11.so" ]; then SSH_TOKENS+=("/Library/OpenSC/lib/opensc-pkcs11.so") fi function add_ssh_keys { local ssh_keys=() for pattern in "${SSH_KEY_PATTERN[@]}"; do ssh_keys=( ${ssh_keys[@]} $(find ~/.ssh -name "$pattern" -not -name '*.pub') ) done for token in "${SSH_TOKENS[@]}"; do ssh-add -s $token done for key in "${ssh_keys[@]}"; do ssh-add $key done } func_export add_ssh_keys # Try to detect (tar|zip)file type and unpack accordingly function untar { local file_type=`file "$1" | awk -F: '{ gsub(/^ +/, "", $2); split($2, p, " "); print p[1] }'` case "$file_type" in "POSIX") tar -xvf "$1" ;; "bzip2") tar -xvjf "$1" ;; "gzip") tar -xvzf "$1" ;; "XZ") tar -xvJf "$1" ;; "Zip") unzip "$1" ;; "RAR") unrar x "$1" ;; *) echo "Unable to determine type of tarball." ;; esac } func_export untar # Freshen stale tmux window environment when a tmux session and window spans # multiple SSH sessions function freshen_tmux { eval $(tmux show-environment -g | grep -E '^(SSH_|DISPLAY|TERM_BG_SHADE)' | xargs -n1 -d '\n' echo "export") } func_export freshen_tmux # Enable colors for man # # The _ variable prevents color "bleed" when running `env` as $'' variables are # treated as raw terminal control codes and printed literally and need some # form of clearing terminator function man() { LESS_TERMCAP_md=$'\e[01;31m' \ LESS_TERMCAP_me=$'\e[0m' \ LESS_TERMCAP_se=$'\e[0m' \ LESS_TERMCAP_so=$'\e[01;44;33m' \ LESS_TERMCAP_ue=$'\e[0m' \ LESS_TERMCAP_us=$'\e[01;32m' \ _=$'\e[0m' \ command man "$@" } func_export man # # Configure Homebrew # [[ -r ~/.homebrew_github_api_token ]] && export HOMEBREW_GITHUB_API_TOKEN=$(cat ~/.homebrew_github_api_token) if [[ -d /opt/homebrew ]]; then export HOMEBREW_PREFIX="/opt/homebrew"; export HOMEBREW_CELLAR="/opt/homebrew/Cellar"; export HOMEBREW_REPOSITORY="/opt/homebrew"; add_to_path_var MANPATH "/opt/homebrew/share/man" add_to_path_var INFOPATH "/opt/homebrew/share/info" add_to_path_var PATH "/opt/homebrew/bin" add_to_path_var PATH "/opt/homebrew/sbin" export MANPATH INFOPATH PATH fi # # APPLICATION SPECIFIC HACKS # # Make the Oracle client and cxOracle work correctly on OS X # Without this cxOracle will not import with "image not found" for _ORACLE_HOME in "$HOME/local/lib/oracle/instantclient_10_2" "/opt/oracle/instantclient_10_2"; do if [[ -d "$_ORACLE_HOME" ]]; then export ORACLE_HOME=$_ORACLE_HOME add_to_path_var DYLD_LIBRARY_PATH "$ORACLE_HOME" 1 fi done [[ -f ~/.bashrc_local ]] && source ~/.bashrc_local # Getting SSH agent can be pretty slow on some systems. Do this in # .bashrc_local if it's really needed.