From ed56a837c33caba7788b96a70f6c0f7eb5642e27 Mon Sep 17 00:00:00 2001 From: Vidar Holen Date: Fri, 20 Mar 2015 10:03:56 -0700 Subject: [PATCH] Killed Zsh support --- ShellCheck/AST.hs | 8 +++---- ShellCheck/Analytics.hs | 48 ++++++++++++++--------------------------- ShellCheck/Options.hs | 2 +- ShellCheck/Parser.hs | 45 +++++++++++--------------------------- shellcheck.1.md | 39 ++++++++++++++++++++++++++------- shellcheck.hs | 2 +- 6 files changed, 65 insertions(+), 79 deletions(-) diff --git a/ShellCheck/AST.hs b/ShellCheck/AST.hs index f911f41..09e37f3 100644 --- a/ShellCheck/AST.hs +++ b/ShellCheck/AST.hs @@ -28,7 +28,6 @@ data Dashed = Dashed | Undashed deriving (Show, Eq) data AssignmentMode = Assign | Append deriving (Show, Eq) data FunctionKeyword = FunctionKeyword Bool deriving (Show, Eq) data FunctionParentheses = FunctionParentheses Bool deriving (Show, Eq) -data ForInType = NormalForIn | ShortForIn deriving (Show, Eq) data CaseType = CaseBreak | CaseFallThrough | CaseContinue deriving (Show, Eq) data Token = @@ -49,7 +48,6 @@ data Token = | T_Arithmetic Id Token | T_Array Id [Token] | T_IndexedElement Id Token Token - | T_ Id [Token] | T_Assignment Id AssignmentMode String (Maybe Token) Token | T_Backgrounded Id Token | T_Backticked Id [Token] @@ -83,7 +81,7 @@ data Token = | T_Fi Id | T_For Id | T_ForArithmetic Id Token Token Token [Token] - | T_ForIn Id ForInType [String] [Token] [Token] + | T_ForIn Id String [Token] [Token] | T_Function Id FunctionKeyword FunctionParentheses String Token | T_GREATAND Id | T_Glob Id String @@ -207,7 +205,7 @@ analyze f g i = delve (T_BraceGroup id l) = dl l $ T_BraceGroup id delve (T_WhileExpression id c l) = dll c l $ T_WhileExpression id delve (T_UntilExpression id c l) = dll c l $ T_UntilExpression id - delve (T_ForIn id t v w l) = dll w l $ T_ForIn id t v + delve (T_ForIn id v w l) = dll w l $ T_ForIn id v delve (T_SelectIn id v w l) = dll w l $ T_SelectIn id v delve (T_CaseExpression id word cases) = do newWord <- round word @@ -316,7 +314,7 @@ getId t = case t of T_BraceGroup id _ -> id T_WhileExpression id _ _ -> id T_UntilExpression id _ _ -> id - T_ForIn id _ _ _ _ -> id + T_ForIn id _ _ _ -> id T_SelectIn id _ _ _ -> id T_CaseExpression id _ _ -> id T_Function id _ _ _ _ -> id diff --git a/ShellCheck/Analytics.hs b/ShellCheck/Analytics.hs index db7b62f..e031722 100644 --- a/ShellCheck/Analytics.hs +++ b/ShellCheck/Analytics.hs @@ -71,10 +71,6 @@ checksFor Sh = [ checksFor Ksh = [ checkEchoSed ] -checksFor Zsh = [ - checkTimeParameters - ,checkEchoSed - ] checksFor Bash = [ checkTimeParameters ,checkBraceExpansionVars @@ -116,7 +112,6 @@ shellForExecutable "ksh" = return Ksh shellForExecutable "ksh88" = return Ksh shellForExecutable "ksh93" = return Ksh -shellForExecutable "zsh" = return Zsh shellForExecutable "bash" = return Bash shellForExecutable _ = Nothing @@ -723,17 +718,17 @@ prop_checkForInQuoted4 = verify checkForInQuoted "for f in 1,2,3; do true; done" prop_checkForInQuoted4a = verifyNot checkForInQuoted "for f in foo{1,2,3}; do true; done" prop_checkForInQuoted5 = verify checkForInQuoted "for f in ls; do true; done" prop_checkForInQuoted6 = verifyNot checkForInQuoted "for f in \"${!arr}\"; do true; done" -checkForInQuoted _ (T_ForIn _ _ f [T_NormalWord _ [word@(T_DoubleQuoted id list)]] _) = +checkForInQuoted _ (T_ForIn _ f [T_NormalWord _ [word@(T_DoubleQuoted id list)]] _) = when (any (\x -> willSplit x && not (mayBecomeMultipleArgs x)) list || (liftM wouldHaveBeenGlob (getLiteralString word) == Just True)) $ err id 2066 "Since you double quoted this, it will not word split, and the loop will only run once." -checkForInQuoted _ (T_ForIn _ _ f [T_NormalWord _ [T_SingleQuoted id s]] _) = +checkForInQuoted _ (T_ForIn _ f [T_NormalWord _ [T_SingleQuoted id s]] _) = warn id 2041 $ "This is a literal string. To run as a command, use $(" ++ s ++ ")." -checkForInQuoted _ (T_ForIn _ _ f [T_NormalWord _ [T_Literal id s]] _) = +checkForInQuoted _ (T_ForIn _ f [T_NormalWord _ [T_Literal id s]] _) = if ',' `elem` s then unless ('{' `elem` s) $ warn id 2042 "Use spaces, not commas, to separate loop elements." - else warn id 2043 $ "This loop will only run once, with " ++ head f ++ "='" ++ s ++ "'." + else warn id 2043 $ "This loop will only run once, with " ++ f ++ "='" ++ s ++ "'." checkForInQuoted _ _ = return () prop_checkForInCat1 = verify checkForInCat "for f in $(cat foo); do stuff; done" @@ -741,7 +736,7 @@ prop_checkForInCat1a= verify checkForInCat "for f in `cat foo`; do stuff; done" prop_checkForInCat2 = verify checkForInCat "for f in $(cat foo | grep lol); do stuff; done" prop_checkForInCat2a= verify checkForInCat "for f in `cat foo | grep lol`; do stuff; done" prop_checkForInCat3 = verifyNot checkForInCat "for f in $(cat foo | grep bar | wc -l); do stuff; done" -checkForInCat _ (T_ForIn _ _ f [T_NormalWord _ w] _) = mapM_ checkF w +checkForInCat _ (T_ForIn _ f [T_NormalWord _ w] _) = mapM_ checkF w where checkF (T_DollarExpansion id [T_Pipeline _ _ r]) | all isLineBased r = @@ -757,9 +752,9 @@ prop_checkForInLs2 = verify checkForInLs "for f in `ls *.mp3`; do mplayer \"$f\" prop_checkForInLs3 = verify checkForInLs "for f in `find / -name '*.mp3'`; do mplayer \"$f\"; done" checkForInLs _ = try where - try (T_ForIn _ _ f [T_NormalWord _ [T_DollarExpansion id [x]]] _) = + try (T_ForIn _ f [T_NormalWord _ [T_DollarExpansion id [x]]] _) = check id f x - try (T_ForIn _ _ f [T_NormalWord _ [T_Backticked id [x]]] _) = + try (T_ForIn _ f [T_NormalWord _ [T_Backticked id [x]]] _) = check id f x try _ = return () check id f x = @@ -1068,7 +1063,6 @@ prop_checkNumberComparisons6 = verify checkNumberComparisons "[[ 3.14 -eq $foo ] prop_checkNumberComparisons7 = verifyNot checkNumberComparisons "[[ 3.14 == $foo ]]" prop_checkNumberComparisons8 = verify checkNumberComparisons "[[ foo <= bar ]]" prop_checkNumberComparisons9 = verify checkNumberComparisons "[ foo \\>= bar ]" -prop_checkNumberComparisons10= verify checkNumberComparisons "#!/bin/zsh -x\n[ foo >= bar ]]" 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 @@ -1097,9 +1091,7 @@ checkNumberComparisons params (TC_Binary id typ op lhs rhs) = do isLtGt = flip elem ["<", "\\<", ">", "\\>"] isLeGe = flip elem ["<=", "\\<=", ">=", "\\>="] - supportsDecimals = - let sh = shellType params in - sh == Ksh || sh == Zsh + supportsDecimals = (shellType params) == Ksh checkDecimals hs = when (isFraction hs && not supportsDecimals) $ err (getId hs) 2072 decimalError @@ -1882,7 +1874,7 @@ checkSpuriousExec _ = doLists doLists (T_BraceGroup _ cmds) = doList cmds doLists (T_WhileExpression _ _ cmds) = doList cmds doLists (T_UntilExpression _ _ cmds) = doList cmds - doLists (T_ForIn _ _ _ _ cmds) = doList cmds + doLists (T_ForIn _ _ _ cmds) = doList cmds doLists (T_ForArithmetic _ _ _ _ cmds) = doList cmds doLists (T_IfExpression _ thens elses) = do mapM_ (\(_, l) -> doList l) thens @@ -2010,7 +2002,7 @@ prop_subshellAssignmentCheck11 = verifyTree subshellAssignmentCheck "cat /etc/pa prop_subshellAssignmentCheck12 = verifyTree subshellAssignmentCheck "cat /etc/passwd | while read line; do let ++n; done\necho $n" prop_subshellAssignmentCheck13 = verifyTree subshellAssignmentCheck "#!/bin/bash\necho foo | read bar; echo $bar" prop_subshellAssignmentCheck14 = verifyNotTree subshellAssignmentCheck "#!/bin/ksh93\necho foo | read bar; echo $bar" -prop_subshellAssignmentCheck15 = verifyNotTree subshellAssignmentCheck "#!/bin/zsh\ncat foo | while read bar; do a=$bar; done\necho \"$a\"" +prop_subshellAssignmentCheck15 = verifyNotTree subshellAssignmentCheck "#!/bin/ksh\ncat foo | while read bar; do a=$bar; done\necho \"$a\"" prop_subshellAssignmentCheck16 = verifyNotTree subshellAssignmentCheck "(set -e); echo $@" subshellAssignmentCheck params t = let flow = variableFlow params @@ -2069,7 +2061,6 @@ leadType shell parents t = Bash -> True Sh -> True Ksh -> False - Zsh -> False getModifiedVariables t = case t of @@ -2097,7 +2088,7 @@ getModifiedVariables t = [(t, t, fromMaybe "COPROC" name, DataArray SourceExternal)] --Points to 'for' rather than variable - T_ForIn id _ strs words _ -> map (\str -> (t, t, str, DataString $ SourceFrom words)) strs + T_ForIn id str words _ -> [(t, t, str, DataString $ SourceFrom words)] T_SelectIn id str words _ -> [(t, t, str, DataString $ SourceFrom words)] _ -> [] @@ -2782,7 +2773,7 @@ prop_checkCdAndBack3 = verifyNot checkCdAndBack "while [[ $PWD != / ]]; do cd .. checkCdAndBack params = doLists where shell = shellType params - doLists (T_ForIn _ _ _ _ cmds) = doList cmds + doLists (T_ForIn _ _ _ cmds) = doList cmds doLists (T_ForArithmetic _ _ _ _ cmds) = doList cmds doLists (T_WhileExpression _ _ cmds) = doList cmds doLists (T_UntilExpression _ _ cmds) = doList cmds @@ -2806,7 +2797,7 @@ checkCdAndBack params = doLists warn (getId $ head cds) 2103 message message = - if shell == Bash || shell == Zsh + if shell == Bash then "Consider using ( subshell ), 'cd foo||exit', or pushd/popd instead." else "Consider using ( subshell ) or 'cd foo||exit' instead." @@ -2846,7 +2837,6 @@ checkFunctionDeclarations params (T_Function id (FunctionKeyword hasKeyword) (FunctionParentheses hasParens) _ _) = case shellType params of Bash -> return () - Zsh -> return () Ksh -> when (hasKeyword && hasParens) $ err id 2111 "ksh does not allow 'function' keyword and '()' at the same time." @@ -2939,7 +2929,7 @@ checkInteractiveSu params = checkCommand "su" f prop_checkStderrPipe1 = verify checkStderrPipe "#!/bin/ksh\nfoo |& bar" -prop_checkStderrPipe2 = verifyNot checkStderrPipe "#!/bin/zsh\nfoo |& bar" +prop_checkStderrPipe2 = verifyNot checkStderrPipe "#!/bin/bash\nfoo |& bar" checkStderrPipe params = case shellType params of Ksh -> match @@ -3076,8 +3066,6 @@ checkTildeInPath _ (T_SimpleCommand _ vars _) = isQuoted _ = False checkTildeInPath _ _ = return () -prop_checkUnsupported1 = verifyNot checkUnsupported "#!/bin/zsh\nfunction { echo cow; }" -prop_checkUnsupported2 = verify checkUnsupported "#!/bin/sh\nfunction { echo cow; }" prop_checkUnsupported3 = verify checkUnsupported "#!/bin/sh\ncase foo in bar) baz ;& esac" prop_checkUnsupported4 = verify checkUnsupported "#!/bin/ksh\ncase foo in bar) baz ;;& esac" checkUnsupported params t = @@ -3092,15 +3080,11 @@ checkUnsupported params t = -- TODO: Move more of these checks here shellSupport t = case t of - T_Function _ _ _ "" _ -> ("anonymous functions", [Zsh]) - T_ForIn _ _ (_:_:_) _ _ -> ("multi-index for loops", [Zsh]) - T_ForIn _ ShortForIn _ _ _ -> ("short form for loops", [Zsh]) - T_ProcSub _ "=" _ -> ("=(..) process substitution", [Zsh]) T_CaseExpression _ _ list -> forCase (map (\(a,_,_) -> a) list) otherwise -> ("", []) where forCase seps | CaseContinue `elem` seps = ("cases with ;;&", [Bash]) - forCase seps | CaseFallThrough `elem` seps = ("cases with ;&", [Bash, Ksh, Zsh]) + forCase seps | CaseFallThrough `elem` seps = ("cases with ;&", [Bash, Ksh]) forCase _ = ("", []) @@ -3109,7 +3093,7 @@ getCommandSequences (T_BraceGroup _ cmds) = [cmds] getCommandSequences (T_Subshell _ cmds) = [cmds] getCommandSequences (T_WhileExpression _ _ cmds) = [cmds] getCommandSequences (T_UntilExpression _ _ cmds) = [cmds] -getCommandSequences (T_ForIn _ _ _ _ cmds) = [cmds] +getCommandSequences (T_ForIn _ _ _ cmds) = [cmds] getCommandSequences (T_ForArithmetic _ _ _ _ cmds) = [cmds] getCommandSequences (T_IfExpression _ thens elses) = map snd thens ++ [elses] getCommandSequences _ = [] diff --git a/ShellCheck/Options.hs b/ShellCheck/Options.hs index d1a044c..5966e4a 100644 --- a/ShellCheck/Options.hs +++ b/ShellCheck/Options.hs @@ -1,6 +1,6 @@ module ShellCheck.Options where -data Shell = Ksh | Zsh | Sh | Bash +data Shell = Ksh | Sh | Bash deriving (Show, Eq) data AnalysisOptions = AnalysisOptions { diff --git a/ShellCheck/Parser.hs b/ShellCheck/Parser.hs index 10ac167..7009226 100644 --- a/ShellCheck/Parser.hs +++ b/ShellCheck/Parser.hs @@ -765,11 +765,10 @@ readDollarBracedLiteral = do prop_readProcSub1 = isOk readProcSub "<(echo test | wc -l)" prop_readProcSub2 = isOk readProcSub "<( if true; then true; fi )" -prop_readProcSub3 = isOk readProcSub "=(ls)" readProcSub = called "process substitution" $ do id <- getNextId dir <- try $ do - x <- oneOf "<>=" + x <- oneOf "<>" char '(' return [x] allspacing @@ -1358,7 +1357,6 @@ prop_readSimpleCommand3 = isOk readSimpleCommand "export foo=(bar baz)" prop_readSimpleCommand4 = isOk readSimpleCommand "typeset -a foo=(lol)" prop_readSimpleCommand5 = isOk readSimpleCommand "time if true; then echo foo; fi" prop_readSimpleCommand6 = isOk readSimpleCommand "time -p ( ls -l; )" -prop_readSimpleCommand7 = isOk readSimpleCommand "cat =(ls)" readSimpleCommand = called "simple command" $ do id1 <- getNextId id2 <- getNextId @@ -1634,7 +1632,6 @@ prop_readForClause7 = isOk readForClause "for ((;;)) do echo $i\ndone" prop_readForClause8 = isOk readForClause "for ((;;)) ; do echo $i\ndone" prop_readForClause9 = isOk readForClause "for i do true; done" prop_readForClause10= isOk readForClause "for ((;;)) { true; }" -prop_readForClause11= isOk readForClause "for a b in *; do echo $a $b; done" prop_readForClause12= isWarning readForClause "for $a in *; do echo \"$a\"; done" readForClause = called "for loop" $ do pos <- getPosition @@ -1663,25 +1660,10 @@ readForClause = called "for loop" $ do readRegular id pos = do acceptButWarn (char '$') ErrorC 1086 "Don't use $ on the iterator name in for loops." - names <- readNames - readShort names <|> readLong names - where - readLong names = do - values <- readInClause <|> (optional readSequentialSep >> return []) - group <- readDoGroup pos - return $ T_ForIn id NormalForIn names values group - readShort names = do - char '(' - allspacing - words <- many (readNormalWord `thenSkip` allspacing) - char ')' - allspacing - command <- readAndOr - return $ T_ForIn id ShortForIn names words [command] - - readNames = - reluctantlyTill1 (readVariableName `thenSkip` spacing) $ - disregard g_Do <|> disregard readInClause <|> disregard readSequentialSep + name <- readVariableName `thenSkip` spacing + values <- readInClause <|> (optional readSequentialSep >> return []) + group <- readDoGroup pos + return $ T_ForIn id name values group prop_readSelectClause1 = isOk readSelectClause "select foo in *; do echo $foo; done" prop_readSelectClause2 = isOk readSelectClause "select foo; do echo $foo; done" @@ -1802,7 +1784,7 @@ readFunctionDefinition = called "function" $ do g_Rparen return () - readFunctionName = many functionChars + readFunctionName = many1 functionChars prop_readCoProc1 = isOk readCoProc "coproc foo { echo bar; }" prop_readCoProc2 = isOk readCoProc "coproc { echo bar; }" @@ -1892,9 +1874,6 @@ readAssignmentWord = try $ do pos <- getPosition optional (char '$' >> parseNote ErrorC 1066 "Don't use $ on the left side of assignments.") variable <- readVariableName - notFollowedBy2 $ do -- Special case for zsh =(..) syntax - spacing1 - string "=(" optional (readNormalDollar >> parseNoteAt pos ErrorC 1067 "For indirection, use (associative) arrays or 'read \"var$n\" <<< \"value\"'") index <- optionMaybe readArrayIndex @@ -2093,8 +2072,8 @@ readScript = do verifyShell pos s = case isValidShell s of Just True -> return () - Just False -> parseProblemAt pos ErrorC 1071 "ShellCheck only supports Bourne based shell scripts, sorry!" - Nothing -> parseProblemAt pos InfoC 1008 "This shebang was unrecognized. Note that ShellCheck only handles Bourne based shells." + Just False -> parseProblemAt pos ErrorC 1071 "ShellCheck only supports sh/bash/ksh scripts. Sorry!" + Nothing -> parseProblemAt pos InfoC 1008 "This shebang was unrecognized. Note that ShellCheck only handles sh/bash/ksh." isValidShell s = let good = s == "" || any (`isPrefixOf` s) goodShells @@ -2108,9 +2087,10 @@ readScript = do goodShells = [ "sh", + "ash", + "dash", "bash", - "ksh", - "zsh" + "ksh" ] badShells = [ "awk", @@ -2118,7 +2098,8 @@ readScript = do "perl", "python", "ruby", - "tcsh" + "tcsh", + "zsh" ] readUtf8Bom = called "Byte Order Mark" $ string "\xFEFF" diff --git a/shellcheck.1.md b/shellcheck.1.md index 7f40062..f7251c6 100644 --- a/shellcheck.1.md +++ b/shellcheck.1.md @@ -16,6 +16,20 @@ errors and pitfalls where the shell just gives a cryptic error message or 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: + + (( area = 3.14*r*r )) + ++ For scripts starting with `#!/bin/sh` (or when using `-s sh`), ShellCheck +will warn that `(( .. ))` is not POSIX compliant (similar to checkbashisms). + ++ For scripts starting with `#!/bin/bash` (or using `-s bash`), ShellCheck +will warn that decimals are not supported. + ++ For scripts starting with `#!/bin/ksh` (or using `-s ksh`), ShellCheck will +not warn at all, as `ksh` supports decimals in arithmetic contexts. + + # OPTIONS **-e**\ *CODE1*[,*CODE2*...],\ **--exclude=***CODE1*[,*CODE2*...] @@ -32,9 +46,9 @@ corner cases can cause delayed failures. **-s**\ *shell*,\ **--shell=***shell* -: Specify Bourne shell dialect. Valid values are *sh*, *bash*, *ksh* and - *zsh*. The default is to use the file's shebang, or *bash* if the target - shell can't be determined. +: Specify Bourne shell dialect. Valid values are *sh*, *bash* and *ksh*. + The default is to use the file's shebang, or *bash* if the target shell + can't be determined. **-V**\ *version*,\ **--version** @@ -83,11 +97,12 @@ corner cases can cause delayed failures. [ { - "line": line, - "column": column, - "level": level, - "code": ####, - "message": message + "file": "filename", + "line": lineNumber, + "column": columnNumber, + "level": "severitylevel", + "code": errorCode, + "message": "warning message" }, ... ] @@ -104,6 +119,14 @@ For example, to suppress SC2035 about using `./*.jpg`: # shellcheck disable=SC2035 echo "Files: " *.jpg +Here a shell brace group is used to suppress on multiple lines: + + # shellcheck disable=SC2016 + { + echo 'Modifying $PATH' + echo 'PATH=foo:$PATH' >> ~/.bashrc + } + Valid keys are: **disable** diff --git a/shellcheck.hs b/shellcheck.hs index 0a83e1e..7111c83 100644 --- a/shellcheck.hs +++ b/shellcheck.hs @@ -57,7 +57,7 @@ options = [ Option "f" ["format"] (ReqArg (Flag "format") "FORMAT") "output format", Option "s" ["shell"] - (ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (bash,sh,ksh,zsh)", + (ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (bash,sh,ksh)", Option "V" ["version"] (NoArg $ Flag "version" "true") "Print version information" ]