{ pkgs, ... }: { programs.bash = { enable = true; enableCompletion = false; initExtra = '' if [[ ! -v BASH_COMPLETION_VERSINFO ]] && [[ -f "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh" ]]; then source "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh" fi [ "$DISPLAY" ] && xset r rate 300 60 #-##################### user specific aliases and functions ###################### if [ -d ~/.bashrc.d ]; then for rc in ~/.bashrc.d/*; do bn=''${rc##*/} if [ -f "''$rc" ] && [ -r "''$rc" ] && [ ! "''${bn::1}" = '.' ]; then \. "''$rc" fi done fi unset rc bn #-########################### shell settings & options ########################### set -E set -o pipefail shopt -s nullglob dotglob globstar extglob shopt -s autocd cdable_vars cdspell shopt -s checkwinsize shopt -s lastpipe shopt -s inherit_errexit shopt -s no_empty_cmd_completion shopt -s histreedit shopt -s lithist shopt -u histverify _have bash_abbr && shopt -u expand_aliases #-############################### shell variables ################################ FIGNORE='.o' # Warning HISTCONTROL=ignoreboth:erasedups HISTTIMEFORMAT='%y-%m-%d %H:%M:%S ' HISTSIZE=-1 HISTFILESIZE=-1 HISTFILE=''$HOME/.''${USER:-default}_bash_history #-############################ environment variables ############################# export PYTHON_BASIC_REPL=1 # I'm good at readline so I'm gonna use readline whereever possible! export LESS='-SRFX' export PAGER='less' GIT_PAGER='less' MANPAGER='less' SYSTEMD_PAGER='less' export BAT_PAGER=''' export EDITOR='vim' VISUAL='vim' FCEDIT='vim' SYSTEMD_EDITOR='vim' export PATH #-########################## generally useful functions ########################## take () { : 'Idempotently create directory recursively and cd into it' local out out=''$(set -o pipefail && mkdir -vp "''$@" | tail -1) || return printf '%s\n' "''$out" eval "out=''${out#"mkdir: created directory "}" CDPATH= cd "''${out:-''${@: -1}}" } safe_ls () { command ls --color=always --quoting-style=shell-escape -lArth 2>/dev/null || \ command ls --color=always -q -lArth 2>/dev/null || \ command ls --color=always -lArth 2>/dev/null || \ command ls --color -lArth 2>/dev/null || \ command ls -lArth 2>/dev/null || \ command ls -lArt 2>/dev/null || \ command ls -lAr 2>/dev/null || \ command ls -lar 2>/dev/null || \ command ls -la 2>/dev/null || \ command ls -l 2>/dev/null || \ command ls -1 2>/dev/null || \ command printf '%q\n' * 2>/dev/null || \ command printf '%s\n' * 2>/dev/null } cd () { : 'Run cd; on success in a terminal, show the 4 newest entries (with a count of hidden ones)' builtin cd "''$@" || return [[ -t 1 ]] || return 0 command -v ls &>/dev/null || return 0 local -a lines mapfile -t lines < <(safe_ls) if (( ''${#lines[@]} > 10 )); then printf '\033[7m... %d more entries ...\033[27m\n' "$(( ''${#lines[@]} - 10 ))"; printf '%s\n' "''${lines[@]: -9}" else printf '%s\n' "''${lines[@]}" fi } drop_caches () { : 'Drop pagecache, dentries and inodes' : 'https://unix.stackexchange.com/a/87909' echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null } respawn_shell () { : 'Intelligently respawn shell' : 'pure posix sh without forking' if [ "''${BASH_VERSINFO-}" ] && command -v bash 1>/dev/null 2>&1; then if _is_login_shell; then exec bash -l else exec bash fi : 'TODO: Implement more detection mechanisms for various shells (zsh, etc.)' elif [ -x /proc/self/exe ]; then if _is_login_shell; then exec -l /proc/self/exe else exec /proc/self/exe fi elif command -v "''${0#-}" 1>/dev/null 2>&1; then : 'TODO: Make it work with shells that actually start with a dash (unlikely, but possible)' printf 'Warning: replacing current shell with unreliable ''$0, which is: %s\n' "''$0" if _is_login_shell; then exec -l "''${0#-}" else exec "''${0#-}" fi else printf 'Error: Don'\'''t know how to reliably replace this shell, not exec'\'''ing and not exiting\n' printf 'You might want to manually run `exec [-l] "''$SHELL"`, however, `''$SHELL` might not be the shell you are currently running' return 1 fi } export_bashrc () { CDPATH= command cd ~/.bashrc.d/ || return : 'Not using an associative array because those are not sorted!' declare -ar plugins=(*) [[ ''${#plugins[@]} -eq 0 ]] || [[ ''${#plugins} -eq 1 && ''${plugins[0]} = '*' ]] && return local IFS=' ' declare -Ai selected declare plugin for plugin in "''${plugins[@]}"; do case "''$plugin" in #-# Comment below case match if you don'\'''t want to have a pre-selection of plugins to export #-# Requires `shopt -s extglob` (+([0-9])-@(util|redact|prompt|docker-tools|funcs|init-and-complete|general-aliases|bash-abbr)) selected[''$plugin]=1 ;; (*) selected[''$plugin]= esac done declare -a plugins_with_selection while :; do local PS3='Which plugins do you want to export/not export (enter a single number)? ' plugins_with_selection=() for plugin in "''${plugins[@]}"; do if [[ ''${selected[''$plugin]} -eq 0 ]]; then plugins_with_selection+=(''$'\033[31m'''$plugin''$'\033[m') else plugins_with_selection+=(''$'\033[32m'''$plugin''$'\033[m') fi done select plugin in ''$'\033[31mQUIT\033[m' ''$'\033[34mDONE\033[m' "''${plugins_with_selection[@]}"; do plugin=''${plugin#''$'\033[3'[0-7]m} plugin=''${plugin%''$'\033[m'} [[ -z ''$REPLY || ''$plugin = QUIT || ''$plugin = DONE ]] && break 2 [[ -z ''$plugin ]] && { echo "Please enter a number (1-''$((''${#plugins[@]}+2)))."; continue; } selected[''$plugin]=1-''${selected[''$plugin]} break done [[ -z ''$REPLY || ''$plugin = QUIT || ''$plugin = DONE ]] && break done [[ -z ''$REPLY || ''$plugin = QUIT ]] && { echo "Aborting"; return; } declare -a exported_plugins=() for plugin in "''${!selected[@]}"; do if [[ ''${selected[''$plugin]} -eq 1 ]]; then exported_plugins+=(".bashrc.d/''$plugin") fi done command rm -vf "exported_bashrc.tar.gz" CDPATH= command cd - >/dev/null CDPATH= command cd ~ as_base64=''$(command tar -cvhzf - ".bashrc" "''${exported_plugins[@]}" | base64 -w0) CDPATH= command cd - >/dev/null as_base64_wrapped=''$(printf %s "''$as_base64" | sed 's#.\{,70\}#'\'''&'\'''\\\n#g' | sed ''\'''$s#\\$##') command install -m775 /dev/stdin ~/bashrc_installer.sh <<-EOF #! /usr/bin/env bash set -e [ "\''$BASH_VERSINFO" ] && command -v declare >/dev/null 2>&1 && command declare -p BASH_VERSINFO | { IFS=' ' read -r _ flags _; case "\''$flags" in (*r*) ;; (*) false; esac; } || { printf 'This installer must be run with bash!\\n'; exit 1; } command -v bash >/dev/null 2>&1 || { printf 'bash must be installed!\\n'; exit 2; } as_base64=''$as_base64_wrapped tempdir=\''$(mktemp -d) trap 'rm -rf -- "\''$tempdir"' EXIT cd -- "\''$tempdir" base64 -d <<< "\''$as_base64" | tar xzf - create_dir_and_move () { src=\''$1 dst=\''$2 [ ! -f "\''$src" ] && { printf '%s\\n' "Source argument '\''$src' is not a regular file"; return 1; } dst_dir=\''$(dirname -- "\''${dst//[^!-~]/-}") [ ! -d "\''$dst_dir" ] && { printf '%s\\n' "Trying to create destination directory '\''$dst_dir'"; mkdir -p -- "\''$dst_dir"; } || : [ -d "\''$dst_dir" ] && mv -v -t "\''$dst_dir" -- "\''$src" || : } ask_overwrite () { file1=\''$1 file2=\''$2 if [[ ! -e \''$file2 ]]; then create_dir_and_move "\''$file1" "\''$file2" elif [[ -d \''$file2 ]]; then printf 'WARNING: Destination file %s is a directory! Skipping this file\\n' "\''${file2@Q}" return elif [[ ! -f \''$file2 ]]; then printf 'WARNING: Destination file %s is not a regular file! Skipping this file\\n' "\''${file2@Q}" return elif ! diff -q "\''$file1" "\''$file2"; then printf 'File to be installed (%s) differs from already installed file (%s)\\n' "\''${file1@Q}" "\''${file2@Q}" read -p 'Press enter to show diff. ' || : diff --color -u "\''$file1" "\''$file2" || : printf 'Giving you a plain bash shell to resolve the situation. Once done, just exit.\\n' printf 'After you exit, the destination file WILL BE FORCEFULLY OVERWRITTEN!!!\n' read -p 'Press enter to continue in the shell. ' || : bash --rcfile <(printf 'cd; printf "You are running bash version \''$BASH_VERSION. Your CWD is \''$PWD. Your HOME is \''$HOME. You are \''$USER and HOSTNAME is \''$HOSTNAME.\\nUse this bash shell to make changes, backups, etc.\\nThen just exit.\\n"') --noprofile || : printf 'Destination file %s will be overwritten. Continue? ' "\''${file2@Q}" read || : create_dir_and_move "\''$file1" "\''$file2" else printf 'File %s was not changed\\n' "\''${file2@Q}" fi } ask_overwrite .bashrc ~/.bashrc set +f shopt -s nullglob dotglob for file in .bashrc.d/*; do ask_overwrite "\''$file" ~/"\''$file" done cd - >/dev/null read -p 'Self destruct (y/N)? ' choice || : [[ \''$choice = y ]] && { printf 'Trying to rm myself (%s)\\n' "\''${0@Q}"; rm -- "\''$0" && printf 'Success\\n' || printf 'Failed. Not trying further\\n'; } EOF } #-#################################### binds ##################################### bind '"\e\C-r": kill-region' \ '"\C-x\C-k": kill-whole-line' \ '"\e\C-p": history-search-backward' \ '"\e\C-n": history-search-forward' \ '"\eP": history-substring-search-backward' \ '"\eN": history-substring-search-forward' \ '"\C-x\C-d": "\e#\C-m exit\C-m"' \ '"\ej": "\C-e |& tac | \\cat -n | tac | \\cat -n | less -SFXR\C-m"' \ '"\eX": ">/dev/null "' \ '"\t": menu-complete' \ '"\e[Z": menu-complete-backward' \ '"\C-x\C-r": forward-search-history' \ 'set show-all-if-ambiguous on' \ 'set completion-query-items 0' \ 'set menu-complete-display-prefix on' \ 'set colored-completion-prefix on' \ 'set colored-stats on' \ 'set history-preserve-point on' #-################################## autoclear ################################### #-# TODO #-# prank () { #-# line_up_until_cursor=''${READLINE_LINE::READLINE_POINT} #-# line_after_cursor=''${READLINE_LINE:READLINE_POINT} #-# if [[ ''$((RANDOM % 5)) -eq 0 ]]; then #-# READLINE_LINE="''$line_up_until_cursor''$line_after_cursor" #-# #READLINE_LINE="''$line_up_until_cursor''${1^}''$line_after_cursor" #-# READLINE_POINT=''$((READLINE_POINT+1)) #-# else #-# READLINE_LINE="''$line_up_until_cursor''$1''$line_after_cursor" #-# READLINE_POINT=''$((READLINE_POINT+1)) #-# fi #-# } #-# bind -x '" ": prank " "' #-# bind -x '"a": prank "a"' ''; }; }