LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Newbie (https://www.linuxquestions.org/questions/linux-newbie-8/)
-   -   Having trouble combining a for loop with find (https://www.linuxquestions.org/questions/linux-newbie-8/having-trouble-combining-a-for-loop-with-find-834531/)

mwesolow 09-26-2010 02:01 AM

Having trouble combining a for loop with find
 
Hello,

I am trying to get a listing of all non-directories of the form *.h or *.C or *.cc or *.cpp, and then iterate over that collection to do some work on those files. Here is my code:

Code:

for x in find `pwd` ! -type d -name '*.h' -o -name '*.C' -o -name '*.cc' -o -name '*.cpp'
do
#some work with $x
done

I tried globbing in one string for the name, but it doesn't seem to work. Aside from that, this combination doesn't seem to work either. I would appreciate your help! Note: this is part of my solution to a programming assignment in bash scripting.

David the H. 09-26-2010 02:11 AM

The values of $x in this string:
Code:

for x in find `pwd` ! -type etc....
will be "find", "`pwd`", "!", "-type", etc. That is, the loop sees your command as a list of words. What you need to do is embed the command.
Code:

for x in $( find . ! -type etc....)
Also, `pwd` is completely unnecessary here. Just use . or perhaps $PWD. And unless you're using a really old shell, $(..) is recommended over `..`

konsolebox 09-26-2010 02:19 AM

Hello mwesolow. In bash that can be something like this:
Code:

while read FILEPATH; do
    # do something with $FILEPATH
done < <(exec find ! -type d -name'*.h' -o -name '*.C' -o -name '*.cc' -o -name '*.cpp')

For the find command I would also prefer:
Code:

find -not -type d -iname '*.c' -or -iname '*.h' -or -iname '*.cc' -or -iname '*.cpp' -or -iname '*.hpp'

David the H. 09-26-2010 04:02 AM

Either way does the same thing in the end, really, although the while-read loop seems to be much more popular for some reason. A for loop reads the input a "word" at a time, while read grabs whole lines by default.

Whichever one you use, you should first make sure that input list is being generated correctly, then ensure that the loop is processing them properly. I usually do this by simply echoing the variable back, like this:
Code:

while read x; do
        echo "$x" | cat -A
done < <( list_generating_command )  #or <file

Piping things through cat -A is useful in cases where there's a chance of unwanted trailing spaces or non-printing characters appearing in the field. It can help you to spot dos-style carriage returns when working on text files that may have been produced on a Windows machine, for example, or where tabs have been used instead of spaces.

Once you know the basic loop is working, you can replace it with the commands you want.

PS: Don't forget that find also has an -exec option, which is useful when you only have to run a single command on the files it finds.

konsolebox 09-26-2010 04:07 AM

Quote:

Originally Posted by David the H. (Post 4109275)
Either way does the same thing in the end, really, although the while-read loop seems to be much more popular for some reason.

For var in (command substitution) were popular for a very long time before until some time when some people started showing while read from pipe commands. In those days people were still conservative about anything that's not compatible with the original sh.

ghostdog74 09-26-2010 04:44 AM

Quote:

Originally Posted by mwesolow (Post 4109210)
Hello,

I am trying to get a listing of all non-directories of the form *.h or *.C or *.cc or *.cpp, and then iterate over that collection to do some work on those files. Here is my code:

Code:

for x in find `pwd` ! -type d -name '*.h' -o -name '*.C' -o -name '*.cc' -o -name '*.cpp'
do
#some work with $x
done


don't do it this way. you are going to encounter error when file names have spaces. Then you will have to set provision for IFS and such. With a while read loop, you don't need to change IFS unnecessarily.
Of course, if your task is simple, using -exec of find will just do.

Code:

find ..... | while read -r file
do
  # echo do something with "$file"
done


konsolebox 09-26-2010 06:46 AM

Quote:

Originally Posted by ghostdog74 (Post 4109297)
Code:

find ..... | while read -r file
do
  # echo do something with "$file"
done


In some shells the while block is placed inside a subshell so sometimes the "while read; do :; done < <()" method is better. e.g. if a data like a counter is required to be manipulated inside the loop and then also referred in the next statements after it, that data may be lost after the loop.

ghostdog74 09-26-2010 06:59 AM

Quote:

Originally Posted by konsolebox (Post 4109358)
In some shells the while block is placed inside a subshell so sometimes the "while read; do :; done < <()" method is better.

true, but its not POSIX compatible.

konsolebox 09-26-2010 07:33 AM

Certainly and also not compatible with the original sh. It is obvious and intended to be only compatible with bash i.e. I've also said something in post #5 about people being conservative with compatibility among shells.

mwesolow 09-26-2010 11:54 AM

Quote:

Originally Posted by David the H. (Post 4109215)
The values of $x in this string:
Code:

for x in find `pwd` ! -type etc....
will be "find", "`pwd`", "!", "-type", etc. That is, the loop sees your command as a list of words. What you need to do is embed the command.
Code:

for x in $( find . ! -type etc....)
Also, `pwd` is completely unnecessary here. Just use . or perhaps $PWD. And unless you're using a really old shell, $(..) is recommended over `..`

Thanks for your reply! The first tip you gave seems to work just fine. I know that $ is for de-referencing a variable, but I would have never though to use it like this. Could you explain briefly why this works?

Thanks! And thank you everyone for your kind replies, I will be sure to incorporate your comments into my test cases!

Mike

mwesolow 09-26-2010 12:04 PM

Never mind, I found out that $() is equivalent to back-ticks in the 'newer' bash shells.


All times are GMT -5. The time now is 08:11 PM.