0

I have a file called files.txt which contains some file names inside. I have a command that reads this file and execute a find to return to me which file names it has found. I'd like to know how can I do the opposite, what I want is to show what file names it did NOT find.

Example:

$ ls
file1  file2  file3  files.txt
$ cat files.txt
file1
file2
file3
file4
$ while read x; do (find . -iname "$x"); done < files.txt
./file1
./file2
./file3

In this example, I want it to show file4 in the results, because this file name is in the .txt, but it does not exist in the folder.

5
  • Uhh but how can you tell then what has been found and what hasn't? Wouldn't you rather print file4 not found? – Panki Dec 10 '19 at 13:11
  • Do you really need to descend into subdirectories (as find does, by default) or just check for existence in the top level directory? Do you really need to match case-insensitively? – steeldriver Dec 10 '19 at 13:14
  • @steeldriver it would be good to descend, but if it's not possible, it's ok to search only in the current folder, no problem! – sdf68dsf7 Dec 10 '19 at 13:16
  • Well I'm not going to say it isn't possible to do it recursively - however it's straightforward to test file existence in the current directory using the shell's test operator ex. [ -e "$x" ] || printf '%s\n' "$x" – steeldriver Dec 10 '19 at 13:26
  • Does this include directories names? – schrodigerscatcuriosity Dec 10 '19 at 13:53
2

Using grep (for files in the current folder, assuming their names don't contain newline characters)):

$ grep -v -i -F -x -f <(printf '%s\n' *) files.txt
file4

Options:

  • -v invert the match, select non-matching lines
  • -f printf '%s\n' * lists the files in the current directory and is used as pattern input file for grep.
  • -F interpret patterns as fixed strings (instead of regular expressions)
  • -x match whole lines
  • -i case insensitive match (like for your -iname).
6
  • printf '%s\n' * matches also directories, anyway +1 – schrodigerscatcuriosity Dec 10 '19 at 13:40
  • It also doesn't work for hidden files. But in bash you could use printf '%s\n' {*,.*}. – Freddy Dec 10 '19 at 13:46
  • @guillermochamorro, directories are just one of many types of files (regular, symlink, device, fifo...), the OP didn't say anything about excluding them. – Stéphane Chazelas Dec 10 '19 at 13:48
  • 1
    In bash, you'd rather use the dotglob option. You'd also want the failglob option to avoid running printf if there's no matching file. – Stéphane Chazelas Dec 10 '19 at 13:53
  • Although this answer has been accepted (which means it is good enough for the user), it doesn't actually care whether the filenames can be found in a lower subdirectory or not. This is in contrast to the search that the code in the question carries out, which does search in subdirectories. – Kusalananda Dec 10 '19 at 16:18
2

Using find:

while IFS= read -r name; do
    if [ -z "$(find . -iname "$name")" ]; then
        printf 'Not found: %s\n' "$name"
    fi
done <files.txt

This loops will read a filename at a time from the files.txt file and will search the entire hierarchy beneath the current directory for this name, case-insensitively. If the name is not found (find does not output anything), a message to that effect is displayed.

If you want to make sure that the find command only looks for regular files (not directories etc.), add -type f to that command.

0

You can do this instead of using find if the depth is one directory:

while IFS= read -r file; do 
  [[ -f "${file}" ]] && echo "${file} exists" \
  || echo "$file doesn't exist"; 
done < files.txt
0

You can run this snippet from your command line to see the non existent files:

$ < files.txt xargs ls -lLd > /dev/null

The existing files will not show up since they will be swallowed by the null bucket whereas, since we did not redirect the stderr, the non existing ones will be shown on the terminal.

0

With zsh:

set -o extendedglob # best in ~/.zshrc
files=(*(ND))
list=(${(f)"$(<files.txt)"})

not_found=(${list:#(#i)(${(j:|:)~${(b)files}})})
if (($#not_found)); then
  echo "Not found:"
  printf ' - %q\n' $not_found
fi

not_in_list=(${files:#(#i)(${(j:|:)~${(b)list}})})
if (($#not_in_list)); then
  echo "Unexpected files (not in files.txt):"
  printf ' - %q\n' $not_in_list
fi

It's doing a case-insensitive subtraction between the list of files in the current directory ($files array) and the list of files in files.txt ($list array).

zsh does have an array subtraction operator (${array1:|array2}) but we can't use it here if we want to be case-insensitive.

Instead we use the ${array:#pattern} operator which expands to the elements of the $array except those that match the pattern.

Here the pattern is built by joining with | (j:|:) the escaped (b) elements of the other array, and using the extendedglob (#i) operator for case-insensitive matching.

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.