Last active
June 12, 2026 17:19
-
-
Save hed0rah/2f2a76cdbc45f1d03d7e6fca12f8a9b6 to your computer and use it in GitHub Desktop.
bashrc functions for fzf, batcat, git, bitwise ops, conversions
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
| # Public Bash helper pack: fzf/bat previews, git pickers, process picker, | |
| # numeric conversions, bitwise helpers, and CIDR scratchpad. | |
| # | |
| # Install: | |
| # cp .bashrc_share ~/.bashrc_share | |
| # printf '\n[ -f "$HOME/.bashrc_share" ] && . "$HOME/.bashrc_share"\n' >> ~/.bashrc | |
| # | |
| # Requires Bash 3.2+. | |
| # Optional tools: | |
| # fzf, bat or batcat, rg, fd, git, pbcopy/wl-copy/xclip/xsel | |
| bashrc_share_help() { | |
| cat <<'EOF' | |
| Portable Bash helper pack | |
| fzf/file: | |
| fe pick file and edit | |
| fc pick file and print with bat/batcat/cat | |
| fcd pick directory and cd | |
| frg PATTERN ripgrep through fzf with file preview | |
| gkill [-SIGNAL] fzf process picker with confirmation | |
| git: | |
| gst short branch status | |
| groot cd to git repository root | |
| glog / glogf commit browser with patch preview | |
| glogc pick/copy/print commit hash | |
| gco / fco branch switcher | |
| gf / gfe tracked file picker / edit | |
| gdf / gds unstaged/staged diff picker | |
| gstash stash patch browser | |
| numbers: | |
| h2b b2d d2b d2h b2h h2d | |
| bit_and bit_or bit_xor bit_not bit_shl bit_shr | |
| set_bit clear_bit toggle_bit check_bit bit_show | |
| ip2b b2ip cidr_mask network_address cidr_info | |
| EOF | |
| } | |
| # ----- prompt ----- | |
| if [[ $- == *i* ]]; then | |
| __share_c_reset='\[\e[0m\]' | |
| __share_c_dim='\[\e[2m\]' | |
| __share_c_green='\[\e[38;5;108m\]' | |
| __share_c_blue='\[\e[38;5;74m\]' | |
| __share_c_orange='\[\e[38;5;173m\]' | |
| __share_c_red='\[\e[38;5;203m\]' | |
| __share_c_gray='\[\e[38;5;245m\]' | |
| __share_git_branch() { | |
| git rev-parse --abbrev-ref HEAD 2>/dev/null | |
| } | |
| __share_prompt_command() { | |
| local exit_code=$? | |
| local status_color="$__share_c_green" | |
| local branch git_part="" | |
| [ "$exit_code" -ne 0 ] && status_color="$__share_c_red" | |
| branch="$(__share_git_branch)" | |
| [ -n "$branch" ] && git_part=" $__share_c_orange($branch)$__share_c_reset" | |
| printf '\033]0;%s@%s: %s\007' "${USER:-user}" "${HOSTNAME:-host}" "$PWD" | |
| PS1="${__share_c_dim}\t [\!]${__share_c_reset} ${status_color}\u@\h${__share_c_reset}:${__share_c_blue}\w${__share_c_reset}${git_part}\n${__share_c_gray}\$${__share_c_reset} " | |
| return "$exit_code" | |
| } | |
| if [[ -n "$PROMPT_COMMAND" ]]; then | |
| PROMPT_COMMAND="__share_prompt_command; $PROMPT_COMMAND" | |
| else | |
| PROMPT_COMMAND="__share_prompt_command" | |
| fi | |
| fi | |
| # ----- fzf + bat/batcat ----- | |
| _fzf_bat() { | |
| if command -v batcat >/dev/null 2>&1; then | |
| printf '%s\n' batcat | |
| elif command -v bat >/dev/null 2>&1; then | |
| printf '%s\n' bat | |
| else | |
| printf '%s\n' cat | |
| fi | |
| } | |
| _share_clipboard_cmd() { | |
| if command -v pbcopy >/dev/null 2>&1; then | |
| printf '%s\n' pbcopy | |
| elif command -v wl-copy >/dev/null 2>&1; then | |
| printf '%s\n' wl-copy | |
| elif command -v xclip >/dev/null 2>&1; then | |
| printf '%s\n' 'xclip -selection clipboard' | |
| elif command -v xsel >/dev/null 2>&1; then | |
| printf '%s\n' 'xsel --clipboard --input' | |
| else | |
| return 1 | |
| fi | |
| } | |
| _FZF_BAT="$(_fzf_bat)" | |
| if [ "$_FZF_BAT" = cat ]; then | |
| _FZF_CTRL_T_PREVIEW='cat {}' | |
| else | |
| _FZF_CTRL_T_PREVIEW="${_FZF_BAT} -n --color=always {}" | |
| fi | |
| alias fz="fzf --height 80% --layout=reverse --border --preview '${_FZF_CTRL_T_PREVIEW}' --bind 'ctrl-/:change-preview-window(down|hidden|)'" | |
| export FZF_CTRL_T_OPTS="--walker-skip .git,node_modules,target,.venv --preview '${_FZF_CTRL_T_PREVIEW}' --bind 'ctrl-/:change-preview-window(down|hidden|)'" | |
| _FZF_SHARE_CLIP="$(_share_clipboard_cmd 2>/dev/null || true)" | |
| if [ -n "$_FZF_SHARE_CLIP" ]; then | |
| export FZF_CTRL_R_OPTS="--bind 'ctrl-y:execute-silent(echo -n {2..} | ${_FZF_SHARE_CLIP})+abort' --color header:italic --header 'Press CTRL-Y to copy command'" | |
| else | |
| export FZF_CTRL_R_OPTS="--color header:italic --header 'Enter accepts command'" | |
| fi | |
| if [[ $- == *i* ]] && command -v fzf >/dev/null 2>&1; then | |
| if fzf --bash >/dev/null 2>&1; then | |
| eval "$(fzf --bash)" | |
| elif [ -f "$HOME/.fzf.bash" ]; then | |
| . "$HOME/.fzf.bash" | |
| fi | |
| fi | |
| # ----- fzf helpers ----- | |
| if [ "$_FZF_BAT" = cat ]; then | |
| _FZF_FILE_PREVIEW="sed -n '1,500p' {}" | |
| _FZF_GREP_PREVIEW="line={2}; start=\$((line > 20 ? line - 20 : 1)); end=\$((line + 200)); sed -n \"\${start},\${end}p\" {1}" | |
| else | |
| _FZF_FILE_PREVIEW="${_FZF_BAT} --color=always --style=numbers --line-range :500 -- {}" | |
| _FZF_GREP_PREVIEW="line={2}; start=\$((line > 20 ? line - 20 : 1)); end=\$((line + 200)); ${_FZF_BAT} --color=always --style=numbers --highlight-line \"\$line\" --line-range \"\$start:\$end\" -- {1}" | |
| fi | |
| _fzf_files() { | |
| if command -v fd >/dev/null 2>&1; then | |
| fd --type f --hidden --follow --exclude .git --exclude node_modules --exclude target --exclude .venv | |
| else | |
| find . -type f \ | |
| -not -path '*/.git/*' \ | |
| -not -path '*/node_modules/*' \ | |
| -not -path '*/target/*' \ | |
| -not -path '*/.venv/*' | |
| fi | |
| } | |
| _fzf_dirs() { | |
| if command -v fd >/dev/null 2>&1; then | |
| fd --type d --hidden --follow --exclude .git --exclude node_modules --exclude target --exclude .venv | |
| else | |
| find . -type d \ | |
| -not -path '*/.git/*' \ | |
| -not -path '*/node_modules/*' \ | |
| -not -path '*/target/*' \ | |
| -not -path '*/.venv/*' | |
| fi | |
| } | |
| _git_repo_required() { | |
| git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { | |
| echo "not in a git repository" >&2 | |
| return 1 | |
| } | |
| } | |
| fe() { | |
| local file | |
| file="$( | |
| _fzf_files | fzf \ | |
| --height 80% --layout=reverse --border \ | |
| --preview "$_FZF_FILE_PREVIEW" \ | |
| --preview-window 'right,60%,border-left' | |
| )" || return | |
| "${EDITOR:-vim}" "$file" | |
| } | |
| fc() { | |
| local file | |
| file="$( | |
| _fzf_files | fzf \ | |
| --height 80% --layout=reverse --border \ | |
| --preview "$_FZF_FILE_PREVIEW" \ | |
| --preview-window 'right,60%,border-left' | |
| )" || return | |
| if [ "$_FZF_BAT" = cat ]; then | |
| cat "$file" | |
| else | |
| "$_FZF_BAT" "$file" | |
| fi | |
| } | |
| fcd() { | |
| local dir | |
| dir="$( | |
| _fzf_dirs | fzf \ | |
| --height 80% --layout=reverse --border \ | |
| --preview 'ls -la {}' \ | |
| --preview-window 'right,60%,border-left' | |
| )" || return | |
| cd "$dir" || return | |
| } | |
| frg() { | |
| if [ $# -eq 0 ]; then | |
| echo "usage: frg <ripgrep-pattern>" >&2 | |
| return 1 | |
| fi | |
| command -v rg >/dev/null 2>&1 || { | |
| echo "rg is required for frg" >&2 | |
| return 1 | |
| } | |
| local selected file line | |
| selected="$( | |
| rg --line-number --column --color=never --smart-case "$@" | | |
| fzf \ | |
| --height 80% --layout=reverse --border \ | |
| --delimiter ':' --nth 1,3.. \ | |
| --preview "$_FZF_GREP_PREVIEW" \ | |
| --preview-window 'right,60%,border-left,+{2}+3/3' | |
| )" || return | |
| file="$(printf '%s\n' "$selected" | awk -F: '{print $1}')" | |
| line="$(printf '%s\n' "$selected" | awk -F: '{print $2}')" | |
| "${EDITOR:-vim}" "+$line" "$file" | |
| } | |
| gkill() { | |
| local signal="${1:-TERM}" | |
| signal="${signal#-}" | |
| case "$signal" in | |
| ''|*[!A-Za-z0-9]*) | |
| echo "usage: gkill [signal]" >&2 | |
| return 2 | |
| ;; | |
| esac | |
| local selected pids answer pid | |
| selected="$( | |
| ps -axo pid=,user=,stat=,command= | | |
| fzf --multi \ | |
| --height 80% --layout=reverse --border \ | |
| --header "tab: mark | enter: kill SIG$signal | example: gkill -9" \ | |
| --delimiter '[[:space:]]+' \ | |
| --preview 'ps -p {1} -o pid,user,ppid,stat,lstart,command' \ | |
| --preview-window 'right,60%,border-left' | |
| )" || return | |
| pids="$(printf '%s\n' "$selected" | awk '{print $1}')" | |
| [ -n "$pids" ] || return | |
| printf 'kill -%s these PIDs?\n%s\n' "$signal" "$pids" | |
| printf 'type y to continue: ' | |
| read -r answer | |
| case "$answer" in | |
| y|Y|yes|YES) | |
| while IFS= read -r pid; do | |
| [ -n "$pid" ] || continue | |
| [ "$pid" = "$$" ] && continue | |
| command kill "-$signal" "$pid" | |
| done <<< "$pids" | |
| ;; | |
| *) return 1 ;; | |
| esac | |
| } | |
| fkill() { | |
| gkill "$@" | |
| } | |
| # ----- git + fzf helpers ----- | |
| gst() { | |
| git status --short --branch | |
| } | |
| groot() { | |
| local root | |
| root="$(git rev-parse --show-toplevel 2>/dev/null)" || { | |
| echo "not in a git repository" >&2 | |
| return 1 | |
| } | |
| cd "$root" || return | |
| } | |
| _glog_select() { | |
| _git_repo_required || return | |
| local mode="${1:-large}" | |
| local header preview_window | |
| local fzf_layout=() | |
| local bind_args=() | |
| case "$mode" in | |
| full|fullscreen) | |
| fzf_layout=(--layout=reverse --border) | |
| preview_window='right,65%,border-left' | |
| ;; | |
| *) | |
| fzf_layout=(--height 95% --layout=reverse --border) | |
| preview_window='right,65%,border-left' | |
| ;; | |
| esac | |
| header='enter: show commit' | |
| if [ -n "$_FZF_SHARE_CLIP" ]; then | |
| header='enter: show commit | ctrl-y: copy hash' | |
| bind_args=(--bind "ctrl-y:execute-silent(printf '%s' {1} | ${_FZF_SHARE_CLIP})") | |
| fi | |
| git log --all --no-show-signature --decorate=short --date=short --pretty=format:'%h %ad %d %s' | | |
| fzf \ | |
| "${fzf_layout[@]}" --no-sort --tiebreak=index \ | |
| --header "$header" \ | |
| --preview 'git show --no-show-signature --stat --patch --color=always {1}' \ | |
| --preview-window "$preview_window" \ | |
| "${bind_args[@]}" | |
| } | |
| _glog_open() { | |
| local mode="$1" | |
| local line commit | |
| line="$(_glog_select "$mode")" || return | |
| commit="$(printf '%s\n' "$line" | awk '{print $1}')" | |
| git show --no-show-signature --stat --patch --color=always "$commit" | less -R | |
| } | |
| glog() { | |
| _glog_open large | |
| } | |
| glogf() { | |
| _glog_open full | |
| } | |
| glogfull() { | |
| glogf | |
| } | |
| glogc() { | |
| local line commit | |
| line="$(_glog_select)" || return | |
| commit="$(printf '%s\n' "$line" | awk '{print $1}')" | |
| if [ -n "$_FZF_SHARE_CLIP" ]; then | |
| printf '%s' "$commit" | eval "$_FZF_SHARE_CLIP" | |
| fi | |
| printf '%s\n' "$commit" | |
| } | |
| gco() { | |
| _git_repo_required || return | |
| local branch | |
| branch="$( | |
| git for-each-ref \ | |
| --sort=-committerdate \ | |
| --format='%(refname:short)' \ | |
| refs/heads refs/remotes | | |
| sed '/^origin\/HEAD$/d' | | |
| fzf \ | |
| --height 80% --layout=reverse --border \ | |
| --preview 'git log --no-show-signature --graph --decorate --oneline --color=always --max-count=30 {}' \ | |
| --preview-window 'right,60%,border-left' | |
| )" || return | |
| case "$branch" in | |
| origin/*) | |
| local local_branch="${branch#origin/}" | |
| if git show-ref --verify --quiet "refs/heads/$local_branch"; then | |
| git switch "$local_branch" | |
| else | |
| git switch --track "$branch" | |
| fi | |
| ;; | |
| *) git switch "$branch" ;; | |
| esac | |
| } | |
| fco() { | |
| gco | |
| } | |
| gf() { | |
| _git_repo_required || return | |
| git ls-files | | |
| fzf \ | |
| --height 80% --layout=reverse --border \ | |
| --preview "$_FZF_FILE_PREVIEW" \ | |
| --preview-window 'right,60%,border-left' | |
| } | |
| gfe() { | |
| local file | |
| file="$(gf)" || return | |
| "${EDITOR:-vim}" "$file" | |
| } | |
| gdf() { | |
| _git_repo_required || return | |
| local file | |
| file="$( | |
| git diff --name-only | | |
| fzf \ | |
| --height 80% --layout=reverse --border \ | |
| --preview 'git diff --color=always -- {}' \ | |
| --preview-window 'right,60%,border-left' | |
| )" || return | |
| git diff --color=always -- "$file" | less -R | |
| } | |
| gds() { | |
| _git_repo_required || return | |
| local file | |
| file="$( | |
| git diff --cached --name-only | | |
| fzf \ | |
| --height 80% --layout=reverse --border \ | |
| --preview 'git diff --cached --color=always -- {}' \ | |
| --preview-window 'right,60%,border-left' | |
| )" || return | |
| git diff --cached --color=always -- "$file" | less -R | |
| } | |
| gstash() { | |
| _git_repo_required || return | |
| local line stash | |
| line="$( | |
| git stash list | | |
| fzf \ | |
| --height 80% --layout=reverse --border \ | |
| --preview 'git stash show -p --color=always {1}' \ | |
| --preview-window 'right,60%,border-left' | |
| )" || return | |
| stash="${line%%:*}" | |
| git stash show -p --color=always "$stash" | less -R | |
| } | |
| # ----- numeric conversion + bitwise helpers ----- | |
| _num_err() { | |
| echo "$*" >&2 | |
| return 2 | |
| } | |
| _strip_num_separators() { | |
| local s="$1" | |
| printf '%s\n' "${s//_/}" | |
| } | |
| _hex_to_dec() { | |
| local s | |
| s="$(_strip_num_separators "$1")" | |
| case "$s" in | |
| 0x*|0X*) s="${s#??}" ;; | |
| esac | |
| [ -n "$s" ] || _num_err "empty hex value" || return | |
| case "$s" in | |
| *[!0-9A-Fa-f]*) _num_err "invalid hex value: $1" || return ;; | |
| esac | |
| printf '%d\n' "$((16#$s))" | |
| } | |
| _bin_to_dec() { | |
| local s | |
| s="$(_strip_num_separators "$1")" | |
| case "$s" in | |
| 0b*|0B*) s="${s#??}" ;; | |
| esac | |
| [ -n "$s" ] || _num_err "empty binary value" || return | |
| case "$s" in | |
| *[!01]*) _num_err "invalid binary value: $1" || return ;; | |
| esac | |
| printf '%d\n' "$((2#$s))" | |
| } | |
| _num_to_dec() { | |
| local s | |
| s="$(_strip_num_separators "$1")" | |
| [ -n "$s" ] || _num_err "empty numeric value" || return | |
| case "$s" in | |
| 0x*|0X*) _hex_to_dec "$s" ;; | |
| 0b*|0B*) _bin_to_dec "$s" ;; | |
| *[!0-9]*) _num_err "invalid numeric value: $1" || return ;; | |
| *) printf '%d\n' "$((10#$s))" ;; | |
| esac | |
| } | |
| _dec_to_bin_raw() { | |
| local n="$1" out="" | |
| [ "$n" -ge 0 ] 2>/dev/null || _num_err "binary conversion expects a non-negative integer: $1" || return | |
| if [ "$n" -eq 0 ]; then | |
| printf '0\n' | |
| return | |
| fi | |
| while [ "$n" -gt 0 ]; do | |
| out="$((n & 1))$out" | |
| n=$((n >> 1)) | |
| done | |
| printf '%s\n' "$out" | |
| } | |
| _bin_pad() { | |
| local width="$1" bin="$2" | |
| while [ "${#bin}" -lt "$width" ]; do | |
| bin="0$bin" | |
| done | |
| printf '%s\n' "$bin" | |
| } | |
| _input_bit_width() { | |
| local s="$(_strip_num_separators "$1")" | |
| local d b | |
| case "$s" in | |
| 0x*|0X*) | |
| s="${s#??}" | |
| printf '%d\n' "$((${#s} * 4))" | |
| ;; | |
| 0b*|0B*) | |
| s="${s#??}" | |
| printf '%d\n' "${#s}" | |
| ;; | |
| *) | |
| d="$(_num_to_dec "$s")" || return | |
| b="$(_dec_to_bin_raw "$d")" || return | |
| printf '%d\n' "${#b}" | |
| ;; | |
| esac | |
| } | |
| _bit_print() { | |
| local value="$1" width="$2" bin | |
| bin="$(_dec_to_bin_raw "$value")" || return | |
| if [ -n "$width" ]; then | |
| bin="$(_bin_pad "$width" "$bin")" | |
| fi | |
| printf 'decimal: %d\n' "$value" | |
| printf 'hex: 0x%X\n' "$value" | |
| printf 'binary: 0b%s\n' "$bin" | |
| } | |
| d2b() { | |
| local d | |
| d="$(_num_to_dec "$1")" || return | |
| _dec_to_bin_raw "$d" | |
| } | |
| b2d() { | |
| _bin_to_dec "$1" | |
| } | |
| h2d() { | |
| _hex_to_dec "$1" | |
| } | |
| d2h() { | |
| local d | |
| d="$(_num_to_dec "$1")" || return | |
| printf '0x%X\n' "$d" | |
| } | |
| b2h() { | |
| local d | |
| d="$(_bin_to_dec "$1")" || return | |
| printf '0x%X\n' "$d" | |
| } | |
| h2b() { | |
| local d | |
| d="$(_hex_to_dec "$1")" || return | |
| _dec_to_bin_raw "$d" | |
| } | |
| hex2dec() { h2d "$@"; } | |
| bin2dec() { b2d "$@"; } | |
| dec2bin() { d2b "$@"; } | |
| dec2hex() { d2h "$@"; } | |
| bin2hex() { b2h "$@"; } | |
| hex2bin() { h2b "$@"; } | |
| input_to_dec() { _num_to_dec "$@"; } | |
| bit_show() { | |
| local value width dec bin i bit line_pos="" line_bit="" | |
| value="$1" | |
| width="$2" | |
| dec="$(_num_to_dec "$value")" || return | |
| if [ -z "$width" ]; then | |
| width="$(_input_bit_width "$value")" || return | |
| fi | |
| bin="$(_bin_pad "$width" "$(_dec_to_bin_raw "$dec")")" | |
| _bit_print "$dec" "$width" | |
| for ((i = width - 1; i >= 0; i--)); do | |
| line_pos="${line_pos}$(printf '%2d ' "$i")" | |
| done | |
| for ((i = 0; i < width; i++)); do | |
| bit="${bin:i:1}" | |
| line_bit="${line_bit} ${bit} " | |
| done | |
| printf 'index: %s\n' "$line_pos" | |
| printf 'bits: %s\n' "$line_bit" | |
| } | |
| bit_and() { | |
| local a b result width | |
| a="$(_num_to_dec "$1")" || return | |
| b="$(_num_to_dec "$2")" || return | |
| result=$((a & b)) | |
| width="${3:-}" | |
| _bit_print "$result" "$width" | |
| } | |
| bit_or() { | |
| local a b result width | |
| a="$(_num_to_dec "$1")" || return | |
| b="$(_num_to_dec "$2")" || return | |
| result=$((a | b)) | |
| width="${3:-}" | |
| _bit_print "$result" "$width" | |
| } | |
| bit_xor() { | |
| local a b result width | |
| a="$(_num_to_dec "$1")" || return | |
| b="$(_num_to_dec "$2")" || return | |
| result=$((a ^ b)) | |
| width="${3:-}" | |
| _bit_print "$result" "$width" | |
| } | |
| bit_not() { | |
| local a width mask result | |
| a="$(_num_to_dec "$1")" || return | |
| width="${2:-$(_input_bit_width "$1")}" || return | |
| [ "$width" -gt 0 ] 2>/dev/null || _num_err "width must be positive" || return | |
| [ "$width" -lt 63 ] 2>/dev/null || _num_err "width must be less than 63 for bash arithmetic" || return | |
| mask=$(((1 << width) - 1)) | |
| result=$(((~a) & mask)) | |
| _bit_print "$result" "$width" | |
| } | |
| bit_shl() { | |
| local a shift result | |
| a="$(_num_to_dec "$1")" || return | |
| shift="$(_num_to_dec "$2")" || return | |
| result=$((a << shift)) | |
| _bit_print "$result" "$3" | |
| } | |
| bit_shr() { | |
| local a shift result | |
| a="$(_num_to_dec "$1")" || return | |
| shift="$(_num_to_dec "$2")" || return | |
| result=$((a >> shift)) | |
| _bit_print "$result" "$3" | |
| } | |
| left_shift() { bit_shl "$@"; } | |
| right_shift() { bit_shr "$@"; } | |
| bitmask() { bit_and "$@"; } | |
| set_bit() { | |
| local value pos result | |
| value="$(_num_to_dec "$1")" || return | |
| pos="$(_num_to_dec "$2")" || return | |
| result=$((value | (1 << pos))) | |
| _bit_print "$result" "$3" | |
| } | |
| clear_bit() { | |
| local value pos result | |
| value="$(_num_to_dec "$1")" || return | |
| pos="$(_num_to_dec "$2")" || return | |
| result=$((value & ~(1 << pos))) | |
| _bit_print "$result" "$3" | |
| } | |
| toggle_bit() { | |
| local value pos result | |
| value="$(_num_to_dec "$1")" || return | |
| pos="$(_num_to_dec "$2")" || return | |
| result=$((value ^ (1 << pos))) | |
| _bit_print "$result" "$3" | |
| } | |
| check_bit() { | |
| local value pos result | |
| value="$(_num_to_dec "$1")" || return | |
| pos="$(_num_to_dec "$2")" || return | |
| result=$(((value >> pos) & 1)) | |
| printf 'bit %s: %s\n' "$pos" "$result" | |
| } | |
| # ----- IPv4/CIDR helpers ----- | |
| _ip_to_dec() { | |
| local ip="$1" a b c d octet | |
| IFS=. read -r a b c d <<< "$ip" | |
| for octet in "$a" "$b" "$c" "$d"; do | |
| case "$octet" in | |
| ''|*[!0-9]*) _num_err "invalid IPv4 address: $ip" || return ;; | |
| esac | |
| [ "$octet" -ge 0 ] 2>/dev/null && [ "$octet" -le 255 ] 2>/dev/null || { | |
| _num_err "invalid IPv4 octet in: $ip" | |
| return | |
| } | |
| done | |
| printf '%u\n' "$(((a << 24) | (b << 16) | (c << 8) | d))" | |
| } | |
| _dec_to_ip() { | |
| local n="$1" | |
| printf '%d.%d.%d.%d\n' "$(((n >> 24) & 255))" "$(((n >> 16) & 255))" "$(((n >> 8) & 255))" "$((n & 255))" | |
| } | |
| _cidr_mask_dec() { | |
| local cidr="$1" | |
| [ "$cidr" -ge 0 ] 2>/dev/null && [ "$cidr" -le 32 ] 2>/dev/null || { | |
| _num_err "CIDR must be 0..32" | |
| return | |
| } | |
| if [ "$cidr" -eq 0 ]; then | |
| printf '0\n' | |
| else | |
| printf '%u\n' "$(((0xffffffff << (32 - cidr)) & 0xffffffff))" | |
| fi | |
| } | |
| ip2b() { | |
| local n bin | |
| n="$(_ip_to_dec "$1")" || return | |
| bin="$(_bin_pad 32 "$(_dec_to_bin_raw "$n")")" | |
| printf '%s.%s.%s.%s\n' "${bin:0:8}" "${bin:8:8}" "${bin:16:8}" "${bin:24:8}" | |
| } | |
| b2ip() { | |
| local s n | |
| s="$(_strip_num_separators "$1")" | |
| s="${s//./}" | |
| n="$(_bin_to_dec "$s")" || return | |
| _dec_to_ip "$n" | |
| } | |
| ip_to_bin() { | |
| ip2b "$1" | tr -d '.' | |
| } | |
| bin_to_ip() { | |
| b2ip "$@" | |
| } | |
| cidr_mask() { | |
| _dec_to_ip "$(_cidr_mask_dec "$1")" | |
| } | |
| subnet_mask_bin() { | |
| ip2b "$(cidr_mask "$1")" | tr -d '.' | |
| } | |
| network_address() { | |
| local ip cidr ip_dec mask_dec | |
| ip="$1" | |
| cidr="$2" | |
| case "$ip" in | |
| */*) cidr="${ip#*/}"; ip="${ip%%/*}" ;; | |
| esac | |
| [ -n "$ip" ] && [ -n "$cidr" ] || { | |
| echo "usage: network_address <ip> <cidr> or network_address <ip/cidr>" >&2 | |
| return 2 | |
| } | |
| ip_dec="$(_ip_to_dec "$ip")" || return | |
| mask_dec="$(_cidr_mask_dec "$cidr")" || return | |
| _dec_to_ip "$((ip_dec & mask_dec))" | |
| } | |
| cidr_info() { | |
| local ip cidr ip_dec mask_dec net_dec wild_dec bc_dec hosts first_dec last_dec | |
| ip="$1" | |
| cidr="$2" | |
| case "$ip" in | |
| */*) cidr="${ip#*/}"; ip="${ip%%/*}" ;; | |
| esac | |
| [ -n "$ip" ] && [ -n "$cidr" ] || { | |
| echo "usage: cidr_info <ip> <cidr> or cidr_info <ip/cidr>" >&2 | |
| return 2 | |
| } | |
| ip_dec="$(_ip_to_dec "$ip")" || return | |
| mask_dec="$(_cidr_mask_dec "$cidr")" || return | |
| net_dec=$((ip_dec & mask_dec)) | |
| wild_dec=$((0xffffffff ^ mask_dec)) | |
| bc_dec=$((net_dec | wild_dec)) | |
| if [ "$cidr" -lt 31 ]; then | |
| hosts=$((bc_dec - net_dec - 1)) | |
| first_dec=$((net_dec + 1)) | |
| last_dec=$((bc_dec - 1)) | |
| else | |
| hosts=$((bc_dec - net_dec + 1)) | |
| first_dec=$net_dec | |
| last_dec=$bc_dec | |
| fi | |
| printf 'ip: %s/%s\n' "$ip" "$cidr" | |
| printf 'mask: %s\n' "$(_dec_to_ip "$mask_dec")" | |
| printf 'wildcard: %s\n' "$(_dec_to_ip "$wild_dec")" | |
| printf 'network: %s\n' "$(_dec_to_ip "$net_dec")" | |
| printf 'broadcast: %s\n' "$(_dec_to_ip "$bc_dec")" | |
| printf 'first: %s\n' "$(_dec_to_ip "$first_dec")" | |
| printf 'last: %s\n' "$(_dec_to_ip "$last_dec")" | |
| printf 'hosts: %s\n' "$hosts" | |
| } | |
| # ----- Archive helpers ----- | |
| tarball() { | |
| local algo="" out="" level="" ext="" ts base first tmp status | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| -h|--help) | |
| cat <<'EOF' | |
| usage: tarball [options] <path> [path ...] | |
| Create a compressed tar archive. | |
| Options: | |
| --zstd | --zst use zstd compression | |
| --gzip | --gz use gzip compression | |
| --xz use xz compression | |
| --none | --plain create an uncompressed .tar | |
| -o, --output FILE write archive to FILE | |
| -N, --level N compression level, e.g. -3, -10, -19 | |
| Defaults to zstd level 10 when zstd is installed, otherwise gzip level 6. | |
| Examples: | |
| tarball mydir | |
| tarball --gzip -9 mydir | |
| tarball --zstd -19 -o mydir.tar.zst mydir | |
| EOF | |
| return 0 | |
| ;; | |
| -o|--output) | |
| shift | |
| [ -n "$1" ] || { | |
| echo "tarball: missing output file" >&2 | |
| return 2 | |
| } | |
| out="$1" | |
| ;; | |
| --zstd|--zst) | |
| algo="zstd" | |
| ;; | |
| --gzip|--gz) | |
| algo="gzip" | |
| ;; | |
| --xz) | |
| algo="xz" | |
| ;; | |
| --none|--plain) | |
| algo="none" | |
| ;; | |
| --level) | |
| shift | |
| [ -n "$1" ] || { | |
| echo "tarball: missing compression level" >&2 | |
| return 2 | |
| } | |
| level="$1" | |
| ;; | |
| -[0-9]|-[0-9][0-9]) | |
| level="${1#-}" | |
| ;; | |
| --) | |
| shift | |
| break | |
| ;; | |
| -*) | |
| echo "tarball: unknown option: $1" >&2 | |
| return 2 | |
| ;; | |
| *) | |
| break | |
| ;; | |
| esac | |
| shift | |
| done | |
| [ $# -gt 0 ] || { | |
| echo "usage: tarball [options] <path> [path ...]" >&2 | |
| return 2 | |
| } | |
| if [ -z "$algo" ]; then | |
| if command -v zstd >/dev/null 2>&1; then | |
| algo="zstd" | |
| else | |
| algo="gzip" | |
| fi | |
| fi | |
| case "$algo" in | |
| zstd) | |
| command -v zstd >/dev/null 2>&1 || { | |
| echo "tarball: zstd not found" >&2 | |
| return 1 | |
| } | |
| ext="zst" | |
| level="${level:-10}" | |
| ;; | |
| gzip) | |
| command -v gzip >/dev/null 2>&1 || { | |
| echo "tarball: gzip not found" >&2 | |
| return 1 | |
| } | |
| ext="gz" | |
| level="${level:-6}" | |
| ;; | |
| xz) | |
| command -v xz >/dev/null 2>&1 || { | |
| echo "tarball: xz not found" >&2 | |
| return 1 | |
| } | |
| ext="xz" | |
| level="${level:-6}" | |
| ;; | |
| none) | |
| ext="tar" | |
| ;; | |
| *) | |
| echo "tarball: unknown compression algorithm: $algo" >&2 | |
| return 2 | |
| ;; | |
| esac | |
| case "$level" in | |
| ''|*[!0-9]*) | |
| if [ "$algo" != "none" ]; then | |
| echo "tarball: compression level must be numeric" >&2 | |
| return 2 | |
| fi | |
| ;; | |
| esac | |
| if [ -z "$out" ]; then | |
| ts="$(date +%Y%m%d-%H%M%S)" | |
| if [ $# -eq 1 ]; then | |
| first="${1%/}" | |
| if [ "$first" = "." ]; then | |
| base="$(basename "$PWD")" | |
| out="../${base}-${ts}.tar.${ext}" | |
| else | |
| base="$(basename "$first")" | |
| [ -n "$base" ] || base="archive" | |
| out="${base}-${ts}.tar.${ext}" | |
| fi | |
| else | |
| out="archive-${ts}.tar.${ext}" | |
| fi | |
| fi | |
| [ ! -e "$out" ] || { | |
| echo "tarball: output already exists: $out" >&2 | |
| return 1 | |
| } | |
| tmp="${TMPDIR:-/tmp}/tarball.$$.$RANDOM.tar.${ext}.tmp" | |
| case "$algo" in | |
| zstd) | |
| ( set -o pipefail; tar -cf - "$@" | zstd "-$level" -T0 -q -o "$tmp" ) | |
| status=$? | |
| ;; | |
| gzip) | |
| ( set -o pipefail; tar -cf - "$@" | gzip "-$level" > "$tmp" ) | |
| status=$? | |
| ;; | |
| xz) | |
| ( set -o pipefail; tar -cf - "$@" | xz "-$level" > "$tmp" ) | |
| status=$? | |
| ;; | |
| none) | |
| tar -cf "$tmp" "$@" | |
| status=$? | |
| ;; | |
| esac | |
| if [ "$status" -ne 0 ]; then | |
| rm -f "$tmp" | |
| return "$status" | |
| fi | |
| mv "$tmp" "$out" || { | |
| status=$? | |
| rm -f "$tmp" | |
| return "$status" | |
| } | |
| printf 'created: %s\n' "$out" | |
| ls -lh "$out" | |
| } | |
| _git_current_branch() { | |
| git symbolic-ref --quiet --short HEAD 2>/dev/null || | |
| git rev-parse --short HEAD 2>/dev/null | |
| } | |
| # gsync: update the current branch from its configured upstream. | |
| gsync() { | |
| git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { | |
| echo "not inside a git repo" >&2 | |
| return 2 | |
| } | |
| local branch upstream | |
| branch="$(_git_current_branch)" || return | |
| upstream="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null)" || { | |
| echo "current branch '${branch}' has no upstream" >&2 | |
| echo "set one with: git push -u origin ${branch}" >&2 | |
| return 2 | |
| } | |
| git fetch --all --prune --tags || return | |
| git pull --rebase --autostash || return | |
| echo | |
| git status -sb | |
| git log --oneline -5 | |
| } | |
| # gclean: delete local branches already merged into the current branch. | |
| # Run from main if you mean "clean branches merged to main". | |
| gclean() { | |
| git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { | |
| echo "not inside a git repo" >&2 | |
| return 2 | |
| } | |
| git fetch --all --prune || return | |
| git branch --merged | | |
| sed 's/^[ *+]*//' | | |
| grep -vE '^(main|master|develop)$' | | |
| while IFS= read -r branch; do | |
| [ -n "$branch" ] || continue | |
| git branch -d "$branch" | |
| done | |
| local gone | |
| gone="$( | |
| git branch -vv | | |
| sed 's/^[ *+]*//' | | |
| awk '/: gone\]/{print $1}' | |
| )" | |
| if [ -n "$gone" ]; then | |
| echo | |
| echo "upstream gone but not deleted with -d:" | |
| echo "$gone" | sed 's/^/ /' | |
| echo | |
| echo "delete manually after review with: git branch -D <branch>" | |
| fi | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment