0

I have a find result saved in a variable and I am protecting the spaces in filenames by adding a single quotes around the output. So the for loop works flawlessly.

My problem is when I intend to grep one of those files, it says "File does not exist" because is literally taking the single quotes as part of the filename.

How can I overcome this??

all_files=$(find . -type f -printf "'%p'")

for file in $all_files
do
       grep 'hello' $file ### this says "file.not found because of the single quotes
done
  • 1
    1) You are missing a single quote after `%p`. 2) It is better to double quote the entire declaration of the variable like this: `"$(find . -type f -printf "'%p")"` – Nasir Riley Mar 24 '19 at 02:31
  • @NasirRiley I corrected this. It's not my code of course. It's only a simplification of the problem. If I surround everything in double quotes, how does that help to solve my problem? Can you clarify? – Matias Barrios Mar 24 '19 at 02:41
  • 2
    Really it all goes bad as soon as you assign `find` results to a string - for a robust solution see [Loop through find command results that have been added to an array? filenames with gaps treats as 2 entries](https://unix.stackexchange.com/a/499575/65304) – steeldriver Mar 24 '19 at 03:04
  • @steeldriver I see your point. Thanks! – Matias Barrios Mar 24 '19 at 03:23
  • 1
    Related: [Why is looping over find's output bad practice?](//unix.stackexchange.com/q/321697) – Stéphane Chazelas Mar 24 '19 at 09:26
  • Related: [Why does my shell script choke on whitespace or other special characters?](//unix.stackexchange.com/q/131766) – Kusalananda Mar 24 '19 at 14:45

4 Answers4

4

You're not protecting the value of the filenames by adding quotes to it in the output of find. The loop would still see 'a filename' as two things to iterate over. The quotes would also be part of the string, as you noticed.

Instead, run your grep from find:

find . -type f -exec grep 'hello' {} \;

This would execute grep once for each file found.

find . -type f -exec grep 'hello' {} +

This would execute grep for as many files as possible at once (and thus also output the file names for batches with more than one file, use the -h option with GNU grep or compatible to not print the file name).

Still with GNU grep:

grep -D skip -r 'hello' .

This would run grep recursively on the current directory, skipping non-regular files (like -type f does for find), but would still look into symlinks to regular files (as if using GNU find -xtype f).

Kusalananda
  • 320,670
  • 36
  • 633
  • 936
3

Print all the file names having the pattern 'hello',

find . -type f -print0 | xargs -0 -L1 grep -l 'hello'

OR with a find with grep inline

find . -type f -exec grep -l 'hello' {} \;

OR more simply (in case you have only regular files)

grep -Rl 'hello' .
GypsyCosmonaut
  • 3,988
  • 7
  • 38
  • 62
  • 1
    Note that `-R` is not a standard option. In modern versions of GNU `grep`, `-R` (as opposed to `-r`) follows symlinks when recursing as if using `find -L`. – Stéphane Chazelas Mar 24 '19 at 09:27
3

If you want to store the file list to later loop over it, best would be to store it in an array.

With bash:

readarray -td '' all_files < <(find . -type f -print0)
for file in "${all_files[@]}"; do
  grep hello "$file"
done

With zsh (same thing but get a sorted list as a bonus):

all_files=(./**/*(ND.))
for file ($all_files) grep hello $file
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
0

For anyone interested in how I solved this, I ended up with :

all_files=$( find . -type f -name *.txt -printf '%p' )

while read file
do
        grep 'hello' "$filename"
        .....
done <<< "$all_files"

I know it's a bad practice. But for my specific use case I needed to do a lot of things over each filename. Not just grepping. And it worked flawlessly now.

Thanks!

  • This would not work if there was more than one `.txt` file in the current directory (the `find` command would fail). The filename in the `grep` command has a trailing single quote inserted, and the names of the files would _still_ have literal single quotes appended to them. I can't see how this solves anything. – Kusalananda Mar 24 '19 at 14:37
  • @Kusalananda yeah. I obviously wrote this by hand. That's why it contains errors. And no. There are several files with spaces in the same directory. There is even files with multiple spaces in both the directory names and the file names simulataneously and it worked perfectly. Having said that, there are two design flaws in place here : – Matias Barrios Mar 24 '19 at 14:41
  • 1) I have to do all of this non sense because some brilliant engineer decided it was an awesome idea to name both directories and files with spaces inside certain MacOs system folders. – Matias Barrios Mar 24 '19 at 14:42
  • 2) I do not understand why spaces in filenames are allowed in the first place by both Linux and MacOs. Juts force the users to use underscores. – Matias Barrios Mar 24 '19 at 14:43
  • 1
    Spaces and newlines and tabs (and `*` and all sort of emojis and other characters) are allowed. Only `/` and null bytes are not allowed in names of files. Did you try any of the other proposed solutions? Did you find problems with them. If so, leave a comment and let us know what was not working so that we may give you an even better answer. – Kusalananda Mar 24 '19 at 14:44