Why does if [ !$(grep -q) ] not work when if grep -q does? - linux

I'm having trouble getting grep to work properly in an if statement. In the following code segment, the if-check always comes up true (i.e. the word is not found), and the program prints NOT FOUND, even though the words are already in ~/.memory.
for (( i=0; i<${#aspellwords[*]}; i++)); do
if [ !$(grep -q "${aspellwords[$i]}" ~/.memory) ]; then
words[$i]="${aspellwords[$i]}"
printf "\nNOT FOUND\n"
fi
done
However, when I test the following code in place of the previous segment:
for (( i=0; i<${#aspellwords[*]}; i++)); do
if grep -q "${aspellwords[$i]}" ~/.memory; then echo FOUND IT; fi
done
It works perfectly fine and finds the word without any issues.
So what's wrong with the first segment of code?

A number of things are wrong with that first snippet.
You don't want [ ... ] if you want to test the return code. Drop those.
[] is not part of the if syntax (as you can see from your second snippet).
[ is a shell built-in and binary on your system. It just exits with a return code. if ...; then tests the return code of ....
$() is command substitution. It replaces itself with the output from the command that was run.
So [ !$(grep ...) ] is actually evaluating [ !output_from_grep ] and [ word ] is interpreted as [ -n word ] which will be true whenever word is non-empty. Given that ! is never non-empty that will always be true.
Simply, as indicated by #thom in his comment (a bit obliquely), add the ! negation to your second snippet with a space between it and grep.

Related

Shell Scripting - How to mock some results based on an input?

I have a small scripts which verifies some conditions on a database server. I want to mock failures on all of those conditions to test the script, so I added the following line:
./print_results ${VAR1} ${VAR2} ... ${VARN}
If any of the variables has a value different than ZERO it because it failed.
so just for testing purpouses I added the line:
VAR1=1 ; VAR2=1 ; ... ; VARN=1
But I need to edit the file every time I want to replace the real results with the fake ones.
What's wrong with this?
[! -z $1 ] && [ "$1" == "Y"] && { echo "Debugging is ACTIVE" ; VAR1=1 ; ... ; VAR2=1 ; }
I want to have the VAR1..N = 1 after passing that line.
Thanks.
The problem is that [ is a command, but [! is not. It is probably cleaner to write your code:
test "{$1}" == Y && { echo "Debugging is ACTIVE"; VAR1=1 VAR2=1 ...; }
No need for semi-colons between the variable assignments, but they don't hurt.
This is one of the warts of sh. For some reason, it was thought to be a good idea to use the symbol [ for a command and pass it ] as an argument, trying to mimic braces in the language. Unfortunately, this leads to a great deal of confusion similar to that demonstrated in this question. It is far better to avoid [ completely and always spell it test. These two are functionally identical (except that the [ command must have ] as the final argument), and using test is much cleaner. (Would you expect test! to work?, or would you recognize that it needs to be written as ! test?)
Need a space between the "Y" and the ]. The non-zero test is pointless, but also requires a space between the [ and the !.
[ "$1" == "Y" ] && { echo "Debugging is ACTIVE" ; VAR1=1 ; ... ; VAR2=1 ; }
Also did you consider just writing this as an if...fi block?
bash provides a way to supply default values for parameters that aren't otherwise set. Presumably, your code has lines like
VAR1=$1
VAR2=$2
VAR3=$3
Replace them with
VAR1=${1-1}
VAR2=${2-1}
VAR3=${3-1}
If $1 is unset, for instance, VAR1 will be assigned the value of 1 instead of the value of $1.

Having trouble with simple Bash if/elif/else statement

I'm writing bash scripts that need to work both on Linux and on Mac.
I'm writing a function that will return a directory path depending on which environment I'm in.
Here is the pseudo code:
If I'm on a Mac OS X machine, I need my function to return the path:
/usr/local/share/
Else if I'm on a Linux machine, I need my function to return the path:
/home/share/
Else, you are neither on a Linux or a Mac...sorry.
I'm very new to Bash, so I apologize in advance for the really simple question.
Below is the function I have written. Whether I'm on a Mac or Linux, it always returns
/usr/local/share/
Please take a look and enlighten me with the subtleties of Bash.
function get_path(){
os_type=`uname`
if [ $os_type=="Darwin" ]; then
path="/usr/local/share/"
elif [ $os_type=="Linux" ]; then
path="/home/share/"
else
echo "${os_type} is not supported"
exit 1
fi
echo $path
}
You need spaces around the operator in a test command: [ $os_type == "Darwin" ] instead of [ $os_type=="Darwin" ]. Actually, you should also use = instead of == (the double-equal is a bashism, and will not work in all shells). Also, the function keyword is also nonstandard, you should leave it off. Also, you should double-quote variable references (like "$os_type") just in case they contain spaces or any other funny characters. Finally, echoing an error message ("...not supported") to standard output may confuse whatever's calling the function, because it'll appear where it expected to find a path; redirect it to standard error (>&2) instead. Here's what I get with these cleaned up:
get_path(){
os_type=`uname`
if [ "$os_type" = "Darwin" ]; then
path="/usr/local/share/"
elif [ "$os_type" = "Linux" ]; then
path="/home/share/"
else
echo "${os_type} is not supported" >&2
exit 1
fi
echo "$path"
}
EDIT: My explanation of the difference between assignments and comparisons got too long for a comment, so I'm adding it here. In many languages, there's a standard expression syntax that'll be the same when it's used independently vs. in test. For example, in C a = b does the same thing whether it's alone on a line, or in a context like if ( a = b ). The shell isn't like that -- its syntax and semantics vary wildly depending on the exact context, and it's the context (not the number of equal signs) that determines the meaning. Here are some examples:
a=b by itself is an assignment
a = b by itself will run a as a command, and pass it the arguments "=" and "b".
[ a = b ] runs the [ command (which is a synonym for the test command) with the arguments "a", "=", "b", and "]" -- it ignores the "]", and parses the others as a comparison expression.
[ a=b ] also runs the [ (test) command, but this time after removing the "]" it only sees a single argument, "a=b" -- and when test is given a single argument it returns true if the argument isn't blank, which this one isn't.
bash's builtin version of [ (test) accepts == as a synonym for =, but not all other versions do.
BTW, just to make things more complicated bash also has [[ ]] expressions (like test, but cleaner and more powerful) and (( )) expressions (which are totally different from everything else), and even ( ) (which runs its contents as a command, but in a subshell).
You need to understand what [ means. Originally, this was a synonym for the /bin/test command. These are identical:
if test -z "$foo"
then
echo "String '$foo' is null."
fi
if [ -z "$foo" ]
then
echo "String '$foo' is null."
fi
Now, you can see why spaces are needed for all of the parameters. These are parameters and not merely boolean expressions. In fact, the test manpage is a great place to learn about the various tests. (Note: The test and [ are built in commands to the BASH shell.)
if [ $os_type=="Darwin" ]
then
This should be three parameters:
"$os_type"
= and not ==
"Darwin"
if [ "$os_type" = "Darwin" ] # Three parameters to the [ command
then
If you use single square brackets, you should be in the habit to surround your parameters with quotation marks. Otherwise, you will run into trouble:
foo="The value of FOO"
bar="The value of BAR"
if [ $foo != $bar ] #This won't work
then
...
In the above, the shell will interpolate $foo and $bar with their values before evaluating the expressions. You'll get:
if [ The value of FOO != The value of BAR ]
The [ will look at this and realize that neither The or value are correct parameters, and will complain. Using quotes will prevent this:
if [ "$foo" != "$bar" ] #This will work
then
This becomes:
if [ "The value of FOO" != "The value of BAR" ]
This is why it's highly recommended that you use double square brackets for your tests: [[ ... ]]. The test looks at the parameters before the shell interpolates them:
if [[ $foo = $bar ]] #This will work even without quotation marks
Also, the [[ ... ]] allows for pattern matching:
if [[ $os_type = D* ]] # Single equals is supported
then
path="/usr/local/share/"
elif [[ $os_type == L* ]] # Double equals is also supported
then
path="/home/share/"
else
echo "${os_type} is not supported"
exit 1
fi
This way, if the string is Darwin32 or Darwin64, the if statement still functions. Again, notice that there has to be white spaces around everything because these are parameters to a command (actually, not anymore, but that's the way the shell parses them).
Adding spaces between the arguments for the conditionals fixed the problem.
This works
function get_path(){
os_type=`uname`
if [ $os_type == "Darwin" ]; then
path="/usr/local/share/"
elif [ $os_type == "Linux" ]; then
path="/home/share/"
else
echo "${os_type} is not supported"
exit 1
fi
echo $path
}

Conditional statement not working as expected

I am using Konsole on kubuntu 14.04.
I want to take arguments to this shell-script, and pass it to a command. The code is basically an infinite loop, and I want one of the arguments to the inner command to be increased once every 3 iterations of the loop. Ignoring the actual details, here's a gist of my code:
#!/bin/bash
ct=0
begin=$1
while :
do
echo "give: $begin as argument to the command"
#actual command
ct=$((ct+1))
if [ $ct%3==0 ]; then
begin=$(($begin+1))
fi
done
I am expecting the begin variable to be increased every 3 iterations, but it is increasing in the every iteration of the loop. What am I doing wrong?
You want to test with
if [ $(expr $cr % 3) = 0 ]; then ...
because this
[ $ct%3==0 ]
tests whether the string $ct%3==0, after parameter substitution, is not empty. A good way for understanding this is reading the manual for test and look at the semantics when it is given 1, 2, 3 or more arguments. In your original script, it only sees one argument, in mine it sees three. White space is very important in the shell. :-)
In BASH you can completely utilize ((...)) and refactor your script like this:
#!/bin/bash
ct=0
begin="$1"
while :
do
echo "give: $begin as argument to the command"
#actual command
(( ct++ % 3 == 0)) && (( begin++ ))
done

first character of string in shell not working

So I have some code where I'm trying to only do the while loop if the first character of a string isn't or is a certain character.
while IFS=$'\t' read -r -a myArray
do
like=${myArray[0]}
position=${myArray[1]}
while [ ${like:0:1}=="E" ]
file=$like."Rput"
echo "$file"
So when I echo the file, the file name is ##file.output which is a file that I do not want at all. In the sense, I want it to completely skip it.
Could someone tell me what's going on?
Thanks!
It works if while is replaced with an if-then statement:
while IFS=$'\t' read -r -a myArray
do
like=${myArray[0]}
position=${myArray[1]}
if [ "${like:0:1}" == "E" ]
then
file=$like."Rput"
echo "$file"
fi
done
Note also that spaces are important. The following tests first character of like for equality with E:
[ "${like:0:1}" == "E" ]
But, the following does something unrelated:
[ "${like:0:1}"=="E" ]
Since the equal sign here is not separated by spaces, "${like:0:1}"=="E" is interpreted simply as a single string. Tests of a single string return true if the string is nonempty and false if it is empty. Since ${like:0:1}"=="E" is always non-empty, it will always return true.

Compare integer in bash, unary operator expected

The following code gives
[: -ge: unary operator expected
when
i=0
if [ $i -ge 2 ]
then
#some code
fi
why?
Your problem arises from the fact that $i has a blank value when your statement fails. Always quote your variables when performing comparisons if there is the slightest chance that one of them may be empty, e.g.:
if [ "$i" -ge 2 ] ; then
...
fi
This is because of how the shell treats variables. Assume the original example,
if [ $i -ge 2 ] ; then ...
The first thing that the shell does when executing that particular line of code is substitute the value of $i, just like your favorite editor's search & replace function would. So assume that $i is empty or, even more illustrative, assume that $i is a bunch of spaces! The shell will replace $i as follows:
if [ -ge 2 ] ; then ...
Now that variable substitutions are done, the shell proceeds with the comparison and.... fails because it cannot see anything intelligible to the left of -gt. However, quoting $i:
if [ "$i" -ge 2 ] ; then ...
becomes:
if [ " " -ge 2 ] ; then ...
The shell now sees the double-quotes, and knows that you are actually comparing four blanks to 2 and will skip the if.
You also have the option of specifying a default value for $i if $i is blank, as follows:
if [ "${i:-0}" -ge 2 ] ; then ...
This will substitute the value 0 instead of $i is $i is undefined. I still maintain the quotes because, again, if $i is a bunch of blanks then it does not count as undefined, it will not be replaced with 0, and you will run into the problem once again.
Please read this when you have the time. The shell is treated like a black box by many, but it operates with very few and very simple rules - once you are aware of what those rules are (one of them being how variables work in the shell, as explained above) the shell will have no more secrets for you.
Judging from the error message the value of i was the empty string when you executed it, not 0.
I need to add my 5 cents. I see everybody use [ or [[, but it worth to mention that they are not part of if syntax.
For arithmetic comparisons, use ((...)) instead.
((...)) is an arithmetic command, which returns an exit status of 0 if
the expression is nonzero, or 1 if the expression is zero. Also used
as a synonym for "let", if side effects (assignments) are needed.
See: ArithmeticExpression
Your piece of script works just great. Are you sure you are not assigning anything else before the if to "i"?
A common mistake is also not to leave a space after and before the square brackets.

Resources