diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,27 +1,68 @@
 # ShellCheck - A shell script static analysis tool
 
-http://www.shellcheck.net
+ShellCheck is a GPLv3 tool that gives warnings and suggestions for bash/sh shell scripts:
 
-Copyright 2012-2015, Vidar 'koala_man' Holen
-Licensed under the GNU General Public License, v3
+![Screenshot of a terminal showing problematic shell script lines highlighted](doc/terminal.png).
 
-The goals of ShellCheck are:
+The goals of ShellCheck are
 
-  - To point out and clarify typical beginner's syntax issues,
+  - To point out and clarify typical beginner's syntax issues
     that causes a shell to give cryptic error messages.
 
-  - To point out and clarify typical intermediate level semantic problems,
+  - To point out and clarify typical intermediate level semantic problems
     that causes a shell to behave strangely and counter-intuitively.
 
-  - To point out subtle caveats, corner cases and pitfalls, that may cause an
+  - To point out subtle caveats, corner cases and pitfalls that may cause an
     advanced user's otherwise working script to fail under future circumstances.
+    
+See [the gallery of bad code](README.md#user-content-gallery-of-bad-code) for examples of what ShellCheck can help you identify!
 
-ShellCheck is written in Haskell, and requires 2 GB of memory to compile.
 
+## How to use
+There are a variety of ways to use ShellCheck!
+
+
+#### On the web
+Paste a shell script on http://www.shellcheck.net for instant feedback.
+
+[ShellCheck.net](http://www.shellcheck.net) is always synchronized to the latest git commit, and is the simplest way to give ShellCheck a go. Tell your friends!
+
+
+#### From your terminal
+Run `shellcheck yourscript` in your terminal for instant output, as seen above.
+
+
+#### In your editor
+
+You can see ShellCheck suggestions directly in a variety of editors.
+
+* Vim, through [Syntastic](https://github.com/scrooloose/syntastic):
+
+![Screenshot of vim showing inlined shellcheck feedback](doc/vim-syntastic.png).
+
+* Emacs, through [Flycheck](https://github.com/flycheck/flycheck):
+
+![Screenshot of emacs showing inlined shellcheck feedback](doc/emacs-flycheck.png).
+
+* Sublime, through [SublimeLinter](https://github.com/SublimeLinter/SublimeLinter-shellcheck).
+
+* Atom, through [Linter](https://github.com/AtomLinter/linter-shellcheck).
+
+* Most other editors, through [GCC error compatibility](blob/master/shellcheck.1.md#user-content-formats).
+
+
+#### In your build or test suites
+While ShellCheck is mostly intended for interactive use, it can easily be added to builds or test suites.
+
+Use ShellCheck's exit code, or it's [CheckStyle compatible XML output](blob/master/shellcheck.1.md#user-content-formats). There's also a simple JSON output format for easy integration.
+
+
 ## Installing
 
-On systems with Cabal:
+The easiest way to install ShellCheck locally is through your package manager.
 
+On systems with Cabal (installs to `~/.cabal/bin`):
+
     cabal update
     cabal install shellcheck
 
@@ -29,54 +70,60 @@
 
     apt-get install shellcheck
 
+On Fedora based distros:
+
+    dnf install ShellCheck
+
 On OS X with homebrew:
 
     brew install shellcheck
 
-ShellCheck is also available as an online service:
+On OS X with MacPorts:
 
-    http://www.shellcheck.net
+    port install shellcheck
 
-## Building with Cabal
+On openSUSE:Tumbleweed:
 
-This sections describes how to build ShellCheck from a source directory.
+    zypper in ShellCheck
 
-First, make sure cabal is installed. On Debian based distros:
+On other openSUSE distributions:
 
-    apt-get install cabal-install
+add OBS devel:languages:haskell repository from https://build.opensuse.org/project/repositories/devel:languages:haskell
 
-On Fedora:
+    zypper ar http://download.opensuse.org/repositories/devel:/languages:/haskell/openSUSE_$(version)/devel:languages:haskell.repo
+    zypper in ShellCheck
 
-    yum install cabal-install
+or use OneClickInstall - https://software.opensuse.org/package/ShellCheck
 
-On Mac OS X with homebrew (http://brew.sh/):
 
-    brew install cabal-install
+## Compiling from source
 
-On Mac OS X with MacPorts (http://www.macports.org/):
+This sections describes how to build ShellCheck from a source directory. ShellCheck is written in Haskell and requires 2GB of RAM to compile.
 
-    port install hs-cabal-install
 
-On native Windows (https://www.haskell.org/platform/):
+#### Installing Cabal
 
-    Download and install the latest version of the Haskell Platform.
+ShellCheck is built and packaged using Cabal. Install the package `cabal-install` from your system's package manager (with e.g. `apt-get`, `yum`, `zypper` or `brew`).
 
-Let cabal update itself, in case your distro version is outdated:
+On MacPorts, the package is instead called `hs-cabal-install`, while native Windows users should install the latest version of the Haskell platform from https://www.haskell.org/platform/
 
+Verify that `cabal` is installed and update its dependency list with
+
     $ cabal update
-    $ cabal install cabal-install
 
-With cabal installed, cd to the ShellCheck source directory and:
+#### Compiling ShellCheck
 
+`git clone` this repository, and `cd` to the ShellCheck source directory to build/install:
+
     $ cabal install
 
-This will install ShellCheck to your `~/.cabal/bin` directory.
+This will compile ShellCheck and install it to your `~/.cabal/bin` directory.
 
-Add the directory to your `PATH` (for bash, add this to your `~/.bashrc`):
+Add this directory to your `PATH` (for bash, add this to your `~/.bashrc`):
 
     export PATH="$HOME/.cabal/bin:$PATH"
 
-Verify that your PATH is set up correctly:
+Log out and in again, and verify that your PATH is set up correctly:
 
     $ which shellcheck
     ~/.cabal/bin/shellcheck
@@ -93,18 +140,175 @@
 
     > [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
 
-## Running tests
+#### Running tests
 
 To run the unit test suite:
 
-    cabal configure --enable-tests
-    cabal build
-    cabal test
+    $ cabal test
 
+
+## Gallery of bad code
+So what kind of things does ShellCheck look for? Here is an incomplete list of detected issues.
+
+#### Quoting
+
+ShellCheck can recognize several types of incorrect quoting:
+
+    echo $1                           # Unquoted variables
+    find . -name *.ogg                # Unquoted find/grep patterns
+    rm "~/my file.txt"                # Quoted tilde expansion
+    v='--verbose="true"'; cmd $v      # Literal quotes in variables
+    for f in "*.ogg"                  # Incorrectly quoted 'for' loops
+    touch $@                          # Unquoted $@
+    echo 'Don't forget to restart!'   # Singlequote closed by apostrophe
+    echo 'Don\'t try this at home'    # Attempting to escape ' in ''
+    echo 'Path is $PATH'              # Variables in single quotes
+    trap "echo Took ${SECONDS}s" 0    # Prematurely expanded trap
+
+
+#### Conditionals
+
+ShellCheck can recognize many types of incorrect test statements.
+
+    [[ n != 0 ]]                      # Constant test expressions
+    [[ -e *.mpg ]]                    # Existence checks of globs
+    [[ $foo==0 ]]                     # Always true due to missing spaces
+    [[ -n "$foo " ]]                  # Always true due to literals
+    [[ $foo =~ "fo+" ]]               # Quoted regex in =~ 
+    [ foo =~ re ]                     # Unsupported [ ] operators
+    [ $1 -eq "shellcheck" ]           # Numerical comparison of strings
+    [ $n && $m ]                      # && in [ .. ]
+    [ grep -q foo file ]              # Command without $(..)
+
+
+#### Frequently misused commands
+
+ShellCheck can recognize instances where commands are used incorrectly:
+
+    grep '*foo*' file                 # Globs in regex contexts
+    find . -exec foo {} && bar {} \;  # Prematurely terminated find -exec
+    sudo echo 'Var=42' > /etc/profile # Redirecting sudo
+    time --format=%s sleep 10         # Passing time(1) flags to time builtin
+    while read h; do ssh "$h" uptime  # Commands eating while loop input
+    alias archive='mv $1 /backup'     # Defining aliases with arguments
+    tr -cd '[a-zA-Z0-9]'              # [] around ranges in tr
+    exec foo; echo "Done!"            # Misused 'exec'
+    find -name \*.bak -o -name \*~ -delete  # Implicit precedence in find
+    f() { whoami; }; sudo f           # External use of internal functions
+
+
+#### Common beginner's mistakes
+
+ShellCheck recognizes many common beginner's syntax errors:
+
+    var = 42                          # Spaces around = in assignments
+    $foo=42                           # $ in assignments
+    for $var in *; do ...             # $ in for loop variables
+    var$n="Hello"                     # Wrong indirect assignment
+    echo ${var$n}                     # Wrong indirect reference
+    var=(1, 2, 3)                     # Comma separated arrays
+    echo "Argument 10 is $10"         # Positional parameter misreference
+    if $(myfunction); then ..; fi     # Wrapping commands in $()
+    else if othercondition; then ..   # Using 'else if'
+
+
+#### Style
+
+ShellCheck can make suggestions to improve style:
+
+    [[ -z $(find /tmp | grep mpg) ]]  # Use grep -q instead
+    a >> log; b >> log; c >> log      # Use a redirection block instead
+    echo "The time is `date`"         # Use $() instead
+    cd dir; process *; cd ..;         # Use subshells instead 
+    echo $[1+2]                       # Use standard $((..)) instead of old $[]
+    echo $(($RANDOM % 6))             # Don't use $ on variables in $((..))
+    echo "$(date)"                    # Useless use of echo
+    cat file | grep foo               # Useless use of cat
+
+
+#### Data and typing errors
+
+ShellCheck can recognize issues related to data and typing:
+
+    args="$@"                         # Assigning arrays to strings
+    files=(foo bar); echo "$files"    # Referencing arrays as strings
+    printf "%s\n" "Arguments: $@."    # Concatenating strings and arrays.
+    [[ $# > 2 ]]                      # Comparing numbers as strings
+    var=World; echo "Hello " var      # Unused lowercase variables
+    echo "Hello $name"                # Unassigned lowercase variables
+    cmd | read bar; echo $bar         # Assignments in subshells
+
+
+#### Robustness
+
+ShellCheck can make suggestions for improving the robustness of a script:
+
+    rm -rf "$STEAMROOT/"*            # Catastrophic rm
+    touch ./-l; ls *                 # Globs that could become options
+    find . -exec sh -c 'a && b {}' \; # Find -exec shell injection
+    printf "Hello $name"             # Variables in printf format
+    for f in $(ls *.txt); do         # Iterating over ls output
+    export MYVAR=$(cmd)              # Masked exit codes
+
+
+#### Portability
+
+ShellCheck will warn when using features not supported by the shebang. For example, if you set the shebang to `#!/bin/sh`, ShellCheck will warn about portability issues similar to `checkbashisms`:
+
+
+    echo {1..$n}                     # Works in ksh, but not bash/dash/sh
+    echo {1..10}                     # Works in ksh and bash, but not dash/sh
+    echo -n 42                       # Works in ksh, bash and dash, undefined in sh
+    trap 'exit 42' sigint            # Unportable signal spec
+    cmd &> file                      # Unportable redirection operator
+    read foo < /dev/tcp/host/22      # Unportable intercepted files
+    foo-bar() { ..; }                # Undefined/unsupported function name
+    [ $UID = 0 ]                     # Variable undefined in dash/sh
+    local var=value                  # local is undefined in sh
+    
+
+#### Miscellaneous
+
+ShellCheck recognizes a menagerie of other issues:
+
+    PS1='\e[0;32m\$\e[0m '            # PS1 colors not in \[..\]
+    PATH="$PATH:~/bin"                # Literal tilde in $PATH
+    rm “file”                         # Unicode quotes
+    echo "Hello world"                # Carriage return / DOS line endings
+    var=42 echo $var                  # Expansion of inlined environment
+    #!/bin/bash -x -e                 # Common shebang errors
+    echo $((n/180*100))               # Unnecessary loss of precision
+    ls *[:digit:].txt                 # Bad character class globs
+    sed 's/foo/bar/ file > file       # Redirecting to input
+
+
+## Testimonials
+
+> At first you're like "shellcheck is awesome" but then you're like "wtf are we still using bash"
+
+Alexander Tarasikov,
+[via Twitter](https://twitter.com/astarasikov/status/568825996532707330)
+
+
 ## Reporting bugs
 
 Please use the Github issue tracker for any bugs or feature suggestions:
 
 https://github.com/koalaman/shellcheck/issues
+
+
+## Contributing
+
+Please submit patches to code or documentation as Github pull requests!
+
+Contributions must be licensed under the GNU GPLv3.
+The contributor retains the copyright.
+
+
+## Copyright
+
+ShellCheck is licensed under the GNU General Public License, v3. A copy of this license is included in the file [LICENSE](LICENSE).
+
+Copyright 2012-2015, Vidar 'koala_man' Holen and contributors.
 
 Happy ShellChecking!
diff --git a/ShellCheck.cabal b/ShellCheck.cabal
--- a/ShellCheck.cabal
+++ b/ShellCheck.cabal
@@ -1,5 +1,5 @@
 Name:             ShellCheck
-Version:          0.4.1
+Version:          0.4.2
 Synopsis:         Shell script analysis tool
 License:          GPL-3
 License-file:     LICENSE
@@ -7,7 +7,7 @@
 Author:           Vidar Holen
 Maintainer:       vidar@vidarholen.net
 Homepage:         http://www.shellcheck.net/
-Build-Type:       Simple
+Build-Type:       Custom
 Cabal-Version:    >= 1.8
 Bug-reports:      https://github.com/koalaman/shellcheck/issues
 Description:
diff --git a/ShellCheck/ASTLib.hs b/ShellCheck/ASTLib.hs
--- a/ShellCheck/ASTLib.hs
+++ b/ShellCheck/ASTLib.hs
@@ -93,14 +93,17 @@
 
 
 -- Turn a SimpleCommand foo -avz --bar=baz into args "a", "v", "z", "bar",
--- each in a tuple of (token, stringFlag).
+-- each in a tuple of (token, stringFlag). Non-flag arguments are added with
+-- stringFlag == "".
 getFlagsUntil stopCondition (T_SimpleCommand _ _ (_:args)) =
-    let textArgs = takeWhile (not . stopCondition . snd) $ map (\x -> (x, concat $ oversimplify x)) args in
-        concatMap flag textArgs
+    let tokenAndText = map (\x -> (x, concat $ oversimplify x)) args
+        (flagArgs, rest) = break (stopCondition . snd) tokenAndText
+    in
+        concatMap flag flagArgs ++ map (\(t, _) -> (t, "")) rest
   where
     flag (x, '-':'-':arg) = [ (x, takeWhile (/= '=') arg) ]
     flag (x, '-':args) = map (\v -> (x, [v])) args
-    flag _ = []
+    flag (x, _) = [ (x, "") ]
 getFlagsUntil _ _ = error "Internal shellcheck error, please report! (getFlags on non-command)"
 
 -- Get all flags in a GNU way, up until --
@@ -223,6 +226,16 @@
         T_Assignment {} -> True
         T_Annotation _ _ w -> isAssignment w
         otherwise -> False
+
+isOnlyRedirection t =
+    case t of
+        T_Pipeline _ _ [x] -> isOnlyRedirection x
+        T_Annotation _ _ w -> isOnlyRedirection w
+        T_Redirecting _ (_:_) c -> isOnlyRedirection c
+        T_SimpleCommand _ [] [] -> True
+        otherwise -> False
+
+isFunction t = case t of T_Function {} -> True; _ -> False
 
 -- Get the list of commands from tokens that contain them, such as
 -- the body of while loops and if statements.
diff --git a/ShellCheck/Analytics.hs b/ShellCheck/Analytics.hs
--- a/ShellCheck/Analytics.hs
+++ b/ShellCheck/Analytics.hs
@@ -74,6 +74,11 @@
     ,checkTimeParameters
     ,checkForDecimals
     ]
+checksFor Dash = [
+    checkBashisms
+    ,checkForDecimals
+    ,checkLocalScope
+    ]
 checksFor Ksh = [
     checkEchoSed
     ]
@@ -82,6 +87,7 @@
     ,checkBraceExpansionVars
     ,checkEchoSed
     ,checkForDecimals
+    ,checkLocalScope
     ]
 
 runAnalytics :: AnalysisSpec -> AnalysisResult
@@ -210,9 +216,11 @@
     ,checkReadWithoutR
     ,checkExportedExpansions
     ,checkLoopVariableReassignment
+    ,checkTrailingBracket
+    ,checkNonportableSignals
+    ,checkMkdirDashPM
     ]
 
-
 filterByAnnotation token =
     filter (not . shouldIgnore)
   where
@@ -224,6 +232,7 @@
         any hasNum anns
       where
         hasNum (DisableComment ts) = num == ts
+        hasNum _ = False
     shouldIgnoreFor _ (T_Include {}) = True -- Ignore included files
     shouldIgnoreFor _ _ = False
     parents = getParentTree token
@@ -577,17 +586,39 @@
 prop_checkBashisms20= verify checkBashisms "read -ra foo"
 prop_checkBashisms21= verify checkBashisms "[ -a foo ]"
 prop_checkBashisms22= verifyNot checkBashisms "[ foo -a bar ]"
-prop_checkBashisms23= verify checkBashisms "trap mything err int"
-prop_checkBashisms24= verifyNot checkBashisms "trap mything int term"
+prop_checkBashisms23= verify checkBashisms "trap mything ERR INT"
+prop_checkBashisms24= verifyNot checkBashisms "trap mything INT TERM"
 prop_checkBashisms25= verify checkBashisms "cat < /dev/tcp/host/123"
 prop_checkBashisms26= verify checkBashisms "trap mything ERR SIGTERM"
 prop_checkBashisms27= verify checkBashisms "echo *[^0-9]*"
 prop_checkBashisms28= verify checkBashisms "exec {n}>&2"
-checkBashisms _ = bashism
+prop_checkBashisms29= verify checkBashisms "echo ${!var}"
+prop_checkBashisms30= verify checkBashisms "printf -v '%s' \"$1\""
+prop_checkBashisms31= verify checkBashisms "printf '%q' \"$1\""
+prop_checkBashisms32= verifyNot checkBashisms "#!/bin/dash\n[ foo -nt bar ]"
+prop_checkBashisms33= verify checkBashisms "#!/bin/sh\necho -n foo"
+prop_checkBashisms34= verifyNot checkBashisms "#!/bin/dash\necho -n foo"
+prop_checkBashisms35= verifyNot checkBashisms "#!/bin/dash\nlocal foo"
+prop_checkBashisms36= verifyNot checkBashisms "#!/bin/dash\nread -p foo -r bar"
+prop_checkBashisms37= verifyNot checkBashisms "HOSTNAME=foo; echo $HOSTNAME"
+prop_checkBashisms38= verify checkBashisms "RANDOM=9; echo $RANDOM"
+prop_checkBashisms39= verify checkBashisms "foo-bar() { true; }"
+prop_checkBashisms40= verify checkBashisms "echo $(<file)"
+prop_checkBashisms41= verify checkBashisms "echo `<file`"
+prop_checkBashisms42= verify checkBashisms "trap foo int"
+prop_checkBashisms43= verify checkBashisms "trap foo sigint"
+prop_checkBashisms44= verifyNot checkBashisms "#!/bin/dash\ntrap foo int"
+prop_checkBashisms45= verifyNot checkBashisms "#!/bin/dash\ntrap foo INT"
+prop_checkBashisms46= verify checkBashisms "#!/bin/dash\ntrap foo SIGINT"
+checkBashisms params = bashism
   where
-    errMsg id s = err id 2040 $ "In sh, " ++ s ++ " not supported, even when sh is actually bash."
-    warnMsg id s = warn id 2039 $ "In POSIX sh, " ++ s ++ " not supported."
-    bashism (T_ProcSub id _ _) = errMsg id "process substitution is"
+    isDash = shellType params == Dash
+    warnMsg id s =
+        if isDash
+        then warn id 2169 $ "In dash, " ++ s ++ " not supported."
+        else warn id 2039 $ "In POSIX sh, " ++ s ++ " undefined."
+
+    bashism (T_ProcSub id _ _) = warnMsg id "process substitution is"
     bashism (T_Extglob id _ _) = warnMsg id "extglob is"
     bashism (T_DollarSingleQuoted id _) = warnMsg id "$'..' is"
     bashism (T_DollarDoubleQuoted id _) = warnMsg id "$\"..\" is"
@@ -599,8 +630,10 @@
     bashism (T_Condition id DoubleBracket _) = warnMsg id "[[ ]] is"
     bashism (T_HereString id _) = warnMsg id "here-strings are"
     bashism (TC_Binary id SingleBracket op _ _)
-        | op `elem` [ "-nt", "-ef", "\\<", "\\>", "==" ] =
-            warnMsg id $ op ++ " is"
+        | op `elem` [ "-nt", "-ef", "\\<", "\\>"] =
+            unless isDash $ warnMsg id $ op ++ " is"
+    bashism (TC_Binary id SingleBracket "==" _ _) =
+            warnMsg id "== in place of = is"
     bashism (TC_Unary id _ "-a" _) =
             warnMsg id "unary -a in place of -e is"
     bashism (TA_Unary id op _)
@@ -621,10 +654,11 @@
         warnMsg id $ fromJust str ++ " is"
       where
         str = getLiteralString t
-        isBashism = isJust str && fromJust str `elem` bashVars
+        isBashism = isJust str && isBashVariable (fromJust str)
     bashism t@(T_DollarBraced id token) = do
         mapM_ check expansion
-        when (var `elem` bashVars) $ warnMsg id $ var ++ " is"
+        when (isBashVariable var) $
+                    warnMsg id $ var ++ " is"
       where
         str = bracedString t
         var = getBracedReference str
@@ -640,10 +674,23 @@
     bashism (T_CoProc id _ _) =
         warnMsg id "coproc is"
 
+    bashism (T_Function id _ _ str _) | not (isVariableName str) =
+        warnMsg id "naming functions outside [a-zA-Z_][a-zA-Z0-9_]* is"
+
+    bashism (T_DollarExpansion id [x]) | isOnlyRedirection x =
+        warnMsg id "$(<file) to read files is"
+    bashism (T_Backticked id [x]) | isOnlyRedirection x =
+        warnMsg id "`<file` to read files is"
+
     bashism t@(T_SimpleCommand _ _ (cmd:arg:_))
         | t `isCommand` "echo" && "-" `isPrefixOf` argString =
-            unless ("--" `isPrefixOf` argString) $ -- echo "-------"
-                warnMsg (getId arg) "echo flags are"
+            unless ("--" `isPrefixOf` argString) $ -- echo "-----"
+                if isDash
+                then
+                    when (argString /= "-n") $
+                        warnMsg (getId arg) "echo flags besides -n"
+                else
+                    warnMsg (getId arg) "echo flags are"
       where argString = concat $ oversimplify arg
     bashism t@(T_SimpleCommand _ _ (cmd:arg:_))
         | t `isCommand` "exec" && "-" `isPrefixOf` concat (oversimplify arg) =
@@ -655,31 +702,47 @@
         let name = fromMaybe "" $ getCommandName t
             flags = getLeadingFlags t
         in do
-            when (name `elem` bashCommands) $ warnMsg id $ "'" ++ name ++ "' is"
+            when (name `elem` unsupportedCommands) $
+                warnMsg id $ "'" ++ name ++ "' is"
             potentially $ do
                 allowed <- Map.lookup name allowedFlags
-                (word, flag) <- listToMaybe $ filter (\x -> snd x `notElem` allowed) flags
+                (word, flag) <- listToMaybe $
+                    filter (\x -> (not . null . snd $ x) && snd x `notElem` allowed) flags
                 return . warnMsg (getId word) $ name ++ " -" ++ flag ++ " is"
 
             when (name == "source") $ warnMsg id "'source' in place of '.' is"
             when (name == "trap") $
                 let
                     check token = potentially $ do
-                        word <- liftM (map toLower) $ getLiteralString token
-                        guard $ word `elem` ["err", "debug", "return"]
-                        return $ warnMsg (getId token) $ "trapping " ++ word ++ " is"
+                        str <- getLiteralString token
+                        let upper = map toUpper str
+                        return $ do
+                            when (upper `elem` ["ERR", "DEBUG", "RETURN"]) $
+                                warnMsg (getId token) $ "trapping " ++ str ++ " is"
+                            when ("SIG" `isPrefixOf` upper) $
+                                warnMsg (getId token)
+                                    "prefixing signal names with 'SIG' is"
+                            when (not isDash && upper /= str) $
+                                warnMsg (getId token)
+                                    "using lower/mixed case for signal names is"
                 in
-                    mapM_ check (reverse rest)
+                    mapM_ check (drop 1 rest)
+
+            when (name == "printf") $ potentially $ do
+                format <- rest !!! 0  -- flags are covered by allowedFlags
+                let literal = onlyLiteralString format
+                guard $ "%q" `isInfixOf` literal
+                return $ warnMsg (getId format) "printf %q is"
       where
-        bashCommands = [
+        unsupportedCommands = [
             "let", "caller", "builtin", "complete", "compgen", "declare", "dirs", "disown",
-            "enable", "mapfile", "readarray", "pushd", "popd", "shopt", "suspend", "type",
+            "enable", "mapfile", "readarray", "pushd", "popd", "shopt", "suspend",
             "typeset"
-            ]
+            ] ++ if not isDash then ["local", "type"] else []
         allowedFlags = Map.fromList [
-            ("read", ["r"]),
+            ("read", if isDash then ["r", "p"] else ["r"]),
             ("ulimit", ["f"]),
-            ("echo", []),
+            ("printf", []),
             ("exec", [])
             ]
 
@@ -687,6 +750,7 @@
 
     varChars="_0-9a-zA-Z"
     expansion = let re = mkRegex in [
+        (re $ "^![" ++ varChars ++ "]", "indirect expansion is"),
         (re $ "^[" ++ varChars ++ "]+\\[.*\\]$", "array references are"),
         (re $ "^![" ++ varChars ++ "]+\\[[*@]]$", "array key expansion is"),
         (re $ "^![" ++ varChars ++ "]+[*@]$", "name matching prefixes are"),
@@ -694,10 +758,20 @@
         (re $ "^[" ++ varChars ++ "]+(\\[.*\\])?/", "string replacement is")
         ]
     bashVars = [
-        "RANDOM", "LINENO", "OSTYPE", "MACHTYPE", "HOSTTYPE", "HOSTNAME",
-        "DIRSTACK", "EUID", "UID", "SECONDS", "SHLVL", "PIPESTATUS", "SHELLOPTS"
+        "LINENO", "OSTYPE", "MACHTYPE", "HOSTTYPE", "HOSTNAME",
+        "DIRSTACK", "EUID", "UID", "SHLVL", "PIPESTATUS", "SHELLOPTS"
         ]
+    bashDynamicVars = [ "RANDOM", "SECONDS" ]
+    isBashVariable var =
+        var `elem` bashDynamicVars
+        || var `elem` bashVars && not (isAssigned var)
+    isAssigned var = any f (variableFlow params)
+      where
+        f x = case x of
+                Assignment (_, _, name, _) -> name == var
+                _ -> False
 
+
 prop_checkForInQuoted = verify checkForInQuoted "for f in \"$(ls)\"; do echo foo; done"
 prop_checkForInQuoted2 = verifyNot checkForInQuoted "for f in \"$@\"; do echo foo; done"
 prop_checkForInQuoted2a = verifyNot checkForInQuoted "for f in *.mp3; do echo foo; done"
@@ -822,6 +896,8 @@
 prop_checkRedirectToSame3 = verifyNot checkRedirectToSame "cat lol | sed -e 's/a/b/g' > foo.bar && mv foo.bar lol"
 prop_checkRedirectToSame4 = verifyNot checkRedirectToSame "foo /dev/null > /dev/null"
 prop_checkRedirectToSame5 = verifyNot checkRedirectToSame "foo > bar 2> bar"
+prop_checkRedirectToSame6 = verifyNot checkRedirectToSame "echo foo > foo"
+prop_checkRedirectToSame7 = verifyNot checkRedirectToSame "sed 's/foo/bar/g' file | sponge file"
 checkRedirectToSame params s@(T_Pipeline _ _ list) =
     mapM_ (\l -> (mapM_ (\x -> doAnalysis (checkOccurrences x) l) (getAllRedirs list))) list
   where
@@ -831,7 +907,8 @@
         when (exceptId /= newId
                 && x == y
                 && not (isOutput t && isOutput u)
-                && not (special t)) $ do
+                && not (special t)
+                && not (any isHarmlessCommand [t,u])) $ do
             addComment $ note newId
             addComment $ note exceptId
     checkOccurrences _ _ = return ()
@@ -854,6 +931,11 @@
                     T_DGREAT _ -> True
                     _ -> False
             _ -> False
+    isHarmlessCommand arg = fromMaybe False $ do
+        cmd <- getClosestCommand (parentMap params) arg
+        name <- getCommandBasename cmd
+        return $ name `elem` ["echo", "printf", "sponge"]
+
 checkRedirectToSame _ _ = return ()
 
 
@@ -901,7 +983,7 @@
             err (getId x) 2068
                 "Double quote array expansions to avoid re-splitting elements."
   where
-    -- Fixme: should detect whether the alterantive is quoted
+    -- Fixme: should detect whether the alternative is quoted
     isAlternative b@(T_DollarBraced _ t) = ":+" `isInfixOf` bracedString b
     isAlternative _ = False
 checkUnquotedDollarAt _ _ = return ()
@@ -944,24 +1026,26 @@
 prop_checkArrayWithoutIndex3 = verifyTree checkArrayWithoutIndex "coproc foo while true; do echo cow; done; echo $foo"
 prop_checkArrayWithoutIndex4 = verifyTree checkArrayWithoutIndex "coproc tail -f log; echo $COPROC"
 prop_checkArrayWithoutIndex5 = verifyTree checkArrayWithoutIndex "a[0]=foo; echo $a"
+prop_checkArrayWithoutIndex6 = verifyTree checkArrayWithoutIndex "echo $PIPESTATUS"
 checkArrayWithoutIndex params _ =
-    concat $ doVariableFlowAnalysis readF writeF Map.empty (variableFlow params)
+    concat $ doVariableFlowAnalysis readF writeF defaultMap (variableFlow params)
   where
+    defaultMap = Map.fromList $ map (\x -> (x,())) arrayVariables
     readF _ (T_DollarBraced id token) _ = do
         map <- get
         return . maybeToList $ do
             name <- getLiteralString token
-            assignment <- Map.lookup name map
+            assigned <- Map.lookup name map
             return [makeComment WarningC id 2128
                     "Expanding an array without an index only gives the first element."]
     readF _ _ _ = return []
 
     writeF _ t name (DataArray _) = do
-        modify (Map.insert name t)
+        modify (Map.insert name ())
         return []
     writeF _ expr name _ = do
         if isIndexed expr
-          then modify (Map.insert name expr)
+          then modify (Map.insert name ())
           else modify (Map.delete name)
         return []
 
@@ -1028,9 +1112,7 @@
     commandName = fromMaybe "" $ do
         cmd <- getClosestCommand parents t
         name <- getCommandBasename cmd
-        if name == "find"
-            then return $ getFindCommand cmd
-            else return name
+        return $ if name == "find" then getFindCommand cmd else name
 
     isProbablyOk =
             any isOkAssignment (take 3 $ getPath parents t)
@@ -1071,8 +1153,9 @@
 prop_checkUnquotedN = verify checkUnquotedN "if [ -n $foo ]; then echo cow; fi"
 prop_checkUnquotedN2 = verify checkUnquotedN "[ -n $cow ]"
 prop_checkUnquotedN3 = verifyNot checkUnquotedN "[[ -n $foo ]] && echo cow"
-checkUnquotedN _ (T_Condition _ SingleBracket (TC_Unary _ SingleBracket "-n" (T_NormalWord id [t]))) | willSplit t =
-       err id 2070 "Always true because you failed to quote. Use [[ ]] instead."
+prop_checkUnquotedN4 = verify checkUnquotedN "[ -n $cow -o -t 1 ]"
+checkUnquotedN _ (TC_Unary _ SingleBracket "-n" (T_NormalWord id [t])) | willSplit t =
+       err id 2070 "-n doesn't work with unquoted arguments. Quote or use [[ ]]."
 checkUnquotedN _ _ = return ()
 
 prop_checkNumberComparisons1 = verify checkNumberComparisons "[[ $foo < 3 ]]"
@@ -1082,9 +1165,9 @@
 prop_checkNumberComparisons5 = verify checkNumberComparisons "[[ $foo -le 2.72 ]]"
 prop_checkNumberComparisons6 = verify checkNumberComparisons "[[ 3.14 -eq $foo ]]"
 prop_checkNumberComparisons7 = verifyNot checkNumberComparisons "[[ 3.14 == $foo ]]"
-prop_checkNumberComparisons8 = verify checkNumberComparisons "[[ foo <= bar ]]"
+prop_checkNumberComparisons8 = verify checkNumberComparisons "[ foo <= bar ]"
 prop_checkNumberComparisons9 = verify checkNumberComparisons "[ foo \\>= bar ]"
-prop_checkNumberComparisons11= verify checkNumberComparisons "[[ $foo -eq 'N' ]]"
+prop_checkNumberComparisons11= verify checkNumberComparisons "[ $foo -eq 'N' ]"
 prop_checkNumberComparisons12= verify checkNumberComparisons "[ x$foo -gt x${N} ]"
 checkNumberComparisons params (TC_Binary id typ op lhs rhs) = do
     if isNum lhs && not (isNonNum rhs)
@@ -1106,15 +1189,15 @@
 
     when (op `elem` ["-lt", "-gt", "-le", "-ge", "-eq"]) $ do
         mapM_ checkDecimals [lhs, rhs]
-        checkStrings [lhs, rhs]
+        when (typ == SingleBracket) $
+            checkStrings [lhs, rhs]
 
   where
       isLtGt = flip elem ["<", "\\<", ">", "\\>"]
       isLeGe = flip elem ["<=", "\\<=", ">=", "\\>="]
 
-      supportsDecimals = (shellType params) == Ksh
       checkDecimals hs =
-        when (isFraction hs && not supportsDecimals) $
+        when (isFraction hs && not (hasFloatingPoint params)) $
             err (getId hs) 2072 decimalError
       decimalError = "Decimals are not supported. " ++
         "Either use integers only, or use bc or awk to compare."
@@ -1127,8 +1210,8 @@
         return . not . all numChar $ s
       numChar x = isDigit x || x `elem` "+-. "
 
-      stringError t = err (getId t) 2130 $
-        op ++ " is for integer comparisons. Use " ++ seqv op ++ " instead."
+      stringError t = err (getId t) 2170 $
+          "Numerical " ++ op ++ " does not dereference in [..]. Expand or use string operator."
 
       isNum t =
         case oversimplify t of
@@ -1204,16 +1287,26 @@
 
         otherwise -> return ()
 
-prop_checkQuotedCondRegex1 = verify checkQuotedCondRegex "[[ $foo =~ \"bar\" ]]"
-prop_checkQuotedCondRegex2 = verify checkQuotedCondRegex "[[ $foo =~ 'cow' ]]"
+prop_checkQuotedCondRegex1 = verify checkQuotedCondRegex "[[ $foo =~ \"bar.*\" ]]"
+prop_checkQuotedCondRegex2 = verify checkQuotedCondRegex "[[ $foo =~ '(cow|bar)' ]]"
 prop_checkQuotedCondRegex3 = verifyNot checkQuotedCondRegex "[[ $foo =~ $foo ]]"
+prop_checkQuotedCondRegex4 = verifyNot checkQuotedCondRegex "[[ $foo =~ \"bar\" ]]"
+prop_checkQuotedCondRegex5 = verifyNot checkQuotedCondRegex "[[ $foo =~ 'cow bar' ]]"
 checkQuotedCondRegex _ (TC_Binary _ _ "=~" _ rhs) =
     case rhs of
-        T_NormalWord id [T_DoubleQuoted _ _] -> error id
-        T_NormalWord id [T_SingleQuoted _ _] -> error id
+        T_NormalWord id [T_DoubleQuoted _ _] -> error rhs
+        T_NormalWord id [T_SingleQuoted _ _] -> error rhs
         _ -> return ()
   where
-    error id = err id 2076 "Don't quote rhs of =~, it'll match literally rather than as a regex."
+    error t =
+        unless (isConstantNonRe t) $
+            err (getId t) 2076
+                "Don't quote rhs of =~, it'll match literally rather than as a regex."
+    re = mkRegex "[][*.+()]"
+    hasMetachars s = s `matches` re
+    isConstantNonRe t = fromMaybe False $ do
+        s <- getLiteralString t
+        return . not $ hasMetachars s
 checkQuotedCondRegex _ _ = return ()
 
 prop_checkGlobbedRegex1 = verify checkGlobbedRegex "[[ $foo =~ *foo* ]]"
@@ -1229,16 +1322,23 @@
 
 
 prop_checkConstantIfs1 = verify checkConstantIfs "[[ foo != bar ]]"
-prop_checkConstantIfs2 = verify checkConstantIfs "[[ n -le 4 ]]"
-prop_checkConstantIfs3 = verify checkConstantIfs "[[ $n -le 4 && n -ge 2 ]]"
+prop_checkConstantIfs2a= verify checkConstantIfs "[ n -le 4 ]"
+prop_checkConstantIfs2b= verifyNot checkConstantIfs "[[ n -le 4 ]]"
+prop_checkConstantIfs3 = verify checkConstantIfs "[[ $n -le 4 && n != 2 ]]"
 prop_checkConstantIfs4 = verifyNot checkConstantIfs "[[ $n -le 3 ]]"
 prop_checkConstantIfs5 = verifyNot checkConstantIfs "[[ $n -le $n ]]"
-checkConstantIfs _ (TC_Binary id typ op lhs rhs)
-    | op `elem` [ "==", "!=", "<=", ">=", "-eq", "-ne", "-lt", "-le", "-gt", "-ge", "=~", ">", "<", "="] =
-        when (isJust lLit && isJust rLit) $ warn id 2050 "This expression is constant. Did you forget the $ on a variable?"
-    where
-        lLit = getLiteralString lhs
-        rLit = getLiteralString rhs
+prop_checkConstantIfs6 = verifyNot checkConstantIfs "[[ a -ot b ]]"
+prop_checkConstantIfs7 = verifyNot checkConstantIfs "[ a -nt b ]"
+checkConstantIfs _ (TC_Binary id typ op lhs rhs) | not isDynamic =
+    when (isJust lLit && isJust rLit) $
+        warn id 2050 "This expression is constant. Did you forget the $ on a variable?"
+  where
+    lLit = getLiteralString lhs
+    rLit = getLiteralString rhs
+    isDynamic =
+        op `elem` [ "-lt", "-gt", "-le", "-ge", "-eq", "-ne" ]
+            && typ == DoubleBracket
+        || op `elem` [ "-nt", "-ot", "-ef"]
 checkConstantIfs _ _ = return ()
 
 prop_checkLiteralBreakingTest = verify checkLiteralBreakingTest "[[ a==$foo ]]"
@@ -1249,13 +1349,13 @@
 prop_checkLiteralBreakingTest6 = verify checkLiteralBreakingTest "[ -z $(true)z ]"
 prop_checkLiteralBreakingTest7 = verifyNot checkLiteralBreakingTest "[ -z $(true) ]"
 prop_checkLiteralBreakingTest8 = verifyNot checkLiteralBreakingTest "[ $(true)$(true) ]"
+prop_checkLiteralBreakingTest10 = verify checkLiteralBreakingTest "[ -z foo ]"
 checkLiteralBreakingTest _ t = potentially $
         case t of
             (TC_Noary _ _ w@(T_NormalWord _ l)) -> do
-                guard . not $ isConstant w
+                guard . not $ isConstant w -- Covered by SC2078
                 comparisonWarning l `mplus` tautologyWarning w "Argument to implicit -n is always true due to literal strings."
-            (TC_Unary _ _ op w@(T_NormalWord _ l)) -> do
-                guard $ not $ isConstant w
+            (TC_Unary _ _ op w@(T_NormalWord _ l)) ->
                 case op of
                     "-n" -> tautologyWarning w "Argument to -n is always true due to literal strings."
                     "-z" -> tautologyWarning w "Argument to -z is always false due to literal strings."
@@ -1312,7 +1412,8 @@
 checkBraceExpansionVars _ _ = return ()
 
 prop_checkForDecimals = verify checkForDecimals "((3.14*c))"
-checkForDecimals _ t@(TA_Expansion id _) = potentially $ do
+checkForDecimals params t@(TA_Expansion id _) = potentially $ do
+    guard $ not (hasFloatingPoint params)
     str <- getLiteralString t
     first <- str !!! 0
     guard $ isDigit first && '.' `elem` str
@@ -1562,17 +1663,21 @@
 
 -- Check whether a word is entirely output from a single command
 tokenIsJustCommandOutput t = case t of
-    T_NormalWord id [T_DollarExpansion _ _] -> True
-    T_NormalWord id [T_DoubleQuoted _ [T_DollarExpansion _ _]] -> True
-    T_NormalWord id [T_Backticked _ _] -> True
-    T_NormalWord id [T_DoubleQuoted _ [T_Backticked _ _]] -> True
+    T_NormalWord id [T_DollarExpansion _ cmds] -> check cmds
+    T_NormalWord id [T_DoubleQuoted _ [T_DollarExpansion _ cmds]] -> check cmds
+    T_NormalWord id [T_Backticked _ cmds] -> check cmds
+    T_NormalWord id [T_DoubleQuoted _ [T_Backticked _ cmds]] -> check cmds
     _ -> False
+  where
+    check [x] = not $ isOnlyRedirection x
+    check _ = False
 
 prop_checkUuoeCmd1 = verify checkUuoeCmd "echo $(date)"
 prop_checkUuoeCmd2 = verify checkUuoeCmd "echo `date`"
 prop_checkUuoeCmd3 = verify checkUuoeCmd "echo \"$(date)\""
 prop_checkUuoeCmd4 = verify checkUuoeCmd "echo \"`date`\""
 prop_checkUuoeCmd5 = verifyNot checkUuoeCmd "echo \"The time is $(date)\""
+prop_checkUuoeCmd6 = verifyNot checkUuoeCmd "echo \"$(<file)\""
 checkUuoeCmd _ = checkUnqualifiedCommand "echo" (const f) where
     msg id = style id 2005 "Useless echo? Instead of 'echo $(cmd)', just use 'cmd'."
     f [token] = when (tokenIsJustCommandOutput token) $ msg (getId token)
@@ -1586,6 +1691,7 @@
 prop_checkUuoeVar6 = verifyNot checkUuoeVar "foo \"$(echo files: *.png)\""
 prop_checkUuoeVar7 = verifyNot checkUuoeVar "foo $(echo $(bar))" -- covered by 2005
 prop_checkUuoeVar8 = verifyNot checkUuoeVar "#!/bin/sh\nz=$(echo)"
+prop_checkUuoeVar9 = verify checkUuoeVar "foo $(echo $(<file))"
 checkUuoeVar _ p =
     case p of
         T_Backticked id [cmd] -> check id cmd
@@ -1723,8 +1829,22 @@
 prop_checkTestRedirects1 = verify checkTestRedirects "test 3 > 1"
 prop_checkTestRedirects2 = verifyNot checkTestRedirects "test 3 \\> 1"
 prop_checkTestRedirects3 = verify checkTestRedirects "/usr/bin/test $var > $foo"
-checkTestRedirects _ (T_Redirecting id redirs@(redir:_) cmd) | cmd `isCommand` "test" =
-    warn (getId redir) 2065 "This is interpretted as a shell file redirection, not a comparison."
+prop_checkTestRedirects4 = verifyNot checkTestRedirects "test 1 -eq 2 2> file"
+checkTestRedirects _ (T_Redirecting id redirs cmd) | cmd `isCommand` "test" =
+    mapM_ check redirs
+  where
+    check t =
+        when (suspicious t) $
+            warn (getId t) 2065 "This is interpreted as a shell file redirection, not a comparison."
+    suspicious t = -- Ignore redirections of stderr because these are valid for squashing e.g. int errors,
+        case t of  -- and >> and similar redirections because these are probably not comparisons.
+            T_FdRedirect _ fd (T_IoFile _ op _) -> fd /= "2" && isComparison op
+            otherwise -> False
+    isComparison t =
+        case t of
+            T_Greater _ -> True
+            T_Less _ -> True
+            otherwise -> False
 checkTestRedirects _ _ = return ()
 
 prop_checkSudoRedirect1 = verify checkSudoRedirect "sudo echo 3 > /proc/file"
@@ -1808,7 +1928,8 @@
 
 prop_checkBackticks1 = verify checkBackticks "echo `foo`"
 prop_checkBackticks2 = verifyNot checkBackticks "echo $(foo)"
-checkBackticks _ (T_Backticked id _) =
+prop_checkBackticks3 = verifyNot checkBackticks "echo `#inlined comment` foo"
+checkBackticks _ (T_Backticked id list) | not (null list) =
     style id 2006 "Use $(..) instead of legacy `..`."
 checkBackticks _ _ = return ()
 
@@ -2096,6 +2217,7 @@
     lastCreatesSubshell =
         case shell of
             Bash -> True
+            Dash -> True
             Sh -> True
             Ksh -> False
 
@@ -2144,7 +2266,7 @@
         "export" -> if "f" `elem` flags
             then []
             else concatMap getReference rest
-        "declare" -> if "x" `elem` flags
+        "declare" -> if any (`elem` flags) ["x", "p"]
             then concatMap getReference rest
             else []
         "readonly" -> concatMap getReference rest
@@ -2305,6 +2427,10 @@
 
         TC_Unary id _ "-v" token -> getIfReference t token
         TC_Unary id _ "-R" token -> getIfReference t token
+        TC_Binary id DoubleBracket op lhs rhs ->
+            if isDereferencing op
+            then concatMap (getIfReference t) [lhs, rhs]
+            else []
 
         t@(T_FdRedirect _ ('{':var) op) -> -- {foo}>&- references and closes foo
             [(t, t, takeWhile (/= '}') var) | isClosingFileOp op]
@@ -2330,6 +2456,8 @@
             when (isDigit $ head str) $ fail "is a number"
             return (context, token, getBracedReference str)
 
+    isDereferencing = (`elem` ["-eq", "-ne", "-lt", "-le", "-gt", "-ge"])
+
 -- Try to get referenced variables from a literal string like "$foo"
 -- Ignores tons of cases like arithmetic evaluation and array indices.
 prop_getVariablesFromLiteral1 =
@@ -2445,6 +2573,8 @@
 prop_checkSpacefulness28= verifyNotTree checkSpacefulness "exec {n}>&1; echo $n"
 prop_checkSpacefulness29= verifyNotTree checkSpacefulness "n=$(stuff); exec {n}>&-;"
 prop_checkSpacefulness30= verifyTree checkSpacefulness "file='foo bar'; echo foo > $file;"
+prop_checkSpacefulness31= verifyNotTree checkSpacefulness "echo \"`echo \\\"$1\\\"`\""
+prop_checkSpacefulness32= verifyNotTree checkSpacefulness "var=$1; [ -v var ]"
 
 checkSpacefulness params t =
     doVariableFlowAnalysis readF writeF (Map.fromList defaults) (variableFlow params)
@@ -2461,7 +2591,7 @@
     readF _ token name = do
         spaced <- hasSpaces name
         return [makeComment InfoC (getId token) 2086 warning |
-                  spaced
+                  isExpansion token && spaced
                   && not (isArrayExpansion token) -- There's another warning for this
                   && not (isCounting token)
                   && not (isQuoteFree parents token)
@@ -2483,6 +2613,11 @@
 
     parents = parentMap params
 
+    isExpansion t =
+        case t of
+            (T_DollarBraced _ _ ) -> True
+            _ -> False
+
     isCounting (T_DollarBraced id token) =
         case concat $ oversimplify token of
             '#':_ -> True
@@ -2525,6 +2660,7 @@
 prop_checkQuotesInLiterals6a= verifyNotTree checkQuotesInLiterals "param='my\\ file'; cmd=\"rm ${#param}\"; $cmd"
 prop_checkQuotesInLiterals7 = verifyTree checkQuotesInLiterals "param='my\\ file'; rm $param"
 prop_checkQuotesInLiterals8 = verifyTree checkQuotesInLiterals "param=\"/foo/'bar baz'/etc\"; rm $param"
+prop_checkQuotesInLiterals9 = verifyNotTree checkQuotesInLiterals "param=\"/foo/'bar baz'/etc\"; rm ${#param}"
 checkQuotesInLiterals params t =
     doVariableFlowAnalysis readF writeF Map.empty (variableFlow params)
   where
@@ -2556,12 +2692,18 @@
         then return $ getId t
         else Nothing
 
+    squashesQuotes t =
+        case t of
+            T_DollarBraced id _ -> "#" `isPrefixOf` bracedString t
+            otherwise -> False
+
     readF _ expr name = do
         assignment <- getQuotes name
         return
           (if isJust assignment
               && not (isParamTo parents "eval" expr)
               && not (isQuoteFree parents expr)
+              && not (squashesQuotes expr)
               then [
                   makeComment WarningC (fromJust assignment) 2089
                       "Quotes/backslashes will be treated literally. Use an array.",
@@ -2643,6 +2785,9 @@
 prop_checkUnused24= verifyNotTree checkUnusedAssignments "mapfile -C a b; echo ${b[@]}"
 prop_checkUnused25= verifyNotTree checkUnusedAssignments "readarray foo; echo ${foo[@]}"
 prop_checkUnused26= verifyNotTree checkUnusedAssignments "declare -F foo"
+prop_checkUnused27= verifyTree checkUnusedAssignments "var=3; [ var -eq 3 ]"
+prop_checkUnused28= verifyNotTree checkUnusedAssignments "var=3; [[ var -eq 3 ]]"
+prop_checkUnused29= verifyNotTree checkUnusedAssignments "var=(a b); declare -p var"
 checkUnusedAssignments params t = execWriter (mapM_ warnFor unused)
   where
     flow = variableFlow params
@@ -2753,7 +2898,7 @@
     isGuarded _ = False
 
     match var candidate =
-        if var /= candidate && (map toLower var) == (map toLower candidate)
+        if var /= candidate && map toLower var == map toLower candidate
         then 1
         else dist var candidate
 
@@ -2765,9 +2910,7 @@
     mapM_ check $ takeWhile (not . isEndOfArgs) args
   where
     check v@(T_NormalWord _ (T_Glob id s:_)) | s == "*" || s == "?" =
-        info id 2035 $
-            "Use ./" ++ concat (oversimplify v)
-                ++ " so names with dashes won't become options."
+        info id 2035 "Use ./*glob* or -- *glob* so names with dashes won't become options."
     check _ = return ()
 
     isEndOfArgs t =
@@ -2923,11 +3066,18 @@
     subshellType t = case leadType (shellType params) (parentMap params) t of
         NoneScope -> Nothing
         SubshellScope str -> return str
-    isFunction t = case t of T_Function {} -> True; _ -> False
     relevant t = isLoop t || isFunction t || isJust (subshellType t)
 checkLoopKeywordScope _ _ = return ()
 
 
+prop_checkLocalScope1 = verify checkLocalScope "local foo=3"
+prop_checkLocalScope2 = verifyNot checkLocalScope "f() { local foo=3; }"
+checkLocalScope params t | t `isCommand` "local" && not (isInFunction t) =
+    err (getId t) 2168 "'local' is only valid in functions."
+  where
+    isInFunction t = any isFunction $ getPath (parentMap params) t
+checkLocalScope _ _ = return ()
+
 prop_checkFunctionDeclarations1 = verify checkFunctionDeclarations "#!/bin/ksh\nfunction foo() { command foo --lol \"$@\"; }"
 prop_checkFunctionDeclarations2 = verify checkFunctionDeclarations "#!/bin/dash\nfunction foo { lol; }"
 prop_checkFunctionDeclarations3 = verifyNot checkFunctionDeclarations "foo() { echo bar; }"
@@ -2938,7 +3088,11 @@
         Ksh ->
             when (hasKeyword && hasParens) $
                 err id 2111 "ksh does not allow 'function' keyword and '()' at the same time."
-        Sh -> do
+        Dash -> forSh
+        Sh   -> forSh
+
+    where
+        forSh = do
             when (hasKeyword && hasParens) $
                 warn id 2112 "'function' keyword is non-standard. Delete it."
             when (hasKeyword && not hasParens) $
@@ -3417,6 +3571,8 @@
 prop_checkUncheckedCd4 = verifyNotTree checkUncheckedCd "if cd foo; then rm foo; fi"
 prop_checkUncheckedCd5 = verifyTree checkUncheckedCd "if true; then cd foo; fi"
 prop_checkUncheckedCd6 = verifyNotTree checkUncheckedCd "cd .."
+prop_checkUncheckedCd7 = verifyNotTree checkUncheckedCd "#!/bin/bash -e\ncd foo\nrm bar"
+prop_checkUncheckedCd8 = verifyNotTree checkUncheckedCd "set -o errexit; cd foo; rm bar"
 checkUncheckedCd params root =
     if hasSetE then [] else execWriter $ doAnalysis checkElement root
   where
@@ -3430,9 +3586,12 @@
     hasSetE = isNothing $ doAnalysis (guard . not . isSetE) root
     isSetE t =
         case t of
-            T_SimpleCommand {} ->
-                t `isUnqualifiedCommand` "set" && "e" `elem` map snd (getAllFlags t)
+            T_Script _ str _ -> str `matches` re
+            T_SimpleCommand {}  ->
+                t `isUnqualifiedCommand` "set" &&
+                    ("errexit" `elem` oversimplify t || "e" `elem` map snd (getAllFlags t))
             _ -> False
+    re = mkRegex "[[:space:]]-[^-]*e"
 
 prop_checkLoopVariableReassignment1 = verify checkLoopVariableReassignment "for i in *; do for i in *.bar; do true; done; done"
 prop_checkLoopVariableReassignment2 = verify checkLoopVariableReassignment "for i in *; do for((i=0; i<3; i++)); do true; done; done"
@@ -3448,7 +3607,7 @@
         next <- listToMaybe $ filter (\x -> loopVariable x == Just str) path
         return $ do
             warn (getId token) 2165 "This nested loop overrides the index variable of its parent."
-            warn (getId next)  2165 "This parent loop has its index variable overridden."
+            warn (getId next)  2167 "This parent loop has its index variable overridden."
     path = drop 1 $ getPath (parentMap params) token
     loopVariable :: Token -> Maybe String
     loopVariable t =
@@ -3460,6 +3619,91 @@
                         (TA_Expansion _ [T_Literal _ var]) _])
                             _ _ _ -> return var
             _ -> fail "not loop"
+
+prop_checkTrailingBracket1 = verify checkTrailingBracket "if -z n ]]; then true; fi "
+prop_checkTrailingBracket2 = verifyNot checkTrailingBracket "if [[ -z n ]]; then true; fi "
+prop_checkTrailingBracket3 = verify checkTrailingBracket "a || b ] && thing"
+prop_checkTrailingBracket4 = verifyNot checkTrailingBracket "run [ foo ]"
+prop_checkTrailingBracket5 = verifyNot checkTrailingBracket "run bar ']'"
+checkTrailingBracket _ token =
+    case token of
+        T_SimpleCommand _ _ tokens@(_:_) -> check (last tokens) token
+        otherwise -> return ()
+  where
+    check t command =
+        case t of
+            T_NormalWord id [T_Literal _ str] -> potentially $ do
+                guard $ str `elem` [ "]]", "]" ]
+                let opposite = invert str
+                    parameters = oversimplify command
+                guard $ opposite `notElem` parameters
+                return $ warn id 2171 $
+                    "Found trailing " ++ str ++ " outside test. Missing " ++ opposite ++ "?"
+            otherwise -> return ()
+    invert s =
+        case s of
+            "]]" -> "[["
+            "]" -> "["
+            x -> x
+
+prop_checkNonportableSignals1 = verify checkNonportableSignals "trap f 8"
+prop_checkNonportableSignals2 = verifyNot checkNonportableSignals "trap f 0"
+prop_checkNonportableSignals3 = verifyNot checkNonportableSignals "trap f 14"
+prop_checkNonportableSignals4 = verify checkNonportableSignals "trap f SIGKILL"
+prop_checkNonportableSignals5 = verify checkNonportableSignals "trap f 9"
+prop_checkNonportableSignals6 = verify checkNonportableSignals "trap f stop"
+checkNonportableSignals _ = checkUnqualifiedCommand "trap" (const f)
+  where
+    f = mapM_ check
+    check param = potentially $ do
+        str <- getLiteralString param
+        let id = getId param
+        return $ sequence_ $ mapMaybe (\f -> f id str) [
+            checkNumeric,
+            checkUntrappable
+            ]
+
+    checkNumeric id str = do
+        guard $ not (null str)
+        guard $ all isDigit str
+        guard $ str /= "0" -- POSIX exit trap
+        guard $ str `notElem` ["1", "2", "3", "6", "9", "14", "15" ] -- XSI
+        return $ warn id 2172
+            "Trapping signals by number is not well defined. Prefer signal names."
+
+    checkUntrappable id str = do
+        guard $ map toLower str `elem` ["kill", "9", "sigkill", "stop", "sigstop"]
+        return $ err id 2173
+            "SIGKILL/SIGSTOP can not be trapped."
+
+prop_checkMkdirDashPM0 = verify checkMkdirDashPM "mkdir -p -m 0755 a/b"
+prop_checkMkdirDashPM1 = verify checkMkdirDashPM "mkdir -pm 0755 $dir"
+prop_checkMkdirDashPM2 = verify checkMkdirDashPM "mkdir -vpm 0755 a/b"
+prop_checkMkdirDashPM3 = verify checkMkdirDashPM "mkdir -pm 0755 -v a/b"
+prop_checkMkdirDashPM4 = verify checkMkdirDashPM "mkdir --parents --mode=0755 a/b"
+prop_checkMkdirDashPM5 = verify checkMkdirDashPM "mkdir --parents --mode 0755 a/b"
+prop_checkMkdirDashPM6 = verify checkMkdirDashPM "mkdir -p --mode=0755 a/b"
+prop_checkMkdirDashPM7 = verify checkMkdirDashPM "mkdir --parents -m 0755 a/b"
+prop_checkMkdirDashPM8 = verifyNot checkMkdirDashPM "mkdir -p a/b"
+prop_checkMkdirDashPM9 = verifyNot checkMkdirDashPM "mkdir -m 0755 a/b"
+prop_checkMkdirDashPM10 = verifyNot checkMkdirDashPM "mkdir a/b"
+prop_checkMkdirDashPM11 = verifyNot checkMkdirDashPM "mkdir --parents a/b"
+prop_checkMkdirDashPM12 = verifyNot checkMkdirDashPM "mkdir --mode=0755 a/b"
+prop_checkMkdirDashPM13 = verifyNot checkMkdirDashPM "mkdir_func -pm 0755 a/b"
+prop_checkMkdirDashPM14 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 singlelevel"
+checkMkdirDashPM _ t@(T_SimpleCommand _ _ args) = potentially $ do
+    name <- getCommandName t
+    guard $ name == "mkdir"
+    dashP <- find ((\f -> f == "p" || f == "parents") . snd) flags
+    dashM <- find ((\f -> f == "m" || f == "mode") . snd) flags
+    guard $ any couldHaveSubdirs (drop 1 args) -- mkdir -pm 0700 dir  is fine, but dir/subdir is not.
+    return $ warn (getId $ fst dashM) 2174 "When used with -p, -m only applies to the deepest directory."
+  where
+    flags = getAllFlags t
+    couldHaveSubdirs t = fromMaybe True $ do
+        name <- getLiteralString t
+        return $ '/' `elem` name
+checkMkdirDashPM _ _ = return ()
 
 return []
 runTests =  $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
diff --git a/ShellCheck/Checker.hs b/ShellCheck/Checker.hs
--- a/ShellCheck/Checker.hs
+++ b/ShellCheck/Checker.hs
@@ -147,6 +147,9 @@
 prop_cantSourceDynamic =
     [1090] == checkWithIncludes [("lib", "")] ". \"$1\""
 
+prop_cantSourceDynamic2 =
+    [1090] == checkWithIncludes [("lib", "")] "source ~/foo"
+
 prop_canSourceDynamicWhenRedirected =
     null $ checkWithIncludes [("lib", "")] "#shellcheck source=lib\n. \"$1\""
 
diff --git a/ShellCheck/Data.hs b/ShellCheck/Data.hs
--- a/ShellCheck/Data.hs
+++ b/ShellCheck/Data.hs
@@ -25,13 +25,14 @@
     "FUNCNEST", "GLOBIGNORE", "HISTCONTROL", "HISTFILE", "HISTFILESIZE",
     "HISTIGNORE", "HISTSIZE", "HISTTIMEFORMAT", "HOME", "HOSTFILE", "IFS",
     "IGNOREEOF", "INPUTRC", "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
-    "LC_MESSAGES", "LC_NUMERIC", "LINES", "MAIL", "MAILCHECK", "MAILPATH",
-    "OPTERR", "PATH", "POSIXLY_CORRECT", "PROMPT_COMMAND",
-    "PROMPT_DIRTRIM", "PS1", "PS2", "PS3", "PS4", "SHELL", "TIMEFORMAT",
-    "TMOUT", "TMPDIR", "auto_resume", "histchars", "COPROC",
+    "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "LINES", "MAIL",
+    "MAILCHECK", "MAILPATH", "OPTERR", "PATH", "POSIXLY_CORRECT",
+    "PROMPT_COMMAND", "PROMPT_DIRTRIM", "PS1", "PS2", "PS3", "PS4", "SHELL",
+    "TIMEFORMAT", "TMOUT", "TMPDIR", "auto_resume", "histchars", "COPROC",
 
     -- Other
-    "USER", "TZ", "TERM"
+    "USER", "TZ", "TERM", "LOGNAME", "LD_LIBRARY_PATH", "LANGUAGE", "DISPLAY",
+    "HOSTNAME", "KRB5CCNAME", "XAUTHORITY"
   ]
 
 variablesWithoutSpaces = [
@@ -41,6 +42,12 @@
     "COLUMNS", "HISTFILESIZE", "HISTSIZE", "LINES"
   ]
 
+arrayVariables = [
+    "BASH_ALIASES", "BASH_ARGC", "BASH_ARGV", "BASH_CMDS", "BASH_LINENO",
+    "BASH_REMATCH", "BASH_SOURCE", "BASH_VERSINFO", "COMP_WORDS", "COPROC",
+    "DIRSTACK", "FUNCNAME", "GROUPS", "MAPFILE", "PIPESTATUS", "COMPREPLY"
+  ]
+
 commonCommands = [
     "admin", "alias", "ar", "asa", "at", "awk", "basename", "batch",
     "bc", "bg", "break", "c99", "cal", "cat", "cd", "cflow", "chgrp",
@@ -76,13 +83,12 @@
   ]
 
 shellForExecutable :: String -> Maybe Shell
-shellForExecutable "sh" = return Sh
-shellForExecutable "ash" = return Sh
-shellForExecutable "dash" = return Sh
-
-shellForExecutable "ksh" = return Ksh
-shellForExecutable "ksh88" = return Ksh
-shellForExecutable "ksh93" = return Ksh
-
-shellForExecutable "bash" = return Bash
-shellForExecutable _ = Nothing
+shellForExecutable name =
+    case name of
+        "sh"    -> return Sh
+        "bash"  -> return Bash
+        "dash"  -> return Dash
+        "ksh"   -> return Ksh
+        "ksh88" -> return Ksh
+        "ksh93" -> return Ksh
+        otherwise -> Nothing
diff --git a/ShellCheck/Formatter/TTY.hs b/ShellCheck/Formatter/TTY.hs
--- a/ShellCheck/Formatter/TTY.hs
+++ b/ShellCheck/Formatter/TTY.hs
@@ -27,15 +27,15 @@
 import System.Info
 import System.IO
 
-format :: IO Formatter
-format = return Formatter {
+format :: FormatterOptions -> IO Formatter
+format options = return Formatter {
     header = return (),
     footer = return (),
-    onFailure = outputError,
-    onResult = outputResult
+    onFailure = outputError options,
+    onResult = outputResult options
 }
 
-colorForLevel level = 
+colorForLevel level =
     case level of
         "error"   -> 31 -- red
         "warning" -> 33 -- yellow
@@ -44,13 +44,13 @@
         "message" -> 1 -- bold
         "source"  -> 0 -- none
         otherwise -> 0 -- none
-    
-outputError file error = do
-    color <- getColorFunc
+
+outputError options file error = do
+    color <- getColorFunc $ foColorOption options
     hPutStrLn stderr $ color "error" $ file ++ ": " ++ error
 
-outputResult result contents = do
-    color <- getColorFunc
+outputResult options result contents = do
+    color <- getColorFunc $ foColorOption options
     let comments = crComments result
     let fileLines = lines contents
     let lineCount = fromIntegral $ length fileLines
@@ -75,10 +75,15 @@
 
 code code = "SC" ++ show code
 
-getColorFunc = do
+getColorFunc colorOption = do
     term <- hIsTerminalDevice stdout
     let windows = "mingw" `isPrefixOf` os
-    return $ if term && not windows then colorComment else const id
+    let isUsableTty = term && not windows
+    let useColor = case colorOption of
+                       ColorAlways -> True
+                       ColorNever -> False
+                       ColorAuto -> isUsableTty
+    return $ if useColor then colorComment else const id
   where
     colorComment level comment =
         ansi (colorForLevel level) ++ comment ++ clear
diff --git a/ShellCheck/Interface.hs b/ShellCheck/Interface.hs
--- a/ShellCheck/Interface.hs
+++ b/ShellCheck/Interface.hs
@@ -72,8 +72,15 @@
     arComments :: [TokenComment]
 }
 
+
+-- Formatter options
+data FormatterOptions = FormatterOptions {
+    foColorOption :: ColorOption
+}
+
+
 -- Supporting data types
-data Shell = Ksh | Sh | Bash deriving (Show, Eq)
+data Shell = Ksh | Sh | Bash | Dash deriving (Show, Eq)
 data ExecutionMode = Executed | Sourced deriving (Show, Eq)
 
 type ErrorMessage = String
@@ -89,6 +96,12 @@
 data Comment = Comment Severity Code String deriving (Show, Eq)
 data PositionedComment = PositionedComment Position Comment deriving (Show, Eq)
 data TokenComment = TokenComment Id Comment deriving (Show, Eq)
+
+data ColorOption =
+    ColorAuto
+    | ColorAlways
+    | ColorNever
+  deriving (Ord, Eq, Show)
 
 -- For testing
 mockedSystemInterface :: [(String, String)] -> SystemInterface Identity
diff --git a/ShellCheck/Parser.hs b/ShellCheck/Parser.hs
--- a/ShellCheck/Parser.hs
+++ b/ShellCheck/Parser.hs
@@ -359,19 +359,36 @@
     readCondBinaryOp = try $ do
         optional guardArithmetic
         id <- getNextId
-        op <- choice (map tryOp ["==", "!=", "<=", ">=", "=~", ">", "<", "=", "\\<=", "\\>=", "\\<", "\\>"]) <|> otherOp
+        op <- getOp
         spacingOrLf
         return op
       where
-        tryOp s = try $ do
-            id <- getNextId
-            string s
-            return $ TC_Binary id typ s
-        otherOp = try $ do
+        flaglessOps = [ "==", "!=", "<=", ">=", "=~", ">", "<", "=" ]
+
+        getOp = do
             id <- getNextId
+            op <- anyQuotedOp <|> anyEscapedOp <|> anyOp
+            return $ TC_Binary id typ op
+
+        -- hacks to read quoted operators without having to read a shell word
+        anyEscapedOp = try $ do
+            char '\\'
+            escaped <$> anyOp
+        anyQuotedOp = try $ do
+            c <- oneOf "'\""
+            s <- anyOp
+            char c
+            return s
+
+        anyOp = flagOp <|> flaglessOp <|> fail
+                    "Expected comparison operator (don't wrap commands in []/[[]])"
+        flagOp = try $ do
             s <- readOp
             when (s == "-a" || s == "-o") $ fail "Unexpected operator"
-            return $ TC_Binary id typ s
+            return s
+        flaglessOp =
+            choice $ map (try . string) flaglessOps
+        escaped s = if any (`elem` s) "<>" then '\\':s else s
 
     guardArithmetic = do
         try . lookAhead $ disregard (oneOf "+*/%") <|> disregard (string "- ")
@@ -394,10 +411,17 @@
         return $ TC_Unary id typ s
 
     readOp = try $ do
-        char '-'
-        s <- many1 letter
+        char '-' <|> weirdDash
+        s <- many1 letter <|> fail "Expected a test operator"
         return ('-':s)
 
+    weirdDash = do
+        pos <- getPosition
+        oneOf "\x058A\x05BE\x2010\x2011\x2012\x2013\x2014\x2015\xFE63\xFF0D"
+        parseProblemAt pos ErrorC 1100
+            "This is a unicode dash. Delete and retype as ASCII minus."
+        return '-'
+
     readCondWord = do
         notFollowedBy2 (try (spacing >> string "]"))
         x <- readNormalWord
@@ -429,6 +453,7 @@
         return $ TC_Or id typ x
 
     readAndOrOp op requiresSpacing = do
+        optional $ lookAhead weirdDash
         x <- string op
         condSpacing requiresSpacing
         return x
@@ -598,7 +623,7 @@
             readDoubleQuoted,
             readNormalDollar,
             readBraced,
-            readBackTicked,
+            readUnquotedBackTicked,
             readNormalLiteral "+-*/=%^,]?:"
             ]
         spacing
@@ -706,6 +731,10 @@
 prop_readCondition11= isOk readCondition "[[ a == b ||\n c == d ]]"
 prop_readCondition12= isWarning readCondition "[ a == b \n -o c == d ]"
 prop_readCondition13= isOk readCondition "[[ foo =~ ^fo{1,3}$ ]]"
+prop_readCondition14= isOk readCondition "[ foo '>' bar ]"
+prop_readCondition15= isOk readCondition "[ foo \">=\" bar ]"
+prop_readCondition16= isOk readCondition "[ foo \\< bar ]"
+prop_readCondition17= isOk readCondition "[[ ${file::1} = [-.\\|/\\\\] ]]"
 readCondition = called "test expression" $ do
     opos <- getPosition
     id <- getNextId
@@ -725,7 +754,7 @@
     condition <- readConditionContents single
 
     cpos <- getPosition
-    close <- try (string "]]") <|> string "]" <|> fail "Expected test to end here"
+    close <- try (string "]]") <|> string "]" <|> fail "Expected test to end here (don't wrap commands in []/[[]])"
     when (open == "[[" && close /= "]]") $ parseProblemAt cpos ErrorC 1033 "Did you mean ]] ?"
     when (open == "[" && close /= "]" ) $ parseProblemAt opos ErrorC 1034 "Did you mean [[ ?"
     spacing
@@ -805,7 +834,7 @@
         readGlob,
         readNormalDollar,
         readBraced,
-        readBackTicked,
+        readUnquotedBackTicked,
         readProcSub,
         readNormalLiteral end,
         readLiteralCurlyBraces
@@ -841,7 +870,7 @@
     list <- many readDollarBracedPart
     return $ T_NormalWord id list
 
-readDollarBracedPart = readSingleQuoted <|> readDoubleQuoted <|> readExtglob <|> readNormalDollar <|> readBackTicked <|> readDollarBracedLiteral
+readDollarBracedPart = readSingleQuoted <|> readDoubleQuoted <|> readExtglob <|> readNormalDollar <|> readUnquotedBackTicked <|> readDollarBracedLiteral
 
 readDollarBracedLiteral = do
     id <- getNextId
@@ -899,15 +928,18 @@
     readSingleEscaped
     <|> many1 (noneOf "'\\\x2018\x2019")
 
-prop_readBackTicked = isOk readBackTicked "`ls *.mp3`"
-prop_readBackTicked2 = isOk readBackTicked "`grep \"\\\"\"`"
-prop_readBackTicked3 = isWarning readBackTicked "´grep \"\\\"\"´"
-prop_readBackTicked4 = isOk readBackTicked "`echo foo\necho bar`"
+
+prop_readBackTicked = isOk (readBackTicked False) "`ls *.mp3`"
+prop_readBackTicked2 = isOk (readBackTicked False) "`grep \"\\\"\"`"
+prop_readBackTicked3 = isWarning (readBackTicked False) "´grep \"\\\"\"´"
+prop_readBackTicked4 = isOk readSimpleCommand "`echo foo\necho bar`"
 prop_readBackTicked5 = isOk readSimpleCommand "echo `foo`bar"
 prop_readBackTicked6 = isWarning readSimpleCommand "echo `foo\necho `bar"
 prop_readBackTicked7 = isOk readSimpleCommand "`#inline comment`"
 prop_readBackTicked8 = isOk readSimpleCommand "echo `#comment` \\\nbar baz"
-readBackTicked = called "backtick expansion" $ do
+readQuotedBackTicked = readBackTicked True
+readUnquotedBackTicked = readBackTicked False
+readBackTicked quoted = called "backtick expansion" $ do
     id <- getNextId
     startPos <- getPosition
     backtick
@@ -926,6 +958,7 @@
     return $ T_Backticked id result
   where
     unEscape [] = []
+    unEscape ('\\':'"':rest) | quoted = '"' : unEscape rest
     unEscape ('\\':x:rest) | x `elem` "$`\\" = x : unEscape rest
     unEscape ('\\':'\n':rest) = unEscape rest
     unEscape (c:rest) = c : unEscape rest
@@ -993,7 +1026,7 @@
     parseProblemAt endPos InfoC 1079
         "This is actually an end quote, but due to next char it looks suspect."
 
-doubleQuotedPart = readDoubleLiteral <|> readDoubleQuotedDollar <|> readBackTicked
+doubleQuotedPart = readDoubleLiteral <|> readDoubleQuotedDollar <|> readQuotedBackTicked
 
 readDoubleQuotedLiteral = do
     doubleQuote
@@ -1020,6 +1053,9 @@
 prop_readGlob3 = isOk readGlob "[a[:alpha:]]"
 prop_readGlob4 = isOk readGlob "[[:alnum:]]"
 prop_readGlob5 = isOk readGlob "[^[:alpha:]1-9]"
+prop_readGlob6 = isOk readGlob "[\\|]"
+prop_readGlob7 = isOk readGlob "[^[]"
+prop_readGlob8 = isOk readGlob "[*?]"
 readGlob = readExtglob <|> readSimple <|> readClass <|> readGlobbyLiteral
     where
         readSimple = do
@@ -1030,11 +1066,11 @@
         readClass = try $ do
             id <- getNextId
             char '['
-            s <- many1 (predefined <|> liftM return (letter <|> digit <|> oneOf globchars))
+            s <- many1 (predefined <|> readNormalLiteralPart "]" <|> globchars)
             char ']'
             return $ T_Glob id $ "[" ++ concat s ++ "]"
           where
-           globchars = "^-_:?*.,!~@#$%=+{}/~"
+           globchars = liftM return . oneOf $ "!$[" ++ extglobStartChars
            predefined = do
               try $ string "[:"
               s <- many1 letter
@@ -1152,12 +1188,19 @@
 prop_readBraced4 = isOk readBraced "{1,{2,3}}"
 prop_readBraced5 = isOk readBraced "{JP{,E}G,jp{,e}g}"
 prop_readBraced6 = isOk readBraced "{foo,bar,$((${var}))}"
+prop_readBraced7 = isNotOk readBraced "{}"
+prop_readBraced8 = isNotOk readBraced "{foo}"
 readBraced = try braceExpansion
   where
     braceExpansion =
         T_BraceExpansion `withParser` do
             char '{'
             elements <- bracedElement `sepBy1` char ','
+            guard $
+                case elements of
+                    (_:_:_) -> True
+                    [t] -> ".." `isInfixOf` onlyLiteralString t
+                    [] -> False
             char '}'
             return elements
     bracedElement =
@@ -1529,7 +1572,11 @@
 readSource :: Monad m => SourcePos -> Token -> SCParser m Token
 readSource pos t@(T_Redirecting _ _ (T_SimpleCommand _ _ (cmd:file:_))) = do
     override <- getSourceOverride
-    let literalFile = override `mplus` getLiteralString file
+    let literalFile = do
+        name <- override `mplus` getLiteralString file
+        -- Hack to avoid 'source ~/foo' trying to read from literal tilde
+        guard . not $ "~/" `isPrefixOf` name
+        return name
     case literalFile of
         Nothing -> do
             parseNoteAt pos WarningC 1090
@@ -1933,6 +1980,9 @@
 prop_readFunctionDefinition6 = isOk readFunctionDefinition "?(){ foo; }"
 prop_readFunctionDefinition7 = isOk readFunctionDefinition "..(){ cd ..; }"
 prop_readFunctionDefinition8 = isOk readFunctionDefinition "foo() (ls)"
+prop_readFunctionDefinition9 = isOk readFunctionDefinition "function foo { true; }"
+prop_readFunctionDefinition10= isOk readFunctionDefinition "function foo () { true; }"
+prop_readFunctionDefinition11= isWarning readFunctionDefinition "function foo{\ntrue\n}"
 readFunctionDefinition = called "function" $ do
     functionSignature <- try readFunctionSignature
     allspacing
@@ -1950,8 +2000,11 @@
                 whitespace
             spacing
             name <- readFunctionName
-            spacing
+            spaces <- spacing
             hasParens <- wasIncluded readParens
+            when (not hasParens && null spaces) $
+                acceptButWarn (lookAhead (oneOf "{("))
+                    ErrorC 1095 "You need a space or linefeed between the function name and body."
             return $ T_Function id (FunctionKeyword True) (FunctionParentheses hasParens) name
 
         readWithoutFunction = try $ do
@@ -2064,6 +2117,8 @@
 prop_readAssignmentWord8 = isOk readAssignmentWord "a[4''$(cat foo)]=42"
 prop_readAssignmentWord9 = isOk readAssignmentWord "IFS= "
 prop_readAssignmentWord9a= isOk readAssignmentWord "foo="
+prop_readAssignmentWord9b= isOk readAssignmentWord "foo=  "
+prop_readAssignmentWord9c= isOk readAssignmentWord "foo=  #bar"
 prop_readAssignmentWord10= isWarning readAssignmentWord "foo$n=42"
 prop_readAssignmentWord11= isOk readAssignmentWord "foo=([a]=b [c] [d]= [e f )"
 prop_readAssignmentWord12= isOk readAssignmentWord "a[b <<= 3 + c]='thing'"
@@ -2082,7 +2137,7 @@
     isEndOfCommand <- liftM isJust $ optionMaybe (try . lookAhead $ (disregard (oneOf "\r\n;&|)") <|> eof))
     if not hasLeftSpace && (hasRightSpace || isEndOfCommand)
       then do
-        when (variable /= "IFS" && hasRightSpace) $
+        when (variable /= "IFS" && hasRightSpace && not isEndOfCommand) $
             parseNoteAt pos WarningC 1007
                 "Remove space after = if trying to assign a value (for empty string, use var='' ... )."
         value <- readEmptyLiteral
@@ -2334,6 +2389,7 @@
 
 isWarning p s = parsesCleanly p s == Just False
 isOk p s =      parsesCleanly p s == Just True
+isNotOk p s =   parsesCleanly p s == Nothing
 
 testParse string = runIdentity $ do
     (res, _) <- runParser (mockedSystemInterface []) readScript "-" string
diff --git a/shellcheck.1 b/shellcheck.1
--- a/shellcheck.1
+++ b/shellcheck.1
@@ -1,7 +1,7 @@
-.TH SHELLCHECK 1 "" "Shell script analysis tool"
+.TH "SHELLCHECK" "1" "" "Shell script analysis tool" ""
 .SH NAME
 .PP
-shellcheck - Shell script analysis tool
+shellcheck \- Shell script analysis tool
 .SH SYNOPSIS
 .PP
 \f[B]shellcheck\f[] [\f[I]OPTIONS\f[]...] \f[I]FILES\f[]...
@@ -14,7 +14,7 @@
 advanced issues where corner cases can cause delayed failures.
 .PP
 ShellCheck gives shell specific advice.
-Consider the line:
+Consider this line:
 .IP
 .nf
 \f[C]
@@ -23,47 +23,57 @@
 .fi
 .IP \[bu] 2
 For scripts starting with \f[C]#!/bin/sh\f[] (or when using
-\f[C]-s\ sh\f[]), ShellCheck will warn that \f[C]((\ ..\ ))\f[] is not
+\f[C]\-s\ sh\f[]), ShellCheck will warn that \f[C]((\ ..\ ))\f[] is not
 POSIX compliant (similar to checkbashisms).
 .IP \[bu] 2
 For scripts starting with \f[C]#!/bin/bash\f[] (or using
-\f[C]-s\ bash\f[]), ShellCheck will warn that decimals are not
+\f[C]\-s\ bash\f[]), ShellCheck will warn that decimals are not
 supported.
 .IP \[bu] 2
 For scripts starting with \f[C]#!/bin/ksh\f[] (or using
-\f[C]-s\ ksh\f[]), ShellCheck will not warn at all, as \f[C]ksh\f[]
+\f[C]\-s\ ksh\f[]), ShellCheck will not warn at all, as \f[C]ksh\f[]
 supports decimals in arithmetic contexts.
 .SH OPTIONS
 .TP
-.B \f[B]-e\f[]\ \f[I]CODE1\f[][,\f[I]CODE2\f[]...],\ \f[B]--exclude=\f[]\f[I]CODE1\f[][,\f[I]CODE2\f[]...]
+.B \f[B]\-C\f[][\f[I]WHEN\f[]],\ \f[B]\-\-color\f[][=\f[I]WHEN\f[]]
+For TTY outut, enable colors \f[I]always\f[], \f[I]never\f[] or
+\f[I]auto\f[].
+The default is \f[I]auto\f[].
+\f[B]\-\-color\f[] without an argument is equivalent to
+\f[B]\-\-color=always\f[].
+.RS
+.RE
+.TP
+.B \f[B]\-e\f[]\ \f[I]CODE1\f[][,\f[I]CODE2\f[]...],\ \f[B]\-\-exclude=\f[]\f[I]CODE1\f[][,\f[I]CODE2\f[]...]
 Explicitly exclude the specified codes from the report.
-Subsequent \f[B]-e\f[] options are cumulative, but all the codes can be
-specified at once, comma-separated as a single argument.
+Subsequent \f[B]\-e\f[] options are cumulative, but all the codes can be
+specified at once, comma\-separated as a single argument.
 .RS
 .RE
 .TP
-.B \f[B]-f\f[] \f[I]FORMAT\f[], \f[B]--format=\f[]\f[I]FORMAT\f[]
+.B \f[B]\-f\f[] \f[I]FORMAT\f[], \f[B]\-\-format=\f[]\f[I]FORMAT\f[]
 Specify the output format of shellcheck, which prints its results in the
 standard output.
-Subsequent \f[B]-f\f[] options are ignored, see \f[B]FORMATS\f[] below
+Subsequent \f[B]\-f\f[] options are ignored, see \f[B]FORMATS\f[] below
 for more information.
 .RS
 .RE
 .TP
-.B \f[B]-s\f[]\ \f[I]shell\f[],\ \f[B]--shell=\f[]\f[I]shell\f[]
+.B \f[B]\-s\f[]\ \f[I]shell\f[],\ \f[B]\-\-shell=\f[]\f[I]shell\f[]
 Specify Bourne shell dialect.
-Valid values are \f[I]sh\f[], \f[I]bash\f[] and \f[I]ksh\f[].
+Valid values are \f[I]sh\f[], \f[I]bash\f[], \f[I]dash\f[] and
+\f[I]ksh\f[].
 The default is to use the file\[aq]s shebang, or \f[I]bash\f[] if the
 target shell can\[aq]t be determined.
 .RS
 .RE
 .TP
-.B \f[B]-V\f[],\ \f[B]--version\f[]
+.B \f[B]\-V\f[],\ \f[B]\-\-version\f[]
 Print version information and exit.
 .RS
 .RE
 .TP
-.B \f[B]-x\f[],\ \f[B]-external-sources\f[]
+.B \f[B]\-x\f[],\ \f[B]\-\-external\-sources\f[]
 Follow \[aq]source\[aq] statements even when the file is not specified
 as input.
 By default, \f[C]shellcheck\f[] will only follow files specified on the
@@ -84,9 +94,10 @@
 Useful for editors that support compiling and showing syntax errors.
 .RS
 .PP
-For example, in Vim, \f[C]:set\ makeprg=shellcheck\\\ -f\\\ gcc\\\ %\f[]
-will allow using \f[C]:make\f[] to check the script, and \f[C]:cnext\f[]
-to jump to the next error.
+For example, in Vim,
+\f[C]:set\ makeprg=shellcheck\\\ \-f\\\ gcc\\\ %\f[] will allow using
+\f[C]:make\f[] to check the script, and \f[C]:cnext\f[] to jump to the
+next error.
 .IP
 .nf
 \f[C]
@@ -103,7 +114,7 @@
 .IP
 .nf
 \f[C]
-<?xml\ version=\[aq]1.0\[aq]\ encoding=\[aq]UTF-8\[aq]?>
+<?xml\ version=\[aq]1.0\[aq]\ encoding=\[aq]UTF\-8\[aq]?>
 <checkstyle\ version=\[aq]4.3\[aq]>
 \ \ <file\ name=\[aq]file\[aq]>
 \ \ \ \ <error
@@ -150,7 +161,7 @@
 .nf
 \f[C]
 #\ shellcheck\ key=value\ key=value
-command-or-structure
+command\-or\-structure
 \f[]
 .fi
 .PP
@@ -211,12 +222,28 @@
 .IP
 .nf
 \f[C]
-export\ SHELLCHECK_OPTS=\[aq]--shell=bash\ --exclude=SC2016\[aq]
+export\ SHELLCHECK_OPTS=\[aq]\-\-shell=bash\ \-\-exclude=SC2016\[aq]
 \f[]
 .fi
 .PP
 Its value will be split on spaces and prepended to the command line on
 each invocation.
+.SH RETURN VALUES
+.PP
+ShellCheck uses the follow exit codes:
+.IP \[bu] 2
+0: All files successfully scanned with no issues.
+.IP \[bu] 2
+1: All files successfully scanned with some issues.
+.IP \[bu] 2
+2: Some files could not be processed (e.g.
+file not found).
+.IP \[bu] 2
+3: ShellCheck was invoked with bad syntax (e.g.
+unknown flag).
+.IP \[bu] 2
+4: ShellCheck was invoked with bad options (e.g.
+unknown formatter).
 .SH AUTHOR
 .PP
 ShellCheck is written and maintained by Vidar Holen.
@@ -227,7 +254,7 @@
 https://github.com/koalaman/shellcheck/issues
 .SH COPYRIGHT
 .PP
-Copyright 2012-2015, Vidar Holen.
+Copyright 2012\-2015, Vidar Holen.
 Licensed under the GNU General Public License version 3 or later, see
 http://gnu.org/licenses/gpl.html
 .SH SEE ALSO
diff --git a/shellcheck.1.md b/shellcheck.1.md
--- a/shellcheck.1.md
+++ b/shellcheck.1.md
@@ -16,7 +16,7 @@
 strange behavior, but it also reports on a few more advanced issues where
 corner cases can cause delayed failures.
 
-ShellCheck gives shell specific advice. Consider the line:
+ShellCheck gives shell specific advice. Consider this line:
 
     (( area = 3.14*r*r ))
 
@@ -32,6 +32,12 @@
 
 # OPTIONS
 
+**-C**[*WHEN*],\ **--color**[=*WHEN*]
+
+:   For TTY outut, enable colors *always*, *never* or *auto*. The default
+    is *auto*. **--color** without an argument is equivalent to
+    **--color=always**.
+
 **-e**\ *CODE1*[,*CODE2*...],\ **--exclude=***CODE1*[,*CODE2*...]
 
 :   Explicitly exclude the specified codes from the report. Subsequent **-e**
@@ -46,7 +52,7 @@
 
 **-s**\ *shell*,\ **--shell=***shell*
 
-:   Specify Bourne shell dialect. Valid values are *sh*, *bash* and *ksh*.
+:   Specify Bourne shell dialect. Valid values are *sh*, *bash*, *dash* and *ksh*.
     The default is to use the file's shebang, or *bash* if the target shell
     can't be determined.
 
@@ -54,7 +60,7 @@
 
 :   Print version information and exit.
 
-**-x**,\ **-external-sources**
+**-x**,\ **--external-sources**
 
 :   Follow 'source' statements even when the file is not specified as input.
     By default, `shellcheck` will only follow files specified on the command
@@ -158,6 +164,16 @@
 
 Its value will be split on spaces and prepended to the command line on each
 invocation.
+
+# RETURN VALUES
+
+ShellCheck uses the follow exit codes:
+
++ 0: All files successfully scanned with no issues.
++ 1: All files successfully scanned with some issues.
++ 2: Some files could not be processed (e.g. file not found).
++ 3: ShellCheck was invoked with bad syntax (e.g. unknown flag).
++ 4: ShellCheck was invoked with bad options (e.g. unknown formatter).
 
 # AUTHOR
 ShellCheck is written and maintained by Vidar Holen.
diff --git a/shellcheck.hs b/shellcheck.hs
--- a/shellcheck.hs
+++ b/shellcheck.hs
@@ -48,7 +48,6 @@
 data Status =
     NoProblems
     | SomeProblems
-    | BadInput
     | SupportFailure
     | SyntaxFailure
     | RuntimeException
@@ -60,12 +59,16 @@
 
 data Options = Options {
     checkSpec :: CheckSpec,
-    externalSources :: Bool
+    externalSources :: Bool,
+    formatterOptions :: FormatterOptions
 }
 
 defaultOptions = Options {
     checkSpec = emptyCheckSpec,
-    externalSources = False
+    externalSources = False,
+    formatterOptions = FormatterOptions {
+        foColorOption = ColorAuto
+    }
 }
 
 usageHeader = "Usage: shellcheck [OPTIONS...] FILES..."
@@ -74,8 +77,11 @@
         (ReqArg (Flag "exclude") "CODE1,CODE2..") "exclude types of warnings",
     Option "f" ["format"]
         (ReqArg (Flag "format") "FORMAT") "output format",
+    Option "C" ["color"]
+        (OptArg (maybe (Flag "color" "always") (Flag "color")) "WHEN")
+        "Use color (auto, always, never)",
     Option "s" ["shell"]
-        (ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (bash,sh,ksh)",
+        (ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (sh,bash,dash,ksh)",
     Option "x" ["external-sources"]
         (NoArg $ Flag "externals" "true") "Allow 'source' outside of FILES.",
     Option "V" ["version"]
@@ -92,12 +98,12 @@
             printErr $ concat errors ++ "\n" ++ usageInfo usageHeader options
             throwError SyntaxFailure
 
-formats :: Map.Map String (IO Formatter)
-formats = Map.fromList [
+formats :: FormatterOptions -> Map.Map String (IO Formatter)
+formats options = Map.fromList [
     ("checkstyle", ShellCheck.Formatter.CheckStyle.format),
     ("gcc",  ShellCheck.Formatter.GCC.format),
     ("json", ShellCheck.Formatter.JSON.format),
-    ("tty",  ShellCheck.Formatter.TTY.format)
+    ("tty",  ShellCheck.Formatter.TTY.format options)
     ]
 
 getOption [] _ = Nothing
@@ -144,7 +150,6 @@
     case status of
         NoProblems -> ExitSuccess
         SomeProblems -> ExitFailure 1
-        BadInput -> ExitFailure 5
         SyntaxFailure -> ExitFailure 3
         SupportFailure -> ExitFailure 4
         RuntimeException -> ExitFailure 2
@@ -154,12 +159,13 @@
     options <- foldM (flip parseOption) defaultOptions flags
     verifyFiles files
     let format = fromMaybe "tty" $ getOption flags "format"
+    let formatters = formats $ formatterOptions options
     formatter <-
-        case Map.lookup format formats of
+        case Map.lookup format formatters of
             Nothing -> do
                 printErr $ "Unknown format " ++ format
                 printErr "Supported formats:"
-                mapM_ (printErr . write) $ Map.keys formats
+                mapM_ (printErr . write) $ Map.keys formatters
                 throwError SupportFailure
               where write s = "  " ++ s
             Just f -> ExceptT $ fmap Right f
@@ -197,6 +203,13 @@
             then NoProblems
             else SomeProblems
 
+parseColorOption colorOption =
+    case colorOption of
+        "auto" -> ColorAuto
+        "always" -> ColorAlways
+        "never" -> ColorNever
+        _ -> error $ "Bad value for --color `" ++ colorOption ++ "'"
+
 parseOption flag options =
     case flag of
         Flag "shell" str ->
@@ -221,9 +234,16 @@
             liftIO printVersion
             throwError NoProblems
 
-        Flag "externals" _ -> do
+        Flag "externals" _ ->
             return options {
                 externalSources = True
+            }
+
+        Flag "color" color ->
+            return options {
+                formatterOptions = (formatterOptions options) {
+                    foColorOption = parseColorOption color
+                }
             }
 
         _ -> return options
