Fine tuning of an informative replacement for rm I want to replace rm by a more informative variant: I would like to see which files will be deleted, along with their size, and I would like this information to use the same coloring as ls. As an example (please imagine the coloring):

# remove -r foo/ bar/*
4K   bar/file1
1.2M foo/
6.1M bar/file2
remove -r 3 files, 7.3M [yn]? _


I built on suggestions found in another question, and now have a bash script that I begin to like. I have been using it for a few days without noticing any obvious errors. However, I would appreciate any help with making sure that the script will not misbehave in cases I did not foresee.

Here is my code for the coloring:

du_colored (){
    # read ls --color output into ls_colored_array
    # use \13 as a trick to handle names with spaces 
    read -d '\n' -r -a ls_colored_array &lt;&lt;&lt; $(ls -Ad --color "$@" | tr " " "\13")

    if [[ ${#ls_colored_array[@]} = 0 ]]; then return 1; fi

    # - loop over the array and issue du -sh for every element (without coloring)
    # - exchange du's ouput with ls's
    # - finally sort the output
    for i in "${ls_colored_array[@]}"; do
        i=`echo $i | tr "\13" " "`
        printf '%s' "${i}" | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" | \
            xargs -n1 -0 du -sh | awk -v i="$i" '{ printf "%-4s ", $1; print i }'
    done | sort -h
}


and the removal script:

# Separate options to rm from 'real' arguments
for i in "$@"; do
    case $i in
        -*) options+=( "$i" ) ;;
        *)  toRemove+=( "$i" ) ;;
    esac
done

# Print file list
# Abort if no files match
du_colored "${toRemove[@]}"
if [ "_$?" != "_0" ]; then exit 1; fi

# Print summary
count=$(find "${toRemove[@]}" 2&gt;/dev/null | wc -l 2&gt;/dev/null)
size=$(du -sch "${toRemove[@]}" 2&gt;/dev/null | tail -1 | tr '\t' ' ')
plural="s"
if [ $count -eq 1 ]; then plural=""; fi
printf "remove ${options[@]} ($count file$plural, $size) [ny]? " ; read
if [ "_$REPLY" = "_y" ]; then
    /bin/rm ${options[@]} ${toRemove[@]}
else
    echo '(cancelled)'
fi



Does this look safe in the sense that what du_colored presents is exactly what will be deleted? Both files and directories seem to be treated correctly, even if they include spaces or wildcards. Are there scenarios that I missed? An obvious one are file names that contain a \13 character.
The initial ls in du_colored throws errors for non-existing files, which I like because it mimicks the behavior of regular rm. However, I currently discard all error messages of the final find and grep commands, such that non-existing files do not produce multiple errors. This seems like a dirty trick. Is there a cleaner way?
Bonus question: Is there an easy way to make the output use multiple columns, just as a regular ls would? I tried several ways to pipe the output of du_colored to other tools, but either the colors were lost completely or the control characters for the coloring broke the alignment, as they do in ls --color | pr -2 -t.
Bonus question: Does ls -C help?

-C     list entries by columns


You need -r to remove directories, so do you intend to impose this check as well when directories are specified?

plural="s"
if [ $count -eq 1 ]; then plural=""; fi


That can be rewritten as

[ $count -eq 1 ] &amp;&amp; plural="" || plural="s"


Is the use of _ really necessary here?

if [ "_$?" != "_0" ]; then exit 1; fi
if [ "_$REPLY" = "_y" ]; then


edit: Bonus question, round 2...

You could try this for du_colored:

du_colored() {
    du -sh "$@" | awk -F'\t' -v COL=4 \
        '{if(++c&gt;COL){c=1;print""}("ls -Ad --color \""$2"\"")|getline entry;
        printf "%6s %s\t",$1,entry}END{print""}' | \
    column -s $'\t' -t
}


Instead of parsing ls output carefully to feed into du, I started from the output of du and then replacing each entry with its ls --Ad --color output. COL is used to fix the number of 'columns' we want column to pick up afterwards, which is in turn used to print the appropriate newlines.

Finally, I feed the output to column as a final attempt at pretty-printing. \t is used as the delimiter throughout. 

It still doesn't look as nice as the default non-piped ls output, but at least it's something I suppose...

edit 2: Bonus question, round 3...

I hope this is what's required... :)

du_colored() {
    awk -F'/' 'NR==FNR{k=$0;gsub(/\x1B\[[;[:digit:]]*m/,"",k);s[k]=$0}
        NR!=FNR{l=$0;for(i=3;i&lt;=NF;i+=2){v=$i;gsub(/[ ]+$/,"",v);sub(v,s[v],l)};
            gsub("/"," ",l);print l}' \
    &lt;(ls -Ad --color "$@") \
    &lt;(du -sh "$@" | awk -F'\t' -v C=4 '{if(++c&gt;C){c=1;print""}printf "/%6s/%s\t",$1,$2}
        END{print""}' | column -s $'\t' -t)
}



Get both the output of ls -Ad --color "$*" and du -sh "$@" ... using bash process substitution to pass into awk.


The du -sh command is in turn piped to an 'internal' awk command, similar to my earlier suggestion, for column to render properly. What's crucial to note here is that the start, the size and the file/directory entry are delimited using the / character, which is the safest delimiter as files and directories cannot have that (nested directories yes though).

The 'main' awk command uses / to split each line into 1 + C * 2 columns (C=4 used above). By using the combination of NR==FNR/NR!=FNR conditions, we can specify different handling for the two 'files'.


For the ls output, i.e. NR==FNR, we construct a map from the file/directory (by stripping away the color codes - gsub(/\x1B\[[;[:digit:]]*m/,"",k)) to its colored output.
For the du output, we then substitute the file/directory in the appropriate fields (after trimming the extra spaces behind - gsub(/[ ]+$/,"",v)) with the colored output from the mapping.
After substituting each / character back to a  character, we reprint the entire line, which now contains the colored output.



This is arguably more efficient as both ls and du are performed once on the arguments. The lesson learned here is that column doesn't play ball with most control characters (seemingly \t is the only exception here). For some reason, sort-ing on du's output is still introducing minor quirks in my own testing, so I'm leaving that out for now.