• Stars
    star
    685
  • Rank 65,712 (Top 2 %)
  • Language
  • Created about 10 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A style guide for writing safe, predictable, and portable bash scripts (not sh!)

Bash Style Guide

This style guide is meant to outline how to write bash scripts with a style that makes them safe and predictable. This guide is based on this wiki, specifically this page:

http://mywiki.wooledge.org/BashGuide/Practices

If anything is not mentioned explicitly in this guide, it defaults to matching whatever is outlined in the wiki.

Fork this style guide on GitHub https://github.com/bahamas10/bash-style-guide

Preface

I wrote this guide originally for a project I had worked on called Basher. The idea was to make a program like Puppet or Chef but using nothing but Bash - simple scripts that could do automation tasks instead of complex ruby scripts or whatever else is used by existing configuration management software.

Basher was fun to write, and for what it does it works pretty well. As part of writing it I also wrote this style guide to show 1. how I write bash and 2. how bash can be safe and predictable if written carefully.

This guide will try to be as objective as possible, providing reasoning for why certain decisions were made. For choices that are purely aesthetic (and may not be universally agreeable) they will exist in the Aesthetics section below.

Aesthetics

Tabs / Spaces

tabs.

Columns

not to exceed 80.

Semicolons

You don't use semicolons on the command line (I hope), don't use them in scripts.

# wrong
name='dave';
echo "hello $name";

#right
name='dave'
echo "hello $name"

The exception to this rule is outlined in the Block Statements section below. Namely, semicolons should be used for control statements like if or while.

Functions

Don't use the function keyword. All variables created in a function should be made local.

# wrong
function foo {
    i=foo # this is now global, wrong depending on intent
}

# right
foo() {
    local i=foo # this is local, preferred
}

Block Statements

then should be on the same line as if, and do should be on the same line as while.

# wrong
if true
then
    ...
fi

# also wrong, though admittedly looks kinda cool
true && {
    ...
}

# right
if true; then
    ...
fi

Spacing

No more than 2 consecutive newline characters (ie. no more than 1 blank line in a row)

Comments

No explicit style guide for comments. Don't change someones comments for aesthetic reasons unless you are rewriting or updating them.

Bashisms

This style guide is for bash. This means when given the choice, always prefer bash builtins or keywords instead of external commands or sh(1) syntax.

test(1)

Use [[ ... ]] for conditional testing, not [ .. ] or test ...

# wrong
test -d /etc

# also wrong
[ -d /etc ]

# correct
[[ -d /etc ]]

See http://mywiki.wooledge.org/BashFAQ/031 for more information

Sequences

Use bash builtins for generating sequences

n=10

# wrong
for f in $(seq 1 5); do
    ...
done

# wrong
for f in $(seq 1 "$n"); do
    ...
done

# right
for f in {1..5}; do
    ...
done

# right
for ((i = 0; i < n; i++)); do
    ...
done

Command Substitution

Use $(...) for command substitution.

foo=`date`  # wrong
foo=$(date) # right

Math / Integer Manipulation

Use ((...)) and $((...)).

a=5
b=4

# wrong
if [[ $a -gt $b ]]; then
    ...
fi

# right
if ((a > b)); then
    ...
fi

Do not use the let command.

Parameter Expansion

Always prefer parameter expansion over external commands like echo, sed, awk, etc.

name='bahamas10'

# wrong
prog=$(basename "$0")
nonumbers=$(echo "$name" | sed -e 's/[0-9]//g')

