Some useful programs for use in shell scripts and general system administration tasks:
Do something to all matching files in a directory:
for i in *.txt; do
echo "This is file ${i}"
done
Do something to all matching files in a directory structure:
for i in $( find ./dir1 -type f -name "*.txt" ); do
echo "This is file ${i}"
done
Iterate over a number sequence:
for i in $( seq 1 9 ); do
echo "Count ${i}"
done
Strip prefixes and substitute suffixes on filenames
filename="img_foo.JPG"
# Remove the "img_" prefix
newname=${filename#img_}
# Remove the ".JPG" suffix and replace it with ".jpg"
newname=${newname%.JPG}.jpg
Sadly, I don't know a way to strip both the prefix and suffix in one command without using sed or some similar tool. If you know how to do it purely within the shell, then I'd like to know!
$( foo ) notation for command substitution rather than backticks (`foo`):
it's more robust against nesting and, not unimportantly, emacs does better syntax highlighting with the
bracket notation.
#! /usr/bin/env bash rather than a direct #! /bin/bash "shebang" shell
incantation at the top of your script files. This goes for any interpreter actually: the env
program runs the script in the user's environment, thus avoiding the issues involved with specifying an
absolute path when they system has an inadequate copy of the interpreter at that location. Sweet :-)
$ test -n ""; echo $?
1
$ test -n "fds"; echo $?
0
$ test -n ; echo $?
0
So you can see that the test command, being used to test for non-zero length strings
will return the same value (0) for a real non-zero length string ("fds") as for a null argument,
even though you would probably expect a null argument to be treated as having zero length. So be
careful!
test statements and &&/|| conditional operators.
Remember, code blocks are defined with curly braces e.g.
dosomething || { echo "Oops, it's all gone wrong" 1>&2; exit 1; }
The last semicolon before the right-hand curly brace is essential if you want to have the last
statement and the closing brace on the same line. Note that if you want to implement binary conditionals
(i.e. "do this if return value = 0, or do this if return value = 0" constructions) using these
operators, you may run into trouble. If the "or" block (as above) doesn't explicitly exit or
otherwise return false, the return value of the last statement in the "or" block will feed into the
corresponding "and" block, e.g.
$ echo foo | grep baz || { echo "Oops" 1>&2; } && { echo "This probably isn't what you want"; }
Oops
This probably isn't what you want
$ echo foo | grep baz || { echo "Oops" 1>&2; false; } && { echo "This probably isn't what you want"; }
Oops
where the second statement alleviates the problem by forcing the final return value of the "or"
block to be 1, ensuring that the "and" block will not be executed.
imgresize --maxheight=800 $(ls *.jpg).
A while ago, I was responsible for a rather complex script which had to be sourced (rather than just executed) by bash, sh, ksh and zsh shells recently and it turned out to be a bit of a nightmare to make it compliant. I'll put some pointers here as I remember them. More recently, I was doing a lot of work using the GNU automake and autoconf packages. For maximum portability across systems, again a very portable base of sh constructs is allowed: the 1977 subset of sh if I remember rightly! The autoconf documentation has a comprehensive section on writing portable sh scripts in the context of autoconf M4 macros.
ls="ls -h"
(which makes the file sizes reported by ls "human-readable" then
a sourced script which tries to use ls and cut to extract
file size information will fail. You can avoid this by only ever calling
utility programs with their full path: $( which ls ) -l (or using backticks
instead of the $(foo) construction) will do the trick.
$1, $2, ...) of the current context. But if there
are no arguments then set spews out the whole environment definition of the shell.
The problem is that there's no difference between the case where you genuinely call
set with no arguments and the case where an automatically generated argument list
is empty: the latter results in a dramatic and confusing failure of the script!
$ env | wc -l
$ source thescript.sh
$ env | wc -l
do the number of lines change after sourcing the script? If so, you're affecting the
shell environment.