linux compare two variable - bash

The script found error but it always goes to Else condition "No Found Error". Am I missing how to compare two variables?
ERROR="Error:"
for i in `find /logs -mtime -1`
do
CHECK=`cat $i |grep -i "Error"|cut -f 1 -d " "`
if [ "$CHECK" == $ERROR ]
then
echo "Found Error"
else
echo "Not Found Error"
fi
done

Did you tried something like if [[ "$CHECK" == $ERROR ]] ?
To simply detect error without printing the error message, you can use
CHECK=$(cat $i | grep "Error" | wc -l)
if [[ $CHECK -ne 0 ]]
then
echo "Found error"
else
echo "Not found error"
fi

You are using grep -i for case-insensitive matching, but then testing the result for exact equality with the string Error:. If the case-insensitive matching is important then the exact equality test is not an appropriate complement.
You are also capturing a potentially multi-line output and comparing it to a string that can be the result only of a single-line output.
And you are matching "Error:" anywhere on the line, but assuming that it will appear at the beginning.
Overall, you are going about this a very convoluted way, as grep tells you via its exit status whether it found any matches. For example:
#!/bin/bash
for log in `find /logs -mtime -1`; do
if grep -i -q '^Error:' "$log"; then
echo "Found Error"
else
echo "Not Found Error"
fi
done

There is two things that I would advise and may fix your issue:
Add #!/bin/bash on the first line, to make sure it is interpreted as bash and not sh. Many time I had trouble with comparison because of this
When comparing two variables, uses double brackets ([[ and ]]) Also, if it strings, always put quotes "$ERROR" around it. It's missing for the $ERROR variable.
Look at the other answers also, there are many ways to do the same thing in a much simpler way.
Note: When comparing numbers you should use -eq

Related

Syntax error: "then" unexpected (expecting "done")

