2

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.

José
  • 354
  • 5
  • 18

2 Answers2

5

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
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
  • 1
    With bash or ksh, this works: `files=( *irt* ); if (( ${#files[@]} > 0 )); then echo TEST; fi` -- expand the pattern, store the filenames in an array, check the size of the array. – glenn jackman Dec 17 '17 at 16:33
  • Fantastic answer by the way. – glenn jackman Dec 17 '17 at 16:34
  • Your explanation covered it precisely (the single/double square bracket problem)! I also never knew double square bracket was a keyword. As a side note, I actually stumbled upon this problem while testing comparison commands (considering the real result will be multi-lined and/or with special characters) which doesn't cover exactly what I was really doing. The script will actually be saving the result of `git pull` in `r` for every repository inside a give directory (reason for the mentioned loop) meant to find not up to date repositories to make up to date packages to be reinstalled. – José Dec 17 '17 at 17:08
1

Since you're checking for files with a certain file name, I'd suggest to use find explicitly. Something like

r="$(find . -name '*irt*' 2> /dev/null)"
if [ ! -z "$r" ]; then
  echo "found: $r"
fi
Pavel
  • 7,436
  • 2
  • 29
  • 42
  • 2
    Storing a list of names in a string is extremely bad form. There's no way to tell when each name starts and stops, since names containing newline literals (as well as more conventional whitespace) are completely valid. – Charles Duffy Dec 17 '17 at 16:00