Loop through all the files with a specific extension
BashFileGlobBash Problem Overview
for i in $(ls);do
if [ $i = '*.java' ];then
echo "I do something with the file $i"
fi
done
I want to loop through each file in the current folder and check if it matches a specific extension. The code above doesn't work, do you know why?
Bash Solutions
Solution 1 - Bash
No fancy tricks needed:
for i in *.java; do
[ -f "$i" ] || break
...
done
The guard ensures that if there are no matching files, the loop will exit without trying to process a non-existent file name *.java
.
In bash
(or shells supporting something similar), you can use the nullglob
option
to simply ignore a failed match and not enter the body of the loop.
shopt -s nullglob
for i in *.java; do
...
done
Some more detail on the break
-vs-continue
discussion in the comments. I consider it somewhat out of scope whether you use break
or continue
, because what the first loop is trying to do is distinguish between two cases:
*.java
had no matches, and so is treated as literal text.*.java
had at least one match, and that match might have included an entry named*.java
.
In case #1, break
is fine, because there are no other values of $i
forthcoming, and break
and continue
would be equivalent (though I find break
more explicit; you're exiting the loop, not just waiting for the loop to exit passively).
In case #2, you still have to do whatever filtering is necessary on any possible matches. As such, the choice of break
or continue
is less relevant than which test (-f
, -d
, -e
, etc) you apply to $i
, which IMO is the wrong way to determine if you entered the loop "incorrectly" in the first place.
That is, I don't want to be in the position of examining the value of $i
at all in case #1, and in case #2 what you do with the value has more to do with your business logic for each file, rather than the logic of selecting files to process in the first place. I would prefer to leave that logic to the individual user, rather than express one choice or the other in the question.
As an aside, zsh
provides a way to do this kind of filtering in the glob itself. You can match only regular files ending with .java
(and disable the default behavior of treating unmatched patterns as an error, rather than as literal text) with
for f in *.java(.N); do
...
done
With the above, you are guaranteed that if you reach the body of the loop, then $f
expands to the name of a regular file. The .
makes *.java
match only regular files, and the N
causes a failed match to expand to nothing instead of producing an error.
There are also other such glob qualifiers for doing all sorts of filtering on filename expansions. (I like to joke that zsh
's glob expansion replaces the need to use find
at all.)
Solution 2 - Bash
Recursively add subfolders,
for i in `find . -name "*.java" -type f`; do
echo "$i"
done
Solution 3 - Bash
Loop through all files ending with: .img
, .bin
, .txt
suffix, and print the file name:
for i in *.img *.bin *.txt;
do
echo "$i"
done
Or in a recursive manner (find also in all subdirectories):
for i in `find . -type f -name "*.img" -o -name "*.bin" -o -name "*.txt"`;
do
echo "$i"
done
Solution 4 - Bash
the correct answer is @chepner's
EXT=java
for i in *.${EXT}; do
...
done
however, here's a small trick to check whether a filename has a given extensions:
EXT=java
for i in *; do
if [ "${i}" != "${i%.${EXT}}" ];then
echo "I do something with the file $i"
fi
done
Solution 5 - Bash
as @chepner says in his comment you are comparing $i to a fixed string.
To expand and rectify the situation you should use [[ ]] with the regex operator =~
eg:
for i in $(ls);do
if [[ $i =~ .*\.java$ ]];then
echo "I want to do something with the file $i"
fi
done
the regex to the right of =~ is tested against the value of the left hand operator and should not be quoted, ( quoted will not error but will compare against a fixed string and so will most likely fail"
but @chepner 's answer above using glob is a much more efficient mechanism.
Solution 6 - Bash
I agree withe the other answers regarding the correct way to loop through the files. However the OP asked:
> The code above doesn't work, do you know why?
Yes!
An excellent article http://mywiki.wooledge.org/BashFAQ/031"> What is the difference between test, [ and [[ ?] explains in detail that among other differences, you cannot use expression matching
or pattern matching
within the test
command (which is shorthand for [
)
Feature new test [[ old test [ Example
Pattern matching = (or ==) (not available) [[ $name = a* ]] || echo "name does not start with an 'a': $name"
Regular Expression =~ (not available) [[ $(date) =~ ^Fri\ ...\ 13 ]] && echo "It's Friday the 13th!" matching
So this is the reason your script fails. If the OP is interested in an answer with the [[
syntax (which has the disadvantage of not being supported on as many platforms as the [
command), I would be happy to edit my answer to include it.
EDIT: Any protips for how to format the data in the answer as a table would be helpful!
Solution 7 - Bash
I found this solution to be quite handy. It uses the -or
option in find
:
find . -name \*.tex -or -name "*.png" -or -name "*.pdf"
It will find the files with extension tex
, png
, and pdf
.