7

How could I check if a file has a file of extension .java?

So far I have:

  for javaFile in *.java ; do
    {
        echo "The file $javaFile has : " >> ~/Desktop/externalServers.txt
        grep -E '^[^/]{2}.*http' $javaFile >> ~/Desktop/externalServers.txt     

        grep -E '^[^/]{2}.*ftp' $javaFile  >> ~/Desktop/externalServers.txt

        echo "----------------------------------------" >> ~/Desktop/externalServers.txt
        sed -e "s/[[:space:]]\+/ /g" ~/Desktop/externalServers.txt >> ~/Desktop/externalServersTemp.txt
        rm ~/Desktop/externalServers.txt
        mv ~/Desktop/externalServersTemp.txt ~/Desktop/externalServers.txt
        sed 's/^\n//' ~/Desktop/externalServers.txt >> ~/Desktop/externalServersTemp.txt
        rm ~/Desktop/externalServers.txt
        mv ~/Desktop/externalServersTemp.txt ~/Desktop/externalServers.txt

    } done

But every time I do that, I get the error:

grep: *.java: No such file or directory

Basically I want to first see if the folder has any files of type .java and only continue with the script.

2
  • 3
    That's a useless use of ls .. you have there. Let the shell expand the arguments for you as Joseph's answer shows.
    – slm
    Jun 2, 2014 at 12:56
  • @slm - Joseph's answer demonstrate how a shell can expand the arguments in a way that is neither more readable nor more concise than my answer does for any shell.
    – mikeserv
    Jun 3, 2014 at 5:54

4 Answers 4

4

In Bash, simply use

shopt -s nullglob
for pdfFile in *.java;do
    # your code goes here
done

This syntax is for Bourne-like shells; the nullglob option is specific to bash. The braces you used ({}) are for C-style shells.

shopt -s nullglob sets the nullglob option, which basically tells Bash that globs that failed to match should be expanded into the null string. By default, if *.java fails to match, it is expanded into itself (the asterisk stays).

7
  • I just prefer to have the ( {} ) because it helps me differentiate the loops.. :/
    – gkmohit
    Jun 2, 2014 at 13:02
  • 3
    @Unknown Well, if you're using Bash, you can't. It's syntactically incorrect.
    – Joseph R.
    Jun 2, 2014 at 13:03
  • ugh okay will change it :( . . . Could you check the new edited question?
    – gkmohit
    Jun 2, 2014 at 13:03
  • @Unknown Please see the update.
    – Joseph R.
    Jun 2, 2014 at 13:08
  • what does shopt -s nullglob do??
    – gkmohit
    Jun 2, 2014 at 13:23
4

My own rendition of your script would probably look like:

set -- *.java
test -e "$1" && {
    fortyequals=$(printf '%040d\n' | tr 0 \=)
    for javaFile do
        printf '%s\nIn file: %s\n%s\n' \
            $fortyequals "$javaFile" $fortyequals
        grep -E '^[^/]{2}.*(ftp|http)' "$javaFile"       
    done 
} >>~/Desktop/externalservers.txt

The solution already offered is needlessly shell-specific. You could achieve the same effect with portable syntax - which makes for less to remember in the long run with the added advantage of being more robust like:

set -- *.java
test -e "$1" &&
    for javaFile do
#       ...iterate on $javaFile here...
    done

Another advantage is that you not only retain the most recent value of $javaFile following the loop, you also retain all of the values that $javaFile ever had in $@. This makes the following possible:

...
done

echo "The previous for loop processed $# files."

echo "The first file processed was:"
printf "///\t'%s'\t///\n" "$1"

echo "The last file processed was:"
printf "///\t'%s'\t///\n" "$javaFile"

echo 'All files processed in the for loop were:'
printf "///\t'%s'\t///\n" "$@"

And if you do really like the { curlies } you can use them - even in bash (though they are unnecessary) - but you have to delimit between the two shell reserved words } and done like:

for ... do {
...
} ; done

Though my recommendation is that you enclose the entire block - around the for loop and any post processing you do - in curlies dependent on the && reserved words like:

set -- *.java
test -e "$1" && {
    for ... done
#   ...further processing on $@...
} 

Looking back at it and I believe I can help a great deal with the regex as well... It looks like we're looking for lines containing the words http and/or ftp that do not begin with two //.