Here is my problem statement.
Write a shell script that takes a name of a folder as a command line argument, and produce a file that contains the names of all sub folders with size 0 (that is empty sub folders)
This is my shell script.
ls $1
while read folder
do
files = 'ls $folder | wc -l'
if[$files -eq 0];
then
echo "$folder">>output.txt
echo "File deleted"
else
echo "File is not empty"
fi
done
When I execute my command (using 'sh filename'), it shows syntax error!
Syntax error: "then" unexpected (expecting "done")
Is there any wrong with my script?
Don't forget, in shell [ is a binary that take parameters and return true or false (0 or 1).
if is a keyword that verifies the return of next binary called is true (0).
So, when you do
if[$files -eq 0]
Your shell understand nothing because it try to launch the if[2 programm, and he find a then after without detecting the if.
For fix your problem, you have to put a space after your if and after the [ because binary must have a space between between his name and their arguments.
ls $1
while read folder
do
files = `ls $folder | wc -l`
if [ $files -eq 0 ]
then
echo "$folder">>output.txt
echo "File deleted"
else
echo "File is not empty"
fi
done
Try something like this
ls $1
while read folder
do
files=`ls $folder | wc -l`
if [ $files -eq 0 ]; then
echo "$folder">>output.txt
echo "File deleted"
else
echo "File is not empty"
fi
done
Notice no space files=.., and there is `(back tick) not '(single quote)
Notice space between 'if' and '[' ...
There may be spacing error:
Just do 2 steps:
run hexdump -C yourscript.sh
run cat yourscript.sh | tr -d '\r' >> yournewscript.sh
it will create new correct file then run new file.
You've already got answers describing how your existing script needs to be fixed:
no spaces around the = when you set the $files variable,
backquotes instead of single ticks for your command substitution,
a space after if, and spaces around the parts of the conditional expression.
Your script suffers from the Parsing LS issue, in that filenames may be treated badly if they contain special characters like newlines. While you may think this isn't a big issue when all you want to do is check for the existence or nonexistence of files (i.e. count == 0), but the way you're doing it is still cumbersome, and encourages bad habits.
How about, instead consider:
while read folder; do
files=0
for files in $folder/*; do
files=1
break
done
if [ $files -eq 0 ]; then
echo "$folder" >> output.txt
else
echo "not empty: $folder" >&2
fi
done
Instead of counting files in a command substitution and pipe, this uses a for loop to set a semaphore if any files exist. This will always be faster.
Note that this is POSIX-compliant. If your shell is a more advanced one, like bash or zsh, you have more elegant/efficient options available.

grep, else print message for no matches

In a bash script, I have a list of lines in a file I wish to grep and then display on standard out, which is easiest done with a while read:
grep "regex" "filepath" | while read line; do
printf "$line\n"
done
However, I would like to inform the user if no lines were matched by the grep. I know that one can do this by updating a variable inside the loop but it seems like a much more elegant approach (if possible) would be to try to read a line in an until loop, and if there were no output, an error message could be displayed.
This was my first attempt:
grep "regex" "filepath" | until [[ -z ${read line} ]]; do
if [[ -z $input ]]; then
printf "No matches found\n"
break
fi
printf "$line\n"
done
But in this instance the read command is malformed, and I wasn't sure of another way the phrase the query. Is this approach possible, and if not, is there a more suitable solution to the problem?
You don't need a loop at all if you simply want to display a message when there's no match. Instead you can use grep's return code. A simple if statement will suffice:
if ! grep "regex" "filepath"; then
echo "no match" >&2
fi
This will display the results of grep matches (since that's grep's default behavior), and will display the error message if it doesn't.
A popular alternative to if ! is to use the || operator. foo || bar can be read as "do foo or else do bar", or "if not foo then bar".
grep "regex" "filepath" || echo "no match" >&2
John Kugelman's answer is the correct and succinct one and you should accept it. I am addressing your question about syntax here just for completeness.
You cannot use ${read line} to execute read -- the brace syntax actually means (vaguely) that you want the value of a variable whose name contains a space. Perhaps you were shooting for $(read line) but really, the proper way to write your until loop would be more along the lines of
grep "regex" "filepath" | until read line; [[ -z "$line" ]]; do
... but of course, when there is no output, the pipeline will receive no lines, so while and until are both wrong here.
It is worth amphasizing that the reason you need a separate do is that you can have multiple commands in there. Even something like
while output=$(grep "regex filepath"); echo "grep done, please wait ...";
count=$(echo "$output" | wc -l); [[ $count -gt 0 ]]
do ...
although again, that is much more arcane than you would ever really need. (And in this particular case, you would want probably actually want if , not while.)
As others already noted, there is no reason to use a loop like that here, but I wanted to sort out the question about how to write a loop like this for whenever you actually do want one.
As mentioned by #jordanm, there is no need for a loop in the use case you mentioned.
output=$(grep "regex" "file")
if [[ -n $output ]]; then
echo "$output"
else
echo "Sorry, no results..."
fi
If you need to iterate over the results for processing (rather than just displaying to stdout) then you can do something like this:
output=$(grep "regex" "file")
if [[ -n $output ]]; then
while IFS= read -r line; do
# do something with $line
done <<< "$output"
else
echo "Sorry, no results..."
fi
This method avoids using a pipeline or subshell so that any variable assignments made within the loop will be available to the rest of the script.
Also, i'm not sure if this relates to what you are trying to do at all, but grep does have the ability to load patterns from a file (one per line). It is invoked as follows:
grep search_target -f pattern_file.txt

Syntax error near unexpected token 'fi'

I'm trying to write a script that removes all the .jpg's that end in an odd number. This is my code:
#!/bin/bash
echo "start\n"
for f in *.jpg
do
fname=$(basename "$f")
echo "fname is $fname\n"
fname="${filename%.*}"
echo "fname is $fname\n"
if[$((fname % 2)) -eq 1 ] then
echo "removing $fname\n"
rm $f
fi
done
When I run it it outputs start and then says "syntax error near unexpected token 'fi'"
When I had then on the line after if it said "syntax error near unexpected token 'then'"
How do i fix this?
As well as having then on a new line, you also need a space before and after the [, which is a special symbol in BASH.
#!/bin/bash
echo "start\n"
for f in *.jpg
do
fname=$(basename "$f")
echo "fname is $fname\n"
fname="${filename%.*}"
echo "fname is $fname\n"
if [ $((fname % 2)) -eq 1 ]
then
echo "removing $fname\n"
rm "$f"
fi
done
Use Notepad ++ and use the option to Convert the file to UNIX format.
That should solve this problem.
"Then" is a command in bash, thus it needs a ";" or a newline before it.
#!/bin/bash
echo "start\n"
for f in *.jpg
do
fname=$(basename "$f")
echo "fname is $fname\n"
fname="${filename%.*}"
echo "fname is $fname\n"
if [$[fname%2] -eq 1 ]
then
echo "removing $fname\n"
rm $f
fi
done
The first problem with your script is that you have to put a space after the [.
Type type [ to see what is really happening. It should tell you that [ is an alias to test command, so [ ] in bash is not some special syntax for conditionals, it is just a command on its own. What you should prefer in bash is [[ ]]. This common pitfall is greatly explained here and here.
Another problem is that you didn't quote "$f" which might become a problem later. This is explained here
You can use arithmetic expressions in if, so you don't have to use [ ] or [[ ]] at all in some cases. More info here
Also there's no need to use \n in every echo, because echo places newlines by default. If you want TWO newlines to appear, then use echo -e 'start\n' or echo $'start\n' . This $'' syntax is explained here
To make it completely perfect you should place -- before arbitrary filenames, otherwise rm might treat it as a parameter if the file name starts with dashes. This is explained here.
So here's your script:
#!/bin/bash
echo "start"
for f in *.jpg
do
fname="${f##*/}"
echo "fname is $fname"
if (( fname % 2 == 1 )); then
echo "removing $fname"
rm -- "$f"
fi
done
The original question has long since been answered, but since this was the first Google result for "unexpected token fi", I'm posting the mistake I made that I have not seen mentioned yet.
I copy-pasted some code from a different question which looked something like:
REPLY="Hello"
if [[ $REPLY =~ "Server is up" ]]; then
# echo "Something"
fi
The problem with the above is that the only line of code in the conditional block is commented out, and an empty conditional block is invalid syntax in bash.
Uncommenting the echo statement resolves the syntax error. Alternatively, if I intended a no-op block, I could have written : as my only line of code in the block:
REPLY="Hello"
if [[ $REPLY =~ "Server is up" ]]; then
:
fi
#sagar's answer solved my problem (Save file to UNIX format), please note that if you are using VSCode, you can select the file's end of line sequence (LF or CRLF) at the bottom right.
Also see this CRLF vs LF if you would like to understand the why. Common problem when you are working on different OS.

Echo: Argument List too long

I'm running into an issue where my argument list for echo is too long and would like some ideas on how to get around this issue, or at least test for the condition so I can properly handle it, and it won't kill my script
for file in `cat filelist`; do
PROTOCOLS1=`egrep -i 'rsh|rsync|remsh' "$file" | egrep -v '^[ | ]*#'`
FIELDS=`echo $PROTOCOLS1|wc -l`
if [[ $FIELDS -gt 1024 ]]; then
echo $file >> $debuglog
else
set -A myarray $PROTOCOLS1
do stuff.....
fi
done
So the problem is that when my arg list for echo is too long, $FIELDS is set to null, and thus my test for $FIELDS -gt 1024 always is true and does not get caught.
Problem is when it goes to the array it's obviously too big and I get a subscript out of range error and my script exits.
Any ideas are greatly appreciated.
Edit 9/18
OK so the problem is a little more basic.
myserver-v1> echo $variable
myserver-v1> /usr/bin/echo: too many args
I want to test for this in my script
I tried the following, which works, but I get all this crap to stdout, which fills up my debug log and is annoying
echo $variable
if [[ $? -ne 0 ]]; then
write to error log
fi
Is there a way to test echo $variable....without sending it to stdout?
I tried the following, but neither seemed to work, so I am kind of at a loss here.
[[ ! `echo $variable ]]
[[ `echo $variable ]]
If you keep the unquoted variable $PROTOCOLS1 in the echo, you could simplify life by replacing:
FIELDS=`echo $PROTOCOLS1|wc -l`
with
FIELDS=1
This is because when you echo $PROTOCOLS1 without any quotes around it, you will only have one (possibly very long) line of output. Alternatively, you can use:
FIELDS=$(echo "$PROTOCOLS1" | wc -l)
where the double quotes will preserve the newlines in the value of PROTOCOLS1 (but it gets you back to the 'argument list too long' problem).
So, you need to think about using:
FIELDS=$(egrep -i 'rsh|rsync|remsh' "$file" | egrep -c -v '^[ | ]*#')
which gets the second egrep to do the line counting for you. Obviously, since the later portion of the script uses $PROTOCOLS1, you will need to re-evaluate the egreps to get the data, but you should think about whether your processing scheme is appropriate. If you are running into a string value that is too long, you are probably not doing the processing in the best way. What the alternatives are depends on what you are trying to do, and the question does not reveal that. It might be appropriate to do the extra processing with a scripting language such as Perl, Python or Ruby.

Error using [[ ]] and -eq

Shell script snippet:
tagSearch= $(grep '^\#ctags$' ./"$1" | wc -l)
if [[ $tagSearch -ne "0" ]]
then
...
fi
Results in:
line 2: /bb/bin/1: Permission denied
Context:
I'm trying to detect whether a particular pattern exists in a file so I can take a particular action.
I understand the error I'm getting, the detection is working but the script is trying to evaluate the result '1' and run the program '1' in my path. This isn't what I want. How do I get the behavior I'm looking for?
The problem is
tagSearch= $(grep '^\#ctags$' ./"$1" | wc -l)
----------^
You can't use spaces around the equal sign; what you're actually doing here is to temporarily set tagSearch to the empty string in the environment, then invoking grep '^\#ctags$' ./"$1" | wc -l, then trying to run that as a command since the $() will have inserted the result into the command line.
tagSearch=$(grep '^\#ctags$' ./"$1" | wc -l)
Variable assignments in the bash shell should not have a space after the equals. Actually it should never have whitespace in it at all. See below.
tagSearch=$(grep '^\#ctags$' "./$1" | wc -l)
if [[ $tagSearch -ne 0 ]]
then
...
fi
Not important to your error but also of note, when using the double bracket syntax, you don't need to quote that zero any more than the variable you are comparing it with.
Actually your whole code could be re-factored using grep's quite mode and evaluating the return code to see if you got any matches:
if grep '^\#ctags$' "./$1"
then
...
fi
Actually you can have that simpler, because the return code of grep will be 0 if something is found (1 otherwise), so you don't need wc -l. And you can just write:
if `grep -q pattern file`; then echo "yes"; else echo "no"; fi;

Resources