Something I wish people teaching intermediate or advanced Bash tricks would emphasize more is how to make your program compatible with other shells. With the rise of Zsh's popularity, and the switch to Dash for Ubuntu/some Debian derivatives, I see a lot of people repeating bashisms in code they share without the knowledge that a) their code may not work for an unexpectedly large number of people, and b) switching to compatible equivalents doesn't make their code worse or less performant in many/most cases.

The most common bashisms and ways to avoid them are:

- Double brackets ([[) around conditions. Yes, I know that [
is a program (don't believe me? "which ["). That doesn't mean Bash uses it; it uses a builtin which is (almost) equivalent to [[ instead. Use that and your code will work in zsh/dash/all other POSIX shells. And while you're at it, stop using "which" as an authority for "is this a shell builtin or not?" [type()](http://linuxcommand.org/lc3_man_pages/typeh.html) is your friend.

- When comparing strings for equality, use a single equals sign "=", not "==" (e.g. 'if [ "$foo" = "some string" ]'). I know it feels dirty if you've programmed in any other language, but it changes nothing about your code's behavior and makes it compatible with several other shells.

- Don't use "function funcname()" syntax. It adds nothing over the basic "funcname()" syntax, but prevents your code running in many/most non-Bash shells. And consider putting your function-opening brace on a separate line (someone once told me that there are shells that won't accept any other function declaration style, but I've never seen one, so ymmv).

- Don't use "local" if you need to interoperate with ksh. Abandoning "local" pollutes global namespaces, though, so your call.

- Don't use substring expansion (e.g. extracting the 3rd-10th characters of a string via 'substr="${somevar:3:7}"'. That's not supported in many other shells. Alternatives include sed/awk/etc., or, if invoking external programs is absolutely unacceptable to you, something horrific like:

substr()
{
local input="${1:?String is required}"
local dist_from_start="${2:?Start position is required}"
local dist_from_end="${3:-${#input}}" # Here, it's actually 'offset', not distance.
local start_nulls=
local end_nulls=

dist_from_end=$(( 5 * (${#input} - ($dist_from_start + $dist_from_end)) ))
dist_from_start=$(( 5 * $dist_from_start ))

# Make a string of the regex for "any not null character" that "masks" the
# characters in the input before the start point, and the characters after
# the end of the substring. This is disgusting, and is only done because the
# parameter expansion statements can't contain repetitions (e.g. [^\0]{5})
# without the bash-only 'extglob' shell option.
# The not-null character is used because it will never match in a shell
# string.
while true; do
if [ "${#start_nulls}" -lt $dist_from_start ]; then
start_nulls="${start_nulls}[^\0]"
elif [ "${#end_nulls}" -lt $dist_from_end ]; then
end_nulls="${end_nulls}[^\0]"
else
break
fi
done

input="${input#$start_nulls}"
echo "${input%$end_nulls}"

}

substr "$@"

...actually, please never use that code. Ew.

Anyway, some more bashisms: https://mywiki.wooledge.org/Bashism