Skip to content

Generated bash completion is not ShellCheck-clean #9400

@drindt

Description

@drindt

What is the problem?

The generated Bash completion script from rclone completion bash - is not clean under ShellCheck.
One finding is an error (SC2145) and the generated script exits ShellCheck with status 1.

This matters for environments that keep generated shell completions under dotfile management and lint
all sourced shell fragments.

What is your rclone version?

rclone v1.73.0
- os/version: fedora 43 (64 bit)
- os/kernel: 6.19.14-200.fc43.x86_64 (x86_64)
- os/type: linux
- os/arch: amd64
- go/version: go1.25.6 X:nodwarf5
- go/linking: dynamic
- go/tags: none

Which OS you are using and how many bits?

Fedora 43, amd64.

Which cloud storage system are you using?

Not storage-backend specific. This is about generated Bash completion.

The command you were trying to run

rclone completion bash - | shellcheck -s bash -

What happened?

ShellCheck exits with status 1. The generated script reports, among others:

SC2048: Use "${array[@]}" (with quotes) to prevent whitespace problems.
SC2086: Double quote to prevent globbing and word splitting.
SC2034: x appears unused. Verify use (or export if used externally).
SC2145: Argument mixes string and array. Use * or separate argument.
SC2053: Quote the right-hand side of == in [[ ]] to prevent glob matching.
SC2295: Expansions inside ${..} need to be quoted separately, otherwise they match as patterns.

The SC2145 case is in __rclone_reprint_commandLine():

printf "%s" "${PS1@P}${COMP_LINE[@]}"

COMP_LINE is a scalar Bash completion variable, so treating it as an array is unnecessary and
triggers ShellCheck.

Patch sketch

This downstream patch made the generated completion ShellCheck-clean locally:

--- rclone-completion-before.sh
+++ rclone-completion-after.sh
@@ -112,9 +112,7 @@
         # File extension filtering
         local fullFilter="" filter filteringCmd
 
-        # Do not use quotes around the $completions variable or else newline
-        # characters will be kept.
-        for filter in ${completions[*]}; do
+        for filter in "${completions[@]}"; do
             fullFilter+="$filter|"
         done
 
@@ -147,7 +145,7 @@
 __rclone_handle_activeHelp() {
     # Print the activeHelp statements
     if ((${#activeHelp[*]} != 0)); then
-        if [ -z $COMP_TYPE ]; then
+        if [ -z "${COMP_TYPE:-}" ]; then
             # Bash v3 does not set the COMP_TYPE variable.
             printf "\n";
             printf "%s\n" "${activeHelp[@]}"
@@ -157,7 +155,7 @@
         fi
 
         # Only print ActiveHelp on the second TAB press
-        if [ $COMP_TYPE -eq 63 ]; then
+        if [ "$COMP_TYPE" -eq 63 ]; then
             printf "\n"
             printf "%s\n" "${activeHelp[@]}"
 
@@ -185,7 +183,7 @@
                 # not be doing it itself.
                 __rclone_reprint_commandLine
             fi
-        elif [ $COMP_TYPE -eq 37 ] || [ $COMP_TYPE -eq 42 ]; then
+        elif [ "$COMP_TYPE" -eq 37 ] || [ "$COMP_TYPE" -eq 42 ]; then
             # For completion type: menu-complete/menu-complete-backward and insert-completions
             # the completions are immediately inserted into the command-line, so we first
             # print the activeHelp message and reprint the command-line since the shell won't.
@@ -200,12 +198,12 @@
 __rclone_reprint_commandLine() {
     # The prompt format is only available from bash 4.4.
     # We test if it is available before using it.
-    if (x=${PS1@P}) 2> /dev/null; then
-        printf "%s" "${PS1@P}${COMP_LINE[@]}"
+    if (: "${PS1@P}") 2> /dev/null; then
+        printf "%s%s" "${PS1@P}" "$COMP_LINE"
     else
         # Can't print the prompt.  Just print the
         # text the user had typed, it is workable enough.
-        printf "%s" "${COMP_LINE[@]}"
+        printf "%s" "$COMP_LINE"
     fi
 }
 
@@ -218,7 +216,7 @@
     while IFS='' read -r comp; do
         [[ -z $comp ]] && continue
 
-        if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then
+        if [[ ${comp:0:endIndex} == "$activeHelpMarker" ]]; then
             comp=${comp:endIndex}
             __rclone_debug "ActiveHelp found: $comp"
             if [[ -n $comp ]]; then
@@ -298,7 +296,7 @@
         # characters because those escape characters are part of what the user typed.
         # Don't call "printf" in a sub-shell because it will be much slower
         # since we are in a loop.
-        printf -v comp "%q" "${compline%%$tab*}" &>/dev/null || comp=$(printf "%q" "${compline%%$tab*}")
+        printf -v comp "%q" "${compline%%"$tab"*}" &>/dev/null || comp=$(printf "%q" "${compline%%"$tab"*}")
 
         # Only consider the completions that match
         [[ $comp == "$cur"* ]] || continue
@@ -312,7 +310,7 @@
         # Strip any description before checking the length, and again, don't escape
         # the completion because this length is only used when printing the completions
         # in a list and we don't want show escape characters in that list.
-        comp=${compline%%$tab*}
+        comp=${compline%%"$tab"*}
         if ((${#comp}>longest)); then
             longest=${#comp}
         fi
@@ -325,7 +323,7 @@
         __rclone_debug "Removed description from single completion, which is now: ${COMPREPLY[0]}"
     else
         # Format the descriptions
-        __rclone_format_comp_descriptions $longest
+        __rclone_format_comp_descriptions "$longest"
     fi
 }
 
@@ -334,7 +332,7 @@
     local comp="$1"
     local char=$2
     if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
-        local word=${comp%"${comp##*${char}}"}
+        local word=${comp%"${comp##*"${char}"}"}
         local idx=${#COMPREPLY[*]}
         while ((--idx >= 0)); do
             COMPREPLY[idx]=${COMPREPLY[idx]#"$word"}
@@ -354,8 +352,8 @@
         # Properly format the description string which follows a tab character if there is one
         if [[ "$comp" == *$tab* ]]; then
             __rclone_debug "Original comp: $comp"
-            desc=${comp#*$tab}
-            comp=${comp%%$tab*}
+            desc=${comp#*"$tab"}
+            comp=${comp%%"$tab"*}
 
             # $COLUMNS stores the current shell width.
             # Remove an extra 4 because we add 2 spaces and 2 parentheses.
@@ -391,6 +389,8 @@
 __start_rclone()
 {
     local cur prev words cword split
+    # shellcheck disable=SC2034
+    : "$prev" "$split"
 
     COMPREPLY=()

If this completion code comes from Cobra templates rather than rclone-specific code, this may need
to be forwarded upstream to Cobra.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions