1

From a directory and its subdirectories, one finds all the .c files by

find /user/home/me/tests/ -name \*\.c

Now, a list of directories are stored in a file 'list_of_dir'

/user/home/me/test1
/user/home/me/test2
/user/home/me/test3
...

How can one feed them to the find command to find all the .c files in them and their subdirectories?

Thanks in advance.

Edit: the default shell for me is tcsh.

2

If you have a newline-separated list of directories in file list_of_dir and you want to pass that list to find safely, without word splitting or pathname expansion and without exceeding line length limits, use xargs (here assuming the GNU implementation for its -d and -r options):

xargs -r -d'\n' -I{} find {} -name '*.c' <list_of_dir

To be truly safe, one should use a NUL-separated list of directories, not a newline-separated list. If list_of_dir0 contains a NUL-separated list of directories, then use:

xargs -r -0 -I{} find {} -name '*.c' <list_of_dir0

How it works

  • -r tells xargs not to run find unless the directory list is non-empty.

  • -0 tells xargs to expect a NUL-separated list on input. This is the safer alternative to -d'\n' which tells xargs to expect a newline-separated list.

  • -I{} tells xargs to put the directories in the find command where it sees the {} characters (one at a time, one find invocation will be run for each directory).

  • Thanks a lot - detailed explanation. With bash, the first one works fine for me; while the second one gives 'filename too long' after outputting a long list of files - the list of directories is pretty long in my case. However, both will not work with tcsh, which is the default at my work place. – jianz Jan 17 '18 at 0:10
  • (1) "the second one gives 'filename too long'" That likely means that the list_of_dir file was not NUL-separated. (2) What OS are you on? The use of tcsh sometimes indicates Sun/Solaris which tends to have buggy tools. – John1024 Jan 17 '18 at 0:14
  • You're right. The file was NOT NUL-separated. The first worked, so I didn't pay much attention on the output of the second. – jianz Jan 17 '18 at 0:45
  • @jianz Very good. Since we are not using any fancy shell features, my suspicion is that the problems you see are not with tcsh but with aged versions of the tools xargs & find on your OS. If you are on Sun/Solaris, you can install GNU tools and that will save a world of troubles now and in the future. They might even be already installed: look in directories like /usr/xpg4/bin or /usr/xpg6/bin. – John1024 Jan 17 '18 at 1:30
  • Thank you very much for the following up. There are no other versions of xargs or find in subdirectories of /usr. Fortunately, I can use 'bash -c' to get around the problem - inspired by Hauke Laging's answer. Thanks again. – jianz Jan 17 '18 at 17:53
2

In tcsh, when you quote a command substitution, it is split on a one per non empty line basis, which sounds like exactly what you want here, so you can do:

find -- "`cat list.txt`" -name '*.c'

In POSIX-like shells, you'd need to do something like:

(set -o noglob; IFS='
'; find -- $(cat list.txt) -name '*.c')

to achieve the same.

That assumes list.txt contains at least one non-empty line (otherwise, depending on the find implementation, you'll get an error message or it will look in the current directory).

That also assumes that none of the lines start with - or are find predicates (most of find predicates start with - (-ok, -print, -type...), but there's also !, (, )...).

That also assumes list.txt is small enough to fit in the maximum size of arguments to a command.

Another alternative is to use xargs.

<list.txt sed 's/"/"\\""/g;s/.*/"&"/'|xargs tcsh -c 'find $argv:q -name "*.c"'

Where sed quotes each line in a format understood by xargs.

Same limitations as above except that it should work around the limitation on number of arguments by running as many find commands as needed to avoid it, and it assumes the content of list.txt is valid text in the current locale. Some xargs implementations also have a rather low limit on the maximum size of any argument which can be significantly smaller than the maximum size of a path.

  • Your knowledge of even obscure shells constantly impresses me! – John1024 Jan 18 '18 at 3:16
1
$ (echo /tmp; echo /var/tmp) > file
$ find $(cat file) -prune
/tmp
/var/tmp
$ 

However! This may not be so grand an idea if there are, say, newlines in the directory names, or if the list of filenames is too long for the execve(2) maximum on arguments...with tcsh you must use backticks which the bourne shells these days disfavor over the $(...) form

% find `cat file` -prune
/tmp
/var/tmp
% 

though this again has all the same problems as suffered under the bourne shells.

  • Thanks a lot. This works in bash. What about tcsh? – jianz Jan 16 '18 at 23:53
  • 2
    Not just newlines would cause problems but spaces and tabs, too. – Hauke Laging Jan 17 '18 at 0:55
  • Also glob (pathname expansion) characters: *, ? and [. – G-Man Jan 18 '18 at 6:02
0
xargs -d \\n bash -c 'echo find "$@" -name \*.c' bash <input_file

Remove the echo after checking that it does what you want.

  • Thanks a lot... This does not work for me for unknown reasons - probably my very old xargs/find as pointed out by @John1024. However, from you answer, I could use bash -c 'find `cat intput_file` -name \*\.c |xargs grep main' to get what I want now. Thanks again. – jianz Jan 17 '18 at 17:58
  • 1
    @jianz, that's not about old/new, it's about what implementation is used. The -d options is not standard and is GNU-specific. – Stéphane Chazelas Jan 17 '18 at 22:11
0

Another way to do it is with this code:

(echo directory1 ; echo directory2 ; echo directory3) > file
for d in $(cat file); do find $d -name '*.c'; done

That will put the directory names into a file run the find using the names of the directories and output the results. The apostrophes will take care of any spaces or reserved characters in the file names.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.