0

I am writing a bash script and want it to tell me if the names of the files in a directory appear in a text file and if not, remove them.

Something like this:

counter = 1
numFiles = ls -1 TestDir/ | wc -l 
while [$counter -lt $numFiles]
do
     if [file in TestDir/ not in fileNames.txt]
     then
          rm file
     fi
     ((counter++))
done

So what I need help with is the if statement, which is still pseudo-code.

CJK
  • 5,732
  • 1
  • 8
  • 26
J. Tate
  • 163
  • 1
  • 10

2 Answers2

1

You can simplify your script logic a lot :

#/bin/bash

# for loop to iterate over all files in the testdir
for file in TestDir/*
do
    # if grep exit code is 1 (file not found in the text document), we delete the file
    [[ ! $(grep -x "$file" fileNames.txt &> /dev/null) ]] && rm "$file"
done
Aserre
  • 4,916
  • 5
  • 33
  • 56
  • This makes sense to me but something is not working because it is just removing all the files. Should there be quotes around fileNames.txt? – J. Tate Mar 16 '18 at 13:08
  • Nvm, got it! Thanks for the help! – J. Tate Mar 16 '18 at 13:21
  • The grep line could be even more simplified (not tested): `grep "$file" fileNames.txt &> /dev/null || rm -f "$file"` – yolenoyer Mar 16 '18 at 13:43
  • 1
    This will delete the file `foobar12.txt` if you have a file called `fo` in the list. Use `-x` to avoid that. And why would you use `-f`? Why make the command more destructive than it needs to be? And what if `fileNames.txt` does't exist or isn't readable? You just deleted every single file in the directory with no warning (and this would include special files since you're using `-f`). – terdon Mar 16 '18 at 16:39
  • no idea why, but when I tried it didn't work for me - till I removed the "&> /dev/null" part (I did it as one liner with semicolons if that matters); Ubuntu WSL2 – Borek Jan 29 '23 at 13:10
0

It looks like you've got a solution that works, but I thought I'd offer this one as well, as it might still be of help to you or someone else.

find /Path/To/TestDir -type f ! -name '.*' -exec basename {} + | grep -xvF -f /Path/To/filenames.txt"

Breakdown

find: This gets file paths in the specified directory (which would be TestDir) that match the given criteria. In this case, I've specified it return only regular files (-type f) whose names don't start with a period (-name '.*'). It then uses its own builtin utility to execute the next command:

basename: Given a file path (which is what find spits out), it will return the base filename only, or, more specifically, everything after the last /.

|: This is a command pipe, that takes the output of the previous command to use as input in the next command.

grep: This is a regular-expression matching utility that, in this case, is given two lists of files: one fed in through the pipe from find—the files of your TestDir directory; and the files listed in filenames.txt. Ordinarily, the filenames in the text file would be used to match against filenames returned by find, and those that match would be given as the output. However, the -v flag inverts the matching process, so that grep returns those filenames that do not match.

What results is a list of files that exist in the directory TestDir, but do not appear in the filenames.txt file. These are the files you wish to delete, so you can simply use this line of code inside a parameter expansion $(...) to supply rm with the files it's able to delete.

The full command chain—after you cd into TestDir—looks like this:

rm $(find . -type f ! -name '.*' -exec basename {} + | grep -xvF -f filenames.txt")
CJK
  • 5,732
  • 1
  • 8
  • 26