diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![GitHub release][release-img]][release]
 [![Github downloads][downloads-img]]()
 <img align="right" alt="pipecat" width="150"
-src="http://hadolint.lukasmartinelli.ch/img/cat_container.png" />
+src="https://hadolint.github.io/hadolint/img/cat_container.png" />
 
 # Haskell Dockerfile Linter
 
@@ -13,9 +13,9 @@
 top of the AST. It is standing on the shoulders of [ShellCheck][] to lint
 the Bash code inside `RUN` instructions.
 
-<!-- [:globe_with_meridians: **Check the online version on
- hadolint.lukasmartinelli.ch**](http://hadolint.lukasmartinelli.ch/.)
-[![Screenshot](screenshot.png)](http://hadolint.lukasmartinelli.ch/) -->
+[:globe_with_meridians: **Check the online version on
+ hadolint.github.io/hadolint**](https://hadolint.github.io/hadolint)
+[![Screenshot](screenshot.png)](https://hadolint.github.io/hadolint)
 
 ## How to use
 
diff --git a/hadolint.cabal b/hadolint.cabal
--- a/hadolint.cabal
+++ b/hadolint.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 099ae897b9dd6e8bd846e847751883f65ef076c560a6157945fbeb7be74a5859
+-- hash: 0f8b34c24db80480b579a95b0f4a3ff72ce38bb93a16e92f0578975efa60cbcc
 
 name:           hadolint
-version:        1.17.1
+version:        1.17.2
 synopsis:       Dockerfile Linter JavaScript API
 description:    A smarter Dockerfile linter that helps you build best practice Docker images.
 category:       Development
diff --git a/src/Hadolint/Rules.hs b/src/Hadolint/Rules.hs
--- a/src/Hadolint/Rules.hs
+++ b/src/Hadolint/Rules.hs
@@ -263,7 +263,8 @@
 
 -- | Converts ShellCheck errors into our own errors type
 commentMetadata :: ShellCheck.Interface.PositionedComment -> Metadata
-commentMetadata c = Metadata (Text.pack ("SC" ++ show (code c))) (severity c) (Text.pack (message c))
+commentMetadata c =
+    Metadata (Text.pack ("SC" ++ show (code c))) (severity c) (Text.pack (message c))
   where
     severity pc = ShellCheck.Interface.cSeverity $ ShellCheck.Interface.pcComment pc
     code pc = ShellCheck.Interface.cCode $ ShellCheck.Interface.pcComment pc
@@ -291,7 +292,7 @@
     check _ = True
 
 -- Check if a command contains a program call in the Run instruction
-usingProgram :: String -> Shell.ParsedShell -> Bool
+usingProgram :: Text.Text -> Shell.ParsedShell -> Bool
 usingProgram prog args = not $ null [cmd | cmd <- Shell.findCommandNames args, cmd == prog]
 
 multipleCmds :: Rule
@@ -444,14 +445,14 @@
         \install <package>=<version>`"
     check (Run args) = argumentsRule (all versionFixed . aptGetPackages) args
     check _ = True
-    versionFixed package = "=" `isInfixOf` package
+    versionFixed package = "=" `Text.isInfixOf` package
 
-aptGetPackages :: Shell.ParsedShell -> [String]
+aptGetPackages :: Shell.ParsedShell -> [Text.Text]
 aptGetPackages args =
     [ arg
-    | cmd <- dropTarget <$> Shell.findCommands args
+    | cmd <- Shell.presentCommands args
     , Shell.cmdHasArgs "apt-get" ["install"] cmd
-    , arg <- Shell.getArgsNoFlags cmd
+    , arg <- Shell.getArgsNoFlags (dropTarget cmd)
     , arg /= "install"
     ]
   where
@@ -478,8 +479,8 @@
         | not (hasUpdate args) || not (imageIsUsed line baseimage) = True
         | otherwise = hasCleanup args
     hasCleanup args =
-        any (Shell.cmdHasArgs "rm" ["-rf", "/var/lib/apt/lists/*"]) (Shell.findCommands args)
-    hasUpdate args = any (Shell.cmdHasArgs "apt-get" ["update"]) (Shell.findCommands args)
+        any (Shell.cmdHasArgs "rm" ["-rf", "/var/lib/apt/lists/*"]) (Shell.presentCommands args)
+    hasUpdate args = any (Shell.cmdHasArgs "apt-get" ["update"]) (Shell.presentCommands args)
     imageIsUsed line baseimage = isLastImage line baseimage || imageIsUsedLater line baseimage
     isLastImage line baseimage =
         case reverse (allFromImages dockerfile) of
@@ -509,14 +510,14 @@
         "Pin versions in apk add. Instead of `apk add <package>` use `apk add <package>=<version>`"
     check (Run args) = argumentsRule (\as -> and [versionFixed p | p <- apkAddPackages as]) args
     check _ = True
-    versionFixed package = "=" `isInfixOf` package
+    versionFixed package = "=" `Text.isInfixOf` package
 
-apkAddPackages :: Shell.ParsedShell -> [String]
+apkAddPackages :: Shell.ParsedShell -> [Text.Text]
 apkAddPackages args =
     [ arg
-    | cmd <- dropTarget <$> Shell.findCommands args
+    | cmd <- Shell.presentCommands args
     , Shell.cmdHasArgs "apk" ["add"] cmd
-    , arg <- Shell.getArgsNoFlags cmd
+    , arg <- Shell.getArgsNoFlags (dropTarget cmd)
     , arg /= "add"
     ]
   where
@@ -548,22 +549,22 @@
             ]
     check _ = True
     archiveFormats =
-      [ ".tar"
-      , ".tar.bz2"
-      , ".tb2"
-      , ".tbz"
-      , ".tbz2"
-      , ".tar.gz"
-      , ".tgz"
-      , ".tpz"
-      , ".tar.lz"
-      , ".tar.lzma"
-      , ".tlz"
-      , ".tar.xz"
-      , ".txz"
-      , ".tar.Z"
-      , ".tZ"
-      ]
+        [ ".tar"
+        , ".tar.bz2"
+        , ".tb2"
+        , ".tbz"
+        , ".tbz2"
+        , ".tar.gz"
+        , ".tgz"
+        , ".tpz"
+        , ".tar.lz"
+        , ".tar.lzma"
+        , ".tlz"
+        , ".tar.xz"
+        , ".txz"
+        , ".tar.Z"
+        , ".tZ"
+        ]
 
 invalidPort :: Rule
 invalidPort = instructionRule code severity message check
@@ -589,23 +590,22 @@
     forgotToPinVersion cmd =
         isPipInstall cmd && not (hasBuildConstraint cmd) && not (all versionFixed (packages cmd))
     -- Check if the command is a pip* install command, and that specific pacakges are being listed
-    isPipInstall cmd =
-        case Shell.getCommandName cmd of
-            Just ('p':'i':'p':_) -> relevantInstall cmd
-            _ -> False
+    isPipInstall cmd@(Shell.Command name _ _) = "pip" `Text.isPrefixOf` name && relevantInstall cmd
     -- If the user is installing requirements from a file or just the local module, then we are not interested
     -- in running this rule
     relevantInstall cmd =
-        ["install"] `isInfixOf` Shell.getAllArgs cmd &&
-        not (["-r"] `isInfixOf` Shell.getAllArgs cmd || ["."] `isInfixOf` Shell.getAllArgs cmd)
+        ["install"] `isInfixOf` Shell.getArgs cmd &&
+        not (["-r"] `isInfixOf` Shell.getArgs cmd || ["."] `isInfixOf` Shell.getArgs cmd)
     hasBuildConstraint = Shell.hasFlag "constraint"
-    packages cmd = stripInstallPrefix $ Shell.getArgsNoFlags $ Shell.dropFlagArg ["i", "index-url", "extra-index-url"] cmd
+    packages cmd =
+        stripInstallPrefix $
+        Shell.getArgsNoFlags $ Shell.dropFlagArg ["i", "index-url", "extra-index-url"] cmd
     versionFixed package = hasVersionSymbol package || isVersionedGit package
-    isVersionedGit package = "git+http" `isInfixOf` package && "@" `isInfixOf` package
+    isVersionedGit package = "git+http" `Text.isInfixOf` package && "@" `Text.isInfixOf` package
     versionSymbols = ["==", ">=", "<=", ">", "<", "!=", "~=", "==="]
-    hasVersionSymbol package = or [s `isInfixOf` package | s <- versionSymbols]
+    hasVersionSymbol package = or [s `Text.isInfixOf` package | s <- versionSymbols]
 
-stripInstallPrefix :: [String] -> [String]
+stripInstallPrefix :: [Text.Text] -> [Text.Text]
 stripInstallPrefix = dropWhile (== "install")
 
 {-|
@@ -638,13 +638,13 @@
             then isVersionedGit package
             else hasVersionSymbol package
     gitPrefixes = ["git://", "git+ssh://", "git+http://", "git+https://"]
-    hasGitPrefix package = or [p `isPrefixOf` package | p <- gitPrefixes]
-    isVersionedGit package = "#" `isInfixOf` package
-    hasVersionSymbol package = "@" `isInfixOf` dropScope package
+    hasGitPrefix package = or [p `Text.isPrefixOf` package | p <- gitPrefixes]
+    isVersionedGit package = "#" `Text.isInfixOf` package
+    hasVersionSymbol package = "@" `Text.isInfixOf` dropScope package
       where
         dropScope pkg =
-            if "@" `isPrefixOf` pkg
-                then dropWhile ('/' <) pkg
+            if "@" `Text.isPrefixOf` pkg
+                then Text.dropWhile ('/' <) pkg
                 else pkg
 
 aptGetYes :: Rule
@@ -657,11 +657,7 @@
     check _ = True
     forgotAptYesOption cmd = isAptGetInstall cmd && not (hasYesOption cmd)
     isAptGetInstall = Shell.cmdHasArgs "apt-get" ["install"]
-    hasYesOption cmd =
-        "y" `elem` allFlags cmd ||
-        "yes" `elem` allFlags cmd ||
-        length (filter (== "q") (allFlags cmd)) > 1 || "assume-yes" `elem` allFlags cmd
-    allFlags cmd = snd <$> Shell.getAllFlags cmd
+    hasYesOption = Shell.hasAnyFlag ["y", "yes", "q", "assume-yes"]
 
 aptGetNoRecommends :: Rule
 aptGetNoRecommends = instructionRule code severity message check
@@ -675,7 +671,7 @@
     isAptGetInstall = Shell.cmdHasArgs "apt-get" ["install"]
     disablesRecommendOption cmd =
         Shell.hasFlag "no-install-recommends" cmd ||
-        "APT::Install-Recommends=false" `elem` Shell.getAllArgs cmd
+        Shell.hasArg "APT::Install-Recommends=false" cmd
 
 isArchive :: Text.Text -> Bool
 isArchive path =
@@ -798,17 +794,17 @@
         | otherwise = (argumentsRule hasPipefailOption args, True)
     check False _ (Run args) = (False, argumentsRule notHasPipes args)
     check st _ _ = (st, True)
-    isPowerShell (Shell.ParsedShell orig _) = "pwsh" `Text.isPrefixOf` orig
+    isPowerShell (Shell.ParsedShell orig _ _) = "pwsh" `Text.isPrefixOf` orig
     notHasPipes script = not (Shell.hasPipes script)
     hasPipefailOption script =
         not $
         null
             [ True
-            | cmd <- Shell.findCommands script
+            | cmd@(Shell.Command name arguments _) <- Shell.presentCommands script
             , validShell <- ["/bin/bash", "/bin/zsh", "/bin/ash", "bash", "zsh", "ash"]
-            , Shell.getCommandName cmd == Just validShell
+            , name == validShell
             , Shell.hasFlag "o" cmd
-            , arg <- Shell.getAllArgs cmd
+            , arg <- Shell.arg <$> arguments
             , arg == "pipefail"
             ]
 
@@ -840,16 +836,16 @@
         \install <gem>:<version>`"
     check (Run args) = argumentsRule (all versionFixed . gems) args
     check _ = True
-    versionFixed package = ":" `isInfixOf` package
+    versionFixed package = ":" `Text.isInfixOf` package
 
-gems :: Shell.ParsedShell -> [String]
-gems args =
+gems :: Shell.ParsedShell -> [Text.Text]
+gems shell =
     [ arg
-    | cmd <- Shell.findCommands args
+    | cmd <- Shell.presentCommands shell
     , Shell.cmdHasArgs "gem" ["install", "i"] cmd
     , not (Shell.cmdHasArgs "gem" ["-v"] cmd)
     , arg <- Shell.getArgsNoFlags cmd
     , arg /= "install"
     , arg /= "i"
-    , not ("--" `isPrefixOf` arg)
+    , arg /= "--"
     ]
diff --git a/src/Hadolint/Shell.hs b/src/Hadolint/Shell.hs
--- a/src/Hadolint/Shell.hs
+++ b/src/Hadolint/Shell.hs
@@ -1,14 +1,16 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 
 module Hadolint.Shell where
 
 import Control.Monad.Writer (Writer, execWriter, tell)
 import Data.Functor.Identity (runIdentity)
-import Data.List (nub)
 import Data.Maybe (listToMaybe, mapMaybe)
 import Data.Semigroup ((<>))
 import qualified Data.Set as Set
 import qualified Data.Text as Text
+import Data.Text (Text)
 import qualified ShellCheck.AST
 import ShellCheck.AST (Id(..), Token(..))
 import qualified ShellCheck.ASTLib
@@ -16,9 +18,21 @@
 import ShellCheck.Interface
 import qualified ShellCheck.Parser
 
+data CmdPart = CmdPart
+    { arg :: !Text
+    , partId :: !Int
+    } deriving (Show)
+
+data Command = Command
+    { name :: !Text.Text
+    , arguments :: [CmdPart]
+    , flags :: [CmdPart]
+    } deriving (Show)
+
 data ParsedShell = ParsedShell
-    { original :: Text.Text
-    , parsed :: ParseResult
+    { original :: !Text.Text
+    , parsed :: !ParseResult
+    , presentCommands :: ![Command]
     }
 
 data ShellOpts = ShellOpts
@@ -48,24 +62,26 @@
 setShell s (ShellOpts _ v) = ShellOpts s v
 
 shellcheck :: ShellOpts -> ParsedShell -> [PositionedComment]
-shellcheck (ShellOpts sh env) (ParsedShell txt _) =
+shellcheck (ShellOpts sh env) (ParsedShell txt _ _) =
     if "pwsh" `Text.isPrefixOf` sh
         then [] -- Do no run for powershell
         else runShellCheck
   where
     runShellCheck = crComments $ runIdentity $ checkScript si spec
     si = mockedSystemInterface [("", "")]
-    spec = emptyCheckSpec {
-        csFilename = "", -- filename can be ommited because we only want the parse results back
-        csScript = script,
-        csCheckSourced = False,
-        csExcludedWarnings = exclusions,
-        csShellTypeOverride = Nothing,
-        csMinSeverity = StyleC
-    }
+    spec =
+        emptyCheckSpec
+            { csFilename = "" -- filename can be ommited because we only want the parse results back
+            , csScript = script
+            , csCheckSourced = False
+            , csExcludedWarnings = exclusions
+            , csShellTypeOverride = Nothing
+            , csMinSeverity = StyleC
+            }
     script = "#!" ++ extractShell sh ++ "\n" ++ printVars ++ Text.unpack txt
     exclusions =
         [ 2187 -- exclude the warning about the ash shell not being supported
+        , 1090 -- requires a directive (shell comment) that can't be expressed in a Dockerfile 
         ]
     -- | Shellcheck complains when the shebang has more than one argument, so we only take the first
     extractShell s =
@@ -76,34 +92,45 @@
     printVars = Text.unpack . Text.unlines . Set.toList $ Set.map (\v -> "export " <> v <> "=1") env
 
 parseShell :: Text.Text -> ParsedShell
-parseShell txt =
-    ParsedShell
-    { original = txt
-    , parsed =
-          runIdentity $
-          ShellCheck.Parser.parseScript
-              (mockedSystemInterface [("", "")])
-              newParseSpec
-              { psFilename = "" -- There is no filename
-              , psScript = "#!/bin/bash\n" ++ Text.unpack txt
-              , psCheckSourced = False
-              }
-    }
+parseShell txt = ParsedShell {original = txt, parsed = parsedResult, presentCommands = commands}
+  where
+    parsedResult =
+        runIdentity $
+        ShellCheck.Parser.parseScript
+            (mockedSystemInterface [("", "")])
+            newParseSpec
+                { psFilename = "" -- There is no filename
+                , psScript = "#!/bin/bash\n" ++ Text.unpack txt
+                , psCheckSourced = False
+                }
+    -- | Extract all commands with their name
+    commands = mapMaybe extractNames (findCommandsInResult parsedResult)
+    extractNames token =
+        case ShellCheck.ASTLib.getCommandName token of
+            Nothing -> Nothing
+            Just n -> Just $ Command (Text.pack n) allArgs (getAllFlags allArgs)
+      where
+        allArgs = extractAllArgs token
 
-extractTokensWith :: (Token -> Maybe Token) -> ParsedShell -> [Token]
-extractTokensWith extractor (ParsedShell _ ast) =
+findCommandsInResult :: ParseResult -> [Token]
+findCommandsInResult = extractTokensWith commandsExtractor
+  where
+    commandsExtractor = ShellCheck.ASTLib.getCommand
+
+extractTokensWith :: forall a. (Token -> Maybe a) -> ParseResult -> [a]
+extractTokensWith extractor ast =
     case prRoot ast of
         Nothing -> []
-        Just script -> nub . execWriter $ ShellCheck.AST.doAnalysis extract script
+        Just script -> execWriter $ ShellCheck.AST.doAnalysis extract script
   where
-    extract :: Token -> Writer [Token] ()
+    extract :: Token -> Writer [a] ()
     extract token =
         case extractor token of
             Nothing -> return ()
             Just t -> tell [t]
 
 findPipes :: ParsedShell -> [Token]
-findPipes = extractTokensWith pipesExtractor
+findPipes (ParsedShell _ ast _) = extractTokensWith pipesExtractor ast
   where
     pipesExtractor pipe@T_Pipe {} = Just pipe
     pipesExtractor _ = Nothing
@@ -111,52 +138,58 @@
 hasPipes :: ParsedShell -> Bool
 hasPipes = not . null . findPipes
 
-findCommands :: ParsedShell -> [Token]
-findCommands = extractTokensWith commandsExtractor
-  where
-    commandsExtractor = ShellCheck.ASTLib.getCommand
-
-allCommands :: (Token -> Bool) -> ParsedShell -> Bool
-allCommands check script = all check (findCommands script)
+allCommands :: (Command -> Bool) -> ParsedShell -> Bool
+allCommands check script = all check (presentCommands script)
 
-noCommands :: (Token -> Bool) -> ParsedShell -> Bool
+noCommands :: (Command -> Bool) -> ParsedShell -> Bool
 noCommands check = allCommands (not . check)
 
-getCommandName :: Token -> Maybe String
-getCommandName = ShellCheck.ASTLib.getCommandName
+findCommandNames :: ParsedShell -> [Text]
+findCommandNames script = map name (presentCommands script)
 
-findCommandNames :: ParsedShell -> [String]
-findCommandNames = mapMaybe getCommandName . findCommands
+cmdHasArgs :: Text.Text -> [Text.Text] -> Command -> Bool
+cmdHasArgs expectedName expectedArgs (Command n args _)
+    | expectedName /= n = False
+    | otherwise = not $ null [arg | CmdPart arg _ <- args, arg `elem` expectedArgs]
 
-cmdHasArgs :: String -> [String] -> Token -> Bool
-cmdHasArgs command arguments token@T_SimpleCommand {}
-    | ShellCheck.ASTLib.getCommandName token /= Just command = False
-    | otherwise = not $ null [arg | arg <- getAllArgs token, arg `elem` arguments]
-cmdHasArgs _ _ _ = False
+extractAllArgs :: Token -> [CmdPart]
+extractAllArgs (T_SimpleCommand _ _ (_:allArgs)) = map mkPart allArgs
+  where
+    mkPart token =
+        CmdPart
+            (Text.pack . concat $ ShellCheck.ASTLib.oversimplify token)
+            (mkId (ShellCheck.AST.getId token))
+    mkId (Id i) = i
+extractAllArgs _ = []
 
-getAllArgs :: Token -> [String]
-getAllArgs (T_SimpleCommand _ _ (_:allArgs)) = concatMap ShellCheck.ASTLib.oversimplify allArgs
-getAllArgs _ = []
+getArgs :: Command -> [Text.Text]
+getArgs cmd = map arg (arguments cmd)
 
-getArgsNoFlags :: Token -> [String]
-getArgsNoFlags cmd@(T_SimpleCommand _ _ (_:allArgs)) = concatMap ShellCheck.ASTLib.oversimplify args
+getAllFlags :: [CmdPart] -> [CmdPart]
+getAllFlags = concatMap flag
   where
-    flags = [t | (t, _) <- getAllFlags cmd]
-    args = [a | a <- allArgs, a `notElem` flags]
-getArgsNoFlags _ = []
+    flag (CmdPart arg pId)
+        | arg == "--" || arg == "-" = []
+        | "--" `Text.isPrefixOf` arg = [CmdPart (Text.drop 2 . Text.takeWhile (/= '=') $ arg) pId]
+        | "-" `Text.isPrefixOf` arg = map (`CmdPart` pId) (Text.chunksOf 1 (Text.tail arg))
+        | otherwise = []
 
-getAllFlags :: Token -> [(Token, String)]
-getAllFlags cmd@T_SimpleCommand {} = [(t, f) | (t, f) <- ShellCheck.ASTLib.getAllFlags cmd, f /= ""]
-getAllFlags _ = []
+getArgsNoFlags :: Command -> [Text.Text]
+getArgsNoFlags args = map arg $ filter (notAFlagId . partId) (arguments args)
+  where
+    notAFlagId pId = pId `notElem` map partId (flags args)
 
-hasFlag :: String -> Token -> Bool
-hasFlag flag = any (\(_, f) -> f == flag) . getAllFlags
+hasFlag :: Text.Text -> Command -> Bool
+hasFlag flag Command {flags} = not $ null [f | CmdPart f _ <- flags, f == flag]
 
-dropFlagArg :: [String] -> Token -> Token
-dropFlagArg flags cmd@(T_SimpleCommand cid b allArgs) = T_SimpleCommand cid b filterdArgs
+hasAnyFlag :: [Text.Text] -> Command -> Bool
+hasAnyFlag fs Command {flags} = not $ null [f | CmdPart f _ <- flags, f `elem` fs]
+
+hasArg :: Text.Text -> Command -> Bool
+hasArg arg Command {arguments} = not $ null [a | CmdPart a _ <- arguments, a == arg]
+
+dropFlagArg :: [Text.Text] -> Command -> Command
+dropFlagArg flagsToDrop Command {name, arguments, flags} = Command name filterdArgs flags
   where
-    filterdArgs = [arg | arg <- allArgs, isNotNextToken arg]
-    isNotNextToken arg = ShellCheck.AST.getId arg `notElem` findTokensToDrop
-    findTokensToDrop = [next (ShellCheck.AST.getId t) | (t, f) <- getAllFlags cmd, f `elem` flags]
-    next (Id i) = Id (i + 2)
-dropFlagArg _ token = token
+    idsToDrop = Set.fromList [fId + 2 | CmdPart f fId <- flags, f `elem` flagsToDrop]
+    filterdArgs = [arg | arg@(CmdPart _ aId) <- arguments, not (aId `Set.member` idsToDrop)]
