The script below searches (text)files in a given directory recursively, for occurrences of a given string, no matter if it is in upper or lowercase, or any combination of those.
It will give you a list of found matches, the paths to the files, combined with the filenam and the actual occurrences of the string in the file, looking like:
/path/to/file1 ['numlock', 'numlocK']
/longer/path/to/file2 ['NuMlOck']
etc.
To limit the search time, I would look for matches in specific directories, so not for 2TB of files ;).
To use it:
1] Copy the text below, paste it into an empty textfile (gedit). 2] Edit the two lines in the headsection to define the string to look for and the directory to search. 3] Save it as searchfor.py. 4] To run it: open a terminal, type python3
+space
, then drag the script on to the terminalwindow and press return. The list of found matches will appear in the terminalwindow
In case of an error, the script will mention it.
#!/usr/bin/python3
import os
#-----------------------------------------------------
# give the searched word here in lowercase(!):
searchfor = "string_to_look_for"
# give the aimed directory here:
searchdir = "/path/to/search"
#-----------------------------------------------------
wordsize = len(searchfor)
unreadable = []
print("\nFound matches:")
for root, dirs, files in os.walk(searchdir, topdown=True):
for name in files:
file_subject = root+"/"+name
try:
with open(file_subject) as check_file:
words = check_file.read()
words_lower = words.lower()
found_matches_list = [i for i in range(len(words_lower)) if words_lower.startswith(searchfor, i)]
found_matches = [words[index:index+wordsize] for index in found_matches_list]
if len(found_matches) != 0:
print(file_subject, found_matches)
else:
pass
except Exception:
unreadable.append(file_subject)
if len(unreadable) != 0:
print("\ncould not read the following files:")
for item in unreadable:
print("unreadable:", item)
With bash
Assuming the disk in question is mounted under /media/disk1
:
$ shopt -s globstar dotglob
$ stat -c '"%n",%s,%y' /media/disk1/**/* >disk1.csv
shopt -s globstar dotglob
turns on recursive globbing feature of bash (enables use of '**', see https://unix.stackexchange.com/questions/49913/recursive-glob). It also turns on matching of files starting with a .
, als known as hidden files.
stat
is the program used to get file meta data. Basically this program will be run for each file on the disk.
-c '"%n",%s,%y'
specifies the output format for the stat command. %n
is the file name, enclosed in double quotes, %s
is the file size, %y
is the last modification time. (see stat --help
)
/media/disk1/**/*
tells bash to hand all the file names recursively found under that path to the pogram (stat), for both, normal and hidden files, since dotglob is enabled.
>disk1.csv
redirects output into a file named disk1.csv.
The output in disk1.csv will look like this for my home for instance:
$ stat -c '"%n",%s,%y' /home/seb/**/*
"/home/seb/111",82,2018-03-26 18:38:04.048099912 +0200
"/home/seb/app",4096,2017-07-13 23:39:06.509862769 +0200
"/home/seb/Applications",4096,2018-03-14 20:20:48.552005660 +0100
"/home/seb/Applications/arduino-1.8.2",4096,2017-05-29 20:45:01.184017517 +0200
"/home/seb/Applications/arduino-1.8.2/arduino",946,2017-03-22 13:32:41.000000000 +0100
[...]
I tested to import the resulting csv into libreoffice calc and it worked nicely, also with funny file names with line breaks in them. It will probably choke on file names with double quotes in them.
ARG_MAX
The above command will fail if the total number of files is too high or the total number of characters in all file names is too high. For small drives (USB thumb drives etc.) it should be enough, but if you are indexing a big disk with millions of files you would probably hit that limit.
You can run the following instead, it will produce the same output (and eat less memory):
find /media/disk1 -type f -print0 | xargs -0 stat -c '"%n",%s,%y' >disk1.csv
For the "find .. -print0 | xargs -0 .." pattern you will find many answers here already, e. g. Difference between "xargs" and command substitution?
Best Answer
Because after performing command substitution, the shell perform word splitting (or field splitting) on the result if you don't quote them.
POSIX defined word expansion as:
In your case,
*
matched all thing in current working directory. You must quote the result to get the expected output: