Master Shell Script Best Practices: Clean Code, Safety, and Efficiency
This guide consolidates essential shell scripting conventions—including shebang usage, comprehensive commenting, parameter validation, variable handling, indentation, naming, encoding, permission settings, logging, password safety, line continuation, code brevity, quoting, main‑function patterns, scope control, indirect references, heredocs, path resolution, parallel execution, and static analysis with ShellCheck—to help developers write readable, maintainable, and performant scripts.
Preface
Due to work requirements, I recently started reorganizing my shell scripts. Although I frequently use most commands, the scripts often look messy, and reading others' scripts can be difficult because shell scripts are more of a tool than a formal programming language.
Consequently, many scripts become a long "main" function without clear structure, and the variety of shell versions and overlapping commands makes standardization hard.
After reviewing various documents, I found many scattered articles addressing these issues, so I compiled them here as a personal technical specification for future scripts.
Code Style Guidelines
Shebang at the Top
The shebang (#!) on the first line specifies the interpreter when none is explicitly set, e.g.: #!/bin/bash You can list supported interpreters with:
$ cat /etc/shells
#/etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/screenIf you run ./a.sh without a shebang, the script will use the interpreter defined by $SHELL; otherwise it follows the shebang. The shebang approach is recommended.
Include Comments
Comments are essential in shell scripts because many one‑line commands are not self‑explanatory. They serve as a README, explaining the shebang, script parameters, purpose, cautions, author, timestamps, and function descriptions.
Shebang
Script parameters
Script purpose
Precautions
Author, time, copyright
Function description comments
Complex one‑liner explanations
Validate Parameters
When a script accepts arguments, first check that they meet expectations and provide clear feedback:
if [[ $# != 2 ]]; then
echo "Parameter incorrect."
exit 1
fiAvoid Magic Numbers
Define important environment variables at the top, 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 exist, set JAVA_HOME and PATH explicitly. Prefer defining constants as variables rather than hard‑coding values.
Consistent Indentation
Use either soft tabs (2 or 4 spaces) or hard tabs (\t). Keep keywords like then and do on the same line as the condition to improve readability.
Standard Naming
File names should end with .sh Variable names must be meaningful and correctly spelled
Use lowercase with underscores for shell scripts
Unified Encoding
Write scripts in UTF‑8. Prefer English for comments and logs to avoid garbled output on machines without Chinese locale support. When editing on Windows, ensure UTF‑8 without BOM; otherwise the leading BOM bytes can cause syntax errors on Linux.
Be aware of line‑ending differences: Windows uses \r\n, Unix uses \n. Tools like dos2unix and unix2dos can convert them.
Set Execute Permissions
Remember to add execute permission to scripts; otherwise they cannot be run directly.
Logging and Echoing
Logging aids debugging, especially in large projects. Real‑time echoing of progress improves user experience; consider using ANSI/VT100 sequences for colors or effects.
Never Hard‑code Passwords
Never embed passwords in scripts, especially when the code is stored in public repositories.
Split Long Lines
Use a backslash with a preceding space to break long command lines for readability:
./configure \
--prefix=/usr \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \Detailed Coding Tips
Efficiency
Choose commands wisely; for example, sed -n '1p;1q' file reads only the first line, whereas sed -n '1p' file reads the whole file.
Prefer Double Quotes
Wrap variable expansions in double quotes to avoid word splitting and globbing:
#!/bin/sh
var="*.sh"
echo $var # expands to file list
echo "$var" # prints *.shUse a Main Function
Structure scripts with a main entry point similar to compiled languages:
#!/usr/bin/env bash
func1(){
# do something
}
func2(){
# do something else
}
main(){
func1
func2
}
main "$@"Scope Control
Variables are global by default. Use local readonly or declare inside functions to limit scope.
Function Return Values
Shell functions return integer status codes. To return strings, echo the value and capture it:
func(){
echo "2333"
}
res=$(func)
echo "This is from $res."Indirect Variable Reference
Use ${!VAR} for simple indirect lookup, avoiding eval when possible.
Heredocs
Heredocs simplify multi‑line input, 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 Resolution
Obtain the script's directory reliably with:
script_dir=$(cd $(dirname $0) && pwd)
# or
script_dir=$(dirname $(readlink -f $0))Keep Commands Short
Prefer a single command over pipelines when possible, e.g. grep root /etc/passwd instead of cat /etc/passwd | grep root. Combine multiple sed replacements into one expression for speed.
Parallel Execution
Run functions in background and wait for completion, or use xargs -P for parallel processing.
Full‑Text Search
When searching files with spaces in names, quote the filenames or use -a with grep to treat binary files as text.
Modern Syntax
Define functions with func(){} instead of func{} Prefer [[ ]] over [ ] Use $(...) instead of backticks for command substitution
Prefer printf over echo for formatted output
Additional Tips
Prefer absolute paths; if using relative paths, prefix with ./ Prefer Bash parameter expansion over awk / sed for simple tasks
Use && and || for concise one‑line conditionals
Namespace exported variables to avoid conflicts
Use trap to handle termination signals
Generate temporary files with mktemp Redirect unwanted output to /dev/null Check file existence before operating on it
Avoid parsing ls output
Read files with while read loops instead of for When copying directories, be aware of destination behavior
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 detailed explanations.
Installation
It supports many platforms, including Debian, Arch, Gentoo, EPEL, Fedora, macOS, openSUSE, etc., and can be installed via the respective package managers.
Integration
ShellCheck can be integrated into CI pipelines such as Travis CI to automatically lint shell‑script‑heavy projects.
Sample Usage
The project's "Gallery of bad code" offers many examples of problematic scripts, useful for learning what to avoid.
Core Value
The most valuable part is its extensive wiki, which explains each warning, why it matters, and how to fix it, making it an excellent resource for developers who want to understand the reasoning behind best practices.
Author: Myths
Link: https://blog.mythsman.com/2017/07/23/1/
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.
