Дата и время публикации:
Проблема и решение
1. Суть проблемы
Большинство операций с файлами заключаются в анализе текстовых данных, содержащие некую важную и понятную человеческому глазу информацию, например, лог сборки и установки программного обеспечения, выполнения перечня важных системных операций или просто записи реляционной базы данных, на роль которой можете подойти любой текстовый файл содержащий данные соотносимые с другими такими же файлами, образующие таким образом файловую базу данных.
Поэтому давайте взглянем на пример, который будем крутить, в виде нескольких связанных между собой файлов /etc/passwd и /etc/group
2. Решение
2.1 Открытие текстового файла
Показано в листинге 2.1.1
Листинг 2.1.1
... 10 import os 11 import sys ... 45 def outfile_read( fl_name ) : 46 try: 47 with open(fl_name,'r') as f: 48 content = f.read() 49 return len(content) 50 except FileNotFoundError: 51 print("%s file is missing" % fl_name ) 52 return 0 53 except PermissionError: 54 print("You are not allowed to read %s" % fl_name ) 55 return 0 ... 200 if __name__ == '__main__': 201 if len (sys.argv) > 1 : 202 outfl_nm = sys.argv[1] 203 if outfl_nm : 204 n = outfile_read(outfl_nm) 205 print ( 'Outfile %s : ' % outfl_nm, ' read : %d' % int(n), ' bytes') ...
Как показано в строке 47, открытие текстового файла, передаваемого в первом аргументе (см. строки 201 и 202) в вызываемом коде cкрипте Python, производится путем обрашения к встроенной функции open() [3.1] с передачей ей аргументов — имени файла (fl_name) и режима открытия "r" — только чтение
При этом, нам нет надобности импортировать модуль errno, потому что в последних версиях Pytnon3 нет надобности собственноручной инспекции атрибутов исключений типа ENOENT, EACCES, EPERM. Вместо них теперь обещесистемные исключения FileNotFoundError и PermissionError [3.2]
2.2 Построчное чтение данных
В случае с текстовым файлом нам интересно в первую очередь построчное чтение содержимого, которое можно реализовать путем использования непосредственно вызова readline(), к примеру как показано в листинге 2.2.1
Листинг 2.2.1
... 41 n = 0 42 while True : 43 line=f.readline() 44 if line != '' : 46 print ( line ) 47 n += len(line) 48 continue 49 break 50 return n ...
Текст которого на мой взгляд избыточен, чем тоже самое чтение, с использованием встроенной функции read(size), которая может принимать один единственный аргумент size – размер читаемой информации в байтах. Если же его не указывать, то по умолчанию, read() будет читать до тех пор, пока не дойдет до конца читаемого файла, т.е. встретит пустую строку (line=''), как показано в листинге 2.2.2
Листинг 2.2.2
... 38 def passwd_read( csvfl ) : ... 41 n = 0 42 for line in f: 43 n += len(line) ...
2.3 Извлечение данных из прочитанной строки
Cамое простое использовать split() из Python-модуля re [3.4], указывая сразу несколько возможных разделителей текста [3.5], как показано в листинге 2.3.1
Листинг 2.3.1
... 38 def passwd_read( csvfl ) : ... 41 n = 0 42 for line in f: 43 n += len(line) 44 chunks = re.split(':|\\n', line) 45 if len(chunks) > 6 : 46 if re.match ( '.*/false$|.*/nologin$' , chunks[6] ) : 47 continue; 48 print(chunks) ...
В результате выполнения кода, как в случае с файлом passwd, будет выдано на консоль нечто подобное, как показано в дампе 2.3.2
Дамп 2.3.2
Truncate: /home/user/Projects/Pythons/passwd_report.csv ['root', 'x', '0', '0', 'root', '/root', '/bin/bash', ''] \=====> ['root', 'x', '0', '', ''] ['sync', 'x', '4', '65534', 'sync', '/bin', '/bin/sync', ''] \=====> ['nogroup', 'x', '65534', '', ''] ['user', 'x', '1000', '1000', '', '/home/user', '/bin/bash', ''] \=====> ['user', 'x', '1000', '', ''] ['sen-sira', 'x', '1001', '1001', '', '/home/sen-sira', '/bin/sh', ''] \=====> ['sen-sira', 'x', '1001', '', ''] ['admin', 'x', '1002', '1002', '', '/home/admin', '/usr/bin/bash', ''] \=====> ['admin', 'x', '1002', '', ''] Passwd /etc/passwd : read : 2680 bytes Outfile /home/user/Projects/Pythons/passwd_report.csv : was created.
т.е. объект chunks будет представлять собой не просто строку с текстовыми данными, а уже некую реляцию, которую можно идентифицировать по первому столбцу (символьному имени пользователя), либо по 3 и 4-му столбцам, содержащие соответственно идентификатору пользователя UID и группы (GID).
Тоже самое получим с файлом group, как показано в листинге 2.3.3
Листинг 2.3.2
... 22 def group_read( gid ) : 23 try: 24 with open(group_nm,'r') as f: 25 for line in f : 26 chunks = re.split(':|\\n', line) 27 if len(chunks) > 3 and chunks[2] == str(gid) : 28 print ( '\=====>', chunks ) 29 return chunks 30 return None 31 except PermissionError: 32 print("You are not allowed to read %s" % group_nm ) 33 return None ... 36 def passwd_read( csvfl ) : ... 47 print ( chunks ) 48 gchunks=group_read( chunks[3] ) ...
Для составление отчета, в котором сопоставляется несколько текстовых значений и добавляются одной строкой в текстовый файл, как показано в листинге 2.4.1
Листинг 2.4.1
... 17 pwdir=os.path.dirname(os.path.realpath(__file__)) ... 38 def passwd_read( csvfl ) : ... 48 gchunks=group_read( chunks[3] ) 49 if gchunks : 50 print ( '\"%s\"' % chunks[0], ';%s' % chunks[2], ';%s' % gchunks[0], ';%s' % chunks[5], ';%s' % chunks[6], file=csvfl ) ... 61 def csvreport_truncate(fl_path) : 62 try: 63 with open(fl_path,'w') as f: 64 f.write('LOGIN;UID;GROUP;HOME;SHELL\n') 65 f.close() 66 return True 67 except PermissionError: 68 print("You are not allowed to read %s" % fl_path ) 69 return False 70 71 if __name__ == '__main__': 72 csvreport_nm = pwdir+'/'+'passwd_report.csv' 73 if csvreport_truncate(csvreport_nm) : 74 csvrepo_fl=open(csvreport_nm,'a+') 75 n = passwd_read(csvrepo_fl) 76 csvrepo_fl.close() ...
Как показано в листинге 2.4.1 в строке 50 через передачу аргументу файлового объекта csvrepo_fl в который будет производится перенаправление вывода print() [3.6]. При этом,
- в строке 63 путем передачи символа 'w' во втором аргументе open(), производится обрезание файла. Из обрабатываемых исключений, в этом случае только обрабатывается PermissionError, которое может возникнуть при каких-либо проблем связанных с определением текущей рабочей директории pwdir и/или ошибках в работе файловой системы;
- В строке 74 производится открытие файла-отчета, куда будем сливать данные в формате, определенным в строке 50;
- в конце работы, в строке 76, закрываем файл-отчет куда перенаправляли найденные по описанным критериям выше.
Как показано на рисунке 2.4.2, нами согласно определенных критериев были получены, отобраны и затем сопоставлены данные, что указывает на то, что файлы /etc/passwd и /etc/group относятся к реаляционной базы данных, которые позволяют осуществлять локально процедуру аутентификации и идентифкации, т. е. авторизировать действия и самого пользователя в системе.
Рисунок 2.4.2
При этом, дамп 2.3.2 и рисунк 2.4.2 подсказывают, что каждая строка содержит реляцию, которая имеет постоянную структуру и как минимум содержит одно идетифицирующее свойство с другим файлом.
В данном случае 3-й столбец (при отсчете от нуля) файла /etc/passwd содержит GID, по которому была найдена имя группы в соответствующей реляции в файле /etc/group . В тоже время, сам файла /etc/passwd тоже содержит реляции, т.к. по первый столбец LOGIN содержит всегда уникальное символьное имя.
3. Библиография
3.1 Python's Built-in Functions. Open()
3.2 Python's Built-in exceptions
3.4 Python Split String by Space
3.5 Stackoverflow. Split string with multiple delimiters in python
3.6 Stackoverflow. How to redirect the output of print to a txt file