Master Shell Script Style: Essential Guidelines for Clean, Efficient Bash Code
This article consolidates practical shell scripting standards—covering shebang usage, commenting, parameter validation, naming conventions, encoding, indentation, function structuring, variable scope, efficient command patterns, parallel execution, and static analysis with ShellCheck—to help developers write readable, maintainable, and performant Bash scripts.
Preface
Due to work requirements, I revisited shell scripting. Although most commands are familiar, scripts often look messy and are hard to read, especially compared to other people's scripts. Shell scripts are more of a tool than a formal programming language, used to glue together various programs.
Many scripts become a long main function without structure, and the variety of shell versions and overlapping commands make standardization difficult.
After researching, I found scattered articles on these issues, so I organized them here as a technical specification for my future scripts.
Code Style Guidelines
Shebang
The shebang (
#!) at the first line specifies the interpreter, e.g.:
<code>#!/bin/bash</code>You can list supported interpreters with
cat /etc/shells:
<code>$ cat /etc/shells
#/etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/screen</code>Running
./a.shwithout a shebang uses the interpreter defined by
$SHELL; otherwise the shebang interpreter is used. This is the recommended approach.
Comments
Comments are essential in shell scripts because many one‑line commands are not self‑explanatory. A good comment acts like a README, explaining:
shebang
script parameters
purpose
cautions
author, date, license
function description
complex one‑liner explanation
Parameter Validation
Always check that parameters meet expectations and provide clear feedback. For example, ensure the correct number of arguments:
<code>if [[ $# != 2 ]]; then
echo "Parameter incorrect."
exit 1
fi</code>Variables and Magic Numbers
Define important environment variables at the script top, e.g.
JAVA_HOMEand
PATH. Avoid hard‑coded magic numbers; use named variables instead.
<code>source /etc/profile
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/apps/bin/"</code>Indentation Rules
Use consistent indentation (soft tabs with 2 or 4 spaces, or hard tabs). Keep
thenand
doon the same line as the preceding statement to improve readability.
soft tab: spaces
hard tab: literal
\tNaming Standards
Follow these conventions:
File names end with
.shVariable names are meaningful and correctly spelled
Use lowercase with underscores for identifiers
Encoding Consistency
Write scripts in UTF‑8. Prefer English for comments and logs to avoid garbled output on machines without Chinese locale. When editing on Windows, ensure UTF‑8 without BOM; otherwise the BOM bytes may cause “command not found” errors on Linux.
Be aware of line‑ending differences: Windows uses
\r\n, Unix uses
\n. Tools like
dos2unixand
unix2doscan convert them.
Permission
Remember to add execute permission to scripts; otherwise they cannot be run directly.
Logging and Echo
Logging aids debugging, especially in large projects. For user‑facing scripts, provide real‑time echo output, optionally with ANSI colors for better UX.
Password Removal
Never hard‑code passwords in scripts; this is critical when scripts are stored in public repositories.
Line Continuation
Split long command lines with a backslash and a trailing space for readability:
<code>./configure \
--prefix=/usr \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf</code>Efficiency
Prefer a single command over multiple when possible. Example:
<code># less efficient
cat /etc/passwd | grep root
# more efficient
grep root /etc/passwd</code>Combine multiple
sedreplacements into one command to reduce the number of
findexecutions.
<code># single find, combined sed
find . -name '*.txt' | xargs sed -i "s/233/666/g;s/235/626/g;s/333/616/g;s/233/664/g"</code>Use
xargs -P $(nproc)for parallel processing.
<code>find . -name '*.txt' | xargs -P $(nproc) sed -i "s/233/666/g;s/235/626/g;s/333/616/g;s/233/664/g"</code>Double Quotes
Wrap variable expansions in double quotes to prevent word splitting and globbing.
<code>var="*.sh"
echo $var # expands to file list
echo "$var" # prints literal *.sh</code>Using a Main Function
Structure scripts with functions and a
mainentry point, similar to compiled languages:
<code>#!/usr/bin/env bash
func1(){
# do something
}
func2(){
# do something else
}
main(){
func1
func2
}
main "$@"</code>Scope Considerations
Variables are global by default. Use
localor
declareto limit scope and avoid unintended side effects.
<code>#!/usr/bin/env bash
var=1
func(){
local var=2
}
func
echo $var # prints 1</code>Function Return Values
Shell functions return integer status codes. To return strings, echo them and capture the output:
<code>func(){
echo "result"
}
res=$(func)
echo "This is from $res."</code>Indirect Reference
Access a variable whose name is stored in another variable using
${!VAR}:
<code>VAR1="value"
VAR2="VAR1"
echo ${!VAR2} # prints "value"</code>For assignments,
evalis required but generally discouraged.
Heredocs
Use heredocs to embed multi‑line content, e.g., generating configuration files:
<code>cat >>/etc/rsyncd.conf <<EOF
log file = /usr/local/logs/rsyncd.log
transfer logging = yes
log format = %t %a %m %f %b
syslog facility = local3
EOF</code>Path Lookup
Obtain the script’s directory reliably:
<code>script_dir=$(cd $(dirname $0) && pwd)
# or
script_dir=$(dirname $(readlink -f $0))</code>Code Conciseness
Prefer the shortest command that accomplishes the task, e.g., use
grep root /etc/passwdinstead of piping
catinto
grep.
Parallel Execution
Run functions in background and wait for completion:
<code>func(){
# do something
}
for ((i=0;i<10;i++)); do
func &
done
wait</code>Full‑Text Search
Search across files while handling spaces and binary files:
<code>find . -type f | xargs -i echo '"{}"' | xargs grep 2333
find . -type f | xargs grep -a 2333</code>New Syntax Recommendations
Define functions with
func(){}instead of
func{}Prefer
[[ ]]over
[ ]Use
$()for command substitution instead of backticks
Prefer
printfover
echofor formatted output
Other Tips
Prefer absolute paths; if using relative paths, prefix with
./Use Bash’s parameter expansion instead of external tools like
awkor
sedwhen possible
Write simple
ifstatements with
&&and
||on a single line
Namespace exported variables to avoid collisions
Use
trapto handle termination signals
Generate temporary files with
mktempRedirect unwanted output to
/dev/nullCheck command exit status to determine success
Test file existence before operating on it
Avoid parsing
lsoutput
Read files with
while readloops instead of
forBe aware of
cp -rbehavior regarding destination directories
Static Analysis Tool: ShellCheck
Overview
ShellCheck is an open‑source static analysis tool for shell scripts (over 8 k stars on GitHub) that helps catch common pitfalls and provides explanations and fixes.
Installation
It is available on many platforms (Debian, Arch, Gentoo, EPEL, Fedora, macOS, openSUSE, etc.) via standard package managers.
Integration
ShellCheck can be integrated into CI pipelines, such as Travis CI, to automatically lint shell‑script‑centric projects.
Examples
The tool’s “Gallery of bad code” offers many real‑world examples, similar to a “Java Puzzlers” book for shell scripting.
Essence
The most valuable part is its extensive wiki, which explains each warning, why it matters, and how to fix it, making it ideal for developers who want to understand the reasoning behind best practices.
Efficient Ops
This public account is maintained by Xiaotianguo and friends, regularly publishing widely-read original technical articles. We focus on operations transformation and accompany you throughout your operations career, growing together happily.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.