Shell Script Coding Standards and Best Practices
This article presents a comprehensive set of guidelines for writing clean, maintainable, and efficient Bash/Shell scripts, covering shebang usage, comments, parameter validation, variable handling, indentation, naming, encoding, permissions, logging, security, parallel execution, and tooling such as ShellCheck.
Preface
Due to historical reasons, many different shell versions exist, and several commands provide overlapping functionality, making script style hard to unify. After reviewing various documents, I have collected scattered best‑practice articles into a single reference for future script development.
Code Style Guidelines
Shebang
The shebang (#!) on the first line tells the system which interpreter to use when none is specified, e.g.: #!/bin/bash Other interpreters can be listed with cat /etc/shells:
#/etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/screenIf a script is executed as ./a.sh without a shebang, the interpreter defined by $SHELL is used; 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, describing the shebang, script parameters, purpose, cautions, author, copyright, and function explanations.
Shebang
Script parameters
Script purpose
Precautions
Author, time, license
Function documentation
Complex command notes
Parameter Validation
When a script accepts arguments, first verify that they meet the expected format and provide helpful messages. For example, check the number of arguments:
if [[ $# != 2 ]]; then
echo "Parameter incorrect."
exit 1
fiVariables and Magic Numbers
Define important environment variables at the top of the script and avoid hard‑coding magic numbers. Use variables instead, e.g.:
source /etc/profile
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/apps/bin/"When multiple Java versions are installed, set JAVA_HOME and PATH explicitly.
Indentation Rules
Consistent indentation (soft tabs with 2 or 4 spaces, or hard tabs) improves readability, especially inside functions and control structures. Avoid placing then or do on separate lines.
Naming Conventions
File names end with .sh Variable names are meaningful and correctly spelled
Use lowercase letters and underscores for shell identifiers
Encoding Consistency
Prefer UTF‑8 without BOM. When editing on Windows, ensure the file is saved as UTF‑8 without BOM; otherwise the three BOM bytes may cause “command not found” errors on Linux. Also watch line‑ending differences ( \r\n vs \n) and use dos2unix or unix2dos as needed.
Permissions
Remember to add execute permission ( chmod +x script.sh) so the script can be run directly.
Logging and Echo
Logging helps debugging large projects. For user‑facing scripts, provide real‑time echo output, optionally with colors using ANSI/VT100 sequences.
Password Removal
Never hard‑code passwords in scripts; remove them before committing to public repositories.
Line Continuation
For long command lines, use a backslash with a preceding space:
./configure \
--prefix=/usr \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \Efficiency Details
Choose commands wisely; for example, sed -n '1p;1q' file reads only the first line, whereas sed -n '1p' file reads the whole file.
Use Double Quotes
Always double‑quote variable expansions to avoid word splitting and globbing:
#!/bin/sh
# assume a.sh exists in the current directory
var="*.sh"
echo $var # expands to file list
echo "$var" # prints *.shMain Function Trick
Structure scripts like compiled languages by defining a main function and calling it at the end:
#!/usr/bin/env bash
func1(){
# do something
}
func2(){
# do something else
}
main(){
func1
func2
}
main "$@"Scope Considerations
By default, variables are global. Use local or declare inside functions to limit scope:
#!/usr/bin/env bash
var=1
func(){
local var=2
}
func
echo $var # prints 1Function Return Values
Functions return integer status codes; to return strings, echo them and capture with command substitution:
func(){
echo "2333"
}
res=$(func)
echo "This is from $res."Indirect Variable Reference
Use ${!VAR} for simple indirect expansion, avoiding eval when possible:
VAR1="value"
VAR2="VAR1"
echo ${!VAR2} # prints valueHeredocs
Generate multi‑line files conveniently with heredocs:
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 Discovery
Obtain the script’s directory reliably:
script_dir=$(cd $(dirname $0) && pwd)
# or
script_dir=$(dirname $(readlink -f $0))Code Brevity
Prefer single commands over pipelines when possible, e.g. grep root /etc/passwd instead of cat /etc/passwd | grep root. Combine multiple sed expressions into one call for speed, and use xargs -P $(nproc) for parallel execution.
Command Parallelization
Run background jobs with & and wait for them:
func(){
# do something
}
for i in {1..10}; do
func &
done
waitFull Text Search
Search across many files safely by handling spaces and binary files:
find . -type f -print0 | xargs -0 grep -a "search_term"New Syntax Recommendations
Prefer func(){} over func{}, use [[ ]] instead of [ ], and command substitution $( ) over backticks. Use printf for complex output.
Other Tips
Prefer absolute paths; if using relative paths, prefix with ./.
Prefer Bash variable substitution over awk / sed for simple tasks.
Write simple if statements as one‑liners with && and ||.
Namespace exported variables to avoid clashes.
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 loops.
When copying directories, be aware of target‑directory behavior of cp -r.
Static Analysis with ShellCheck
Overview
To enforce script quality, integrate a static analysis tool. ShellCheck is a popular open‑source linter with over 8 k stars on GitHub.
Installation
ShellCheck is available on most platforms (Debian, Arch, Gentoo, Fedora, macOS, openSUSE, etc.) via standard package managers.
Integration
It can be added to CI pipelines such as Travis CI to automatically lint shell scripts.
Samples
The project’s “Gallery of bad code” provides concrete examples of problematic patterns and their fixes.
Core
ShellCheck’s wiki explains each warning, the reasoning behind it, and recommended fixes, making it an excellent learning resource.
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.
Laravel Tech Community
Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.
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.
