Operations 20 min read

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.

Laravel Tech Community
Laravel Tech Community
Laravel Tech Community
Shell Script Coding Standards and Best Practices

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/screen

If 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
fi

Variables 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 *.sh

Main 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 1

Function 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 value

Heredocs

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
EOF

Path 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
wait

Full 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

best practicescoding standardsstatic analysisBashscript
Laravel Tech Community
Written by

Laravel Tech Community

Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.