Skip to content

Instantly share code, notes, and snippets.

@hed0rah
Last active June 12, 2026 17:19
Show Gist options
  • Select an option

  • Save hed0rah/2f2a76cdbc45f1d03d7e6fca12f8a9b6 to your computer and use it in GitHub Desktop.

Select an option

Save hed0rah/2f2a76cdbc45f1d03d7e6fca12f8a9b6 to your computer and use it in GitHub Desktop.
bashrc functions for fzf, batcat, git, bitwise ops, conversions
# 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