Skip to content

Instantly share code, notes, and snippets.

@rsvp
Created February 8, 2012 03:41
Show Gist options
  • Save rsvp/1765163 to your computer and use it in GitHub Desktop.
Save rsvp/1765163 to your computer and use it in GitHub Desktop.
lstest.sh : list files by type (regular, directory, symbolic link, named pipe, etc.), and optionally show detailed information unavailable via the ls command (while also properly handling dot files). Linux bash script.
#!/usr/bin/env bash
# bash 4.1.5(1) Linux Ubuntu 10.04 Date : 2012-02-04
#
# _______________| lstest : list files by type, optionally show further info.
#
# Usage: lstest [test flag] [-0|-1|-A|-i|-I|-a] [--count]
#
# ^attributes
# ^terse "stat"
# ^info via "stat"
# ^All files; default.
# ^only non-dot files.
# ^only dot files.
#
# ^test flag uses Bash built-in "test", so e.g.:
# -f regular file (default flag)
# -s non-zero size file
# -d directory
# -h symbolic link (-l unofficially here)
# -p named pipe
# -S socket
# -r readable
# -w writable
# -x executable
# -e file exists (all types will show up)
# etc.
#
# The arguments take their meaning positionally.
# Filtered items from stdout can be piped to a regex program.
# --count of such items can be shown via stderr
# (useful for logs; otherwise consider pipe to wc -l).
# Count of zero is considered fatal.
#
# Examples: % lstest
# # list only regular files including such dot files.
# % lstest -d -0 --count
# # list and count only dot directories.
# % lstest -d | grep '^[A-Z]'
# # list all directory names beginning with upper case.
# % lstest -f -i
# # display detailed info on all regular files,
# # including access/modify/change times with inode.
# % while read i ; do readlink "$i" ; done < <(lstest -h)
# # list the values of symbolic links
# # (without parsing on "->" :-)
# # but see the Easter egg below ).
#
# Dependencies: stat, readlink, lsattr.
# CHANGE LOG LATEST version available: https://bitbucket.org/rsvp/gists/src
#
# 2012-02-04 Add second arg -I to show terse file stat info;
# second arg -a to show file attributes using lsattr -d.
# 2012-02-03 Use case on second arg for faster execution,
# now also allows exclusively dot files to show.
# Add another second arg -i to show detailed file stat info.
# 2012-02-02 First version originates from frustration that ls has no flag
# for showing ONLY regular files.
# Most solutions use: ls and grep ^- combo,
# or even: ls and awk combo,
# or recursive hammer: find with -maxdepth
# (see example at the end).
# DOT files will receive special handling,
# esp. the notorious pair "." and ".." ;-)
#
# MAIN IDEA: simply use Bash test as filter. So let's build on:
# for f in *; do [[ -f "$f" ]] && echo "$f"; done
# _____ PREAMBLE: settings, variables, and error handling.
#
LC_ALL=POSIX
# locale means "ASCII, US English, no special rules,
# output per ISO and RFC standards."
# Esp. use ASCII encoding for glob and sorting characters.
set -e
# ^errors checked: immediate exit if a command has non-zero status.
set -u
# ^unassigned variables shall be errors.
arg1=${1:-'-f'}
# ^regular file as default.
[ "$arg1" = "-l" -o "$arg1" = "-h" ] && arg1="-L"
# Convert officially for symbolic link.
arg2=${2:-'-A'}
# ^All including dot files as default, else use -1.
arg3=${3:-'--no-count'}
# --count would show summary to stderr.
program=${0##*/} # similar to using basename
cleanup () {
:
}
# --------------------------------------------------------------------
warn () {
# Message with basename to stderr. Usage: warn "message"
echo -e "\n !! ${program}: $1 " >&2
}
# --------------------------------------------------------------------
die () {
# WARN, CLEANUP, then EXIT with the status
# of most recent command or custom status.
# Usage: fatal_command || die "message" [status]
local status=${2:-"$?"}
warn "$1" && cleanup && exit $status
}
# --------------------------------------------------------------------
# Clean INTERRUPT where 1=SIGHUP, 2=SIGINT, 3=SIGQUIT, 15=SIGTERM
trap "cleanup && die 'SIG disruption but cleanup OK.' 114" 1 2 3 15
#
# _______________ :: BEGIN Script ::::::::::::::::::::::::::::::::::::::::
GLOBIGNORE=.:..
# ^^^^ when matching all dot files, exclude . and ..
shopt -s dotglob
# ^set Let globs match hidden dot files.
shopt -s nullglob
# ^set just in case the current directory is empty,
# else the array will have one literal item '*' instead of zero.
n=0
# ^initialize counter for qualified items.
items=( * )
# ^an array which we will sweep through.
# echo "DEBUG: ${items[@]}"
[ ${#items[@]} -eq 0 ] && die "vacant directory: $PWD" 115
case "$arg2" in
'-A') # both dot and non-dot files...
for i in "${items[@]}" ; do
[ "$arg1" "$i" ] && echo "$i" && n=$(( $n + 1 ))
done 2> /dev/null ;;
# Bad first argument causes verbose mess.
'-0') # only dot files...
for i in "${items[@]}" ; do
# faster than grep
[ "$i" != "${i/#./}" ] && \
[ "$arg1" "$i" ] && echo "$i" && n=$(( $n + 1 ))
done 2> /dev/null ;;
# Bad first argument causes verbose mess.
'-1') # only non-dot files...
for i in "${items[@]}" ; do
# faster than grep
[ "$i" = "${i/#./}" ] && \
[ "$arg1" "$i" ] && echo "$i" && n=$(( $n + 1 ))
done 2> /dev/null ;;
# Bad first argument causes verbose mess.
'-i') # both dot and non-dot files, get detailed stat info.
# Report inode and file times, record delimited by @@.
for i in "${items[@]}" ; do
[ "$arg1" "$i" ] && stat "$i" && \
echo "@@" && n=$(( $n + 1 ))
done 2> /dev/null ;;
# Bad first argument causes verbose mess.
'-I') # both dot and non-dot files, get terse stat info
# on one line (useful for post-processing).
for i in "${items[@]}" ; do
[ "$arg1" "$i" ] && stat -t "$i" && \
n=$(( $n + 1 ))
done 2> /dev/null ;;
# Bad first argument causes verbose mess.
'-a') # both dot and non-dot files, get file attributes
# using lsattr ("man chattr" for their meanings).
for i in "${items[@]}" ; do
[ "$arg1" "$i" ] && lsattr -d "$i" && \
n=$(( $n + 1 ))
done 2> /dev/null ;;
# Bad first argument causes verbose mess.
'-v') # Easter egg: get value of every symbolic link...
# -L or -h or -l as first argument will do:
[ "$arg1" = "-L" ] && \
for i in "${items[@]}" ; do
[ "$arg1" "$i" ] && readlink "$i" && n=$(( $n + 1 ))
done 2> /dev/null ;;
# Bad first argument causes verbose mess.
*) die "undefined second arg: $arg2" 117 ;;
esac
[ $n -eq 0 ] && die "NONE match $arg1 $arg2 in $PWD" 113
[ "$arg3" = "--count" ] && warn "$n match $arg1 $arg2 in $PWD"
cleanup
exit 0
# _______________ EOS :: END of Script ::::::::::::::::::::::::::::::::::::::::
# Scripts which parse ls output should definitely
# be avoided due to portability issues and the
# overhead of properly handling weird filenames
# which include spaces.
#
# The nearest acceptable alternative would be:
#
# find . -maxdepth 1 -type f
#
# where "find" supports the following major "-type"
# f regular file
# d directory
# l symbolic link
# p named pipe
# s socket
# and selector options such as -executable are available.
# Filtering by file size can be fine-tuned.
#
# It also handles dot files but includes "." as directory.
# From its stdout we would have to generally delete
# the prefix "./" and also sort the results for appearance.
# To filter further, regex is available as an option, e.g. see -iregex.
#
# Unfortunately, in the case where the number of filtered
# items is zero, or where the current directory is empty,
# "find" signals success by an exit 0. Sure, we could examine
# an external output file, and do all the tweaks...
# at the cost of simplicity and execution speed.
# vim: set fileencoding=utf-8 ff=unix tw=78 ai syn=sh :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment