Skip to content

Commit dd747b2

Browse files
committed
SC2325/SC2326: Warn about ! ! foo and foo | ! bar (fixes #2810)
1 parent 9490b94 commit dd747b2

File tree

3 files changed

+43
-7
lines changed

3 files changed

+43
-7
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## Git
22
### Added
33
- SC2324: Warn when x+=1 appends instead of increments
4+
- SC2325: Warn about multiple `!`s in dash/sh.
5+
- SC2326: Warn about `foo | ! bar` in bash/dash/sh.
46

57
### Fixed
68
- source statements with here docs now work correctly

src/ShellCheck/Checks/ShellSupport.hs

+26
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ checks = [
6060
,checkBraceExpansionVars
6161
,checkMultiDimensionalArrays
6262
,checkPS1Assignments
63+
,checkMultipleBangs
64+
,checkBangAfterPipe
6365
]
6466

6567
testChecker (ForShell _ t) =
@@ -566,5 +568,29 @@ checkPS1Assignments = ForShell [Bash] f
566568
escapeRegex = mkRegex "\\\\x1[Bb]|\\\\e|\x1B|\\\\033"
567569

568570

571+
prop_checkMultipleBangs1 = verify checkMultipleBangs "! ! true"
572+
prop_checkMultipleBangs2 = verifyNot checkMultipleBangs "! true"
573+
checkMultipleBangs = ForShell [Dash, Sh] f
574+
where
575+
f token = case token of
576+
T_Banged id (T_Banged _ _) ->
577+
err id 2325 "Multiple ! in front of pipelines are a bash/ksh extension. Use only 0 or 1."
578+
_ -> return ()
579+
580+
581+
prop_checkBangAfterPipe1 = verify checkBangAfterPipe "true | ! true"
582+
prop_checkBangAfterPipe2 = verifyNot checkBangAfterPipe "true | ( ! true )"
583+
prop_checkBangAfterPipe3 = verifyNot checkBangAfterPipe "! ! true | true"
584+
checkBangAfterPipe = ForShell [Dash, Sh, Bash] f
585+
where
586+
f token = case token of
587+
T_Pipeline _ _ cmds -> mapM_ check cmds
588+
_ -> return ()
589+
590+
check token = case token of
591+
T_Banged id _ ->
592+
err id 2326 "! is not allowed in the middle of pipelines. Use command group as in cmd | { ! cmd; } if necessary."
593+
_ -> return ()
594+
569595
return []
570596
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])

src/ShellCheck/Parser.hs

+15-7
Original file line numberDiff line numberDiff line change
@@ -2296,14 +2296,18 @@ readSource t = return t
22962296
prop_readPipeline = isOk readPipeline "! cat /etc/issue | grep -i ubuntu"
22972297
prop_readPipeline2 = isWarning readPipeline "!cat /etc/issue | grep -i ubuntu"
22982298
prop_readPipeline3 = isOk readPipeline "for f; do :; done|cat"
2299+
prop_readPipeline4 = isOk readPipeline "! ! true"
2300+
prop_readPipeline5 = isOk readPipeline "true | ! true"
22992301
readPipeline = do
23002302
unexpecting "keyword/token" readKeyword
2301-
do
2302-
(T_Bang id) <- g_Bang
2303-
pipe <- readPipeSequence
2304-
return $ T_Banged id pipe
2305-
<|>
2306-
readPipeSequence
2303+
readBanged readPipeSequence
2304+
2305+
readBanged parser = do
2306+
pos <- getPosition
2307+
(T_Bang id) <- g_Bang
2308+
next <- readBanged parser
2309+
return $ T_Banged id next
2310+
<|> parser
23072311

23082312
prop_readAndOr = isOk readAndOr "grep -i lol foo || exit 1"
23092313
prop_readAndOr1 = isOk readAndOr "# shellcheck disable=1\nfoo"
@@ -2359,7 +2363,7 @@ readTerm = do
23592363

23602364
readPipeSequence = do
23612365
start <- startSpan
2362-
(cmds, pipes) <- sepBy1WithSeparators readCommand
2366+
(cmds, pipes) <- sepBy1WithSeparators (readBanged readCommand)
23632367
(readPipe `thenSkip` (spacing >> readLineBreak))
23642368
id <- endSpan start
23652369
spacing
@@ -2389,6 +2393,10 @@ readCommand = choice [
23892393
]
23902394

23912395
readCmdName = do
2396+
-- If the command name is `!` then
2397+
optional . lookAhead . try $ do
2398+
char '!'
2399+
whitespace
23922400
-- Ignore alias suppression
23932401
optional . try $ do
23942402
char '\\'

0 commit comments

Comments
 (0)