An attempt at making a neat ls This is a simple script to make ls output first folders, then files, then other stuff (symlink). I think this is really neat and would like to share the script in exchange for comments.

It should be noted that I'm working with GNU findutils and coreutils.

Some basic criteria that I'm aiming for include: 


lssort must accept the same arguments as ls 
lssort should depend on bash, find, ls and xargs
the output must not be prefixed with "./" 

Problems:


arguments can not be augmented (-CFXtrs does not work)


Script


   
#! /bin/bash
#source $HOME/.scripts/string_manipulation.mergeme
function trim {
    local var="$@"
    #$ var="  hello space    "
    #$ echo ">"
    #$ >
    var="${var#"${var%%[![:space:]]*}"}" # rm leading whitespace characters
    var="${var%"${var##*[![:space:]]}"}" # rm trailing whitespace characters
    echo -n "$var"
}

function arrayContains {
    # remove the shortest needle from array and compare lengths
    # needs more testing...
    declare -a array
    declare -a arrayLess
    declare needle
    array=( "${!1}" ); shift        # expand the passed array name
    needle="$@"                     # try to match the rest as a string
    arrayLess=( `trim "${array[@]#${needle}}"` )    # remove value $needle
    if [ ${#array[@]} -eq 0 ];then                  return 1 ;fi
    if [ ${#array[@]} -eq ${#arrayLess[@]} ];then   return 1 ;fi
    if [ ${#array[@]} -lt ${#arrayLess[@]} ];then   return 1 ;fi
    return 0
}

# d dir f file p named pipe (FIFO) l sylink s socket D door (Solaris) 
# c character (unbuffered) special b block (buffered) special

function finddirs {
    find "$@" \
        -maxdepth 1 -depth -type d \
        -regextype gnu-awk -regex "$REGEX" \
        -printf '%f\0'
}
function findfiles {
    find "$@" \
        -maxdepth 1 -depth -type f \
        -regextype gnu-awk -regex "$REGEX" \
        -printf "%f\0"
}
function findspecials {
    find "$@" \
        -maxdepth 1 -depth \( -type l   -o -type p   -o -type s \) \
        -regextype gnu-awk -regex "$REGEX" \
        -printf "%f\0"
}
##
## vars and such
##
validswitches=(-X -l -r -t -C -F -1 -a -A -d -s)
PATHS=()
FPATHS=""
LSSWITCHES=""
ORIGIN_DIR="`pwd`"
LSHIDDEN="^\./.+"       # gnu-awk regex for find, single dot does not match
LSNORMAL="^\./[^\.]+.+" # exclude files starting with .
LS=ls\ --color=auto

ARG=$1
ARGC=0
while [ "$ARG" != "" ];
do
    if [ `arrayContains validswitches[@] $ARG ;echo $?` -eq 0 ];then
        LSSWITCHES="${LSSWITCHES} $ARG"
        unset ARG
    fi

    if [ -d "$ARG" ];then
        #PATHS="${PATHS} $ARG"
        PATHS[${ARGC}]=$ARG
        let ARGC=ARGC+1
    else
        FPATHS="${FPATHS} $ARG"
    fi
    shift
    ARG=$1
done

# FIX only -A excludes . and .. 
if [[ "$LSSWITCHES" =~ '-a' || "$LSSWITCHES" =~ '-A' ]]; then
    REGEX=$LSHIDDEN
else
    REGEX=$LSNORMAL
fi

#PATHS=`trim $PATHS`
FPATHS=`trim $FPATHS`

if [ ${#FPATHS} -eq 0 ]; then 
    if [ ${#PATHS[@]} -eq 0 ]; then
        PATHS=(".")
    fi
fi
for arg in "${PATHS[@]}";
do
    if [[ ${#arg} -eq 1 && "$arg" == "/" ]];then 
        p=/
    else
        #p=${arg%%/}
        p=$arg
    fi

    cd "$p"
    if [[ "$p" != "." && "$p" != "./" ]];then echo "$p": ;fi
    finddirs | xargs -r0 $LS $LSSWITCHES -d 
    findfiles | xargs -r0 $LS $LSSWITCHES 
    findspecials | xargs -r0 $LS $LSSWITCHES -d
    [ ${#PATHS[@]} -gt 1 ] && echo 
    cd $ORIGIN_DIR
done

echo $FPATHS |xargs -r $LS $LSSWITCHES 
#echo paths.:\ size: ${#PATHS[@]}
#echo fpaths:\



lssort
If you use getopt or getopts to parse the options, you can use multiple options together, and it would simplify your code quite a bit. Example.
When you create arrays explicitly (name=(values)) you don't need to declare it first.
arrayContains should take one needle and then the contents of the array in question. This is how it works in other languages, and is easier to program and more explicit than using an array reference and concatenated needles. Also, when expanding needle the values will be concatenated with a space between them, which is rather arbitrary.
If needle is empty, arrayContains should return 0 regardless of the array contents.
You can merge the if statements in arrayContains by separating them with -o:

if [ $a -eq 0 -o $b -eq 0 -o $c -eq $a ]

ls works with globs rather than regular expressions by default. The find* functions will not return the same filenames as ls with the same input. For example, run touch example &amp;&amp; mkdir eclectic and compare

ls [e]*


with

find . -maxdepth 1 -depth -type f -regextype gnu-awk -regex "[e]*"

You should be able to get the current working directory with $PWD rather than pwd to save a fork. This makes the code a tiny bit faster, without losing clarity, and fixes a very common but little known bug: The `command` construct (identical to the preferred $(command) when using Bash) removes newlines from the end of the command output. That means if you mkdir $'foo\n' &amp;&amp; cd $'foo\n' , ORIGIN_DIR="`pwd`" will give the wrong result, but ORIGIN_DIR="$PWD" will give the right one.
Use More Quotes, for example do ARG="$1" to enable working with whitespace in filenames. This is a very tricky subject.
If you want to get this working with all filenames, you'll need to use an array to store the find results before sending them to ls