Skip to content

Instantly share code, notes, and snippets.

@seefood
Last active November 2, 2025 09:36
Show Gist options
  • Select an option

  • Save seefood/896a042ea975b778d93159c6a9e3e0a5 to your computer and use it in GitHub Desktop.

Select an option

Save seefood/896a042ea975b778d93159c6a9e3e0a5 to your computer and use it in GitHub Desktop.
curated favourite bash_it stuff for zsh
# Just the git aliases from bash-it, till I decide if I prefer the ones in the zsh plugin or not.
alias g='git'
alias get='git'
alias got='git'
# add
alias ga='git add'
alias gall='git add -A'
alias gap='git add -p'
alias gav='git add -v'
# branch
alias gb='git branch'
alias gba='git branch --all'
alias gbd='git branch -d'
alias gbD='git branch -D'
alias gbl='git branch --list'
alias gbla='git branch --list --all'
alias gblr='git branch --list --remotes'
alias gbm='git branch --move'
alias gbr='git branch --remotes'
alias gbt='git branch --track'
# for-each-ref
alias gbc='git for-each-ref --format="%(authorname) %09 %(if)%(HEAD)%(then)*%(else)%(refname:short)%(end) %09 %(creatordate)" refs/remotes/ --sort=authorname DESC' # FROM https://stackoverflow.com/a/58623139/10362396
# commit
alias gc='git commit -v'
alias gca='git commit -v -a'
alias gcaa='git commit -a --amend -C HEAD' # Add uncommitted and unstaged changes to the last commit
alias gcam='git commit -v -am'
alias gcamd='git commit --amend'
alias gc!='git commit -v --amend'
alias gca!='git commit -v -a --amend'
alias gcn!='git commit -v --amend --no-edit'
alias gcm='git commit -v -m'
alias gci='git commit --interactive'
alias gcsam='git commit -S -am'
# checkout
alias gcb='git checkout -b'
alias gco='git checkout'
alias gcob='git checkout -b'
alias gcobu='git checkout -b ${USER}/'
alias gcom='git checkout $(get_default_branch)'
alias gcpd='git checkout $(get_default_branch); git pull; git branch -D'
alias gct='git checkout --track'
# clone
alias gcl='git clone'
# clean
alias gclean='git clean -fd'
# cherry-pick
alias gcp='git cherry-pick'
alias gcpx='git cherry-pick -x'
# diff
alias gd='git diff'
alias gds='git diff --staged'
alias gdt='git difftool'
# archive
alias gexport='git archive --format zip --output'
# fetch
alias gf='git fetch --all --prune'
alias gft='git fetch --all --prune --tags'
alias gftv='git fetch --all --prune --tags --verbose'
alias gfv='git fetch --all --prune --verbose'
alias gmu='git fetch origin -v; git fetch upstream -v; git merge upstream/$(get_default_branch)'
alias gup='git fetch && git rebase'
# log
alias gg='git log --graph --pretty=format:'\''%C(bold)%h%Creset%C(magenta)%d%Creset %s %C(yellow)<%an> %C(cyan)(%cr)%Creset'\'' --abbrev-commit --date=relative'
alias ggf='git log --graph --date=short --pretty=format:'\''%C(auto)%h %Cgreen%an%Creset %Cblue%cd%Creset %C(auto)%d %s'\'''
alias ggs='gg --stat'
alias ggup='git log --branches --not --remotes --no-walk --decorate --oneline' # FROM https://stackoverflow.com/questions/39220870/in-git-list-names-of-branches-with-unpushed-commits
alias gll='git log --graph --pretty=oneline --abbrev-commit'
alias gnew='git log HEAD@{1}..HEAD@{0}' # Show commits since last pull, see http://blogs.atlassian.com/2014/10/advanced-git-aliases/
alias gwc='git whatchanged'
alias ghist='git log --pretty=format:'\''%h %ad | %s%d [%an]'\'' --graph --date=short' # Use it to be fast and without color.
alias gprogress='git log --pretty=format:'\''%C(yellow)%h %Cblue%ad %Creset%s%Cgreen [%cn] %Cred%d'\'' --decorate --date=short' #Usually use "git progress" in the file .gitconfig. The new alias from Git friends will be truly welcome.
# ls-files
alias gu='git ls-files . --exclude-standard --others' # Show untracked files
alias glsut='gu'
alias glsum='git diff --name-only --diff-filter=U' # Show unmerged (conflicted) files
# gui
alias ggui='git gui'
# home
alias ghm='cd "$(git rev-parse --show-toplevel)"' # Git home
# appendage to ghm
if ! _command_exists gh; then
alias gh='ghm'
fi
# merge
alias gm='git merge'
alias gma='git merge --abort'
alias gmc='git merge --continue'
alias gms='git merge --squash'
alias gmt='git mergetool'
# mv
alias gmv='git mv'
# patch
alias gpatch='git format-patch -1'
# push
alias gp='git push'
alias gpd='git push --delete'
alias gpf='git push --force-with-lease'
alias gpff='git push --force'
alias gpo='git push origin HEAD'
alias gpom='git push origin $(get_default_branch)'
alias gpu='git push --set-upstream'
alias gpunch='git push --force-with-lease'
alias gpuo='git push --set-upstream origin'
alias gpuoc='git push --set-upstream origin $(git symbolic-ref --short HEAD)'
# pull
alias gl='git pull'
alias glp='git pull --prune'
alias glum='git pull upstream $(get_default_branch)'
alias gpl='git pull'
alias gpp='git pull && git push'
alias gpr='git pull --rebase'
# remote
alias gr='git remote'
alias gra='git remote add'
alias grv='git remote -v'
# rm
alias grm='git rm'
alias grmc='git rm --cached' # Removes the file only from the Git repository, but not from the filesystem. This is useful to undo some of the changes you made to a file before you commit it.
# rebase
alias grb='git rebase'
alias grba='git rebase --abort'
alias grbc='git rebase --continue'
alias grbm='git rebase $(get_default_branch)'
alias grbmi='git rebase $(get_default_branch) --interactive'
alias grbma='GIT_SEQUENCE_EDITOR=: git rebase $(get_default_branch) --interactive --autosquash'
alias gprom='git fetch origin $(get_default_branch) && git rebase origin/$(get_default_branch) && git update-ref refs/heads/$(get_default_branch) origin/$(get_default_branch)' # Rebase with latest remote
# reset
alias gus='git reset HEAD' # read as: 'git unstage'
alias grh='git reset' # equivalent to: git reset HEAD
alias grh!='git reset --hard'
alias gpristine='git reset --hard && git clean -dfx'
# status
alias gs='git status'
alias gss='git status -s'
# shortlog
alias gcount='git shortlog -sn'
alias gsl='git shortlog -sn'
# show
alias gsh='git show'
alias gshn='git show --name-only'
alias gshns='git show --name-status'
# svn
alias gsd='git svn dcommit'
alias gsr='git svn rebase' # Git SVN
# stash
alias gst='git stash'
alias gstb='git stash branch'
alias gstd='git stash drop'
alias gstl='git stash list'
alias gstp='git stash pop' # kept due to long-standing usage
alias gstpo='git stash pop' # recommended for it's symmetry with gstpu (push)
## 'stash push' introduced in git v2.13.2
alias gstpu='git stash push'
alias gstpum='git stash push -m'
## 'stash save' deprecated since git v2.16.0, alias is now push
alias gsts='git stash push'
alias gstsm='git stash push -m'
# submodules
alias gsu='git submodule update --init --recursive'
# switch
# these aliases requires git v2.23+
alias gsw='git switch'
alias gswc='git switch --create'
alias gswm='git switch $(get_default_branch)'
alias gswt='git switch --track'
# tag
alias gt='git tag'
alias gta='git tag -a'
alias gtd='git tag -d'
alias gtl='git tag -l'
#worktree
alias gw='git worktree'
alias gwa='git worktree add'
alias gwl='git worktree list'
alias gwr='git worktree remove'
case $OSTYPE in
darwin*)
alias gtls="git tag -l | gsort -V"
;;
*)
alias gtls='git tag -l | sort -V'
;;
esac
# functions
function gdv() {
git diff --ignore-all-space "$@" | vim -R -
}
function get_default_branch() {
branch=$(git symbolic-ref refs/remotes/origin/HEAD)
${branch#refs/remotes/origin/}
}
# To use this, add the following line to your .zshrc (assuming you have zinit installed!)
# zinit snippet https://gist.github.com/seefood/896a042ea975b778d93159c6a9e3e0a5/raw/aliases.sh
# For more info, dig into my dotfiles: https://github.com/seefood/dotfiles
function _command_exists() {
if [[ -n "$ZSH_VERSION?" ]]; then
type -w "${1?}" > /dev/null
else #assume bash
type -t "${1?}" > /dev/null
fi
}
### Ansible
if _command_exists ansible; then
alias ans=ansible
alias ap=ansible-playbook
fi
### TheFUCK
# https://github.com/nvbn/thefuck
if _command_exists thefuck; then
# shellcheck disable=SC2046
eval $(thefuck --alias)
alias please=fuck
alias plz=please
alias fucking=sudo
fi
### CURL
if _command_exists curl; then
# follow redirects
alias cl='curl -L'
# follow redirects, download as original name
alias clo='curl -L -O'
# follow redirects, download as original name, continue
alias cloc='curl -L -C - -O'
# follow redirects, download as original name, continue, retry 5 times
alias clocr='curl -L -C - -O --retry 5'
# follow redirects, fetch banner
alias clb='curl -L -I'
# see only response headers from a get request
alias clhead='curl -D - -so /dev/null'
fi
### HOMEBREW (on mac)
if _command_exists brew; then
alias bed='brew edit'
alias bls='brew list'
alias bsr='brew search'
alias bdr='brew doctor'
alias bin='brew install'
alias bcl='brew cleanup'
alias brm='brew uninstall'
alias bout='brew outdated'
alias binf='brew info'
alias bup='brew update && brew upgrade'
# Cask stuff
alias bcin='brew cask install'
alias bcrm='brew cask uninstall'
alias bczp='brew cask zap'
alias bccl='brew cask cleanup'
alias bcls='brew cask list'
alias bcinf='brew cask info'
alias bcdr='brew cask doctor'
alias bced='brew cask edit'
fi
### MISC
if command ls --color -d . &> /dev/null; then
alias ls='ls --color=auto'
# BSD `ls` doesn't need an argument (`-G`) when `$CLICOLOR` is set.
fi
# List directory contents
alias sl=ls
alias la='ls -AF' # Compact view, show hidden
alias ll='ls -Al'
alias l='ls -A'
alias l1='ls -1'
alias lf='ls -F'
alias _='sudo'
# colored grep
# Need to check an existing file for a pattern that will be found to ensure
# that the check works when on an OS that supports the color option
if command grep --color=auto "a" ~/.zshrc &> /dev/null; then
alias grep='grep --color=auto'
fi
if _command_exists gshuf; then
alias shuf=gshuf
fi
alias c='clear'
alias cls='clear'
alias edit='${EDITOR:-${ALTERNATE_EDITOR:-nano}}'
alias pager='${PAGER:=less}'
alias q='exit'
# Language aliases
alias rb='ruby'
alias py='python'
alias ipy='ipython'
alias ..='cd ..' # Go up one directory
alias cd..='cd ..' # Common misspelling for going up one directory
alias ...='cd ../..' # Go up two directories
alias ....='cd ../../..' # Go up three directories
alias -- -='cd -' # Go back
alias dow='cd $HOME/Downloads' # Go to the Downloads directory
# Shell History
alias h='history'
# Tree
if ! _command_exists tree; then
alias tree="find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'"
fi
# Directory
alias md='mkdir -p'
alias rd='rmdir'
# Remove
alias rmrf='rm -rf'
# Shorten extract
_command_exists 'extract' \
&& alias xt='extract'
# sudo editors
alias svim='sudo "${VISUAL:-vim}"'
alias snano='sudo "${ALTERNATE_EDITOR:-nano}"'
# Display whatever file is regular file or folder
function catt() {
for i in "$@"; do
if [[ -d "$i" ]]; then
ls "$i"
else
cat "$i"
fi
done
}
alias edit='${EDITOR:-${ALTERNATE_EDITOR:-nano}}'
alias e='edit'
alias v='${VISUAL:-vim}'
# open the vim help in fullscreen incorporated from
# https://stackoverflow.com/a/4687513
alias vimh='vim -c ":h | only"'
# sudo editors
alias svim='sudo ${VISUAL:-vim}'
alias snano='sudo ${ALTERNATE_EDITOR:-nano}'
alias sedit='sudo ${EDITOR:-${ALTERNATE_EDITOR:-nano}}'
# Shortcuts to edit startup files
alias vbrc='${VISUAL:-vim} ~/.bashrc'
alias vbpf='${VISUAL:-vim} ~/.bash_profile'
### OS specific
if [[ "$OSTYPE" == "darwin"* ]]; then
alias preview='open -a "${PREVIEW?}"'
alias xcode='open -a "/Applications/XCode.app"'
alias filemerge='open -a "/Developer/Applications/Utilities/FileMerge.app"'
alias safari='open -a safari'
alias firefox='open -a firefox'
alias chrome='open -a "Google Chrome"'
alias chromium='open -a chromium'
alias f='open -a Finder '
alias fh='open -a Finder .'
alias textedit='open -a TextEdit'
alias subl='open -a "Sublime Text"'
if [[ -s /usr/bin/firefox ]]; then
unalias firefox
fi
# Get rid of those pesky .DS_Store files recursively
alias dsclean='find . -type f -name .DS_Store -delete'
# From http://apple.stackexchange.com/questions/110343/copy-last-command-in-terminal
# shellcheck disable=SC2142 # The quoting confuses `shellcheck`...
alias copyLastCmd="fc -ln -1 | awk '{\$1=\$1}1' ORS='' | pbcopy"
# Use Finder's Quick Look on a file (^C or space to close)
alias ql='qlmanage -p 2>/dev/null'
# Pin to the tail of long commands for an audible alert after long processes
## curl http://downloads.com/hugefile.zip; lmk
alias lmk='say "Process complete."'
elif [[ "$OSTYPE" == "linux"* ]]; then
# Improve aliases by bringing the common root `sc|scd` + `sre` for action + `u` for user
alias sc='systemctl'
alias scu='systemctl --user'
alias scdr='systemctl daemon-reload'
alias scdru='systemctl --user daemon-reload'
alias scr='systemctl restart'
alias scru='systemctl --user restart'
alias sce='systemctl stop'
alias sceu='systemctl --user stop'
alias scs='systemctl start'
alias scsu='systemctl --user start'
# Keeping previous aliases for a non-breaking change.
alias scue='sceu'
alias scus='scsu'
alias scur='scdru'
fi
### Terraform
if _command_exists terraform; then
alias tf='terraform'
elif _command_exists tofu; then
alias tf='tofu'
fi
if _command_exists tf; then
alias tfa='tf apply'
alias tfp='tf plan'
alias tfd='tf destroy'
alias tfv='tf validate'
alias tfi='tf init'
alias tfo='tf output'
alias tfr='tf refresh'
alias tfw='tf workspace'
alias tfae='tf apply -auto-approve'
alias tfpa='tf plan -out=tfplan && tf apply tfplan'
alias tfpaf='tf plan -out=tfplan && tf apply -auto-approve tfplan'
fi
### TMUX
alias txl='tmux ls'
alias txn='tmux new -s'
alias txa='tmux a -t'
# To use this, add the following line to your .zshrc (assuming you have zinit installed!)
# zinit snippet https://gist.github.com/seefood/896a042ea975b778d93159c6a9e3e0a5/raw/completions.sh
# For more info, dig into my dotfiles: https://github.com/seefood/dotfiles
# This file is mostly skewed for zsh. all sources are based on bash_it completions, so if you want
# these in bash, just install bash_it and pick the completions you want.
_command_exists brew && eval "$(brew shellenv)"
# bash compat mode
autoload -U +X bashcompinit && bashcompinit
autoload -Uz +X compinit && compinit
# Automatically add completion for all aliases to commands having completion functions
function _bash-it-component-completion-callback-on-init-aliases() {
local aliasCommandFunction namespace="alias_completion"
local tmp_file completion_loader alias_name line completions chars
local alias_arg_words new_completion compl_func compl_wrapper alias_defn
# Read each line of `complete -p` output into an array, preserving multi-word lines
completions=("${(@f)$(complete -p)}")
(( ${#completions[@]} == 0 )) && return 0
completions=("${completions[@]##complete -* * -}") # strip all but last option plus trigger(s)
completions=("${completions[@]#complete -}") # strip anything missed
completions=("${completions[@]#? * }") # strip last option and arg, leaving only trigger(s)
completions=("${completions[@]#? }") # strip anything missed
#TODO: this will fail on some completions...
# create temporary file for wrapper functions and completions
tmp_file="$(mktemp -t "${namespace}-${RANDOM}XXXXXX")" || return 1
IFS=$'\n' read -r completion_loader < <(complete -p -D 2> /dev/null)
if [[ "${completion_loader#complete }" =~ '-F'[[:space:]]([[:alnum:]_]+)[[:space:]] ]]; then
completion_loader="${BASH_REMATCH[1]}"
else
completion_loader=""
fi
# read in "<alias> '<aliased command>' '<command args>'" lines from defined aliases
# some aliases do have backslashes that needs to be interpreted
# shellcheck disable=SC2162
while read line; do
line="${line#alias -- }"
line="${line#alias }"
alias_name="${line%%=*}"
# Skip aliases not added by this script that already have completion functions.
# This allows users to define their own alias completion functions.
# For aliases added by this script, we do want to replace them in case the
# alias getting the completion added has changed.
if complete -p "$alias_name" &> /dev/null; then
# Get the -F argument from the existing completion for this alias.
aliasCommandFunction=$(complete -p "$alias_name" | rev | cut -d " " -f 2 | rev)
# Check if aliasCommandFunction starts with our namespace.
if [[ "$aliasCommandFunction" != "_${namespace}::"* ]]; then
continue
fi
# Remove existing completion. It will be replaced by the new one. We need to
# delete it in case the new alias does not support having completion added.
complete -r "$alias_name"
fi
alias_defn="${line#*=\'}" # alias definition
alias_defn="${alias_defn%\'}"
alias_cmd="${alias_defn%%[[:space:]]*}" # first word of alias
if [[ ${alias_defn} == "${alias_cmd}" ]]; then
alias_args=''
else
alias_args="${alias_defn#*[[:space:]]}" # everything after first word
fi
# skip aliases to pipes, boolean control structures and other command lists
chars=$'|&;()<>\n'
if [[ "${alias_defn}" =~ [$chars] ]]; then
continue
fi
# avoid expanding wildcards
read -ra alias_arg_words <<< "$alias_args"
# skip alias if there is no completion function triggered by the aliased command
if ! _bash-it-array-contains-element "$alias_cmd" "${completions[@]}"; then
if [[ -n "$completion_loader" ]]; then
# force loading of completions for the aliased command
"${completion_loader:?}" "${alias_cmd}"
# 124 means completion loader was successful
[[ $? -eq 124 ]] || continue
completions+=("$alias_cmd")
else
continue
fi
fi
new_completion="$(complete -p "$alias_cmd" 2> /dev/null)"
compl_func="${new_completion/#* -F /}"
compl_func="${compl_func%% *}"
# avoid recursive call loops by ignoring our own functions
if [[ "${compl_func#_"$namespace"::}" == "$compl_func" ]]; then
compl_wrapper="_${namespace}::${alias_name}"
if [[ -z $alias_args ]]; then
# Create a wrapper without arguments.
# This allows identifying the completions added by this script on reload.
echo "function $compl_wrapper {
$compl_func \"\$@\"
}" >> "$tmp_file"
else
# Create a wrapper inserting the alias arguments
# The use of printf on alias_arg_words is needed to ensure each element of
# the array is quoted. E.X. (one two three) -> ('one' 'two' 'three')
echo "function $compl_wrapper {
local compl_word=\${2?}
local prec_word=\${3?}
# check if prec_word is the alias itself. if so, replace it
# with the last word in the unaliased form, i.e.,
# alias_cmd + ' ' + alias_args.
if [[ \$COMP_LINE == \"\$prec_word \$compl_word\" ]]; then
prec_word='$alias_cmd $alias_args'
prec_word=\${prec_word#* }
fi
(( COMP_CWORD += ${#alias_arg_words[@]} ))
COMP_WORDS=(\"$alias_cmd\" $(printf "%q " "${alias_arg_words[@]}") \"\${COMP_WORDS[@]:1}\")
(( COMP_POINT -= \${#COMP_LINE} ))
COMP_LINE=\${COMP_LINE/$alias_name/$alias_cmd $alias_args}
(( COMP_POINT += \${#COMP_LINE} ))
\"$compl_func\" \"$alias_cmd\" \"\$compl_word\" \"\$prec_word\"
}" >> "$tmp_file"
fi
new_completion="${new_completion/ -F $compl_func / -F $compl_wrapper }"
fi
# replace completion trigger by alias
if [[ -n $new_completion ]]; then
new_completion="${new_completion% *} $alias_name"
echo "$new_completion" >> "$tmp_file"
fi
done < <(alias -p)
# shellcheck source=/dev/null
source "$tmp_file" && command rm -f "$tmp_file"
}
_bash-it-component-completion-callback-on-init-aliases
_command_exists awless && source <(awless completion zsh)
_command_exists aws && complete -C aws_completer aws
_command_exists terraform && complete -o nospace -C $(which terraform) terraform
_command_exists tofu && complete -o nospace -C $(which tofu) tofu
# To use this, add the following line to your .zshrc (assuming you have zinit installed!)
# zinit snippet https://gist.github.com/seefood/896a042ea975b778d93159c6a9e3e0a5/raw/plugins.sh
# For more info, dig into my dotfiles: https://github.com/seefood/dotfiles
function _command_exists() {
if [[ -n "$ZSH_VERSION?" ]]; then
type -w "${1?}" > /dev/null
else #assume bash
type -t "${1?}" > /dev/null
fi
}
function ips() {
# 'display all ip addresses for this host'
if _command_exists ifconfig; then
ifconfig | awk '/inet /{ gsub(/addr:/, ""); print $2 }'
elif _command_exists ip; then
ip addr | grep -oP 'inet \K[\d.]+'
else
echo "You don't have ifconfig or ip command installed!"
fi
}
function myip() {
# 'displays your ip address, as seen by the Internet'
list=("http://myip.dnsomatic.com/" "http://checkip.dyndns.com/" "http://checkip.dyndns.org/")
for url in "${list[@]}"; do
if res="$(curl -fs "${url}")"; then
break
fi
done
res="$(echo "$res" | grep -Eo '[0-9\.]+')"
echo -e "Your public IP is: ${echo_bold_green-} $res ${echo_normal-}"
}
function pickfrom() {
# 'picks random line from file'
local file=${1:-}
local -i n=0 length
if [[ ! -r "$file" ]]; then
reference "${FUNCNAME[0]}" && return
fi
length="$(wc -l < "$file")"
n=$((RANDOM * length / 32768 + 1))
head -n "$n" "$file" | tail -1
}
function usage() {
# 'disk usage per directory, in Mac OS X and Linux'
case $OSTYPE in
*'darwin'*)
du -hd 1 "$@"
;;
*'linux'*)
du -h --max-depth=1 "$@"
;;
esac
}
# replace multiple file extensions at once
function renex() {
local ext2replace="${1:-}"
local newext="${2:-}"
local files=(*."$ext2replace")
for file in "${files[@]}"; do
local dst=${file/%."$ext2replace"/."$newext"}
mv "$file" "$dst"
done
}
if _command_exists direnv; then
eval "$(direnv hook bash)"
fi
# Show directory stack
alias d="dirs -v -l"
# Change to location in stack by number
alias 1="pushd"
alias 2="pushd +2"
alias 3="pushd +3"
alias 4="pushd +4"
alias 5="pushd +5"
alias 6="pushd +6"
alias 7="pushd +7"
alias 8="pushd +8"
alias 9="pushd +9"
# Clone this location
alias pc='pushd "${PWD}"'
# Push new location
alias pu="pushd"
# Pop current location
alias po="popd"
_command_exists fasd && eval "$(fasd --init auto)"
# Colourful man
alias man="\
LESS_TERMCAP_mb=$'\e[1;32m' \
LESS_TERMCAP_md=$'\e[1;32m' LESS_TERMCAP_me=$'\e[0m' \
LESS_TERMCAP_se=$'\e[0m' LESS_TERMCAP_so=$'\e[01;33m' \
LESS_TERMCAP_ue=$'\e[0m' LESS_TERMCAP_us=$'\e[1;4;31m' \
LESS=--RAW-CONTROL-CHARS \man"
function add_ssh() {
# 'add entry to ssh config'
#param '1: host'
#param '2: hostname'
#param '3: user'
[[ $# -ne 3 ]] && echo "add_ssh host hostname user" && return 1
[[ ! -d ~/.ssh ]] && mkdir -m 700 ~/.ssh
[[ ! -e ~/.ssh/config ]] && touch ~/.ssh/config && chmod 600 ~/.ssh/config
echo -en "\n\nHost $1\n HostName $2\n User $3\n ServerAliveInterval 30\n ServerAliveCountMax 120" >> ~/.ssh/config
}
function sshlist() {
# 'list hosts defined in ssh config'
awk '$1 ~ /Host$/ {for (i=2; i<=NF; i++) print $i}' ~/.ssh/config
}
function ssh-add-all() {
# 'add all ssh private keys to agent'
grep -slR "PRIVATE" ~/.ssh | xargs ssh-add
}
function sudo-command-line() {
# "toggle sudo at the beginning of the current or the previous command by hitting the ESC key twice"
[[ ${#READLINE_LINE} -eq 0 ]] && READLINE_LINE=$(fc -l -n -1 | xargs)
if [[ $READLINE_LINE == sudo\ * ]]; then
READLINE_LINE="${READLINE_LINE#sudo }"
else
READLINE_LINE="sudo $READLINE_LINE"
fi
READLINE_POINT=${#READLINE_LINE}
}
# Define shortcut keys: [Esc] [Esc]
# Readline library requires bash version 4 or later
if [ "${BASH_VERSINFO[0]}" -ge 4 ]; then
bind -x '"\e\e": sudo-command-line'
fi
# Based loosely on:
# https://gist.github.com/SlexAxton/4989674#comment-1199058
# and other sources
# Renamed gifify to v2gif to go avoid clobbering https://github.com/jclem/gifify
# Requirements (Mac OS X using Homebrew): brew install ffmpeg giflossy imagemagick
# Requirements on Ubuntu: sudo apt install ffmpeg imagemagick ; plus install https://github.com/pornel/giflossy
# Optional: install mediainfo for autodetection of original video FPS.
# Optional: if lossy is not important, Ubuntu has gifsicle packaged for apt-get, instead of giflossy
# Optional: gifski (from `brew install gifski` or github.com/ImageOptim/gifski)
# for high quality huge files.
function v2gif() {
# 'Converts a .mov/.avi/.mp4 file into an into an animated GIF.'
# param '1: MOV/AVI/MP4 file name(s)'
# param '2: -w <num> ; Optional: max width in pixels'
# param '3: -l <num> ; Optional: extra lossy level for smaller files (80-200 make sense, needs giflossy instead of gifsicle)'
# param '4: -h ; Optional: high quality using gifski (installed seperately) - overrides "--lossy" above!'
# param '5: -d ; Optional: delete the original video file if succeeded'
# param '6: -t ; Optional: Tag the result with quality stamp for comparison use'
# param '7: -f <num> ; Optional: Change number of frames per second (default 10 or original FPS if mediainfo installed)'
# param '8: -a <num> ; Optional: Alert if resulting file is over <num> kilobytes (default is 5000, 0 turns off)'
# param '9: -m ; Optional: Also create a WebM file (will one day replace GIF, Smaller and higher quality than mp4)'
# example '$ v2gif foo.mov'
# example '$ v2gif foo.mov -w 600'
# example '$ v2gif -l 100 -d *.mp4'
# example '$ v2gif -dh *.avi'
# example '$ v2gif -thw 600 *.avi *.mov'
local convert ffmpeg mediainfo gifsicle getopt args gifski out_size
convert="$(type -p convert)"
[[ -x "$convert" ]] || {
echo "No convert found!"
return 2
}
ffmpeg="$(type -p ffmpeg)"
[[ -x "$ffmpeg" ]] || {
echo "No ffmpeg found!"
return 2
}
mediainfo="$(type -p mediainfo)"
[[ -x "$mediainfo" ]] || {
echo "No mediainfo found!"
return 2
}
gifsicle="$(type -p gifsicle)"
[[ -x "$gifsicle" ]] || {
echo "No gifsicle found!"
return 2
}
getopt="$(type -p getopt)"
if [[ "$OSTYPE" == "darwin"* ]]; then
# Getopt on BSD is incompatible with GNU
getopt=/usr/local/opt/gnu-getopt/bin/getopt
[[ -x "$getopt" ]] || {
echo "No GNU-getopt found!"
return 2
}
fi
# Parse the options
args=$("$getopt" -l "alert:" -l "lossy:" -l "width:" -l del,delete -l high -l help -l tag -l "fps:" -l webm -o "a:l:w:f:dhmt" -- "$@") || {
echo 'Terminating...' >&2
return 2
}
eval set -- "$args"
local use_gifski=""
local opt_del_after=""
local maxsize=()
local lossiness=()
local maxwidthski=()
local giftagopt=""
local giftag=""
local defaultfps=10
local infps=""
local fps=""
local make_webm=""
local alert=5000
local printhelp=""
while [[ $# -ge 1 ]]; do
case "$1" in
--)
# No more options left.
shift
break
;;
-d | --del | --delete)
# Delete after
opt_del_after="true"
shift
;;
--help)
# Print Help
printhelp="true"
shift
;;
-h | --high)
#High Quality, use gifski
gifski="$(type -p gifski)"
[[ -x "$gifski" ]] || {
echo "No gifski found!"
return 2
}
use_gifski=true
giftag="${giftag}-h"
shift
;;
-w | --width)
maxsize=(-vf "scale=$2:-1")
maxwidthski=(-W "$2")
giftag="${giftag}-w$2"
shift 2
;;
-t | --tag)
# mark with a quality tag
giftagopt="true"
shift
;;
-l | --lossy)
# Use giflossy parameter
lossiness=("--lossy=$2")
giftag="${giftag}-l$2"
shift 2
;;
-f | --fps)
# select fps
infps="$2"
giftag="${giftag}-f$2"
shift 2
;;
-a | --alert)
# set size alert
alert="$2"
shift 2
;;
-m | --webm)
# set size alert
make_webm="true"
shift
;;
esac
done
if [[ -z "$*" || "$printhelp" ]]; then
echo "$(tput setaf 1)No input files given. Example: v2gif file [file...] [-w <max width (pixels)>] [-l <lossy level>] $(tput sgr 0)"
echo "-d/--del/--delete Delete original vid if done suceessfully (and file not over the size limit)"
echo "-h/--high High Quality - use Gifski instead of gifsicle"
echo "-w/--width N Lock maximum gif width to N pixels, resize if necessary"
echo "-t/--tag Add a tag to the output gif describing the options used (useful for comparing several options)"
echo "-l/--lossy N Use the Giflossy parameter for gifsicle (If your version supports it)"
echo "-f/--fps N Override autodetection of incoming vid FPS (useful for downsampling)"
echo "-a/--alert N Alert if over N kilobytes (Defaults to 5000)"
echo "-m/--webm Also create a webm file"
return 1
fi
# Prepare the quality tag if requested.
[[ -z "$giftag" ]] && giftag="-default"
[[ -z "$giftagopt" ]] && giftag=""
for file; do
local output_file="${file%.*}${giftag}.gif"
local del_after=$opt_del_after
if [[ -n "$make_webm" ]]; then
$ffmpeg -loglevel warning -i "$file" \
-c:v libvpx -crf 4 -threads 0 -an -b:v 2M -auto-alt-ref 0 \
-quality best -loop 0 "${file%.*}.webm" || return 2
fi
# Set FPS to match the video if possible, otherwise fallback to default.
if [[ -n "$infps" ]]; then
fps=$infps
else
fps=$defaultfps
if [[ -x "$mediainfo" ]]; then
fps=$($mediainfo "$file" | grep "Frame rate " | sed 's/.*: \([0-9.]\+\) .*/\1/' | head -1)
[[ -z "$fps" ]] && fps=$($mediainfo "$file" | grep "Minimum frame rate" | sed 's/.*: \([0-9.]\+\) .*/\1/' | head -1)
fi
fi
echo "$(tput setaf 2)Creating '$output_file' at $fps FPS ...$(tput sgr 0)"
if [[ "$use_gifski" = "true" ]]; then
# I trust @pornel to do his own resizing optimization choices
$ffmpeg -loglevel warning -i "$file" -r "$fps" -vcodec png v2gif-tmp-%05d.png \
&& $gifski "${maxwidthski[@]}" --fps "$(printf "%.0f" "$fps")" -o "$output_file" v2gif-tmp-*.png || return 2
else
$ffmpeg -loglevel warning -i "$file" "${maxsize[@]}" -r "$fps" -vcodec png v2gif-tmp-%05d.png \
&& $convert +dither -layers Optimize v2gif-tmp-*.png GIF:- \
| $gifsicle "${lossiness[@]}" --no-warnings --colors 256 --delay="$(echo "100/$fps" | bc)" --loop --optimize=3 --multifile - > "$output_file" || return 2
fi
rm v2gif-tmp-*.png
# Checking if the file is bigger than Twitter likes and warn
if [[ $alert -gt 0 ]]; then
out_size=$(wc --bytes < "$output_file")
if [[ $out_size -gt $((alert * 1000)) ]]; then
echo "$(tput setaf 3)Warning: '$output_file' is $((out_size / 1000))kb.$(tput sgr 0)"
[[ "$del_after" == "true" ]] && echo "$(tput setaf 3)Warning: Keeping '$file' even though --del requested.$(tput sgr 0)"
del_after=""
fi
fi
[[ "$del_after" = "true" ]] && rm "$file"
done
echo "$(tput setaf 2)Done.$(tput sgr 0)"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment