Skip to content

Instantly share code, notes, and snippets.

@echristopherson
Created October 30, 2010 05:14
Show Gist options
  • Save echristopherson/654982 to your computer and use it in GitHub Desktop.
Save echristopherson/654982 to your computer and use it in GitHub Desktop.
#!/bin/bash
# Some bash utility functions (and associated aliases) that can be convenient to
# have. Tested with 3.2.48(1)-release on Mac OS X 10.6.
# Add pathname to start or end of a path; take out existing copies first.
# Unfortunately, this doesn't handle weird cases like pathnames with : in
# them. A little experimentation shows that bash doesn't recognize those
# anyway.
add_to_path() {
local append_to
case "$1" in
--end|-e)
append_to='end'
shift
;;
--start|-s)
append_to='start'
shift
;;
esac
local path_in
local dir_to_add
# Apparently, you can't pass empty positional parameters to a bash
# function; this hack works around that.
if [ "$#" = '1' ]; then
path_in=''
dir_to_add="$1"
else
path_in="$1"
dir_to_add="$2"
fi
# If path_in is empty, return path to add.
if [ "$path_in" = '' ]; then
echo "$dir_to_add"
# Nothing left to do; no point in falling through.
return 0
fi
# String matching and replacing is a bashism.
# If dir is only entry in path_in, keep it as is.
if [ "$path_in" = "$dir_to_add" ]; then
# Nothing left to do; no point in falling through.
echo "$path_in"
return 0
fi
IFS=: read -r -a paths <<< "$path_in"
num_elements="${#paths[@]}"
for index in "${!paths[@]}"; do
# If dir is already in path, remove it
if [ "$dir_to_add" = "${paths[$index]}" ]; then
unset paths["$index"]
fi
done
case "$append_to" in
end)
paths+=("$dir_to_add")
;;
start|*)
paths=("$dir_to_add" "${paths[@]}")
;;
esac
IFS=':'
echo "${paths[*]}"
return 0
}
# Shortcut for adding to start of $PATH
add_to_PATH_start() {
PATH="$(add_to_path --start "$PATH" "$1")"
}
# Shortcut for adding to end of $PATH
add_to_PATH_end() {
PATH="$(add_to_path --end "$PATH" "$1")"
}
# List one component of path per line, from top to bottom.
show_path() {
local path="$1"
path="${path//:/
}"
echo "$path"
return 0
}
alias showpath='show_path "$PATH"'
# Find and list (with ls) either directories or executables in given directory
# (or current directory). Easily extensible to other sorts of searches.
# This function is intended to be invoked by aliases. To invoke it directly,
# you must pass either "--dir" or "--exe" as its first parameter.
#
# The following parameters should be whatever you would pass to ls; the
# function currently accepts all options of Mac OS X's (and thus BSD's) ls that
# I could find in the man page. Some might behave idiosyncraticly, however.
#
# In addition, there is a switch -Z which causes all the found
# files/directories to be listed with ls together, which allows the listing to
# be sorted and formatted consistently. However, that output only appears after
# all the files/directories have been found, so you don't get instant
# gratification with a recursive search of a deep directory hierarchy.
#
# Thanks to nDuff on Freenode #bash for the help.
findls() {
local find_pattern
case "$1" in
--dir*)
find_pattern=(-type d)
;;
--exe*)
find_pattern=(-type f -perm +uog+x)
;;
*)
echo "You must pass either --dir or --exe to findls." 1>&2
return 1
;;
esac
shift
OPTIND=1
# TODO: Parse these options from output of alias command
local lsopts=(-G -F)
local should_recurse=0
local should_list_dot_files=0
local should_list_dot_and_dotdot=0
local should_descend=1
local should_collect=0
while getopts :@1AaBbCcdeFfGgHhikLlmnOoPpqRrSsTtUuvWwxZ arg; do
#echo "Found -$arg"
case "$arg" in
R)
should_recurse=1
;;
a)
should_list_dot_files=1
;;
A)
should_list_dot_files=1
should_list_dot_and_dotdot=1
;;
d)
# TODO: Implement
should_descend=0
;;
h)
# TODO: help text
return 0
;;
@|1|B|b|C|c|d|e|F|f|G|g|H|i|k|L|l|m|n|O|o|P|p|q|r|S|s|T|t|U|u|v|W|w|x)
lsopts+=(-$arg)
;;
# Option that lets us "collect" output and use ls to display it all at
# once at the end. This means it will be formatted consistently, and we
# can use ls's sort options. Unfortunately, it forces us to wait until
# all of find's output is collected before displaying it, so it doesn't
# work well recursively on deep directory hierarchies.
Z)
should_collect=1
;;
esac
# TODO: Allow double-dash ls options (we'll have to go beyond getopts
# for that.)
done
shift $((OPTIND - 1))
if [ "$should_list_dot_files" != '1' ]; then
find_pattern+=(-a ! -name '.*')
fi
if [ "$should_list_dot_and_dotdot" != '1' ]; then
find_pattern+=(-a ! -name '.' -a ! -name '..')
fi
if [ "$should_recurse" != '1' ]; then
find_pattern+=(-maxdepth 1)
fi
#echo "find_pattern[@]: ${find_pattern[@]}"
# Construct array of supplied dir arguments
local dirs=()
if [ "$1" = '' ]; then
dirs+=('.')
else
dirs=("$@")
fi
# TODO: Allow arguments to refer to files/directories *within* a
# directory, like ls does. This would have something to do with the -d
# option.
# The actual work
local num_dirs="${#dirs[@]}"
for dir in "${dirs[@]}"; do
if [ "$num_dirs" -gt '1' ]; then
echo "$dir:"
fi
#echo "Entering directory '$dir'"
pushd . > /dev/null
cd "$dir"
if [ "$should_collect" = '1' ]; then
local found_filenames=()
while IFS= read -r -d $'\0' filename; do
# Strip off initial './'
filename="$(echo "${filename/#.\//}")"
found_filenames+=("$filename")
done < <(find . "${find_pattern[@]}" -print0)
# the above < <() is file redirection + process substitution
ls -d "${lsopts[@]}" "${found_filenames[@]}"
else
# TODO: Strip off initial "./"
find . "${find_pattern[@]}" -exec ls -d "${lsopts[@]}" {} +
#strip_initial_dotslash($output)
fi
popd > /dev/null
if [ "$num_dirs" -gt '1' ]; then
echo
fi
done
}
# List subdirectories
alias lsdirs='findls --dir'
# List executables
alias lsexes='findls --exe'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment