diff --git a/pysh.py b/pysh.py index ebf980d..6770430 100644 --- a/pysh.py +++ b/pysh.py @@ -3,6 +3,7 @@ import os import subprocess from utils.commands_list import * +from utils.boyer_moore_algorithm import BoyerMooreAlgorithm import calendar import getpass import shutil @@ -627,6 +628,24 @@ def do_diff(self, *args): else: print("pysh: diff: incorrect usage: try 'diff [FILE1] [FILE2]'") + def do_grep(self, *args): + commands = args[0].split() + self.save_history("grep " + " ".join(commands)) + pattern = BoyerMooreAlgorithm(commands[0]) + + if '-f' in commands: + if os.path.exists(os.getcwd() + '/' + commands[commands.index('-f') - 1]): + with open(commands[commands.index('-f') - 1], 'r') as file: + line_number = 1 + for line in file: + pattern.find_pattern(line.split(' '), True, line_number) + line_number += 1 + else: + print("pysh: grep: {}: No such file or directory".format(commands[commands.index('-f') - 1])) + + else: + pattern.find_pattern(commands[1:]) + def do_chmod(self, *args): gen_args = args[0].split() @@ -788,6 +807,9 @@ def help_kill(self): def help_diff(self): print(commands_list_manual['diff']) + def help_grep(self): + print(commands_list_manual['grep']) + def help_chmod(self): print(commands_list_manual['chmod']) diff --git a/utils/boyer_moore_algorithm.py b/utils/boyer_moore_algorithm.py new file mode 100644 index 0000000..210c255 --- /dev/null +++ b/utils/boyer_moore_algorithm.py @@ -0,0 +1,65 @@ +from colorama import Fore + +NO_OF_CHARS = 256 + +class BoyerMooreAlgorithm(): + def __init__(self, pattern): + self.pattern = pattern + self.shift_table = [-1] * NO_OF_CHARS + + # Fill the actual value of last occurrence + for char in range(len(pattern)): + self.shift_table[ord(pattern[char])] = char; + + def find_pattern(self, source, file = False, line_number = 0): + source = " ".join(source) + pattern_length = len(self.pattern) + result = [] + source_index = 0 + while(source_index <= len(source) - pattern_length): + j = pattern_length - 1 + + ''' + Keep reducing index j of pattern while + characters of pattern and text are matching + at this shift source_index + ''' + while j >= 0 and self.pattern[j] == source[source_index+j]: + j -= 1 + + ''' + If the pattern is present at current shift, + then index j will become -1 after the above loop + ''' + if j < 0: + result.append(source_index) + ''' + Shift the pattern so that the next character in text + aligns with the last occurrence of it in pattern. + The condition source_index + pattern_length < len(source) is necessary for the case when + pattern occurs at the end of text + ''' + source_index += (pattern_length - self.shift_table[ord(source[source_index + pattern_length])] + if source_index + pattern_length < len(source) else 1) + else: + ''' + Shift the pattern so that the bad character in source text + aligns with the last occurrence of it in pattern. The + max function is used to make sure that we get a positive + shift. We may get a negative shift if the last occurrence + of bad character in pattern is on the right side of the + current character. + ''' + source_index += max(1, j - self.shift_table[ord(source[source_index+j])]) + + if file and result != []: + print(f"Line {line_number}: ", end = '') + + previos_index = 0 + for index in result: + print(source[previos_index:index], end="") + print(Fore.CYAN + source[index:index + len(self.pattern)] + Fore.RESET, end = "") + previos_index = index + len(self.pattern) + + if result != []: + print('\n') diff --git a/utils/commands_list.py b/utils/commands_list.py index b49ce45..7560a6a 100644 --- a/utils/commands_list.py +++ b/utils/commands_list.py @@ -1,8 +1,7 @@ commands_list = ['lf', 'ldir', 'pwd', 'cd', 'manual', 'mkdir', 'calendar', 'calc', 'whoami', 'echo', 'rm', 'cat', 'cp', 'mv', 'date', 'file', 'history', - 'head', 'tail', 'touch', 'wc', 'ip', 'host', 'arch', 'ps', 'wget', 'kill', 'diff', - 'chmod'] + 'head', 'tail', 'touch', 'wc', 'ip', 'host', 'arch', 'ps', 'wget', 'kill', 'diff', 'chmod', 'grep'] commands_list_manual = { 'exit': "Exits the shell where it is currently running. \nusage: 'exit'", @@ -36,5 +35,6 @@ 'wget': "Network downloader - download files from internet. \nusage: 'wget [URL]'", 'kill': "Terminate an unresponsive program. \nusage: 'kill [PID]'", 'diff': "Compares files line by line. \nusage: 'diff [FILE1] [FILE2]'", + 'grep': "Utility for searching plain-text data sets for lines that match a regular expression. \nusage: 'grep [expression] [data-set] -f' {if the data-set is a file}\nusage: 'grep [expression] [data-set]' {if the data-set is an input string}", 'chmod': "Changes access permissions and the special mode flags of file system objects. \nusage: 'chmod [PERMISSIONS] [FILES]...'" }