Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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.png

share|improve this question
TO get code reviewed please post the code into your question. Since other sites may disappear over time few people want to write comments that become meaningless. – Loki Astari Sep 14 '12 at 18:00
PS. You don't need to write your justifications on why you wrote the code. Just an explanation of what it attempts to do. – Loki Astari Sep 14 '12 at 18:00
Alright, done. Good points. – Ярослав Рахматуллин Sep 14 '12 at 18:45

1 Answer

up vote 1 down vote accepted
  1. 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.
  2. When you create arrays explicitly (name=(values)) you don't need to declare it first.
  3. 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.
  4. If needle is empty, arrayContains should return 0 regardless of the array contents.
  5. 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 ]
    
  6. 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 && mkdir eclectic and compare

    ls [e]*
    

    with

    find . -maxdepth 1 -depth -type f -regextype gnu-awk -regex "[e]*"
    
  7. 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' && cd $'foo\n' , ORIGIN_DIR="`pwd`" will give the wrong result, but ORIGIN_DIR="$PWD" will give the right one.
  8. Use More Quotes, for example do ARG="$1" to enable working with whitespace in filenames. This is a very tricky subject.
  9. 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
share|improve this answer
Wow, tanks a lot! I've been avoiding getopts for far too long. I'll read up on it. Would you care to elaborate on why point 7 is relevant? – Ярослав Рахматуллин Sep 27 '12 at 21:35
I meant point 6 about ls working on shell globs rather than regex. What would I gain by running ls with regex? – Ярослав Рахматуллин Sep 28 '12 at 10:52
I'm not sure why you think I have those confused, but thank you anyway. – Ярослав Рахматуллин Sep 28 '12 at 15:56

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.