I think the rest is a result of the separate grep operations you do. You appear to be attempting to clear blank lines, but, as I imagine, those are only caused in the first place by repeated appended writes to the file.

So, instead, we could just write for loop's output directly to the outfile in order to maintain the write descriptor for ~/Desktop/externalservers.txt until the loop has finished, which should avoid any blank lines being written. Maybe like:

for ... done >>outfile

Or

{ grouped ; command ; list ; } >>outfile

At the very least I can tell you that this statement probably does not do what you want:

sed 's/^\n//' $file

sed is \newline delimited - it's impossible to encounter a \newline as the ^first character on a line. You can get \newlines into sed's pattern space by various means, but never without some processing.

2

Another issue with the sample code, you generally want to use double quotes on any of the iterators as you use them within the for loop.

Your code:

for javaFile in *.java ;
    {
        echo "The file $javaFile has : " >> ~/Desktop/externalServers.txt
        grep -E '^[^/]{2}.*http' $javaFile >> ~/Desktop/externalServers.txt     

        grep -E '^[^/]{2}.*ftp' $javaFile  >> ~/Desktop/externalServers.txt

Should be:

for javaFile in *.java ; do
        echo "The file $javaFile has : " >> ~/Desktop/externalServers.txt
        grep -E '^[^/]{2}.*http' "$javaFile" >> ~/Desktop/externalServers.txt

        grep -E '^[^/]{2}.*ftp' "$javaFile"  >> ~/Desktop/externalServers.txt
0

Joseph's nullglob approach is the most elegant but if you don't want to or can't use that (non-bash shell or older bash version for example), you can also do this (assuming your file names contain no newlines):

file="~/Desktop/externalServers.txt"
while IFS= read -r javaFile
do
  echo "The file $javaFile has : " >> "$file"
  grep -E '^[^/]{2}.*http' "$javaFile" >> "$file"
  grep -E '^[^/]{2}.*ftp' "$javaFile"  >> "$file"

  echo "----------------------------------------" >> "$file"
  ## The -i flag enables in-place editing so you don't need
  ## to fiddle about with temp files.
  sed -i -e "s/[[:space:]]\+/ /g" "$file"
  sed -i -n '/[^[:space:]]/p' "$file"
done < <(find . -maxdepth 1 -name '*.java')

The IFS= sets the input field separator to empty so that you can deal with filenames that contain spaces correctly and the -r option to read means that backslashes are not treated specially (in case your filenames can contain them). The <(command) construct is called process substitution and is a way of passing the output of one command as input to another.

I introduced the variable $file so that you don't need to edit every line if you ever want to change the output file name.

Note that I used the -i flag to sed which allows you to edit the original file and removes the need for temp files. Also note that there is no reason to do rm foo.txt; mv bar.txt foo.txt, you can always just do mv bar.txt foo.txt and that will overwrite the target file. Now, I have no idea what this command was supposed to do:

sed 's/^\n//' ~/Desktop/externalServers.txt >> ~/Desktop/externalServersTemp.txt

I am guessing that you want it to remove blank lines but that won't work so I changed it in the above with

sed -i -n '/[^[:space:]]/p' ~/Desktop/externalServers.txt

The -n suppresses sed's default behavior of printing each line, the /[^[:space:]]/ will match any lines that match any non-space character and the p at the end means that these lines and only these lines will be printed. If this is not what you wanted to do with your sed command, let me know and I will edit as needed.

3
  • Process substitution only exists in bash, ksh and zsh anyway. You could use a pipe instead, with the caveat that in shells other than ATT ksh and zsh, the right-hand side of the pipeline runs in a subshell. -maxdepth is specific to GNU and BSD find. Running find for this is overkill and chokes on file names containing newlines. There are more reliable ways to do this, and simpler if you don't need cross-shell portability. Jun 2, 2014 at 23:27
  • @Gilles - simpler still, and including cross-shell portability would be the use of set.
    – mikeserv
    Jun 3, 2014 at 2:19
  • @terdon - I added an answer demonstrating how I think this should be best handled portably. If you set -- $glob ; test -e "$1" you can know if the $glob expanded without any unnecessary shell options.
    – mikeserv
    Jun 3, 2014 at 3:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .