Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
# While GNU coreutil's ls is generally more featureful (including colorization
# based on file extensions, SI size formats, ISO date and time formats,
# semantic version sort, etc.), OS X's fork of FreeBSD ls allows viewing HFS+
# extended attributes and access control lists through the -@ and -e flags,
# respectively.
#
# This script provides a shim to allow BSD ls to be invoked with GNU-style
# flags and .dircolor files, and a function that shadows ls, invoking coreutils
# ls by default but automatically switching to the shim when the -@ and/or -e
# flags are detected.
#
# Paste into your .bashrc after `eval $(dircolors ~/.dircolors)` and before
# any ls aliases.
if [[ "$OSTYPE" == darwin* ]]; then
# Look for an alternate ls in the $PATH
__bsd_ls=/bin/ls
__gnu_ls=$(which gls || which ls)
if [[ "$__gnu_ls" == "$__bsd_ls" ]]; then unset __gnu_ls; fi
if [[ "$__gnu_ls" ]]; then
if [[ ! $LS_COLORS ]]; then
eval $($(which gdircolors || which dircolors))
fi
# Generate fallback colors for BSD ls
export LSCOLORS=$(python - <<'EOD'
import os, sys
types=['di','ln','so','pi','ex','bd','cd','su','sg','tw','ow']
LS_COLORS=[e.split('=') for e in os.environ['LS_COLORS'].split(':') if len(e) > 0]
LS_COLORS={e[0]:e[1] for e in LS_COLORS if e[0]=='no' or e[0] in types}
LS_COLORS={k:[int(e) for e in v.split(';') if e] for k,v in LS_COLORS.items()}
LS_COLORS=[LS_COLORS.get('no',[])+LS_COLORS.get(k,[]) for k in types]
LSCOLORS=[]
for entry in LS_COLORS:
fgcodes=[code for code in entry if (code/10 == 3 or code/10 == 9)]
fgcolor=chr(fgcodes[-1]%10+ord('a')) if fgcodes else 'x'
brcodes=[code for code in entry if code in [0,1,2,21,22]]
bright=((brcodes and brcodes[-1]==1) or (fgcodes and fgcodes[-1]/10==9))
fgcolor=fgcolor.upper() if bright else fgcolor
bgcodes=[code for code in entry if (code/10 == 4 or code/10 == 10)]
bgcolor=chr(bgcodes[-1]%10+ord('a')) if bgcodes else 'x'
LSCOLORS.append(fgcolor)
LSCOLORS.append(bgcolor)
sys.stdout.write(''.join(LSCOLORS))
EOD
)
fi # [[ $LS_COLORS ]]
function __bsd_ls_shim {
# An abstraction layer to allow calling BSD ls with a subset
# of the GNU ls style parameters, and gracefully handle errors.
python - "$__bsd_ls" "$BASH_SOURCE" $FUNCNAME $@ <<'EOD'
# coding=UTF-8
import os, sys, subprocess
__bsd_ls=sys.argv[1]
BASH_SOURCE=sys.argv[2]
FUNCNAME=sys.argv[3]
lookup = {'--all':'a','a':'a','--almost-all':'A','A':'A',
'--format=single-column':'1','1':'1','--format=vertical':'C',
'C':'C','--format=horizontal':'x','--format=across':'x','x':'x',
'--format=commas':'m','m':'m','--format=verbose':'l',
'--format=long':'l','l':'l','--sort=size':'S','S':'S',
'--sort=time':'t','t':'t','--reverse':'r','r':'r','--inode':'i',
'i':'i','--size':'s','s':'s','o':'o','g':'g',
'--numeric-uid-gid':'n','n':'n','--human-readable':'h','h':'h',
'--full-time':'T','--time=use':'u','--time=access':'u',
'--time=atime':'u','u':'u','--time=ctime':'c','--time=status':'c',
'c':'c','--indicator-style=classify':'F','--classify':'F','F':'F',
'--indicator-style=slash':'p','p':'p','--dereference':'L','L':'L',
'--dereference-command-line':'H','H':'H','--recursive':'R','R':'R',
'@':'@','e':'e','--hide-control-chars':'q','q':'q','--escape':'b',
'b':'b','--directory':'d','d':'d','f':'f'}
in_flags=[]
misc_args=[]
for arg in sys.argv[4:]:
if arg.startswith('--'):
in_flags.append(arg)
elif arg.startswith('-'):
in_flags.extend(list(arg)[1:])
else:
misc_args.append(arg)
color_flag=None
out_flags=[]
err_flags=[]
for flag in in_flags:
if flag.startswith('--color'):
color_flag=flag
elif flag=='f':
color_flag=flag
out_flags = [flag for flag in out_flags
if flag not in ['l','s']]+['f']
else:
try:
out_flags.append(lookup[flag])
except:
err_flags.append(flag)
out_flags=['-'+''.join(out_flags)] if out_flags else []
if err_flags:
err_flags=' '.join(['-'+flag if len(flag)==1 else flag for flag in err_flags])
print('The following flags were not passed to '+__bsd_ls+' by '+FUNCNAME)
print('in '+BASH_SOURCE+': '+err_flags)
# The BSD ls flag -G is equivalent to GNU ls flag --color=auto, but we can
# more precisely match the GNU --color flag with environment variables.
if (not color_flag or
color_flag == 'f' or
color_flag == '--color=never' or
color_flag == '--color=no' or
color_flag == '--color=none'):
os.environ.pop('CLICOLOR',None)
os.environ.pop('CLICOLOR_FORCE',None)
elif (color_flag == '--color=auto' or
color_flag == '--color=tty' or
color_flag == '--color=if-tty'):
os.environ['CLICOLOR']='1'
os.environ.pop('CLICOLOR_FORCE',None)
elif (color_flag == '--color' or
color_flag == '--color=always' or
color_flag == '--color=yes' or
color_flag == '--color=force'):
os.environ['CLICOLOR']='1'
os.environ['CLICOLOR_FORCE']='1'
else:
print('error in '+BASH_SOURCE+' function '+FUNCNAME+':')
# This error message is copied verbatim from GNU ls
print('invalid argument ‘'+color_flag[8:]+'’ for ‘--color’')
print('Valid arguments are:')
print(' - ‘always’, ‘yes’, ‘force’')
print(' - ‘never’, ‘no’, ‘none’')
print(' - ‘auto’, ‘tty’, ‘if-tty’')
exit(1)
# subprocess.Popen() is safe against command injection when shell=False
exit(subprocess.Popen(args=[__bsd_ls]+out_flags+misc_args,
shell=False,
env=os.environ).wait())
EOD
}
function ls {
# regex to match the -@ and -e parameters
if [[ ! "$__gnu_ls" || "$@" =~ (^|[[:space:]])-[[:alnum:]]*[@e] ]]; then
__bsd_ls_shim $@
else
"$__gnu_ls" $@
fi
}
fi # [[ $__gnu_ls ]]
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment