Использование Linux или Unix-подобных операционных систем дает вам множество отличных инструментов командной строки, некоторые из них вы будете использовать несколько раз в день, а некоторые, возможно, никогда не будете использовать.
Ради обучения и получения лучше разбираясь в Python и C, я решил начать воссоздавать уже существующие команды Linux и некоторые инструменты кибербезопасности.
Сегодня мы обсудим то, что я узнал, воссоздав одну из наиболее часто используемых команд Linux, Cat.
Делаем это сначала на Python
Идея начать с Python проста. Я хочу иметь представление о том, что делать и чего ожидать при переходе на C. Это поможет мне сократить время завершения проекта.
Итак, в этой статье будет версия Python, а в следующей статье мы обсудим версию C.
Одно можно сказать наверняка, это самая легкая часть всего этого.
Я начал с написания простой документации и списка дел:
""" ******************************************************************* ** This is a re-creation of the OG Linux command "cat". ** ** This project is for educational purposes only. ** ** No program is free from bugs, so run it at your own risk. ** ** This is the Python version, a C version also be shared later. ** ******************************************************************* Made by shelldawg. -> github.com/shelldawg TODO: [ ] - Check file exist or not [ ] - Basic read in case file exists [ ] - separate options from files [ ] - Work on the options """
Это очень хорошая привычка, к которой можно привыкнуть при написании кода. Начните с определения ваших шагов и разбивки проблемы на более мелкие части, это поможет вам сделать все быстро, поскольку вы уже знаете, что делать.
Я разбил программу на 4 шага:
1. Проверить, существует ли файл
2. Базовое чтение файла, если файл существует
3. Отделить параметры от файлов
4. Работать по вариантам
Шаги 1 и 2 обеспечивают базовую функциональность.
Затем мы переходим к обогащению программы с точки зрения функциональности.
Шаг 1 и 2
import sys import os args = sys.argv[1::] # this to skip the script's name file = args[0] def getPath(file): return os.getcwd() + '/' + file # here we expect that theres on arg and its the file name if os.path.isfile(getPath(file)): with open(getPath(file), 'r') as text_file: f = text_file.readlines() print("".join(f))
Этого фрагмента кода достаточно для решения первых двух задач.
Краткое объяснение: мы импортируем sys и os, а затем получаем встроенные аргументы с sys.argv[1::], здесь я использовал [1::], чтобы пропустить элемент в индексе 0, потому что это будет имя скрипта.
Затем мы берем первый элемент из args[0] и помещаем его в переменную с именем file, это делается только для того, чтобы избежать повторения args[0]. Кроме того, поскольку это простая демонстрация с базовыми функциями, мы ожидаем, что пользователь введет один аргумент, и это будет файл для чтения.
Затем мы создали простую функцию getPath(файл); его роль состоит в том, чтобы взять имя файла в качестве параметра, а затем вернуть os.getcwd() + '/' + файл. Это делает две вещи:
1. >os.getcwd() возвращает текущий рабочий каталог (например, /home/user/lab)
2. затем мы объединяем его с '/' и имя файла
Окончательный путь будет выглядеть так: /home/user/lab/file.txt
Затем мы вводим оператор if, мы использовали os.path.isfile(), он принимает путь к файлу (в этом случае мы использовали нашу собственную функцию для создания полный путь к файлу), если файл существует, он вернет True; в противном случае False.
Если true, то мы продолжаем читать содержимое файла построчно. Это облегчит нам реализацию некоторых опций. Подробнее об этом позже.
Теперь, когда у нас есть очень простая программа, мы можем добавить больше функций.
Шаг 3:
Перед разделением аргументов и имен файлов я протестировал настоящую команду cat в своем терминале, оказалось, если вы указали недопустимую опцию, она вообще не запустится, а если все опции действительны (или вообще отсутствуют), она будет распечатать содержимое файла/ов, если файл существует, в противном случае он сообщает вам, что файла нет.
На основе этой информации я написал этот код для обработки шага № 3:
validOptions= ["-b", "--number-nonblank", "-E", "--show-ends", "-n", "--number", "-s", "--squeeze-blank", "-t", "-T", "--show-tabs", "--help"] options = [] # this will hold options found files = [] # this will hold files found for arg in args: # separationg options from files if '-' in arg[0]: if validOptions.count(arg): options.append(arg) else: print(f"dawgcat: invalid option: {arg}\nTry 'python dawgcat.py --help' for more information.") exit() elif (os.path.isfile(getPath(arg))): files.append(arg) else: print(f"dawgcat: {arg}: : No such file or directory")
Здесь у нас есть простой цикл for, который перебирает переменную args, которую мы создали на шагах 1 и 2.
Первый оператор if должен определить, начинается ли аргумент с «-», так как все наши опции будут начинаться с него (например: — help, -b…)
Это важно, потому что в именах файлов может быть «-». Так что не стоит просто проверять, существует ли «-» в аргументе.
Если это так, мы снова проверяем, существует ли этот аргумент внутри массива validOptions, и если да, то добавляем его в options, который содержит найденные допустимые параметры.
Если его нет в допустимом списке, мы сообщаем пользователю, что параметр недействителен, и мы exit() программа сразу.
Если аргумент не начинается с дефиса '-', мы проверяем, существует ли файл, как мы это делали в шагах 1 и 2. Если да, мы добавляем его в массив files. .
В противном случае мы просто сообщаем пользователю, что файл не существует, и переходим к следующей итерации цикла.
И вот как я справился с этой проблемой. Если у вас есть идея получше, дайте мне знать в комментариях ниже!
Теперь перейдем к финальному бою с боссом, чтобы варианты заработали.
Наконец, Шаг №4!
Здесь я начал с простой функции помощи.
class bcolors: # this is taken from stackoverflow.com/questions/287871/how-do-i-print-colored-text-to-the-terminal HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def printHelp(): print(bcolors.OKGREEN, """ ____ __ / __ \____ __ ______ __________ _/ /_ / / / / __ `/ | /| / / __ `/ ___/ __ `/ __/ / /_/ / /_/ /| |/ |/ / /_/ / /__/ /_/ / /_ /_____/\__,_/ |__/|__/\__, /\___/\__,_/\__/ /____/ """,bcolors.ENDC) print("Usage: python dawgcat.py [OPTIONS] [FILE]") print("Concatenate FILE(s) to standard output.") print(""" -b, --number-nonblank number nonempty output lines, overrides -n -E, --show-ends display $ at end of each lines -n, --number number all output lines -s, --squeeze-blank suppress repeated empty output lines -T, --show-tabs display TAB characters as ^I --help display this help and exist """) print(bcolors.HEADER,"Help menu is literally not from OG cat command.",bcolors.ENDC) exit()
После вызова printHel() он распечатает всю эту информацию и закроет программу. Поскольку почти все команды делают это, я сделал то же самое.
if("--help" in " ".join(args).lower()) or len(args) == 0: printHelp() # the function has exit() so the program stops here
Я также добавил эту проверку, чтобы увидеть, был ли вызов опции — help или нет аргументов.
Я также использовал фрагмент кода из stackoverflow, чтобы придать меню справки еще немного жизни. Кредиты идут на stackoverflow.com/users/19104/joeld.
Теперь варианты:
def applyOptions(file, length = 0): # this returns the file as it is if there are no options if len(options) == 0: return file #removing overwriten option (e.g: if -b and -n exists only -n stays) if "-n" in options and "-b" in options: options.remove("-n") for option in options: if option == "-s" or option == "--squeeze-blank": file = [line for line in file if line.strip() != ''] if option == "-E" or option == "--show-ends": file = [line.replace("\n", "$\n") for line in file] if option == "-T" or option == "--show-tabs": file = [line.replace("\t", "^I") for line in file] if option == "-n" or option == "--number": file = ["{:>6} {}".format(i+1+length, file[i]) for i in range(len(file))] if option == "-b" or option == "--number-nonblank": # file = ["{:>6} {}".format(counter+length, file[i]) if file[i].strip() != '\n' else file[i] for i in range(len(file)) ] # I tried to do it in line but it failed so many times. I guess this is the better way now. counter = 1 for i in range(len(file)): if file[i].strip() != "": file[i] = "{:>6} {}".format(counter+length, file[i]) counter += 1 else: file[i]=file[i] return file
Этот фрагмент кода перебирает каждый элемент массива options, который мы создали ранее.
Он принимает два аргумента, один из которых является обязательным, а другой — необязательным.
Это файл и длина. File для изменения и возврата, а длина предназначена для обработки номеров строк на случай, если нужно прочитать несколько файлов.
Начнем с простого оператора if, который проверяет, пуст ли массив options, если нет, продолжаем; в противном случае мы возвращаем файл без каких-либо изменений.
Основываясь на тестировании cat в моей командной строке в Linux, я решил сделать второй оператор if. Он проверяет, существуют ли обе опции -n и -b в массиве options. Если это так, мы удаляем -n.
ОПЯТЬ, теперь варианты!
Теперь мы перебираем массив options и выполняем ряд проверок, чтобы определить, что делать.
Здесь я не знаю, как объяснить их все, потому что это займет довольно много слов…
Помните, когда я говорил вам, что проще иметь дело с файлами как с массивом строк? Вот почему теперь я могу зацикливаться на каждой строке и обновлять их по одной в зависимости от параметров, это в значительной степени то, что я делал со всеми параметрами.
Возможно, это неэффективно, но это дает Работа выполнена.
Попробуйте прочитать код каждой опции по одному, я обещаю, что они не такие сложные, как вы думаете.
Обновление файла, прочитанного на шагах № 1 и № 2:
Теперь, когда у нас все работает, как задумано, мы изменим последний фрагмент кода, чтобы он вызывал только что написанную функцию applyOptions(), а затем распечатывает файл.
# reading files if len(files) > 0: length = 0 for i in range(len(files)): with open(getPath(files[i]), 'r') as text_file: f = text_file.readlines() print("".join(applyOptions(f, length)), end = '') length += len(f)
Теперь вместо печати файла мы печатаем результат функции applyOptions(). Обратите внимание, что мы добавили переменную длины для хранения длины файла; это не повлияет на первый файл. Но если есть несколько файлов, это важная часть, чтобы заставить его работать так же, как Cat в командной строке.
Теперь собираем детали по порядку!
Окончательный вариант скрипта размещен на моем GitHub здесь:
https://github.com/shelldawg/dawgcat/blob/main/dawgcat.py
Вынос:
А. Начните с разбиения программы на небольшие шаги.
Б. Комментарии помогут вам понять код, когда вы вернетесь к нему в следующем десятилетии.
В. Python — это весело.
Вы человек!
Если вы дошли до этого места, спасибо, честно говоря, это было веселое испытание.
Увидимся в следующий раз! И не забывайте, что версия C скоро выйдет.
Shelldawg — https://github.com/shelldawg