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

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.

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

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

Related

Understanding and Clarification on AIX and Batch File

I am new to AIX and I have trouble understanding the codes stated in the shell script as shown below, I have a few questions.
if [ "$OutChlName" != "" ] ; then
echo START CHANNEL \($OutChlName\)
fi
For the first line, what does the "" mean, does it mean null?
\($OutChlName\) - is there any way to convert this to a batch file format.
Is it right to say that fi is the end tag of if?
Thank you.
The echo is only wanted when the variable OutChkName is filled.
The string is compared with an empty string.
When you read man test, you can find the alternative if [ -n "$OutChlName" ].
echo with double quotes
Within quotes you do not need the backslashes.
echo "START CHANNEL ($OutChlName)"
My echo behaves different when the OutChlName variable has special characters like newlines or *. I think my syntax is a slight bugfix, but when you do not want to change the original behaviour, you can use
echo "START CHANNEL ("$OutChlName")"
Using backslashes is also a valid syntax (batch format).
fi ends if
Also esac ends case, and done ends do (Odd, it should have been od).

comparison [ "$var" == "value" ] always false; how can this be debugged?

I'm having a ton of trouble with an if statement in bash. Here's what I have:
if [ "$returncode" == "HTTP/1.1 200 OK" ]; then
echo "Works!"
fi
This will not work successfully. I've verified over and over again that the variable $returncode is equal to the text specified, I'll print the two values right before this and they are definitely identical.
I've tried a couple different variations of this statement, all with no luck. I feel like a moron because this should be extremely simple! What do you think is wrong here?
If the values truly are identical (no hidden characters), the most likely problem is that your shell isn't actually bash.
/bin/sh only promises compliance with the POSIX sh standard, and == isn't a valid comparison operator in POSIX test; the standard-compliant string comparison operator is =.
Also try eliminating a CR from the end of the line:
#!/bin/bash
# ^^^^ NOT /bin/sh ($'...' is a non-POSIX extension)
# ...and if starting with an explicit interpreter, "bash yourscript", not "sh yourscript"
set -x # this will log each command run to stderr, allowing easy debugging
if [ "${returncode%$'\r'}" = "HTTP/1.1 200 OK" ]; then
echo "Works!"
fi
${foo%val} expands to the contents of $foo with any suffix consisting of val removed; $'\r' is, in bash, a constant referring to the ASCII CR character, which would be present if reading a CRLF-terminated string using standard UNIX tools (which expect LF-only newlines).

bash script: if use with (conditon) or [condition]

