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.: #!/bin/bash You can list supported interpreters with cat /etc/shells:
$ cat /etc/shells
#/etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/screenRunning ./a.sh without 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:
if [[ $# != 2 ]]; then
echo "Parameter incorrect."
exit 1
fiVariables and Magic Numbers
Define important environment variables at the script top, e.g. JAVA_HOME and PATH. Avoid hard‑coded magic numbers; use named variables instead.
source /etc/profile
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/apps/bin/"Indentation Rules
Use consistent indentation (soft tabs with 2 or 4 spaces, or hard tabs). Keep then and do on 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 .sh Variable 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 dos2unix and unix2dos can 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:
./configure \
--prefix=/usr \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.confEfficiency
Prefer a single command over multiple when possible. Example:
# less efficient
cat /etc/passwd | grep root
# more efficient
grep root /etc/passwdCombine multiple sed replacements into one command to reduce the number of find executions.
# 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"Use xargs -P $(nproc) for parallel processing.
find . -name '*.txt' | xargs -P $(nproc) sed -i "s/233/666/g;s/235/626/g;s/333/616/g;s/233/664/g"Double Quotes
Wrap variable expansions in double quotes to prevent word splitting and globbing.
var="*.sh"
echo $var # expands to file list
echo "$var" # prints literal *.shUsing a Main Function
Structure scripts with functions and a main entry point, similar to compiled languages:
#!/usr/bin/env bash
func1(){
# do something
}
func2(){
# do something else
}
main(){
func1
func2
}
main "$@"Scope Considerations
Variables are global by default. Use local or declare to limit scope and avoid unintended side effects.
#!/usr/bin/env bash
var=1
func(){
local var=2
}
func
echo $var # prints 1Function Return Values
Shell functions return integer status codes. To return strings, echo them and capture the output:
func(){
echo "result"
}
res=$(func)
echo "This is from $res."Indirect Reference
Access a variable whose name is stored in another variable using ${!VAR}:
VAR1="value"
VAR2="VAR1"
echo ${!VAR2} # prints "value"For assignments, eval is required but generally discouraged.
Heredocs
Use heredocs to embed multi‑line content, e.g., generating configuration files:
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
EOFPath Lookup
Obtain the script’s directory reliably:
script_dir=$(cd $(dirname $0) && pwd)
# or
script_dir=$(dirname $(readlink -f $0))Code Conciseness
Prefer the shortest command that accomplishes the task, e.g., use grep root /etc/passwd instead of piping cat into grep.
Parallel Execution
Run functions in background and wait for completion:
func(){
# do something
}
for ((i=0;i<10;i++)); do
func &
done
waitFull‑Text Search
Search across files while handling spaces and binary files:
find . -type f | xargs -i echo '"{}"' | xargs grep 2333
find . -type f | xargs grep -a 2333New Syntax Recommendations
Define functions with func(){} instead of func{} Prefer [[ ]] over [ ] Use $() for command substitution instead of backticks
Prefer printf over echo for formatted output
Other Tips
Prefer absolute paths; if using relative paths, prefix with ./ Use Bash’s parameter expansion instead of external tools like awk or sed when possible
Write simple if statements with && and || on a single line
Namespace exported variables to avoid collisions
Use trap to handle termination signals
Generate temporary files with mktemp Redirect unwanted output to /dev/null Check command exit status to determine success
Test file existence before operating on it
Avoid parsing ls output
Read files with while read loops instead of for Be aware of cp -r behavior 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
