Search code examples
linuxbashshelldouble-quotessquare-bracket

What would cause the BASH error "[: too many arguments" after taking measures for special characters in strings?


I'm writing a simple script to check some repositories updates and, if needed, I'm making new packages from these updates to install new versions of those programs it refers to (in Arch Linux). So I made some testing before executing the real script.

The problem is that I'm getting the error [: excessive number of arguments (but I think the proper translation would be [: too many arguments) from this piece of code:

# Won't work despite the double quoted $r
if [ "$r" == *"irt"* ]; then
    echo "TEST"
fi

The code is fixed by adding double square brackets which I did thanks to this SO answer made by @user568458:

# Makes the code works
if [[ "$r" == *"irt"* ]]; then
    echo "TEST"
fi

Note that $r is defined by:

# Double quotes should fix it, right? Those special characters/multi-lines
r="$(ls)"

Also note that everything is inside a loop and the loop progress with success. The problems occurs every time the if comparison matches, not printing the "TEST" issued, jumping straight to the next iteration of the loop (no problem: no code exists after this if).

My question is: why would the error happens every time the string matches? By my understanding, the double quotes would suffice to fix it. Also, If I count on double square brackets to fix it, some shells won't recognize it (refers to the answer mentioned above). What's the alternative?

Shell scripting seems a whole new programming paradigm.. I never quite grasp the details and fail to secure a great source for that.


Solution

  • The single bracket is a shell builtin, as opposed to the double bracket which is a shell keyword. The difference is that a builtin behaves like a command: word splitting, file pattern matching, etc. occur when the shell parses the command. If you have files that match the pattern *irt*, say file1irt.txt and file2irt.txt, then when the shell parses the command

    [ "$r" = *irt* ]
    

    it expands $r, matches all files matching the pattern *irt*, and eventually sees the command:

    [ expansion_of_r = file1irt.txt file2irt.txt ]
    

    which yields an error. No quotes can fix that. In fact, the single bracket form can't handle pattern matching at all.

    On the other hand, the double brackets are not handled like commands; Bash will not perform any word splitting nor file pattern matching, so it really sees

    [[ "expansion_of_r" = *irt* ]]
    

    In this case, the right hand side is a pattern, so Bash tests whether the left hand side matches that pattern.


    For a portable alternative, you can use:

    case "$r" in
        (*irt*) echo "TEST" ;;
    esac
    

    But now you have a horrible anti-pattern here. You're doing:

    r=$(ls)
    if [[ "$r" = *irt* ]]; then
        echo "TEST"
    fi
    

    What I understand is that you want to know whether there are files matching the pattern *irt* in the current directory. A portable possibility is:

    for f in *irt*; do
        if [ -e "$f" ]; then
            echo "TEST"
            break
        fi
    done