#!/bin/sh # An enhanced and more portable ls -l # Licence: LGPLv2 # Author: # http://www.pixelbeat.org/ # Notes: # A symlink target may lose info as there are no mode bits to highlight. # Changes: # V0.1, 18 Sep 2009, Initial release # V0.2, 30 Sep 2009, Correctly strip "total" line for all locales # Modify colors even when dircolors doesn't support $TERM # Don't error for ls without --group-dir or --no-group # V0.3, 21 Oct 2009, Handle older ls' which pepper output with reset codes # # V1.4, 21 Nov 2016 # http://github.com/pixelb/scripts/commits/master/scripts/l # apply thousands separator to file sizes setglobal BLOCK_SIZE = ''''1'; export BLOCK_SIZE # Traditional unix time format with abbreviated month translated from locale. # en_* locales default to ISO format without this for reasons discussed here: # http://lists.gnu.org/archive/html/bug-coreutils/2009-09/msg00433.html # Note as of coreutils 8.6, `ls` will again use traditional rather than ISO # format, unless otherwise specified by translators. setglobal TIME_STYLE = ''+%b %e %Y %b %e %H:%M'' export TIME_STYLE # Add a fancy symlink arrow if echo $LANG | grep -i "utf-*8$" >/dev/null { setglobal SYM_ARROW = '"▪▶'" } else { setglobal SYM_ARROW = '"->'" } setglobal ESC = $[printf '\033] # Disable color if the current $TERM doesn't support it if ! tput setaf 1 >/dev/null 2>&1 && ! tput AF 1 >/dev/null 2>&1 { setglobal color_when = '' # enable highlighting if outputting to terminal or forcing } elif test -t 1 || echo "$ifsjoin(ARGV)" | grep -E -- "--color( |=always|$)" >/dev/null { # Note if the user specifies --color=auto then that will override this # and hence the output will not be colored :( setglobal color_when = 'always' if tput bold >/dev/null 2>&1 { #terminfo setglobal BLD = $[tput bold] setglobal RST = $[tput sgr0] setglobal HLI = $[tput smso] } elif tput md >/dev/null 2>&1 { #termcap setglobal BLD = $[tput md] setglobal RST = $[tput me] setglobal HLI = $[tput so] } setglobal LS_HLI = $[echo $HLI | sed "s/$ESC\[\(.*\)m/\1/] } else { setglobal color_when = 'auto' } setglobal ls = 'ls' #default to standard name do { ls --color -d . >/dev/null 2>&1; } || do { gls --color -d . >/dev/null 2>&1 && setglobal ls = 'gls'; } || setglobal NONGNU = '1' if test $color_when { if test $NONGNU { do { colorls -d .; } >/dev/null 2>&1 && setglobal ls = 'colorls' # for OpenBSD $ls -G -d . >/dev/null 2>&1 && setglobal color_opt = '-G' #not supported on Solaris test $color_when = "always" && do { setglobal CLICOLOR_FORCE = '1'; export CLICOLOR_FORCE; } setglobal color_when = '' # Turn off the confusing different background colors # as we'll highlight the mode bits instead. Also change the # main colors to match the default one on linux (coreutils). setglobal LSCOLORS = '"ExGxcxdxCxegedCxCxExEx'"; export LSCOLORS } else { setglobal color_opt = ''--color='' setglobal color_never = '"--color=never'" # modify the coreutils default colors if distro or user colors not set test $LS_COLORS || eval $[dircolors] # xterm-256color not supported by older dircolors for example test $LS_COLORS || eval $[env TERM=xterm dircolors] # Turn off the confusing entries with different backgrounds # as we'll highlight the mode bits and hardlink count instead. # # Also turn off capability colouring even though we don't # highlight anything in lieu of it, as this will short circuit # the slow capability checks within ls # # Also we reset the dangling symlink color to "highlight" # as it defaults to blinking. We could do all this in ~/.dir_colors # but so this script is more general, we do it here. # Ensure LS_COLORS is delimited with ':' to simplify the following regexes. # Otherwise we'd have to consider using word boundaries (\b, \<, [[:<:]]), # but those are non portable. setglobal LS_COLORS = "":$LS_COLORS:""; export LS_COLORS # Newer ls use "mh" to represent the color for multi hardlinked # files so handle the backwards incompatibility with "hl" if dircolors | grep -F ":mh=" >/dev/null { setglobal hl_no_color = '"s/:hl=[^:]*:/:mh=:/; s/:mh=[^:]*:/:mh=:/'" } elif dircolors | grep -F "hl=" >/dev/null { setglobal hl_no_color = '"s/:mh=[^:]*:/:hl=:/; s/:hl=[^:]*:/:hl=:/'" } # The extra quoting (wrapping in "") is required for # speed in bash where it inefficiently tries to glob # for each *.ext component of LS_COLORS, which is # noticeably slow in directories with many files. eval $[ echo LS_COLORS=''''"'$LS_COLORS'"''''';' | sed '"' $hl_no_color s/:su=[^:]*:/:su=:/ s/:sg=[^:]*:/:sg=:/ s/:ow=[^:]*:/:ow=:/ s/:st=[^:]*:/:st=:/ s/:tw=[^:]*:/:tw=:/ s/:ca=[^:]*:/:ca=:/ s/:mi=[^:]'{'1,'}':/:mi=$LS_HLI:/ '"' echo 'export LS_COLORS] } } # Get locale total string to match on setglobal total = $[$ls -s $color_never / | sed 's/\([^ ]*\).*/\1/;q] # Use these options if available $ls --group-directories-first -d . >/dev/null 2>&1 && setglobal gdf = '--group-directories' $ls --no-group -d . >/dev/null 2>&1 && setglobal ng = '--no-group' $ls --quoting=shell-escape -d . >/dev/null 2>&1 && setglobal quote = '--quoting=shell-escape' # Bypass long format manipulation if any of following formats are specified # FIXME: Matching options like this is a bit brittle setglobal lfmt = 'lfmt' echo "$ifsjoin(ARGV)" | grep -E -- "-(m|i|x|s|C|-version|-help)" >/dev/null && setglobal lfmt = ''"" # set -o pipefail is bash/ksh/zsh specific # So manually propagate the exit status from ls. # See http://stackoverflow.com/a/30658405/4421 exec 4>&1 setglobal ls_status = $[do { do { # Start with the standard long format listing # with colours, and latest files at bottom $ls -lrtq $color_opt$color_when $ng $gdf $quote @ARGV; printf $Status 1>&3; } | # process with sed to... sed " # Remove total line(s) I never use /$total [0-9,. ]\{1,\}$/d # prettify symlink arrows s/ -> / $BLD$SYM_ARROW$RST / # Conditionally process long format output b $lfmt :lfmt # Ignore directory name headings /^[^ ]\{1,\}:$/b # temporarily shrink any prepended reset codes # from older ls' to a single char s/^$ESC\[0*m/0/ # highlight ug+s indicators s/^\(0\{0,1\}.\{3\}\)\([sS]\)/\1$HLI\2$RST/ s/^\(0\{0,1\}.\{6\}\)\([sS]\)/\1$HLI\2$RST/ # highlight +t o+w indicators for directories /^0\{0,1\}d/!b not_dir s/^\(0\{0,1\}.\{9\}\)\([tT]\)/\1$HLI\2$RST/ s/^\(0\{0,1\}.\{8\}\)w/\1$(HLI)w$RST/ s/^\(0\{0,1\}.\{3\}\)\([sS]\)/\1$HLI\2$RST/ s/^\(0\{0,1\}.\{6\}\)\([sS]\)/\1$HLI\2$RST/ :not_dir # highlight multiply linked files /^0\{0,1\}[^d]\{10,\}/!b not_hl /^[^ ]* *1 /b not_hl s/^\(0\{0,1\}[^ ]*\)\( *\)\([0-9]\{1,\}\)/\1\2$HLI\3$RST/ :not_hl # restore any reset codes replaced above s/^0/$(ESC)[0m/ ] setglobal sed_status = $Status # Note if sed fails, then if ls outputs it will usually get # a SIGPIPE and exit with status 141. Though that's dependent # on ls(1) propagating that "error", and also will not be the case # if ls does not output after sed terminates. So factor both # exit values into the final exit status. test $ls_status != 0 && exit $ls_status || exit $sed_status