diff --git a/autocomplete/bash_autocomplete b/autocomplete/bash_autocomplete index b7442e10aa..42eb17b8b2 100755 --- a/autocomplete/bash_autocomplete +++ b/autocomplete/bash_autocomplete @@ -66,7 +66,7 @@ __%[1]s_bash_autocomplete() { local description="" if [[ "${line}" == *:* ]]; then - token="${line%%:*}" + token="${line%%%%:*}" description="${line#*:}" fi diff --git a/completion_test.go b/completion_test.go index 2da7f622e2..a608e53021 100644 --- a/completion_test.go +++ b/completion_test.go @@ -158,6 +158,33 @@ func TestCompletionBashAppendsSpace(t *testing.T) { r.Contains(output, "complete -o bashdefault -o default -F __myapp_bash_autocomplete myapp") } +func TestCompletionBashGreedyColonParsing(t *testing.T) { + // Regression test for https://github.com/urfave/cli/issues/2335 + // The bash completion template uses fmt.Sprintf, so + // literal "%" in the template must be escaped as "%%". The token + // extraction must use the greedy ${line%%:*} (double %%) to split on + // the *first* colon. A single % would use ${line%:*} which splits on + // the *last* colon, breaking descriptions that contain colons + // (e.g. "export:Export configs such as: compose-config"). + + cmd := &Command{ + EnableShellCompletion: true, + } + + r := require.New(t) + + bashRender := shellCompletions["bash"] + r.NotNil(bashRender, "bash completion renderer should exist") + + output, err := bashRender(cmd, "myapp") + r.NoError(err) + + // After fmt.Sprintf, the rendered script must contain ${line%%:*} + // (greedy match) not ${line%:*} (non-greedy match). + r.Contains(output, `${line%%:*}`, "token extraction should use greedy %% to match first colon") + r.NotContains(output, `${line%:*}`, "token extraction must not use non-greedy single % (splits on last colon)") +} + func TestCompletionFishFormat(t *testing.T) { // Regression test for https://github.com/urfave/cli/issues/2285 // Fish completion was broken due to incorrect format specifiers