This is a simple Python command-line text file searching application. It is best used if you are searching for something in a directory with .txt files.
Let's start by importing the modules we will need for this program.
import os
import errno
import glob
import sys
import pathlib
Stub out the main function.
def main():
Don't forget to call it since it's Python.
main()
Print out the intro.
# prints intro text
def intro():
print('WORD SEARCH 0.1')
print('Searches TXT files within given directory and subdirectories. ' +
'Use quotes to clarify search term.')
print('Enter \'q\' to exit.')
Add it to the main function.
def main():
intro()
Now let's read from console using the input() function.
def main():
intro()
# read from console
path = input('Dir: ')
We need to validate the path given before we start trying to search it.
# validates directory
def dirValidate(path):
# check for exit command
if path.lower() == 'q':
sys.exit()
# correct backslashes
path = path.replace('\\', '/')
# check if directory exists
if not os.path.exists(path):
print("must be a working directory\n")
main()
return path
Let's add it to the main function.
def main():
intro()
# read from console
path = input('Dir: ')
# validate dir
path = dirValidate(path)
Get the search term.
def main():
intro()
# read from console
path = input('Dir: ')
# validate dir
path = dirValidate(path)
# receive search term
words = input('Search: ')
Test to see if this is a literal search (usually designated with quotes).
# returns search mode: 1 for literal, 0 for default
def testMode(s):
if ((s.startswith('\'') and s.endswith('\'')) or
(s.startswith('\"') and s.endswith('\"'))):
return 1
else:
return 0
Add it to the main function.
def main():
intro()
# read from console
path = input('Dir: ')
# validate dir
path = dirValidate(path)
# receive search term
words = input('Search: ')
# test search mode
mode = testMode(words)
We are going to use an object to help manage the state of the search as we pass through the directory. This can be placed at the bottom of the file for reference.
# boxes up the strings
class StringSet:
def __init__(self, mode, words, path, count, line):
self.mode = mode
self.words = words
self.path = path
self.count = count
self.line = line.lower()
self.pointer = '^'
We need to define the search function. Remember that it has two modes: a normal search that tests for the words being in the line and a more literal search.
def search(set):
if set.mode == 0:
if set.words in set.line:
return True
elif set.mode == 1:
set.words = set.words.strip('\"')
set.words = set.words.strip('\'')
newSearch = ' ' + set.words + ' '
if newSearch in set.line:
return True
Let's create a function to report each valid match found in the file, what line it is, and where it is on that line.
# print to console
def printToConsole(set):
print('FILE: {} LINE {} \"{}\"'.format(set.path, set.count, set.line))
if len(set.pointer) > POINTER_LENGTH:
print(set.pointer)
Here are some constants that should be defined at the top of the file.
FILE_NAME_LENGTH = 30
COUNT_NAME_LENGTH = 4
POINTER_LENGTH = 55
We need to format the strings we are passing to the console.
# processes the strings to format in console
def formatStrings(set):
if len(set.path) > FILE_NAME_LENGTH:
set.path = '...' + set.path[len(set.path) -
FILE_NAME_LENGTH:len(set.path)] + ' - '
set.count += 1
set.count = str(set.count) + ':'
if len(set.count) > COUNT_NAME_LENGTH:
set.count = set.count.rstrip(':')
set.count = set.count[0:COUNT_NAME_LENGTH - 1] + '~'
else:
set.count = set.count.ljust(COUNT_NAME_LENGTH)
set.line = set.line.rstrip('\n')
index = set.line.find(set.words)
set.pointer = set.pointer.rjust(index + POINTER_LENGTH)
return set
Here is where we use more of our IO modules. Open the directory and iterate through its contents. Notice that we are handling errors and reporting them descriptively. We are also instantiating the StringSet object we just defined to handle state. We then search that line and print to console if there is a match. The printToConsole calls the formatStrings function we created.
# opens and searches files
def openDir(path, words, mode):
foundBool = False
# search and output
os.chdir(path)
for file in glob.iglob(path + '/**/*.txt', recursive=True):
if os.path.isfile(file):
try:
with open(file, errors='ignore') as fileinput:
for count, line in enumerate(fileinput):
set = StringSet(mode, words, file, count, line)
if search(set):
foundBool = True
printToConsole(formatStrings(set))
except IOError as e:
if e.errno == errno.ENOENT:
print(os.path(file), ' does not exist')
if e.errno == errno.EACCES:
print(os.path(file), ' cannot be read')
else:
print(os.path(file), e)
if foundBool is False:
print('**NO WORDS FOUND**')
Finally, add it to the main function. Also, we want to keep the program running in case there are more search terms, so let's add a recursive call to main.
def main():
intro()
# read from console
path = input('Dir: ')
# validate dir
path = dirValidate(path)
# receive search term
words = input('Search: ')
# test search mode
mode = testMode(words)
# begin operation
openDir(path, words, mode)
# restart
main()
I hope you enjoyed this foray into Python!
Here is the entire file:
import os
import errno
import glob
import sys
import pathlib
FILE_NAME_LENGTH = 30
COUNT_NAME_LENGTH = 4
POINTER_LENGTH = 55
def main():
intro()
# read from console
path = input('Dir: ')
# validate dir
path = dirValidate(path)
# receive search term
words = input('Search: ')
# test search mode
mode = testMode(words)
# begin operation
openDir(path, words, mode)
# restart
main()
# prints intro text
def intro():
print('WORD SEARCH 0.1')
print('Searches TXT files within given directory and subdirectories. ' +
'Use quotes to clarify search term.')
print('Enter \'q\' to exit.')
# validates directory
def dirValidate(path):
# check for exit command
if path.lower() == 'q':
sys.exit()
# correct backslashes
path = path.replace('\\', '/')
# check if directory exists
if not os.path.exists(path):
print("must be a working directory\n")
main()
return path
# returns search mode: 1 for literal, 0 for default
def testMode(s):
if ((s.startswith('\'') and s.endswith('\'')) or
(s.startswith('\"') and s.endswith('\"'))):
return 1
else:
return 0
# opens and searches files
def openDir(path, words, mode):
foundBool = False
# search and output
os.chdir(path)
for file in glob.iglob(path + '/**/*.txt', recursive=True):
if os.path.isfile(file):
try:
with open(file, errors='ignore') as fileinput:
for count, line in enumerate(fileinput):
set = StringSet(mode, words, file, count, line)
if search(set):
foundBool = True
printToConsole(formatStrings(set))
except IOError as e:
if e.errno == errno.ENOENT:
print(os.path(file), ' does not exist')
if e.errno == errno.EACCES:
print(os.path(file), ' cannot be read')
else:
print(os.path(file), e)
if foundBool is False:
print('**NO WORDS FOUND**')
def search(set):
if set.mode == 0:
if set.words in set.line:
return True
elif set.mode == 1:
set.words = set.words.strip('\"')
set.words = set.words.strip('\'')
newSearch = ' ' + set.words + ' '
if newSearch in set.line:
return True
# processes the strings to format in console
def formatStrings(set):
if len(set.path) > FILE_NAME_LENGTH:
set.path = '...' + set.path[len(set.path) -
FILE_NAME_LENGTH:len(set.path)] + ' - '
set.count += 1
set.count = str(set.count) + ':'
if len(set.count) > COUNT_NAME_LENGTH:
set.count = set.count.rstrip(':')
set.count = set.count[0:COUNT_NAME_LENGTH - 1] + '~'
else:
set.count = set.count.ljust(COUNT_NAME_LENGTH)
set.line = set.line.rstrip('\n')
index = set.line.find(set.words)
set.pointer = set.pointer.rjust(index + POINTER_LENGTH)
return set
# print to console
def printToConsole(set):
print('FILE: {} LINE {} \"{}\"'.format(set.path, set.count, set.line))
if len(set.pointer) > POINTER_LENGTH:
print(set.pointer)
# boxes up the strings
class StringSet:
def __init__(self, mode, words, path, count, line):
self.mode = mode
self.words = words
self.path = path
self.count = count
self.line = line.lower()
self.pointer = '^'
main()