# right
prog=${0##*/}
nonumbers=${name//[0-9]/}

Listing Files

Do not parse ls(1), instead use bash builtin functions to loop files

# very wrong, potentially unsafe
for f in $(ls); do
    ...
done

# right
for f in *; do
    ...
done

Determining path of the executable (__dirname)

Simply stated, you can't know this for sure. If you are trying to find out the full path of the executing program, you should rethink your software design.

See http://mywiki.wooledge.org/BashFAQ/028 for more information

For a case study on __dirname in multiple languages see my blog post

http://daveeddy.com/2015/04/13/dirname-case-study-for-bash-and-node/

Arrays and lists

Use bash arrays instead of a string separated by spaces (or newlines, tabs, etc.) whenever possible

# wrong
modules='json httpserver jshint'
for module in $modules; do
    npm install -g "$module"
done

# right
modules=(json httpserver jshint)
for module in "${modules[@]}"; do
    npm install -g "$module"
done

Of course, in this example it may be better expressed as:

npm install -g "${modules[@]}"

... if the command supports multiple arguments, and you are not interested in catching individual failures.

read builtin

Use the bash read builtin whenever possible to avoid forking external commands

Example

fqdn='computer1.daveeddy.com'

IFS=. read -r hostname domain tld <<< "$fqdn"
echo "$hostname is in $domain.$tld"
# => "computer1 is in daveeddy.com"

External Commands

GNU userland tools

The whole world doesn't run on GNU or on Linux; avoid GNU specific options when forking external commands like awk, sed, grep, etc. to be as portable as possible.

When writing bash and using all the powerful tools and builtins bash gives you, you'll find it rare that you need to fork external commands to do simple string manipulation.

UUOC

Don't use cat(1) when you don't need it. If programs support reading from stdin, pass the data in using bash redirection.

# wrong
cat file | grep foo

# right
grep foo < file

# also right
grep foo file

Prefer using a command line tools builtin method of reading a file instead of passing in stdin. This is where we make the inference that, if a program says it can read a file passed by name, it's probably more performant to do that.

Style

Quoting

Use double quotes for strings that require variable expansion or command substitution interpolation, and single quotes for all others.

# right
foo='Hello World'
bar="You are $USER"

# wrong
foo="hello world"

# possibly wrong, depending on intent
bar='You are $USER'

All variables that will undergo word-splitting must be quoted (1). If no splitting will happen, the variable may remain unquoted.

foo='hello world'

if [[ -n $foo ]]; then   # no quotes needed:
                         # [[ ... ]] won't word-split variable expansions

    echo "$foo"          # quotes needed
fi

bar=$foo  # no quotes needed - variable assignment doesn't word-split
  1. The only exception to this rule is if the code or bash controls the variable for the duration of its lifetime. For instance, basher has code like:
printf_date_supported=false
if printf '%()T' &>/dev/null; then
    printf_date_supported=true
fi

if $printf_date_supported; then
    ...
fi

Even though $printf_date_supported undergoes word-splitting in the if statement in that example, quotes are not used because the contents of that variable are controlled explicitly by the programmer and not taken from a user or command.

Also, variables like $$, $?, $#, etc. don't required quotes because they will never contain spaces, tabs, or newlines.

When in doubt however, quote all expansions.

Variable Declaration

Avoid uppercase variable names unless there's a good reason to use them. Don't use let or readonly to create variables. declare should only be used for associative arrays. local should always be used in functions.

# wrong
declare -i foo=5
let foo++
readonly bar='something'
FOOBAR=baz

# right
i=5
((i++))
bar='something'
foobar=baz

shebang

Bash is not always located at /bin/bash, so use this line:

#!/usr/bin/env bash

Unless you have a reason to use something else.

Error Checking

cd, for example, doesn't always work. Make sure to check for any possible errors for cd (or commands like it) and exit or break if they are present.

# wrong
cd /some/path # this could fail
rm file       # if cd fails where am I? what am I deleting?

# right
cd /some/path || exit
rm file

set -e

Don't set errexit. Like in C, sometimes you want an error, or you expect something to fail, and that doesn't necessarily mean you want the program to exit.

This is a contreversial opinion that I have on the surface, but the link below will show situations where set -e can do more harm than good because of its implications.

http://mywiki.wooledge.org/BashFAQ/105

eval

Never.

Common Mistakes

Using {} instead of quotes.

Using ${f} is potentially different than "$f" because of how word-splitting is performed. For example.

for f in '1 space' '2  spaces' '3   spaces'; do
    echo ${f}
done

yields

1 space
2 spaces
3 spaces

Notice that it loses the amount of spaces. This is due to the fact that the variable is expanded and undergoes word-splitting because it is unquoted. This loop results in the 3 following commands being executed:

echo 1 space
echo 2  spaces
echo 3   spaces

The extra spaces are effectively ignored here and only 2 arguments are passed to the echo command in all 3 invocations.

If the variable was quoted instead:

for f in '1 space' '2  spaces' '3   spaces'; do
    echo "$f"
done

yields

1 space
2  spaces
3   spaces

The variable $f is expanded but doesn't get split at all by bash, so it is passed as a single string (with spaces) to the echo command in all 3 invocations.

Note that, for the most part $f is the same as ${f} and "$f" is the same as "${f}". The curly braces should only be used to ensure the variable name is expanded properly. For example:

$ echo "$HOME is $USERs home directory"
/home/dave is  home directory
$ echo "$HOME is ${USER}s home directory"
/home/dave is daves home directory

The braces in this example were the difference of $USER vs $USERs being expanded.

Abusing for-loops when while would work better

for loops are great for iteration over arguments, or arrays. Newline separated data is best left to a while read -r ... loop.

users=$(awk -F: '{print $1}' /etc/passwd)
for user in $users; do
    echo "user is $user"
done

This example reads the entire /etc/passwd file to extract the usernames into a variable separated by newlines. The for loop is then used to iterate over each entry.

This approach has a lot of issues if used on other files with data that may contain spaces or tabs.

  1. This reads all usernames into memory, instead of processing them in a streaming fashion.
  2. If the first field of that file contained spaces or tabs, the for loop would break on that as well as newlines
  3. This only works because $users is unquoted in the for loop - if variable expansion only works for your purposes while unquoted this is a good sign that something isn't implemented correctly.

To rewrite this:

while IFS=: read -r user _; do
    echo "$user is user"
done < /etc/passwd

This will read the file in a streaming fashion, not pulling it all into memory, and will break on colons extracting the first field and discarding (storing as the variable _) the rest - using nothing but bash builtin commands.

Extra

License

MIT License

More Repositories

1

zfs-prune-snapshots

Remove snapshots from one or more zpools that match given criteria
Shell
263
star
2

hue-cli

A command line interface to phillips hue
JavaScript
213
star
3

hueadm

A command line management interface to Philips hue
JavaScript
204
star
4

css-color-names

A JSON Object of css color names mapped to their hex value
JavaScript
141
star
5

node-clear

Clear the terminal screen if possible
JavaScript
112
star
6

bash-vsv

Manage and view runit services
Shell
92
star
7

ryb

A color picker that transposes from the primary colors, RYB, to RGB
JavaScript
72
star
8

you-suck-at-programming

You Suck at Programming - a video series
Shell
71
star
9

basher

Configuration Management in Bash
Shell
68
star
10

node-dtrace-examples

Examples of DTrace probes in Node.js
JavaScript
61
star
11

unifi-proxy

a hack to allow direct connections to unifi protect on a different layer 3 network
JavaScript
60
star
12

vsv

Manage and view runit services
Rust
58
star
13

dotfiles

My configuration files
Vim Script
56
star
14

node-sshp

simple, intuitive, no bs approach to parallel ssh
JavaScript
56
star
15

JSONSyntaxHighlight

Add syntax highlighting to JSON objects in Objective C for both Cocoa and iOS without using HTML
Objective-C
52
star
16

node-log-timestamp

Prepend timestamps to functions like console.log, console.warn, etc
JavaScript
52
star
17

node-netscape-bookmarks

Create a netscape format bookmarks file (works with Chrome)
JavaScript
38
star
18

preloadimages.js

Preload images and callback when they are all ready
JavaScript
37
star
19

windows-bash-ssh-agent

Scripts to persist ssh-agent on Bash on Ubuntu on Windows
Shell
32
star
20

node-manta-sync

Rsync style command for Joyent's Manta
JavaScript
31
star
21

wordsearch.js

Generate wordsearch puzzles
JavaScript
31
star
22

zfs-snapshot-all

Recursively snapshot all zpools
Shell
27
star
23

zincrsend

Incremental ZFS send/recv backup script
Shell
27
star
24

node-exec

Call a child process with the ease of exec and safety of spawn
JavaScript
27
star
25

zzz-user-hooks

Call scripts on suspend and resume for the currently logged in user using `zzz`
Shell
24
star
26

node-httpserver

command line HTTP server tool for serving up local files, similar to python -mSimpleHTTPServer
JavaScript
23
star
27

node-musicnamer

Organize your music collection
JavaScript
22
star
28

realtime-dtrace-visualization

A collection of scripts used for realtime DTrace visualizations and analysis
D
21
star
29

node-celery-man

Computer load up celery man
JavaScript
20
star
30

node-autocast

Easily and automatically cast common datatypes in JavaScript
JavaScript
19
star
31

node-log-buffer

Buffer calls to console.log, console.warn, etc. for high performance logging
JavaScript
18
star
32

node-smf

Expose smf(5) to Node.js for Solaris/Illumos based operating systems
JavaScript
17
star
33

sombra

sombra ARG for overwatch
JavaScript
16
star
34

music-directory

Serve your music over the web with a nice UI, or as JSON
JavaScript
15
star
35

node-ryb2rgb

Convert colors in JavaScript from ryb to rgb
JavaScript
15
star
36

Alt-Drag

Linux-style alt+drag for windows on Mac OS X
Objective-C
15
star
37

node-http-host-proxy

HTTP(s) proxy with host based routing to front servers, with optional SSL or authentication
JavaScript
15
star
38

node-dhcpd-dashboard

Create an HTTP dashboard for isc-dhcpd
HTML
14
star
39

node-git-http-server

serve a directory of git repositories over http
JavaScript
13
star
40

node-dhcpd-leases

parse isc-dhcpd dhcpd.leases(5) file format
JavaScript
12
star
41

binary-to-qrcode

Binary or Hex to QR Code site
HTML
12
star
42

node-bpm

Calculate BPM by tapping
JavaScript
12
star
43

iAmp-Mobile

Ampache Client for iOS devices
Objective-C
12
star
44

human

show seconds in a human-readable form
C
11
star
45

node-random-mac

Generate a random Mac Address
JavaScript
11
star
46

bash-2048

2048 written in bash
Shell
11
star
47

omnifetch

Print information about an OmniOS machine.
Rust
11
star
48

node-curl-cmd

Generate a curl command line argument list from an http request object
JavaScript
10
star
49

node-log-prefix

Prefix calls to console.log, console.warn, etc with whatever you'd like
JavaScript
10
star
50

node-latest

Quickly determine the latest available version of a package in npm
JavaScript
9
star
51

node-access-log

Add simple access logs to any http or https server
JavaScript
9
star
52

svlogger

A generic svlogd wrapper for runit services
Shell
9
star
53

sshp

Parallel SSH Executor
C
9
star
54

sos

SmartOS Zone Summary
JavaScript
8
star
55

bics

A modular framework for bash plugin management
Shell
8
star
56

node-dvorak

Convert between the Qwerty and Dvorak keyboard layout
JavaScript
8
star
57

visusage

Visual usage statistics for Illumos-based operating systems using prstat, iostat, etc.
Shell
8
star
58

node-xbox-hid-controller

Original Xbox Controller API using node-hid HID device
JavaScript
8
star
59

node-webamp

Ampache web interface to make browsing, and playing your music a simple task
JavaScript
8
star
60

node-rgb2ryb

Convert colors in JavaScript from rgb to ryb and back
JavaScript
8
star
61

oisd-install

Pull, validate, and install a host list from https://oisd.nl.
Shell
8
star
62

plex-install-manager

Manage plex media server installations (for Linux)
Shell
7
star
63

node-iprange

Generate an array of all ips in a given subnet
JavaScript
7
star
64

tvstatic

Generate TV static in an HTML5 canvas element
JavaScript
7
star
65

someonewhocares

Pull and install the latest host file from someonewhocares.org
Shell
7
star
66

node-perms

Convert Unix style permissions to strings like ls (0755 => 'rwxr-xr-x')
JavaScript
7
star
67

node-ssh-fingerprint

Generate a fingerprint given an SSH public key (without `ssh-keygen` or external dependencies)
JavaScript
7
star
68

node-etc-passwd

Interface to read a standard Unix passwd and group file-format
JavaScript
7
star
69

Viridian

Viridian is an Ampache Client that displays all of your media from your Ampache server in a simple and convenient way that makes choosing and streaming music an easy task.
Python
7
star
70

app-focus

Print notifications to the console when different apps take focus on OS X
Objective-C
6
star
71

remote-notify

WeeChat plugin for remote osd-notify or growl notificiations.
Python
6
star
72

illumos-sockstat

List open sockets on Illumos with process information
C
6
star
73

node-url-shortener

Spawn up a simple webserver to act as a URL shortener
JavaScript
6
star
74

2048.c

2048 written in C
C
6
star
75

music-generator

Programmatically Generate Music with JavaScript
JavaScript
5
star
76

mac-chromium

Install and upgrade chromium on OS X
Shell
5
star
77

bash-path

Functions to modify colon separated variables like `$PATH` or `$MANPATH`
Shell
5
star
78

node-hue-sdk

Phillips Hue API library
JavaScript
5
star
79

node-dnsgen

Generate DNS files using json
JavaScript
5
star
80

node-fs-caching-server

A caching HTTP server/proxy that stores data on the local filesystem
JavaScript
5
star
81

basher-repo

A template repo for use with basher. Use this as a skeleton or even fork this repository to host your own plugins and scripts.
JavaScript
5
star
82

bash-analysis

Various tools for text extraction, representation, and analysis
Shell
5
star
83

node-ampache

Communicate to an Ampache server using the API
JavaScript
4
star
84

prepositions

A JSON array of one-word, english prepositions
Shell
4
star
85

bash-pitfalls-presentation

for the tech summit
Shell
4
star
86

node-stats-page

Create a /stats page http-server for a server application
JavaScript
4
star
87

dtrace-intro-slides

Slides used for an Introduction to DTrace presentation at Voxer
JavaScript
4
star
88

node-autolinks

Automatically turn URL's into links
JavaScript
4
star
89

plex-upgrade

script to install or upgrade plex on Ubuntu 64bit
Shell
4
star
90

node-gmailx

Send email easily on the command line without running a server
JavaScript
4
star
91

cryptogram

cryptogram puzzle solver helper script
JavaScript
3
star
92

smfwatchdog

A health checking daemon to be used with SMF services
C
3
star
93

node-tilde-expansion

Expand a ~ character to a users home directory like bash
JavaScript
3
star
94

pizza-party

A collection of restaurants that satisfy our weird eating habits
3
star
95

wiiu-media-server

An HTTP media server made specifically for the Wii U browser
CSS
3
star
96

unix-tools

a rewrite of common Unix tools in C
C
3
star
97

XBMCXboxHIDController

Control XBMC using an original Xbox controller on OS X
C
3
star
98

node-git-dump

Dump the contents of a remote git repository without directory listing enabled
JavaScript
3
star
99

node-pins

Create Pinterest style pins for your own files over HTTP
JavaScript
3
star
100

di.fm

Command line tool to listen to D I G I T A L L Y I M P O R T E D
3
star