zmodload zsh/datetime zmodload -F zsh/stat b:zstat k () { # ---------------------------------------------------------------------------- # Setup # ---------------------------------------------------------------------------- # Stop stat failing when a directory contains either no files or no hidden files # Track if we _accidentally_ create a new global variable setopt local_options null_glob typeset_silent no_auto_pushd nomarkdirs pushd_silent unsetopt pushd_ignore_dups # Process options and get files/directories typeset -a o_all o_almost_all o_raw o_si o_directory o_mix_directories \ o_no_directory o_no_vcs o_sort o_sort_reverse o_help #typeset -a o_all o_almost_all o_human o_si o_directory o_group_directories \ # o_no_directory o_no_vcs o_sort o_sort_reverse o_help zparseopts -E -D \ a=o_all -all=o_all \ A=o_almost_all -almost-all=o_almost_all \ c=o_sort \ d=o_directory -directory=o_directory \ -mix-directories=o_mix_directories \ b=o_raw -raw-size=o_raw \ -si=o_si \ n=o_no_directory -no-directory=o_no_directory \ -no-vcs=o_no_vcs \ r=o_sort_reverse -reverse=o_sort_reverse \ -sort:=o_sort \ S=o_sort \ t=o_sort \ u=o_sort \ U=o_sort \ -help=o_help #-group-directories-first=o_group_directories \ #h=o_human -human=o_human \ # Print Help if bad usage, or they asked for it if [[ $? != 0 || -n "$o_help" ]] then print -u2 "Usage: k [options] DIR" print -u2 "Options:" print -u2 "\t-a --all list entries starting with ." print -u2 "\t-A --almost-all list all except . and .." print -u2 "\t-c sort by ctime (inode change time)" print -u2 "\t-d --directory list only directories" print -u2 "\t-n --no-directory do not list directories" print -u2 "\t --mix-directories do not group directories first" #print -u2 "\t-h --human show filesizes in human-readable format" print -u2 "\t-b --raw-size show filesizes in byte format" print -u2 "\t --si with -h, use powers of 1000 not 1024" print -u2 "\t-r --reverse reverse sort order" print -u2 "\t-S sort by size" print -u2 "\t-t sort by time (modification time)" print -u2 "\t-u sort by atime (use or access time)" print -u2 "\t-U Unsorted" print -u2 "\t --sort WORD sort by WORD: none (U), size (S)," print -u2 "\t time (t), ctime or status (c)," print -u2 "\t atime or access or use (u)" print -u2 "\t --no-vcs do not get VCS status (much faster)" print -u2 "\t --help show this help" return 1 fi # Check for conflicts if [[ -n "$o_directory" && -n "$o_no_directory" ]]; then print -u2 "$o_directory and $o_no_directory cannot be used together" return 1 fi # case is like a mnemonic for sort order: # lower-case for standard, upper-case for descending local S_ORD="o" R_ORD="O" SPEC="n" # default: by name # translate ls options to glob-qualifiers, # ignoring "--sort" prefix of long-args form case ${o_sort:#--sort} in -U|none) SPEC="N";; -t|time) SPEC="m";; -c|ctime|status) SPEC="c";; -u|atime|access|use) SPEC="a";; # reverse default order for sort by size -S|size) S_ORD="O" R_ORD="o" SPEC="L";; esac if [[ -z "$o_sort_reverse" ]]; then typeset SORT_GLOB="${S_ORD}${SPEC}" else typeset SORT_GLOB="${R_ORD}${SPEC}" fi #if [[ -n "$o_group_directories" ]]; then if [[ -z "$o_mix_directories" ]]; then SORT_GLOB="oe:[[ -d \$REPLY ]];REPLY=\$?:$SORT_GLOB" fi # Check which numfmt available (if any), warn user if not available typeset -i numfmt_available=0 typeset -i gnumfmt_available=0 #if [[ -n "$o_human" ]]; then if [[ -z "$o_raw" ]]; then if (( $+commands[numfmt] )); then numfmt_available=1 elif (( $+commands[gnumfmt] )); then gnumfmt_available=1 else print -u2 "'numfmt' or 'gnumfmt' command not found, human readable output will not work." #print -u2 "\tFalling back to normal file size output" print -u2 "\tFalling back to bytewise file size output" # Set o_human to off #o_human="" # Set o_raw to on o_raw="on" fi fi # Create numfmt local function numfmt_local () { if [[ -n "$o_si" ]]; then if (( numfmt_available )); then numfmt --to=si $1 elif (( gnumfmt_available )); then gnumfmt --to=si $1 fi else if (( numfmt_available )); then numfmt --to=iec $1 elif (( gnumfmt_available )); then gnumfmt --to=iec $1 fi fi } # Setup array of directories to print typeset -a base_dirs typeset base_dir if [[ -z "$@" ]]; then base_dirs=. else base_dirs=($@) fi # Colors # ---------------------------------------------------------------------------- # default colors K_COLOR_DI="01;34" # di:directory K_COLOR_LN="01;36" # ln:symlink K_COLOR_SO="01;35" # so:socket K_COLOR_PI="40;33" # pi:pipe K_COLOR_EX="01;32" # ex:executable K_COLOR_BD="40;33;01" # bd:block special K_COLOR_CD="40;33;01" # cd:character special K_COLOR_SU="37;41" # su:executable with setuid bit set K_COLOR_SG="30;43" # sg:executable with setgid bit set K_COLOR_TW="30;42" # tw:directory writable to others, with sticky bit K_COLOR_OW="34;42" # ow:directory writable to others, without sticky bit K_COLOR_BR="00;90" # branch # read colors if osx and $LSCOLORS is defined if [[ $(uname) == 'Darwin' && -n $LSCOLORS ]]; then # Translate OSX/BSD's LSCOLORS so we can use the same here K_COLOR_DI=$(_k_bsd_to_ansi $LSCOLORS[1] $LSCOLORS[2]) K_COLOR_LN=$(_k_bsd_to_ansi $LSCOLORS[3] $LSCOLORS[4]) K_COLOR_SO=$(_k_bsd_to_ansi $LSCOLORS[5] $LSCOLORS[6]) K_COLOR_PI=$(_k_bsd_to_ansi $LSCOLORS[7] $LSCOLORS[8]) K_COLOR_EX=$(_k_bsd_to_ansi $LSCOLORS[9] $LSCOLORS[10]) K_COLOR_BD=$(_k_bsd_to_ansi $LSCOLORS[11] $LSCOLORS[12]) K_COLOR_CD=$(_k_bsd_to_ansi $LSCOLORS[13] $LSCOLORS[14]) K_COLOR_SU=$(_k_bsd_to_ansi $LSCOLORS[15] $LSCOLORS[16]) K_COLOR_SG=$(_k_bsd_to_ansi $LSCOLORS[17] $LSCOLORS[18]) K_COLOR_TW=$(_k_bsd_to_ansi $LSCOLORS[19] $LSCOLORS[20]) K_COLOR_OW=$(_k_bsd_to_ansi $LSCOLORS[21] $LSCOLORS[22]) fi # ---------------------------------------------------------------------------- # Loop over passed directories and files to display # ---------------------------------------------------------------------------- for base_dir in $base_dirs do # ---------------------------------------------------------------------------- # Display name if multiple paths were passed # ---------------------------------------------------------------------------- if (( $#base_dirs > 1 )); then # Only add a newline if its not the first iteration if [[ "$base_dir" != "${base_dirs[1]}" ]]; then print fi print -r "${base_dir}:" fi # ---------------------------------------------------------------------------- # Vars # ---------------------------------------------------------------------------- typeset -a MAX_LEN A RESULTS STAT_RESULTS typeset TOTAL_BLOCKS # Get now typeset K_EPOCH="${EPOCHSECONDS:?}" typeset -i TOTAL_BLOCKS=0 MAX_LEN=(0 0 0 0 0 0 0) # Array to hold results from `stat` call RESULTS=() # only set once per directory so must be out of the main loop typeset -i IS_GIT_REPO=0 typeset GIT_TOPLEVEL #typeset -i LARGE_FILE_COLOR=196 typeset -i LARGE_FILE_COLOR=1 typeset -a SIZELIMITS_TO_COLOR SIZELIMITS_TO_COLOR=( # 1024 46 # <= 1kb # 2048 82 # <= 2kb # 3072 118 # <= 3kb # 5120 154 # <= 5kb # 10240 190 # <= 10kb # 20480 226 # <= 20kb # 40960 220 # <= 40kb #102400 214 # <= 100kb #262144 208 # <= 0.25mb || 256kb #524288 202 # <= 0.5mb || 512kb 3072 71 # <= 3kb 51200 2 # <= 50kb 262144 3 # <= 0.25mb || 256kb 524288 16 # <= 0.5mb || 512kb ) #typeset -i ANCIENT_TIME_COLOR=236 # > more than 2 years old typeset -i ANCIENT_TIME_COLOR=18 # > more than 2 years old typeset -a FILEAGES_TO_COLOR FILEAGES_TO_COLOR=( # 0 196 # < in the future, #spooky # 60 255 # < less than a min old # 3600 252 # < less than an hour old # 86400 250 # < less than 1 day old # 604800 244 # < less than 1 week old # 2419200 244 # < less than 28 days (4 weeks) old #15724800 242 # < less than 26 weeks (6 months) old #31449600 240 # < less than 1 year old #62899200 238 # < less than 2 years old 0 1 # < in the future, #spooky 3600 15 # < less than an hour old 86400 21 # < less than 1 day old 604800 7 # < less than 1 week old 2419200 20 # < less than 28 days (4 weeks) old 15724800 8 # < less than 26 weeks (6 months) old 31449600 19 # < less than 1 year old ) # ---------------------------------------------------------------------------- # Build up list of files/directories to show # ---------------------------------------------------------------------------- typeset -a show_list show_list=() # Check if it even exists if [[ ! -e $base_dir ]]; then print -u2 "k: cannot access $base_dir: No such file or directory" continue # If its just a file, skip the directory handling elif [[ -f $base_dir ]]; then show_list=($base_dir) #Directory, add its contents elif pushd $base_dir 2>/dev/null; then # Set if we're in a repo or not typeset -i INSIDE_WORK_TREE=0 if $(git rev-parse --is-inside-work-tree 2> /dev/null); then INSIDE_WORK_TREE=1 GIT_TOPLEVEL=$(git rev-parse --show-toplevel) fi # Break total blocks of the front of the stat call, then push the rest to results if [[ -n "$o_all" && -z "$o_almost_all" && -z "$o_no_directory" ]]; then show_list+=(.) show_list+=(..) fi if [[ -n "$o_all" || -n "$o_almost_all" ]]; then if [[ -n "$o_directory" ]]; then show_list+=(*(D/$SORT_GLOB)) elif [[ -n "$o_no_directory" ]]; then #Use (^/) instead of (.) so sockets and symlinks get displayed show_list+=(*(D^/$SORT_GLOB)) else show_list+=(*(D$SORT_GLOB)) fi else if [[ -n "$o_directory" ]]; then show_list+=(*(/$SORT_GLOB)) elif [[ -n "$o_no_directory" ]]; then #Use (^/) instead of (.) so sockets and symlinks get displayed show_list+=(*(^/$SORT_GLOB)) else show_list+=(*($SORT_GLOB)) fi fi else print -u2 "k: permission denied: $base_dir" continue fi # ---------------------------------------------------------------------------- # Stat call to get directory listing # ---------------------------------------------------------------------------- typeset -i i=1 j=1 k=1 typeset -a STATS_PARAMS_LIST typeset fn statvar h typeset -A sv STATS_PARAMS_LIST=() for fn in $show_list do statvar="stats_$i" typeset -A $statvar zstat -H $statvar -Lsn -F "%s^%d^%b^%H:%M^%Y" -- "$fn" # use lstat, render mode/uid/gid to strings STATS_PARAMS_LIST+=($statvar) (( i++ )) done # On each result calculate padding by getting max length on each array member for statvar in "${STATS_PARAMS_LIST[@]}" do sv=("${(@Pkv)statvar}") if [[ ${#sv[mode]} -gt $MAX_LEN[1] ]]; then MAX_LEN[1]=${#sv[mode]} ; fi if [[ ${#sv[nlink]} -gt $MAX_LEN[2] ]]; then MAX_LEN[2]=${#sv[nlink]} ; fi if [[ ${#sv[uid]} -gt $MAX_LEN[3] ]]; then MAX_LEN[3]=${#sv[uid]} ; fi if [[ ${#sv[gid]} -gt $MAX_LEN[4] ]]; then MAX_LEN[4]=${#sv[gid]} ; fi #if [[ -n "$o_human" ]]; then if [[ -z "$o_raw" ]]; then h=$(numfmt_local ${sv[size]}) if (( ${#h} > $MAX_LEN[5] )); then MAX_LEN[5]=${#h}; fi else if [[ ${#sv[size]} -gt $MAX_LEN[5] ]]; then MAX_LEN[5]=${#sv[size]}; fi fi LNAME="'${sv[name]}" if [[ -n ${sv[link]} ]]; then LNAME="$LNAME -> ${sv[link]}"; fi if [[ ${#LNAME} -gt $MAX_LEN[6] ]]; then MAX_LEN[6]=${#LNAME}; fi TOTAL_BLOCKS+=$sv[blocks] done # Print total block before listing echo "total $TOTAL_BLOCKS" # ---------------------------------------------------------------------------- # Loop through each line of stat, pad where appropriate and do git dirty checking # ---------------------------------------------------------------------------- typeset REPOMARKER typeset REPOBRANCH typeset PERMISSIONS HARDLINKCOUNT OWNER GROUP FILESIZE FILESIZE_OUT DATE NAME SYMLINK_TARGET typeset FILETYPE PER1 PER2 PER3 PERMISSIONS_OUTPUT STATUS typeset TIME_DIFF TIME_COLOR DATE_OUTPUT typeset -i IS_DIRECTORY IS_SYMLINK IS_SOCKET IS_PIPE IS_EXECUTABLE IS_BLOCK_SPECIAL IS_CHARACTER_SPECIAL HAS_UID_BIT HAS_GID_BIT HAS_STICKY_BIT IS_WRITABLE_BY_OTHERS typeset -i COLOR k=1 for statvar in "${STATS_PARAMS_LIST[@]}" do sv=("${(@Pkv)statvar}") # We check if the result is a git repo later, so set a blank marker indication the result is not a git repo REPOMARKER=" " REPOBRANCH="" IS_DIRECTORY=0 IS_SYMLINK=0 IS_SOCKET=0 IS_PIPE=0 IS_EXECUTABLE=0 IS_BLOCK_SPECIAL=0 IS_CHARACTER_SPECIAL=0 HAS_UID_BIT=0 HAS_GID_BIT=0 HAS_STICKY_BIT=0 IS_WRITABLE_BY_OTHERS=0 PERMISSIONS="${sv[mode]}" HARDLINKCOUNT="${sv[nlink]}" OWNER="${sv[uid]}" GROUP="${sv[gid]}" FILESIZE="${sv[size]}" DATE=(${(s:^:)sv[mtime]}) # Split date on ^ NAME="${sv[name]}" SYMLINK_TARGET="${sv[link]}" # Check for file types if [[ -d "$NAME" ]]; then IS_DIRECTORY=1; fi if [[ -L "$NAME" ]]; then IS_SYMLINK=1; fi if [[ -S "$NAME" ]]; then IS_SOCKET=1; fi if [[ -p "$NAME" ]]; then IS_PIPE=1; fi if [[ -x "$NAME" ]]; then IS_EXECUTABLE=1; fi if [[ -b "$NAME" ]]; then IS_BLOCK_SPECIAL=1; fi if [[ -c "$NAME" ]]; then IS_CHARACTER_SPECIAL=1; fi if [[ -u "$NAME" ]]; then HAS_UID_BIT=1; fi if [[ -g "$NAME" ]]; then HAS_GID_BIT=1; fi if [[ -k "$NAME" ]]; then HAS_STICKY_BIT=1; fi if [[ $PERMISSIONS[9] == 'w' ]]; then IS_WRITABLE_BY_OTHERS=1; fi # IS_GIT_REPO is a 1 if $NAME is a file/directory in a git repo, OR if $NAME is a git-repo itself # GIT_TOPLEVEL is set to the directory containing the .git folder of a git-repo # Get human readable output if necessary #if [[ -n "$o_human" ]]; then if [[ -z "$o_raw" ]]; then # I hate making this call twice, but its either that, or do a bunch # of calculations much earlier. FILESIZE_OUT=$(numfmt_local $FILESIZE) else FILESIZE_OUT=$FILESIZE fi # Pad so all the lines align - firstline gets padded the other way PERMISSIONS="${(r:MAX_LEN[1]:)PERMISSIONS}" HARDLINKCOUNT="${(l:MAX_LEN[2]:)HARDLINKCOUNT}" OWNER="${(r:MAX_LEN[3]:)OWNER}" GROUP="${(r:MAX_LEN[4]:)GROUP}" FILESIZE_OUT="${(l:MAX_LEN[5]:)FILESIZE_OUT}" # -------------------------------------------------------------------------- # Colour the permissions - TODO # -------------------------------------------------------------------------- # Colour the first character based on filetype FILETYPE="${PERMISSIONS[1]}" # Permissions Owner PER1="${PERMISSIONS[2,4]}" # Permissions Group PER2="${PERMISSIONS[5,7]}" # Permissions User PER3="${PERMISSIONS[8,10]}" PERMISSIONS_OUTPUT="$FILETYPE$PER1$PER2$PER3" # -------------------------------------------------------------------------- # Colour Hardlinkcound, Owner and Group # -------------------------------------------------------------------------- HARDLINKCOUNT=$'\e[38;5;19m'"$HARDLINKCOUNT"$'\e[0m' OWNER=$'\e[38;5;8m'"$OWNER"$'\e[0m' GROUP=$'\e[38;5;8m'"$GROUP"$'\e[0m' # -------------------------------------------------------------------------- # Colour file weights # -------------------------------------------------------------------------- COLOR=LARGE_FILE_COLOR for i j in ${SIZELIMITS_TO_COLOR[@]} do (( FILESIZE <= i )) || continue COLOR=$j break done FILESIZE_OUT=$'\e[38;5;'"${COLOR}m$FILESIZE_OUT"$'\e[0m' # -------------------------------------------------------------------------- # Colour the date and time based on age, then format for output # -------------------------------------------------------------------------- # Setup colours based on time difference TIME_DIFF=$(( K_EPOCH - DATE[1] )) TIME_COLOR=$ANCIENT_TIME_COLOR for i j in ${FILEAGES_TO_COLOR[@]} do (( TIME_DIFF < i )) || continue TIME_COLOR=$j break done # Format date to show year if more than 6 months since last modified if (( TIME_DIFF < 15724800 )); then DATE_OUTPUT="${DATE[3]} ${DATE[2]} ${DATE[4]}" else DATE_OUTPUT="${DATE[3]} ${DATE[2]} ${DATE[5]}" # extra space; 4 digit year instead of 5 digit HH:MM fi; #DATE_OUTPUT[1]="${DATE_OUTPUT[1]//0/ }" # If day of month begins with zero, replace zero with space # Apply colour to formated date DATE_OUTPUT=$'\e[38;5;'"${TIME_COLOR}m${DATE_OUTPUT}"$'\e[0m' # -------------------------------------------------------------------------- # Colour the repomarker # -------------------------------------------------------------------------- # GIT_TOPLEVEL is set to the directory containing the .git folder of a git-repo # is this a git repo if [[ -n "$o_no_vcs" ]]; then STATUS='N' elif (( IS_DIRECTORY )); then if pushd $NAME 2>/dev/null; then if $(git rev-parse --is-inside-work-tree 2>/dev/null); then # If we're not in a repo, still check each directory if it's a repo, and # then mark appropriately if (( INSIDE_WORK_TREE == 0 )); then REPOBRANCH="[$(git rev-parse --abbrev-ref HEAD 2>/dev/null)]" if git diff --stat --quiet --ignore-submodules HEAD &>/dev/null # if dirty then STATUS='' # Show a green vertical bar for clean else STATUS='D' # Show a red vertical bar if dirty fi popd >/dev/null else popd >/dev/null # If the directory isn't ignored or clean, we'll just say it's dirty if git check-ignore --quiet ${NAME} 2>/dev/null; then STATUS='!!' elif git diff --stat --quiet --ignore-submodules ${NAME} 2> /dev/null; then STATUS='' else STATUS='D' fi fi else STATUS='N' popd >/dev/null fi else STATUS='N' fi elif (( INSIDE_WORK_TREE )); then # File STATUS=${$(git status --porcelain --ignored --untracked-files=normal $GIT_TOPLEVEL/${${${NAME:a}##$GIT_TOPLEVEL}#*/})[1]} else STATUS='N' fi case "$STATUS" in (D) REPOMARKER=$'\e[0;31m+\e[0m';; # Tracked & Dirty (M) REPOMARKER=$'\e[0;32m+\e[0m';; # Tracked & Dirty & Added (\?\?) REPOMARKER=$'\e[38;5;16m+\e[0m';; # Untracked (!!) REPOMARKER=$'\e[38;5;19m-\e[0m';; # Ignored (A) REPOMARKER=$'\e[0;32m+\e[0m';; # Added (N) REPOMARKER=' ';; # Not a repo (*) REPOMARKER=$'\e[0;32m|\e[0m';; # Clean esac # -------------------------------------------------------------------------- # Colour branch # -------------------------------------------------------------------------- if [[ -n $REPOBRANCH ]]; then REPOPAD="'$NAME" if [[ -n $SYMLINK_TARGET ]]; then REPOPAD="$REPOPAD $SYMLINK_TARGET"; fi REPOPAD="$(echo "${(l:MAX_LEN[6]:)REPOPAD}" | sed 's/\S.*//')" REPOBRANCH=" $REPOPAD"$'\e['"$K_COLOR_BR"'m'"$REPOBRANCH"$'\e[0m' fi # -------------------------------------------------------------------------- # Colour the filename # -------------------------------------------------------------------------- # Unfortunately, the choices for quoting which escape ANSI color sequences are q & qqqq; none of q- qq qqq work. # But we don't want to quote '.'; so instead we escape the escape manually and use q- NAME="${${NAME##*/}//$'\e'/\\e}" # also propagate changes to SYMLINK_TARGET below if [[ -n "$LS_COLORS" ]] && ls --color -d . &>/dev/null; then # We are using an ls that supports using colors from $LS_COLORS (probably GNU ls here) #pushd "${base_dir}" &>/dev/null NAME="$(ls --color=always -d "$NAME")" #popd &>/dev/null elif (( $IS_DIRECTORY == 1 )); then if (( $IS_WRITABLE_BY_OTHERS == 1 )); then if (( $HAS_STICKY_BIT == 1 )); then NAME=$'\e['"$K_COLOR_TW"'m'"$NAME"$'\e[0m' fi NAME=$'\e['"$K_COLOR_OW"'m'"$NAME"$'\e[0m' fi NAME=$'\e['"$K_COLOR_DI"'m'"$NAME"$'\e[0m' elif (( $IS_SYMLINK == 1 )); then NAME=$'\e['"$K_COLOR_LN"'m'"$NAME"$'\e[0m' elif (( $IS_SOCKET == 1 )); then NAME=$'\e['"$K_COLOR_SO"'m'"$NAME"$'\e[0m' elif (( $IS_PIPE == 1 )); then NAME=$'\e['"$K_COLOR_PI"'m'"$NAME"$'\e[0m' elif (( $HAS_UID_BIT == 1 )); then NAME=$'\e['"$K_COLOR_SU"'m'"$NAME"$'\e[0m' elif (( $HAS_GID_BIT == 1 )); then NAME=$'\e['"$K_COLOR_SG"'m'"$NAME"$'\e[0m' elif (( $IS_EXECUTABLE == 1 )); then NAME=$'\e['"$K_COLOR_EX"'m'"$NAME"$'\e[0m' elif (( $IS_BLOCK_SPECIAL == 1 )); then NAME=$'\e['"$K_COLOR_BD"'m'"$NAME"$'\e[0m' elif (( $IS_CHARACTER_SPECIAL == 1 )); then NAME=$'\e['"$K_COLOR_CD"'m'"$NAME"$'\e[0m' fi # -------------------------------------------------------------------------- # Format symlink target # -------------------------------------------------------------------------- if [[ -n $SYMLINK_TARGET ]]; then SYMLINK_TARGET=" -> ${SYMLINK_TARGET//$'\e'/\\e}"; fi # -------------------------------------------------------------------------- # Display final result # -------------------------------------------------------------------------- print -r -- "$PERMISSIONS_OUTPUT $HARDLINKCOUNT $OWNER $GROUP $FILESIZE_OUT $DATE_OUTPUT $REPOMARKER $NAME$SYMLINK_TARGET$REPOBRANCH" (( k++ )) # Bump loop index done if [[ ! -f $base_dir ]]; then popd >/dev/null fi done } _k_bsd_to_ansi() { local foreground=$1 background=$2 foreground_ansi background_ansi case $foreground in a) foreground_ansi=30;; b) foreground_ansi=31;; c) foreground_ansi=32;; d) foreground_ansi=33;; e) foreground_ansi=34;; f) foreground_ansi=35;; g) foreground_ansi=36;; h) foreground_ansi=37;; x) foreground_ansi=0;; esac case $background in a) background_ansi=40;; b) background_ansi=41;; c) background_ansi=42;; d) background_ansi=43;; e) background_ansi=44;; f) background_ansi=45;; g) background_ansi=46;; h) background_ansi=47;; x) background_ansi=0;; esac printf "%s;%s" $background_ansi $foreground_ansi } # http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg