4

I'm trying to have some tracking on a folder and wanted to use the find -name filters from a file. So I've prepared a file called filter_files that contains the filter, for example:

cat filter_files:
-name "a.txt" -o -name "b.txt"

I've tried running the find command and add the mentioned filter, but it returns nothing, if I write the content of the file and paste it into a find command it succeeds.

The relevant lines from my bash file are:

local filter=$(cat $filter_files)
find_files=$(find "$path" -type f \( $filter \) )

Can you please explain to me if I can do what I want? And if yes, how can I make it work?

avij
  • 43

2 Answers2

6

If the file contains

-name "a.txt" -o -name "b.txt"

and you run

find "$path" -type f \( $(cat file) \) 

Then what happens is that the shell runs the output from cat through word splitting, resulting in the arguments -name, "a.txt", -o, -name, and "b.txt", with the quotes literally as part of the arguments. (Note that the quotes also won't protect the whitespace. An input string like "foo bar" would result in the two fields "foo and bar". Also, the results of splitting would go through filename generation/globbing, so something like foo*.txt would be expanded then and there, not passed to find as-is.)

This is because word splitting is rather simple, it only splits on whitespace, it does not run the output through the full shell syntax parsing. (If it did, that would be actively dangerous, since it would also imply always recursively running any expansions, including command substitutions. It would be impossible to use the shell relative safely for arbitrary data.)

If you want to have the part from the file go though the full syntax processing, you'll have to run the command with eval, e.g.:

eval 'find "$path" -type f \( '"$(cat file)"' \)'

Here, after the outer shell processes the quotes and the command substitution, eval gets the following string, which is then processed as a shell command, including quote processing and all expansions.

find "$path" -type f \( -name "a.txt" -o -name "b.txt" \)

(Note that this means that if the file contains e.g. "$(reboot)", that command will run. However, I left "$path" to be expanded by the inner shell, so that it will not be double-processed.)

You could do the above in multiple steps, e.g. by assigning the words from the file to an array with eval, and then using that array in the final command. The idea and the caveats are the same.


You might wish to reconsider the format of the file. Even e.g. putting each argument on a separate line (and nixing the quotes) would make it easier to parse.

E.g. if the file was:

-name
a.txt
-o
-name
foo bar.txt

You could do something like this, where readarray reads each input line to a separate array element:

#!/bin/bash
readarray -t args < file 
find "$path" -type f \( "${args[@]}" \)

(That's still limited in that it can't process filenames with newlines, but those are likely to be rarer, and would produce other issues too.)

Then again, if all you need is -name matches, you could have the file list only the patterns, and produce the -o -name boilerplate programmatically.

See also:

ilkkachu
  • 138,973
  • Thank you!! :) The eval made it and it's finally worked for me, and I've accepted the answer. I must also thank @J. Cravens for the time he spent with me and helped with the second answer

    Amazing community! Thank you

    – avij Jan 14 '24 at 12:45
0

Use:

You need separate lines, and remove -o from the file, to make it easy.

find "$path" -type f $(sed '1q;d' filter_files) -o $(sed '2q;d' filter_files)

or:

Single string.

filter_conditions=$(<sed '1q;d' filter_files)
find_files=$(find "$path" -type f \( $filter_conditions \))
  • The OP needs to read -name "a.txt" -o -name "b.txt" from a file. They don't have two files, one named a.txt and one named b.txt with one file name in each, they have one file, named filter_files that contains the string -name "a.txt" -o -name "b.txt". – terdon Jan 13 '24 at 15:36
  • @terdon Oh, okay. If they are on separate lines, it's easy. $(sed '1q;d' filter_files) & $(sed '2q;d filter_files) – JayCravens Jan 13 '24 at 15:51
  • 2
    But they aren't. Please read the question again, all the information is there. If you need to change the format of the file, that's fine, but please include the relevant commands in your answer, using the example files the OP provided. – terdon Jan 13 '24 at 15:53
  • That's not the idea, as @terdon mentioned, I don't know in advance the files a.txt/b.txt, they're just examples of the input I get.. maybe I need to reformat the file, I can do it, I eventually want to track files by a filter, and have a utility to track them.

    The list of filters can be larger than 2, hope I didn't got misunderstood in the original post

    – avij Jan 13 '24 at 15:59
  • @avij https://chat.stackexchange.com/rooms/150858/find-command – JayCravens Jan 13 '24 at 16:03
  • @J.Cravens thank you very much, but I'm a noob here and got no reputation to join a chat :( – avij Jan 13 '24 at 16:05
  • @avij I updated it for a single line input. :) – JayCravens Jan 13 '24 at 16:08