I have a subtle question about if statement in bash script: what type of parenthesis should follow "if"?
I have two statements both working right now, but not working if I exchange () and [].
1.compare variable name
NAME="dada"
if [ "$NAME" == "dada" ]; then SOME COMMANDS; fi
If change [] to (), error:
dada: command not found
2.using grep
if ( grep -q "lalala" mytest.txt ); then SOME COMMANDS; fi
If change () to [], error:
[: lalala: binary operator expected
I read the linux man, which suggest using []. So is it because of using grep? Can we say generally if using another program inside if, we should use ()?
A side question about double quotation marks,
is it necessary to quote text in shell? for example, echo "$NAME" or NAME="fadfaj"?
I got confused because when I try to use $NAME plus some special characters such as "_" or space, where I need to use "\", I got different behaviour with and without "".
NAME="myfolder"
echo $NAME\_na
echo "$NAME\_na"
output
myfolder_na
myfolder\_na
I understand these are naive questions but I do appreciate all your help!
The thing following the if keyword is a command. The command is executed, and the condition is true if the command succeeds (exits with a status of 0), false if it fails.
Though it looks like shell syntax, the [ symbol is actually the name of a command (which happens to be built into the shell). For example, you could type just
[ "$NAME" = "dada" ]
at a shell prompt, with no if, and it would evaluate the same condition and set $?. You can also do this:
[ "$NAME" = "dada" ] && echo Yes
The ] is a required last argument to the [ command. Apart from that requirement, the [ command is equivalent to the test command:
if test "$Name" = "data" ; then echo yes ; fi
Note that I've used = rather than ==. The built-in test/[ command in bash recognizes either form, but = is the original syntax, and some implementations don't recognize ==.
[ is the most common command to use with if, but you can use any command, including `grep:
if grep -q "lalala" mytest.txt; then SOME COMMANDS; fi
The parentheses are unnecessary. (If you add them, you're using a compound command rather than a simple command; there's no need to do so.)
See the bash manual (info bash) for more information. Search for
`test'
(with the backtick and apostrophe) for information on the built-in test command.
Bash also has a [[ command which has some advantages over the older [.

Bourne Shell Script test throws error with two of same file-type in folder?

I am writing a Bourne Script and am looking to select files that match certain regular expressions. I am doing this with an if test structure, and it identifies a file ending in ".o". However, when there are two files in a directory which I am searching that end in ".o" I get the following error: "expr: syntax error". How could this be possible?
if test "`expr \"$file\" : ${SPECIFIED_DIRECTORY}/*.o`" != "0"; then
do something
fi
A regular expression test is almost certainly the wrong tool for the job here, but let's assume that specified_directory (lower-case by convention to avoid conflicts with environment variables and builtins) contained a regex to match a directory name, as opposed to a literal name, and thus actually was the right tool. If that were the case, you'd want to write...
if expr "$file" : "$specified_directory"/'.*[.]o' >/dev/null; then
...
fi
No test command, no subshell. Keep it simple.
If you don't escape the . (in this case, by making it a character class, [.]), it has its normal regular-expression meaning, of matching exactly one character. Similarly, .* is the way to match zero-or-more of any character in a regex, not bare * (which is the fnmatch syntax).
This approach works on any POSIX shell, and calls no tools not available built-in to the shell, thus making it efficient at runtime.
contains_any_files() {
set -- "$1"/*
[ "$#" -gt 0 ] && [ -f "$1" ]
}
if contains_any_files "$dir"; then
...
fi
If I understand correctly, if there are any .o files in the directory, then do something:
if find "$dir"/*.o >/dev/null 2>&1; then
# do something
fi
The find command will exit with success only if there are any matching files. Otherwise it will exit with failure, and the then block won't be executed. The >/dev/null 2>&1 is to hide stdout and stderr.

bash double bracket issue

I'm very new to bash scripting and am running into an issue when using double brackets. I can't seem to get them to work at all in Ubuntu Server 11.10. My script below is in if_test.sh.
#!/bin/bash
if [[ "14"=="14" ]]; then
echo "FOO"
fi
When I run this simple shell script the output I get is: if_test.sh: 5: [[: not found
It seems that I'm running GNU bash version 4.2.10 after running bash --version from the terminal. Any help would be greatly appreciated. Thanks!
The problem lies in your script invocation. You're issuing:
$ sudo sh if_test.sh
On Ubuntu systems, /bin/sh is dash, not bash, and dash does not support the double bracket keyword (or didn't at the time of this posting, I haven't double-checked). You can solve your problem by explicitly invoking bash instead:
$ sudo bash if_test.sh
Alternatively, you can make your script executable and rely on the shebang line:
$ chmod +x if_test.sh
$ sudo ./if_test.sh
Also note that, when used between double square brackets, == is a pattern matching operator, not the equality operator. If you want to test for equality, you can either use -eq:
if [[ "14" -eq "14" ]]; then
echo "FOO"
fi
Or double parentheses:
if (( 14 == 14 )); then
echo "FOO"
fi
My answer doesn't apply to #lots_of_questions's question specifically, but you can also run into this problem if you have the wrong specifier at the top of your script:
#!/bin/sh
if [[ ... ]]
...
You should change that to
#!/bin/bash
if [[ ... ]]
...
Since you are new to scripting, you may be unaware that [[ is a bashism. You may not even know what a bashism is, but both answers given so far are leading you down a path towards a stunted scripting future by promoting their use.
To check if a variable matches a string in any flavor of Bourne shell, you can do test $V = 14 If you want to compare integers, use test $V -eq 14. The only difference is that the latter will generate an error if $V does not look like an integer. There are good reasons to quote the variable (test "$V" = 14), but the quotes are often unnecessary and I believe are the root cause of a common confusion, since "14"=="14" is identical to "14==14" where it is more obvious that '==' is not being used as an operator.
There are several things to note: use a single '=' instead of '==' because not all shells recognize '==', the [ command is identical to test but requires a final argument of ] and many sh coding guidelines recommend using test because it often generates more understandable code, [[ is only recognized by a limited number of shells (this is your primary problem, as your shell does not appear to recognize [[ and is looking for a command of that name. This is surprising if your shebang does indeed specify /bin/bash instead of /bin/sh).

Resources