Shell Hacks
This page is for quick shell hacks. More complete bash-scripting documentation is available from the Advanced Bash-Scripting Guide.
In case it isn’t obvious, these are all only tested on bash. ksh might work but I only rarely use it and I don’t test in it.
Listing Non-System Users
awk
is a word-splitter. /etc/passwd
is a colon-delimited list of
words. Ergo:
$ awk -F: '{if ($3 >= 500) { print $1 }}' /etc/passwd
Replace 500 with the beginning UID for non-system users for your system.
Red Hat uses 500; Solaris uses 1000. If you’re using NIS, LDAP, or other
NameServiceSwitch back-end, use the getent
command (which, conveniently
outputs the same format as /etc/passwd
):
getent passwd | awk -F: '{if ($3 >= 500) {print $1}}'
Neither of these commands will work on AIX, but on AIX you’ve got lsusers
already. Note that you can also list only the system users by reversing the
comparison operator. You will likely have a user nfsnobody that is UID
65534 (which corresponds to -1 in signed 16-bit integers) which is also a
system user.
Display Meat of Config File
This removes empty lines and lines that start with a ‘#’, usually used as a comment character.
grep -vE '^($|#)' <foo.conf>
This one is better; it strips comments and whitespace-only lines, whereas the previous only strips comments starting at the beginning of the line and blank lines:
alias nocomment="sed -e 's/\([^#]*\)#.*$/\1/; /^[[:space:]]*$/d; /^#/d;'"
Find Empty Directories
This is the magic for find
that finds empty directories in the current
working directory.
find . -empty -maxdepth 1 -type d
Hourly Statistics from Log Files
This extracts the hour from the syslog timestamps and shows how many log entries occured in each hour. This is most useful if you pre-process <logfile>, or you remove log file and feed it with a pipe.
awk '{print $3}' <logfile> |awk -F: '{print $1 ":00"}' |sort -n |uniq -c
Getting the Script Name
I used to use prog=$(basename $0)
to get the script’s basename (which is
good for help output, temp files, etc). However, I’ve picked up a tip from
SUSE’s init scripts which uses the parameter expansion available in bash and
ksh:
prog=${0##*/}
It’s a little more succinct (if perhaps obscure) and obviates an exec.
Function Template: usage
usage() {
# Default to 0
local exitval="${1:-0}"
if [[ $exitval -eq 1 ]]; then
# Redirect stdout to stderr
exec 1>&2
fi
echo "Usage: ${0##*/} [-h] [-x]"
echo "Do something or other."
echo " -x - Set 'x' to true."
echo " -h - show this help screen."
exit "$exitval"
}
Generate Sequence of Integers without Using ‘seq’
Brace expansion can be use with a range:
$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
Also supports a non-1 increment:
$ echo {1..10..2}
1 3 5 7 9
Descending increment:
$ echo {10..1}
10 9 8 7 6 5 4 3 2 1
Zero-padding:
$ echo {01..10}
01 02 03 04 05 06 07 08 09 10
Iterate Over $PATH
Use the pattern-substitution parameter expansion. Note that you must also not quote the variable (which I usually do as a good practice):
$ for p in ${PATH//:/ }; do echo $p; done
/usr/sbin
/usr/bin
/sbin
/bin
Improved pathmunge
Red Hat’s /etc/profile
defines a shell function called pathmunge
to
conditionally add directories to $PATH
The problem with this
implementation is that it runs egrep
, which involves forking a process
and incurs a modicum of overhead. Generally that’s not a big deal–the overhead
is minimal on modern processors. But if the host is in a state of distress due
to swapping or a fork-bomb or such, these extra processes become a burdensome
overhead.
Here’s my implementation, which also adds a force parameter (which unfortunately means it is not idempotent) and changes the after to before, since after is what I usually want.
pathmunge () {
newpath="$1"
if [[ ! -d "$newpath" ]]; then return 1; fi
if [[ "$2" = "force" || "$3" = "force" ]]; then force=1; else force=0; fi
if [[ $force -ne 1 ]]; then
for p in ${PATH//:/ }; do
if [[ "$p" = "$newpath" ]]; then
: $newpath exists - aborting early
return 1
fi
done
fi
: adding $newpath - $LINENO
if [[ "$2" = "before" ]] ; then
PATH="$newpath:$PATH"
else
PATH="$PATH:$newpath"
fi
}