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.
Related
I'm trying to write a shell script to check if there's a file existing that ends with .txt using an if statement.
Within single bracket conditionals, all of the Shell Expansions will occur, particularly in this case Filename expansion.
The condional construct acts upon the number of arguments it's given: -f expects exactly one argument to follow it, a filename. Apparently your *.txt pattern matches more than one file.
If your shell is bash, you can do
files=(*.txt)
if (( ${#files[#]} > 0 )); then ...
or, more portably:
count=0
for file in *.txt; do
count=1
break
done
if [ "$count" -eq 0 ]; then
echo "no *.txt files"
else
echo "at least one *.txt file"
fi
I finally get your perspective now. I've been giving you some incomplete advice. This is what you need:
for f in *.txt; do
if [ -f "$f" ]; then
do_something_with "$f"
fi
done
The reason: if there are no files matching the pattern then the shell leaves the patten as a plain string. On the first iteration of the loop, we have f="*.txt" and mv responds with "file not found".
I'm used to working in bash with the nullglob option that handles this edge case.
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
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.
I looked some other posts and learnt to match file extension in the following way but why my code is not working? Thanks.
1 #!/bin/sh
2
3 for i in `ls`
4 do
5 if [[ "$i" == *.txt ]]
6 then
7 echo "$i is .txt file"
8 else
9 echo "$i is NOT .txt file"
10 fi
11 done
eidt:
I realized #!/bin/sh and #!/bin/bash are different, if you are looking at this post later, remember to check which one you are using.
The [[ ]] expression is only available in some shells, like bash and zsh. Some more basic shells, like dash, do no support it. I'm guessing you're running this on a recent version of Ubuntu or Debian, where /bin/sh is actually dash, and hence doesn't recognize [[. And actually, you shouldn't use [[ ]] with a #!/bin/sh shebang anyway, since it's unsafe to depend on a feature that the shebang doesn't request.
So, what to do about it? You'll have the [ ] type of test expression available, but it doesn't do pattern matching (like *.txt). There are a number of alternate ways to do it:
The case statement is available in even basic shells, and has the same pattern matching capability as [[ = ]]. This is the most common way to do this type of thing, especially when you have a list of different patterns to check against.
More indirectly, you can use ${var%pattern} to try remove .txt from the end of the end of the value (see "Remove Smallest Suffix Pattern" here), and then check to see if that changed the value:
if [ "$i" != "${i%.txt}" ]
More explanation: suppose $i is "file.txt"; then this expands to [ "file.txt" != "file" ], so they're not equal, and the test (for !=) succeeds. On the other hand, if $i is "file.pdf", then it expands to [ "file.pdf" != "file.pdf" ], which fails because the strings are the same.
Other notes: when using [ ], use a single equal sign for string comparison, and be sure to properly double-quote all variable references to avoid confusion. Also, if you use anything that has special meaning to the shell (like < or >), you need to quote or escape them.
You could use the expr command's : operator to do regular expression matching. (Regular expressions are a different type of pattern from the basic wildcard or "glob" expression.) You could do this, but don't.
#!/bin/sh
for i in `ls`
do
if [[ "$i" = *".txt" ]] ; then
echo "$i is .txt file"
else
echo "$i is NOT .txt file"
fi
done
You don't have to loop in ls output, and sh implementation might vary among OS distributions.
Consider:
#! /bin/sh
for i in *
do
if [[ "$i" == *.txt ]]
then
echo "$i is txt file"
else
echo "$i is NOT txt file"
fi
done
I have doubt in shell script
I will describe the scenario, $file is containing the file name of my interest,
consider $file can contain foo.1, foo.2, foo.3 here foo will be constant,
but .1,.2,.3 will change, i want to test this in single line in if statement something like
if [ $file = "foo.[1-9]" ]; then
echo "File name is $file"
fi'
i know above script doesn't work :) can anyone suggest what should i refer for this ?
Trim any extension, then see if it's "foo"?
base=${file%.[1-9]}
if [ "$base" = "foo" ]; then
echo Smashing success
fi
Equivalently, I always like to recommend case because it's portable and versatile.
case $file in
foo.[1-9] ) echo Smashing success ;;
esac
The syntax may seem weird at first but it's well worth knowing.
Both of these techniques should be portable to any Bourne-compatible shell, including Dash and POSIX sh.
Use [[ instead for regex matching.
if [[ $file =~ ^foo\.[1-9]$ ]] ; ...