diff options
392 files changed, 39927 insertions, 0 deletions
diff --git a/.obsidian/app.json b/.obsidian/app.json new file mode 100644 index 0000000..453053f --- /dev/null +++ b/.obsidian/app.json @@ -0,0 +1,14 @@ +{ + "vimMode": true, + "attachmentFolderPath": "files", + "pdfExportSettings": { + "includeName": true, + "pageSize": "A4", + "landscape": false, + "margin": "0", + "downscalePercent": 100 + }, + "showLineNumber": false, + "autoPairMarkdown": true, + "spellcheck": false +}
\ No newline at end of file diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json new file mode 100644 index 0000000..21c46fc --- /dev/null +++ b/.obsidian/appearance.json @@ -0,0 +1,4 @@ +{ + "accentColor": "", + "theme": "moonstone" +}
\ No newline at end of file diff --git a/.obsidian/core-plugins-migration.json b/.obsidian/core-plugins-migration.json new file mode 100644 index 0000000..436f43c --- /dev/null +++ b/.obsidian/core-plugins-migration.json @@ -0,0 +1,30 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false +}
\ No newline at end of file diff --git a/.obsidian/core-plugins.json b/.obsidian/core-plugins.json new file mode 100644 index 0000000..8e719d8 --- /dev/null +++ b/.obsidian/core-plugins.json @@ -0,0 +1,33 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false, + "webviewer": false, + "footnotes": false, + "bases": true +}
\ No newline at end of file diff --git a/.obsidian/graph.json b/.obsidian/graph.json new file mode 100644 index 0000000..42a46ec --- /dev/null +++ b/.obsidian/graph.json @@ -0,0 +1,22 @@ +{ + "collapse-filter": true, + "search": "", + "showTags": false, + "showAttachments": false, + "hideUnresolved": false, + "showOrphans": true, + "collapse-color-groups": true, + "colorGroups": [], + "collapse-display": true, + "showArrow": false, + "textFadeMultiplier": 0, + "nodeSizeMultiplier": 1, + "lineSizeMultiplier": 1, + "collapse-forces": true, + "centerStrength": 0.518713248970312, + "repelStrength": 10, + "linkStrength": 1, + "linkDistance": 250, + "scale": 1, + "close": true +}
\ No newline at end of file diff --git a/.obsidian/hotkeys.json b/.obsidian/hotkeys.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.obsidian/hotkeys.json @@ -0,0 +1 @@ +{}
\ No newline at end of file diff --git a/.obsidian/templates.json b/.obsidian/templates.json new file mode 100644 index 0000000..cda2aca --- /dev/null +++ b/.obsidian/templates.json @@ -0,0 +1,3 @@ +{ + "folder": "templates" +}
\ No newline at end of file diff --git a/.obsidian/workspace (10).json b/.obsidian/workspace (10).json new file mode 100644 index 0000000..eecbabc --- /dev/null +++ b/.obsidian/workspace (10).json @@ -0,0 +1,210 @@ +{ + "main": { + "id": "22b54cdfe598d436", + "type": "split", + "children": [ + { + "id": "fd025cb4a7192636", + "type": "tabs", + "children": [ + { + "id": "7c0c92b3108c5e91", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "LINUX/WIREGUARD.md", + "mode": "source", + "source": false + } + } + }, + { + "id": "0326d9c93c8b7596", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "LINUX/GPG, PASS.md", + "mode": "source", + "source": false + } + } + } + ], + "currentTab": 1 + } + ], + "direction": "vertical" + }, + "left": { + "id": "ae9ce61e34e2a30b", + "type": "split", + "children": [ + { + "id": "272888dc6bd23a63", + "type": "tabs", + "children": [ + { + "id": "3d69f2baa3152c95", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "byModifiedTimeReverse" + } + } + }, + { + "id": "304816efb0ab556e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "e88a968f3bbf9578", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 211.5 + }, + "right": { + "id": "b155a938b8a7e919", + "type": "split", + "children": [ + { + "id": "3f12dfc7ff91ce6e", + "type": "tabs", + "children": [ + { + "id": "a9fe2861a649f07f", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "LINUX/GPG, PASS.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "f5f51f424be955ad", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "LINUX/GPG, PASS.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "daedec170123f202", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "ab86f8ad95ae5f4c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "LINUX/GPG, PASS.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "0326d9c93c8b7596", + "lastOpenFiles": [ + "LINUX/Systemctl.md", + "LINUX/Автозапуск скрипта.md", + "LINUX/WIREGUARD.md", + "LINUX/SCP.md", + "LINUX/GPG, PASS.md", + "Login.md", + "GIT.md", + "PYTHON/PYINSTALLER.md", + "123.md", + "PYTHON/ФУНКЦИИ.md", + "PYTHON/SET, DICT.md", + "PYTHON/TUPLE.md", + "PYTHON/LIST.md", + "VIM.md", + "RANGER.md", + "PEP8.md", + "PEP8", + "LINUX/CHMOD.md", + "PYTHON", + "SHELL команды.md", + "LINUX/ENVIRONMENT.md", + "YANDEX_DISK.md", + "Образец.md", + "Installing Applications.md", + "LINUX/SSH.md", + "LINUX/HTOP.md", + "LINUX/DIFFERENT.md", + "LINUX/BEGIN.md", + "LINUX/NETWORK", + "files/Aygo-топл_бак_и_горловина.jpg", + "files/Aygo-передний_бампер.jpg", + "files/Aygo-передн_подвеска.jpg", + "files/Aygo-задняя_подвеска.jpg", + "files/Aygo-задний_бампер.jpg", + "files/Aygo-зад_полы_и_панель.jpg", + "files/2016-07-21 21Aygo-глушитель.jpg", + "files/ae81365154ba5166845a7a9171d2248b.png", + "files/6cda81091bcc9f99ad224bd5c5426f8e.png", + "files/4a2b1a7f1e1bee9a26d7885b1d5e0f8c.png", + "templates", + "REPORTS", + "Без названия 1", + "files", + "LINUX/DISK", + "LINUX" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace (11).json b/.obsidian/workspace (11).json new file mode 100644 index 0000000..d775058 --- /dev/null +++ b/.obsidian/workspace (11).json @@ -0,0 +1,199 @@ +{ + "main": { + "id": "22b54cdfe598d436", + "type": "split", + "children": [ + { + "id": "cd9be558ae886730", + "type": "tabs", + "children": [ + { + "id": "f930a2b0468778c4", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "LINUX/SERVER - мониторинг.md", + "mode": "source", + "source": false + } + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "ae9ce61e34e2a30b", + "type": "split", + "children": [ + { + "id": "272888dc6bd23a63", + "type": "tabs", + "children": [ + { + "id": "3d69f2baa3152c95", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + } + } + }, + { + "id": "304816efb0ab556e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "e88a968f3bbf9578", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 241.5 + }, + "right": { + "id": "b155a938b8a7e919", + "type": "split", + "children": [ + { + "id": "3f12dfc7ff91ce6e", + "type": "tabs", + "children": [ + { + "id": "a9fe2861a649f07f", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "LINUX/SERVER - мониторинг.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "f5f51f424be955ad", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "LINUX/SERVER - мониторинг.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "daedec170123f202", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "ab86f8ad95ae5f4c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "LINUX/SERVER - мониторинг.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "f930a2b0468778c4", + "lastOpenFiles": [ + "LINUX/BEGIN.md", + "LINUX/SCP.md", + "LINUX/TERMINAL.md", + "PYTHON/BEGIN + PIP.md", + "SPOOFDPI.md", + "SQL/MySQL.md", + "PYTHON/COM-порт.md", + "SQL/SQLite.md", + "LINUX/BASH_script/BASH. Разное.md", + "LINUX/BASH_github/Основы Bash.md", + "LINUX/BASH_github/bash-guide-1.md", + "SQL/SQLite Commands.md", + "SQL/ШПАРГАЛКА SQLite.md", + "SQL/SQL. Базы Данных.md", + "PYTHON/SET, DICT, LIST, TUPLE, RANGE.md", + "PYTHON/STRING.md", + "PYTHON/Password & key = PATH.md", + "SQL/SQL Cheatsheet.md", + "PYTHON/DJANGO.md", + "LINUX/RSYNC.md", + "PYTHON/PYINSTALLER.md", + "SQL/НОРМАЛЬНЫЕ ФОРМЫ.md", + "files/Снимок экрана 2024-09-16 в 21.45.54.png", + "files/Снимок экрана 2024-09-16 в 21.45.31.png", + "files/Снимок экрана 2024-09-16 в 21.39.34.png", + "files/Снимок экрана 2024-09-16 в 21.36.18.png", + "files/Снимок экрана 2024-09-16 в 21.31.06.png", + "files/Снимок экрана 2024-09-15 в 19.10.29.png", + "files/Снимок экрана 2024-09-15 в 19.02.02.png", + "files/Снимок экрана 2024-09-15 в 18.55.25.png", + "PYTHON/PrettyTable.md", + "files/telegram-cloud-photo-size-2-5413616564063822987-y.jpg", + "PYTHON/PROGRAMS_py.md", + "SQL", + "VSCode.md", + "LINUX/NETWORK/NETWORK.md", + "files/2024-09-05 01.32.11.jpg", + "LINUX/BASH_script", + "RADIO", + "LINUX/BASH_github", + "sync", + "gitSync.sh", + "AUTO", + "_PROGRAMs/C++", + "MacOS", + "_PROGRAMs/ESP8266-ESP32", + "Без названия.canvas" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace (12).json b/.obsidian/workspace (12).json new file mode 100644 index 0000000..f32eadf --- /dev/null +++ b/.obsidian/workspace (12).json @@ -0,0 +1,215 @@ +{ + "main": { + "id": "22b54cdfe598d436", + "type": "split", + "children": [ + { + "id": "ebc417c74e936cbe", + "type": "tabs", + "children": [ + { + "id": "df49f7d5f8bd02a2", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "_PROGRAMs/C++/C UART.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "C UART" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "ae9ce61e34e2a30b", + "type": "split", + "children": [ + { + "id": "272888dc6bd23a63", + "type": "tabs", + "children": [ + { + "id": "3d69f2baa3152c95", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + }, + "icon": "lucide-folder-closed", + "title": "Файловый менеджер" + } + }, + { + "id": "304816efb0ab556e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Поиск" + } + }, + { + "id": "e88a968f3bbf9578", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Закладки" + } + } + ] + } + ], + "direction": "horizontal", + "width": 241.5 + }, + "right": { + "id": "b155a938b8a7e919", + "type": "split", + "children": [ + { + "id": "3f12dfc7ff91ce6e", + "type": "tabs", + "children": [ + { + "id": "a9fe2861a649f07f", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "GIT.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Обратные ссылки для GIT" + } + }, + { + "id": "f5f51f424be955ad", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "GIT.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Исходящие ссылки из GIT" + } + }, + { + "id": "daedec170123f202", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + }, + "icon": "lucide-tags", + "title": "Теги" + } + }, + { + "id": "ab86f8ad95ae5f4c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "GIT.md" + }, + "icon": "lucide-list", + "title": "Структура GIT" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "df49f7d5f8bd02a2", + "lastOpenFiles": [ + "GIT.md", + "_PROGRAMs/C++/C UART.md", + "_PROGRAMs/C++/WiFi.md", + "_PROGRAMs/C++/TIME.md", + "_PROGRAMs/C++/MQTT.md", + "_PROGRAMs/C++/УСРЕДНЕНИЕ.md", + "_PROGRAMs/ESP8266-ESP32/EEPROM.md", + "_PROGRAMs/ESP8266-ESP32/RTC память.md", + "LINUX/GPG, PASS.md", + "DaVinciResolve/HEVC.md", + "DaVinciResolve/ОПТИМИЗАЦИЯ.md", + "YANDEX_DISK.md", + "NuPhy Air75.md", + "LINUX/MOSQUITTO.md", + "DaVinciResolve", + "LINUX/UFW.md", + "LINUX/TMUX.md", + "LINUX/CHMOD, CHOWN.md", + "LINUX/USER.md", + "LINUX/BASH.md", + "LINUX/BEGIN.md", + "LINUX/SHELL команды.md", + "Login.md", + "PixelNAS.md", + "LINUX/DISK/SMART.md", + "LINUX/DISK/Working with DISK.md", + "LINUX/DISK/DF, DU.md", + "files/telegram-cloud-photo-size-2-5406976059292904279-x.jpg", + "files/DB_metall.ods", + "LINUX/TIME", + "files/Снимок экрана 2024-11-01 в 01.39.59.png", + "files/Снимок экрана 2024-09-16 в 21.45.54.png", + "files/Снимок экрана 2024-09-16 в 21.45.31.png", + "files/Снимок экрана 2024-09-16 в 21.39.34.png", + "files/Снимок экрана 2024-09-16 в 21.36.18.png", + "files/Снимок экрана 2024-09-16 в 21.31.06.png", + "files/Снимок экрана 2024-09-15 в 19.10.29.png", + "files/Снимок экрана 2024-09-15 в 19.02.02.png", + "files/Снимок экрана 2024-09-15 в 18.55.25.png", + "SQL", + "LINUX/BASH_script", + "RADIO", + "LINUX/BASH_github", + "sync", + "gitSync.sh", + "AUTO", + "Без названия.canvas" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace (13).json b/.obsidian/workspace (13).json new file mode 100644 index 0000000..229abc5 --- /dev/null +++ b/.obsidian/workspace (13).json @@ -0,0 +1,230 @@ +{ + "main": { + "id": "22b54cdfe598d436", + "type": "split", + "children": [ + { + "id": "ebc417c74e936cbe", + "type": "tabs", + "children": [ + { + "id": "df49f7d5f8bd02a2", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "GIT.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "GIT" + } + }, + { + "id": "edabed50a518b732", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "DaVinciResolve/Горячие клавиши.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "Горячие клавиши" + } + } + ], + "currentTab": 1 + } + ], + "direction": "vertical" + }, + "left": { + "id": "ae9ce61e34e2a30b", + "type": "split", + "children": [ + { + "id": "272888dc6bd23a63", + "type": "tabs", + "children": [ + { + "id": "3d69f2baa3152c95", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + }, + "icon": "lucide-folder-closed", + "title": "Файловый менеджер" + } + }, + { + "id": "304816efb0ab556e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Поиск" + } + }, + { + "id": "e88a968f3bbf9578", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Закладки" + } + } + ] + } + ], + "direction": "horizontal", + "width": 241.5 + }, + "right": { + "id": "b155a938b8a7e919", + "type": "split", + "children": [ + { + "id": "3f12dfc7ff91ce6e", + "type": "tabs", + "children": [ + { + "id": "a9fe2861a649f07f", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "GIT.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Обратные ссылки для GIT" + } + }, + { + "id": "f5f51f424be955ad", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "GIT.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Исходящие ссылки из GIT" + } + }, + { + "id": "daedec170123f202", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + }, + "icon": "lucide-tags", + "title": "Теги" + } + }, + { + "id": "ab86f8ad95ae5f4c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "GIT.md" + }, + "icon": "lucide-list", + "title": "Структура GIT" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "edabed50a518b732", + "lastOpenFiles": [ + "DaVinciResolve/ОПТИМИЗАЦИЯ.md", + "DaVinciResolve/HEVC.md", + "DaVinciResolve/Горячие клавиши.md", + "GIT.md", + "LINUX/CHMOD, CHOWN.md", + "LINUX/RM.md", + "LINUX/BASH_github/bash-guide-1.md", + "LINUX/BASH_github/Основы Bash.md", + "LINUX/BASH.md", + "LINUX/GPG, PASS.md", + "LINUX/TAR.md", + "_PROGRAMs/C++/C UART.md", + "_PROGRAMs/C++/Без названия.md", + "_PROGRAMs/C++/WiFi.md", + "_PROGRAMs/C++/TIME.md", + "_PROGRAMs/C++/MQTT.md", + "_PROGRAMs/C++/УСРЕДНЕНИЕ.md", + "_PROGRAMs/ESP8266-ESP32/EEPROM.md", + "_PROGRAMs/ESP8266-ESP32/RTC память.md", + "YANDEX_DISK.md", + "NuPhy Air75.md", + "LINUX/MOSQUITTO.md", + "DaVinciResolve", + "LINUX/UFW.md", + "LINUX/TMUX.md", + "LINUX/USER.md", + "LINUX/BEGIN.md", + "files/telegram-cloud-photo-size-2-5406976059292904279-x.jpg", + "files/DB_metall.ods", + "LINUX/TIME", + "files/Снимок экрана 2024-11-01 в 01.39.59.png", + "files/Снимок экрана 2024-09-16 в 21.45.54.png", + "files/Снимок экрана 2024-09-16 в 21.45.31.png", + "files/Снимок экрана 2024-09-16 в 21.39.34.png", + "files/Снимок экрана 2024-09-16 в 21.36.18.png", + "files/Снимок экрана 2024-09-16 в 21.31.06.png", + "files/Снимок экрана 2024-09-15 в 19.10.29.png", + "files/Снимок экрана 2024-09-15 в 19.02.02.png", + "files/Снимок экрана 2024-09-15 в 18.55.25.png", + "SQL", + "LINUX/BASH_script", + "RADIO", + "LINUX/BASH_github", + "sync", + "gitSync.sh", + "AUTO", + "Без названия.canvas" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace (2).json b/.obsidian/workspace (2).json new file mode 100644 index 0000000..66df600 --- /dev/null +++ b/.obsidian/workspace (2).json @@ -0,0 +1,155 @@ +{ + "main": { + "id": "22b54cdfe598d436", + "type": "split", + "children": [ + { + "id": "787893e48a6c8630", + "type": "tabs", + "children": [ + { + "id": "dcde0a554b665396", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "AUTO.md", + "mode": "source", + "source": false + } + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "ae9ce61e34e2a30b", + "type": "split", + "children": [ + { + "id": "272888dc6bd23a63", + "type": "tabs", + "children": [ + { + "id": "3d69f2baa3152c95", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + } + } + }, + { + "id": "304816efb0ab556e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "e88a968f3bbf9578", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "b155a938b8a7e919", + "type": "split", + "children": [ + { + "id": "3f12dfc7ff91ce6e", + "type": "tabs", + "children": [ + { + "id": "a9fe2861a649f07f", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "AUTO.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "f5f51f424be955ad", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "AUTO.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "daedec170123f202", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "ab86f8ad95ae5f4c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "AUTO.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "dcde0a554b665396", + "lastOpenFiles": [ + "HOME CURRENT.md", + "27-10-2023.md", + "Без названия 1.md" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace (3).json b/.obsidian/workspace (3).json new file mode 100644 index 0000000..ed8fb4e --- /dev/null +++ b/.obsidian/workspace (3).json @@ -0,0 +1,156 @@ +{ + "main": { + "id": "22b54cdfe598d436", + "type": "split", + "children": [ + { + "id": "787893e48a6c8630", + "type": "tabs", + "children": [ + { + "id": "dcde0a554b665396", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "HOME CURRENT.md", + "mode": "source", + "source": false + } + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "ae9ce61e34e2a30b", + "type": "split", + "children": [ + { + "id": "272888dc6bd23a63", + "type": "tabs", + "children": [ + { + "id": "3d69f2baa3152c95", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + } + } + }, + { + "id": "304816efb0ab556e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "e88a968f3bbf9578", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "b155a938b8a7e919", + "type": "split", + "children": [ + { + "id": "3f12dfc7ff91ce6e", + "type": "tabs", + "children": [ + { + "id": "a9fe2861a649f07f", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "HOME CURRENT.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "f5f51f424be955ad", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "HOME CURRENT.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "daedec170123f202", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "ab86f8ad95ae5f4c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "HOME CURRENT.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "dcde0a554b665396", + "lastOpenFiles": [ + "AUTO.md", + "HOME CURRENT.md", + "27-10-2023.md", + "Без названия 1.md" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace (4).json b/.obsidian/workspace (4).json new file mode 100644 index 0000000..c9b84b3 --- /dev/null +++ b/.obsidian/workspace (4).json @@ -0,0 +1,143 @@ +{ + "main": { + "id": "7120c54e418009c5", + "type": "split", + "children": [ + { + "id": "95827c3665516eb6", + "type": "tabs", + "children": [ + { + "id": "fef83e212ea030ea", + "type": "leaf", + "state": { + "type": "empty", + "state": {} + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "caef8b6d0a2e9854", + "type": "split", + "children": [ + { + "id": "897311b08e08ddd3", + "type": "tabs", + "children": [ + { + "id": "4e7ec7aef4d77191", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + } + } + }, + { + "id": "af93c627d0ddbf57", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "701abe3445c1c9b2", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "49aef91a1183b1c4", + "type": "split", + "children": [ + { + "id": "6d19a527f796b391", + "type": "tabs", + "children": [ + { + "id": "6d74d404f2dc532e", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "234a0fb01aba93bd", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "68c1a8a881843553", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "a045bb67d56f4645", + "type": "leaf", + "state": { + "type": "outline", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "fef83e212ea030ea", + "lastOpenFiles": [] +}
\ No newline at end of file diff --git a/.obsidian/workspace (5).json b/.obsidian/workspace (5).json new file mode 100644 index 0000000..e7fd905 --- /dev/null +++ b/.obsidian/workspace (5).json @@ -0,0 +1,151 @@ +{ + "main": { + "id": "7120c54e418009c5", + "type": "split", + "children": [ + { + "id": "95827c3665516eb6", + "type": "tabs", + "children": [ + { + "id": "fef83e212ea030ea", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "AUTO.md", + "mode": "source", + "source": false + } + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "caef8b6d0a2e9854", + "type": "split", + "children": [ + { + "id": "897311b08e08ddd3", + "type": "tabs", + "children": [ + { + "id": "4e7ec7aef4d77191", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + } + } + }, + { + "id": "af93c627d0ddbf57", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "701abe3445c1c9b2", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "49aef91a1183b1c4", + "type": "split", + "children": [ + { + "id": "6d19a527f796b391", + "type": "tabs", + "children": [ + { + "id": "6d74d404f2dc532e", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "AUTO.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "234a0fb01aba93bd", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "AUTO.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "68c1a8a881843553", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "a045bb67d56f4645", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "AUTO.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "fef83e212ea030ea", + "lastOpenFiles": [] +}
\ No newline at end of file diff --git a/.obsidian/workspace (6).json b/.obsidian/workspace (6).json new file mode 100644 index 0000000..c4cc2a8 --- /dev/null +++ b/.obsidian/workspace (6).json @@ -0,0 +1,153 @@ +{ + "main": { + "id": "7120c54e418009c5", + "type": "split", + "children": [ + { + "id": "95827c3665516eb6", + "type": "tabs", + "children": [ + { + "id": "fef83e212ea030ea", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "HOME CURRENT.md", + "mode": "source", + "source": false + } + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "caef8b6d0a2e9854", + "type": "split", + "children": [ + { + "id": "897311b08e08ddd3", + "type": "tabs", + "children": [ + { + "id": "4e7ec7aef4d77191", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + } + } + }, + { + "id": "af93c627d0ddbf57", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "701abe3445c1c9b2", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "49aef91a1183b1c4", + "type": "split", + "children": [ + { + "id": "6d19a527f796b391", + "type": "tabs", + "children": [ + { + "id": "6d74d404f2dc532e", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "HOME CURRENT.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "234a0fb01aba93bd", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "HOME CURRENT.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "68c1a8a881843553", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "a045bb67d56f4645", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "HOME CURRENT.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "fef83e212ea030ea", + "lastOpenFiles": [ + "AUTO.md" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace (7).json b/.obsidian/workspace (7).json new file mode 100644 index 0000000..c5be379 --- /dev/null +++ b/.obsidian/workspace (7).json @@ -0,0 +1,153 @@ +{ + "main": { + "id": "7120c54e418009c5", + "type": "split", + "children": [ + { + "id": "95827c3665516eb6", + "type": "tabs", + "children": [ + { + "id": "fef83e212ea030ea", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "HOME CURRENT.md", + "mode": "source", + "source": false + } + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "caef8b6d0a2e9854", + "type": "split", + "children": [ + { + "id": "897311b08e08ddd3", + "type": "tabs", + "children": [ + { + "id": "4e7ec7aef4d77191", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + } + } + }, + { + "id": "af93c627d0ddbf57", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "701abe3445c1c9b2", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "49aef91a1183b1c4", + "type": "split", + "children": [ + { + "id": "6d19a527f796b391", + "type": "tabs", + "children": [ + { + "id": "6d74d404f2dc532e", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "HOME CURRENT.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "234a0fb01aba93bd", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "HOME CURRENT.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "68c1a8a881843553", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "a045bb67d56f4645", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "HOME CURRENT.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "4e7ec7aef4d77191", + "lastOpenFiles": [ + "AUTO.md" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace (8).json b/.obsidian/workspace (8).json new file mode 100644 index 0000000..01817a9 --- /dev/null +++ b/.obsidian/workspace (8).json @@ -0,0 +1,197 @@ +{ + "main": { + "id": "22b54cdfe598d436", + "type": "split", + "children": [ + { + "id": "fd025cb4a7192636", + "type": "tabs", + "children": [ + { + "id": "7c0c92b3108c5e91", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "PEP8.md", + "mode": "source", + "source": false + } + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "ae9ce61e34e2a30b", + "type": "split", + "children": [ + { + "id": "272888dc6bd23a63", + "type": "tabs", + "children": [ + { + "id": "3d69f2baa3152c95", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "byModifiedTimeReverse" + } + } + }, + { + "id": "304816efb0ab556e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "e88a968f3bbf9578", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 211.5 + }, + "right": { + "id": "b155a938b8a7e919", + "type": "split", + "children": [ + { + "id": "3f12dfc7ff91ce6e", + "type": "tabs", + "children": [ + { + "id": "a9fe2861a649f07f", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "PEP8.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "f5f51f424be955ad", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "PEP8.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "daedec170123f202", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "ab86f8ad95ae5f4c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "PEP8.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "7c0c92b3108c5e91", + "lastOpenFiles": [ + "VIM.md", + "RANGER.md", + "GIT.md", + "PEP8.md", + "PEP8", + "PYTHON/ФУНКЦИИ.md", + "LINUX/CHMOD.md", + "PYTHON/SET, DICT.md", + "PYTHON/TUPLE.md", + "PYTHON/LIST.md", + "Без названия.md", + "PYTHON", + "SHELL команды.md", + "LINUX/ENVIRONMENT.md", + "YANDEX_DISK.md", + "Образец.md", + "Installing Applications.md", + "LINUX/WIREGUARD.md", + "LINUX/SSH.md", + "LINUX/HTOP.md", + "LINUX/DIFFERENT.md", + "LINUX/BEGIN.md", + "LINUX/CRON.md", + "LINUX/RSYNC.md", + "LINUX/SAMBA.md", + "LINUX/NETWORK/IPERF3.md", + "LINUX/CHRONY.md", + "LINUX/NETWORK/NETPLAN.md", + "LINUX/NETWORK", + "files/Aygo-топл_бак_и_горловина.jpg", + "files/Aygo-передний_бампер.jpg", + "files/Aygo-передн_подвеска.jpg", + "files/Aygo-задняя_подвеска.jpg", + "files/Aygo-задний_бампер.jpg", + "files/Aygo-зад_полы_и_панель.jpg", + "files/2016-07-21 21Aygo-глушитель.jpg", + "files/ae81365154ba5166845a7a9171d2248b.png", + "files/6cda81091bcc9f99ad224bd5c5426f8e.png", + "files/4a2b1a7f1e1bee9a26d7885b1d5e0f8c.png", + "templates", + "REPORTS", + "Без названия 1", + "files", + "LINUX/DISK", + "LINUX" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace (9).json b/.obsidian/workspace (9).json new file mode 100644 index 0000000..a1ec852 --- /dev/null +++ b/.obsidian/workspace (9).json @@ -0,0 +1,197 @@ +{ + "main": { + "id": "22b54cdfe598d436", + "type": "split", + "children": [ + { + "id": "fd025cb4a7192636", + "type": "tabs", + "children": [ + { + "id": "7c0c92b3108c5e91", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "PEP8.md", + "mode": "source", + "source": false + } + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "ae9ce61e34e2a30b", + "type": "split", + "children": [ + { + "id": "272888dc6bd23a63", + "type": "tabs", + "children": [ + { + "id": "3d69f2baa3152c95", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "byModifiedTimeReverse" + } + } + }, + { + "id": "304816efb0ab556e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "e88a968f3bbf9578", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 211.5 + }, + "right": { + "id": "b155a938b8a7e919", + "type": "split", + "children": [ + { + "id": "3f12dfc7ff91ce6e", + "type": "tabs", + "children": [ + { + "id": "a9fe2861a649f07f", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "PEP8.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "f5f51f424be955ad", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "PEP8.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "daedec170123f202", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "ab86f8ad95ae5f4c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "PEP8.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "7c0c92b3108c5e91", + "lastOpenFiles": [ + "PYTHON/PYINSTALLER.md", + "VIM.md", + "RANGER.md", + "GIT.md", + "PEP8.md", + "PEP8", + "PYTHON/ФУНКЦИИ.md", + "LINUX/CHMOD.md", + "PYTHON/SET, DICT.md", + "PYTHON/TUPLE.md", + "PYTHON/LIST.md", + "Без названия.md", + "PYTHON", + "SHELL команды.md", + "LINUX/ENVIRONMENT.md", + "YANDEX_DISK.md", + "Образец.md", + "Installing Applications.md", + "LINUX/WIREGUARD.md", + "LINUX/SSH.md", + "LINUX/HTOP.md", + "LINUX/DIFFERENT.md", + "LINUX/BEGIN.md", + "LINUX/CRON.md", + "LINUX/RSYNC.md", + "LINUX/SAMBA.md", + "LINUX/NETWORK/IPERF3.md", + "LINUX/CHRONY.md", + "LINUX/NETWORK", + "files/Aygo-топл_бак_и_горловина.jpg", + "files/Aygo-передний_бампер.jpg", + "files/Aygo-передн_подвеска.jpg", + "files/Aygo-задняя_подвеска.jpg", + "files/Aygo-задний_бампер.jpg", + "files/Aygo-зад_полы_и_панель.jpg", + "files/2016-07-21 21Aygo-глушитель.jpg", + "files/ae81365154ba5166845a7a9171d2248b.png", + "files/6cda81091bcc9f99ad224bd5c5426f8e.png", + "files/4a2b1a7f1e1bee9a26d7885b1d5e0f8c.png", + "templates", + "REPORTS", + "Без названия 1", + "files", + "LINUX/DISK", + "LINUX" + ] +}
\ No newline at end of file diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json new file mode 100644 index 0000000..e2451f4 --- /dev/null +++ b/.obsidian/workspace.json @@ -0,0 +1,217 @@ +{ + "main": { + "id": "22b54cdfe598d436", + "type": "split", + "children": [ + { + "id": "1b52c0235b9fa69e", + "type": "tabs", + "children": [ + { + "id": "ee4472cdc91cb298", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "GIT.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "GIT" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "ae9ce61e34e2a30b", + "type": "split", + "children": [ + { + "id": "272888dc6bd23a63", + "type": "tabs", + "children": [ + { + "id": "3d69f2baa3152c95", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical", + "autoReveal": false + }, + "icon": "lucide-folder-closed", + "title": "Файловый менеджер" + } + }, + { + "id": "304816efb0ab556e", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Поиск" + } + }, + { + "id": "e88a968f3bbf9578", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Закладки" + } + } + ] + } + ], + "direction": "horizontal", + "width": 263.5 + }, + "right": { + "id": "b155a938b8a7e919", + "type": "split", + "children": [ + { + "id": "3f12dfc7ff91ce6e", + "type": "tabs", + "children": [ + { + "id": "a9fe2861a649f07f", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "GIT.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Обратные ссылки для GIT" + } + }, + { + "id": "f5f51f424be955ad", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "GIT.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Исходящие ссылки из GIT" + } + }, + { + "id": "daedec170123f202", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + }, + "icon": "lucide-tags", + "title": "Теги" + } + }, + { + "id": "ab86f8ad95ae5f4c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "GIT.md" + }, + "icon": "lucide-list", + "title": "Структура GIT" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "bases:Создать новую базу": false, + "switcher:Меню быстрого перехода": false, + "graph:Граф": false, + "canvas:Создать новый холст": false, + "daily-notes:Сегодняшняя заметка": false, + "templates:Вставить шаблон": false, + "command-palette:Открыть палитру команд": false + } + }, + "active": "ee4472cdc91cb298", + "lastOpenFiles": [ + "GIT_new.md", + "GIT_new-2.md", + "LINUX/TMUX.md", + "GIT.md", + "GIT_new-2", + "LINUX/MC.md", + "LINUX/WIREGUARD.md", + "LINUX/VSFTPD.md", + "LINUX/USER.md", + "LINUX/BASH_script/Обновление списка пакетов и установка обновлений.md", + "LINUX/BASH_script/15 однострочных bash-скриптов.md", + "LINUX/AmneziaWG.md", + "LINUX/BASH_script/BASH. Разное.md", + "LINUX/ROOT.md", + "LINUX/SSH.md", + "LINUX/UFW.md", + "LINUX/HOME_SERVER/Очистка системы.md", + "LINUX/HOME_SERVER/5 малоизвестных команд для Linux.md", + "LINUX/BASH_script/BASH. Часть 2.md", + "LINUX/BASH_script/BASH. Часть 1.md", + "LINUX/BASH_script/Скрипты для админов.md", + "LINUX/NETWORK/NETWORK.md", + "files/СкриптыДляАдмина.jpg", + "LINUX/DISK/DF, DU.md", + "PYTHON/BEGIN + PIP.md", + "PYTHON/HTTP.md", + "LINUX/MOSQUITTO.md", + "PYTHON/STRING.md", + "files/6cda81091bcc9f99ad224bd5c5426f8e.png", + "files/32de918111f7fed3e8183b4b9148286b.png", + "files/Pasted image 20250621003225.png", + "files/Pasted image 20250621003213.png", + "files/2025-06-20_22-34-47.png", + "files/telegram-cloud-photo-size-2-5425027665104058215-x.jpg", + "files/Снимок экрана 2025-05-14 в 00.38.05.png", + "files/Снимок экрана 2025-05-14 в 00.35.18.png", + "LINUX/HOME_SERVER", + "files/telegram-cloud-photo-size-2-5230966980947866123-y.jpg", + "files/TG_bot_VPN-main/utils/misc/__init__.py", + "files/TG_bot_VPN-main/keyboards/reply/handlers_reply.py", + "files/TG_bot_VPN-main/keyboards/reply/__init__.py", + "files/TG_bot_VPN-main/keyboards/inline/subscribed.py", + "files/TG_bot_VPN-main/keyboards/inline/servers.py", + "files/TG_bot_VPN-main/keyboards/inline/app_buttons.py", + "files/TG_bot_VPN-main/keyboards/inline/admin_buttons.py", + "files/TG_bot_VPN-main/keyboards/inline/__init__.py", + "Без названия.canvas" + ] +}
\ No newline at end of file diff --git a/AUTO/AUTO.md b/AUTO/AUTO.md new file mode 100644 index 0000000..8c8ce8f --- /dev/null +++ b/AUTO/AUTO.md @@ -0,0 +1,2 @@ + +![[Снимок экрана 2023-10-19 в 09.53.43.png]]![[Снимок экрана 2023-10-19 в 09.53.24.png]]
\ No newline at end of file diff --git a/AUTO/TOYOTA AYGO.md b/AUTO/TOYOTA AYGO.md new file mode 100644 index 0000000..a469309 --- /dev/null +++ b/AUTO/TOYOTA AYGO.md @@ -0,0 +1,24 @@ + + +![[2016-07-21 21Aygo-глушитель.jpg]] + + + + + +![[Aygo-задний_бампер.jpg]] + + + + + +![[Aygo-передн_подвеска.jpg]] + + + + + +![[Aygo-топл_бак_и_горловина.jpg]] + + + diff --git a/DaVinciResolve/Davinci Resolve 19.md b/DaVinciResolve/Davinci Resolve 19.md new file mode 100644 index 0000000..0ef5717 --- /dev/null +++ b/DaVinciResolve/Davinci Resolve 19.md @@ -0,0 +1,55 @@ +https://github.com/leinardi/JDInstaller/blob/master/roles/davinci_resolve/DaVinciResolve.md + +Я создал скрипт Ansible, который полностью автоматизирует процесс установки в Ubuntu 24.04. + +Вот что делает этот скрипт: + +- В отличие от других решений, **он не устанавливает старые версии системных зависимостей .** +- **Контейнеризация** (вроде `distrorun`) не требуется. +- Скрипт **извлекает файлы AppImage** из официального установщика и заменяет устаревшие библиотеки, создавая **символические ссылки на правильные версии** библиотек из вашей системы Ubuntu 24.04. +- Он загружает **официальный установщик** с сайта DaVinci Resolve. +- Устанавливает все в стандартный `/opt/`каталог. +- **Настраивает правила udev** для панели DaVinci и аналогичных периферийных устройств USB. +- Устанавливает необходимые **системные иконки** и **файлы MIME** . +- **Автоматически определяет ваш графический процессор** (AMD, Intel или Nvidia) и устанавливает соответствующие библиотеки OpenCL. + +Чтобы DaVinci Resolve 19 работал в Ubuntu 24.04, выполните следующие действия: + +1. Установите Git и Make, если они еще не установлены: + + ``` + sudo apt install git make + ``` + +2. Клонируйте репозиторий: + + ``` + git clone https://github.com/leinardi/JDInstaller.git + ``` + +3. Перейдите в каталог репозитория: + + ``` + cd JDInstaller + ``` + +4. Проверьте ветку DaVinci Resolve (она по умолчанию отключена в основном скрипте): + + ``` + git checkout davinci + ``` + +5. Запустите установку: + + ``` + make install TAGS=davinci_resolve + ``` + + +Это должно полностью автоматизировать процесс и заставить DaVinci Resolve работать на Ubuntu 24.04 без необходимости ручного понижения пакетов или контейнеров. Полный скрипт можно найти здесь: +[https://github.com/leinardi/JDInstaller](https://github.com/leinardi/JDInstaller) + +DaVinci Resolve, работающий в Ubuntu 24.04 на оборудовании Intel: [](https://i.sstatic.net/3KT2Dunl.png) + +DaVinci Resolve, работающий в Ubuntu 24.04 на оборудовании AMD: [](https://i.sstatic.net/CbybXPPr.png) + diff --git a/DaVinciResolve/HEVC.md b/DaVinciResolve/HEVC.md new file mode 100644 index 0000000..509ce69 --- /dev/null +++ b/DaVinciResolve/HEVC.md @@ -0,0 +1,38 @@ +# Как установить кодек HEVC/H.265 на Windows 10 + +Простой + +[Настольные компьютеры](https://habr.com/ru/hubs/desktops/)[Работа с 3D-графикой*](https://habr.com/ru/hubs/3d_graphics/)[Работа с векторной графикой*](https://habr.com/ru/hubs/vector_graphics/)[Работа с видео*](https://habr.com/ru/hubs/video/)[Софт](https://habr.com/ru/hubs/soft/) + +Ожидает приглашения + +Доброго времени суток, товарищи, при монтировании видео в одной из популярных программ для обработки видео, я столкнулся с ошибкой "Media Offline". Проблема касалась только видеофайлов, которые были закодированы при помощи кодека H.265 или HEVC. Удалось выяснить, что данный формат сжатия видео не установлен у меня, также как и большинства пользователей ОС Windows 10/11. Процесс установки показался мне нетривиальным, поэтому инструкцию, **актуальную на 19.05.2024** я оставлю здесь. + +1. Переходим по ссылке [https://www.free-codecs.com/guides/how_to_download_hevc_video_extension_for_free.htm](https://www.free-codecs.com/guides/how_to_download_hevc_video_extension_for_free.htm) и бегло ознакамливаемся с содержимым. На данном ресурсе обновляются статьи по установке кодеков. + +2. Переходим на [Adguard](https://store.rg-adguard.net/) и в свободное поле вставляем ссылку на страницу HEVC кодека из магазина Microsoft. (Вот сама ссылка для копирования: https://www.microsoft.com/en-us/p/hevc-video-extensions-from-device-manufacturer/9n4wgh0z6vhq). Нажимаем на галочку и получаем 2 ссылки. + + + +Окно на Adguard + +3. Теперь необходимо скачать файл, который заканчивается на .appxbundle и весит 7MB. Просто так сделать это у Вас не получится, поэтому применим небольшую хитрость, которой воспользовался я. Наводим курсор на ссылку, нажимаем правую кнопку мыши и выбираем пункт "Открыть ссылку в новой вкладке". Далее, в открывшейся вкладке просто обновляем страницу нажав на F5 или на символ обновления страницы в верхнем левом углу. Файл начнет загружаться. Возможно необходимо будет подтвердить скачивание непроверенного файла. + +> Стоит отметить, что я использую браузер Google Chrome, в другом браузере данный финт может не сработать. + +4. Теперь переходим в проводник (Win + E) -> загрузки и видим наш установочный файл. (Советую его сразу переименовать на 123 или что-то подобное, это упростит установку). Установить его просто кликнув по нему не выйдет, поэтому немного поиграем с PowerShell. +Нажимаем Win + X и выбираем пункт Windows PowerShell (администратор). + +Убедитесь, что установка из непроверенных источников разрешена: + +- Выполните следующую команду для проверки состояния: + Get-ExecutionPolicy + +- Если возвращенный результат — «Restricted», измените его на менее ограничительный режим, выполнив: + Set-ExecutionPolicy Bypass -Scope Process + + +Теперь начнём установку, выполнив следующую команду: Add-AppxPackage .\YourAppName.appxbundle +Не забудьте изменить "YourAppName" на имя вашего файла, которое я советовал поменять. + +5. Поздравляю, нам осталось только перезагрузить компьютер и радоваться возможности работать с HEVC.
\ No newline at end of file diff --git a/DaVinciResolve/Горячие клавиши.md b/DaVinciResolve/Горячие клавиши.md new file mode 100644 index 0000000..cad9db6 --- /dev/null +++ b/DaVinciResolve/Горячие клавиши.md @@ -0,0 +1,55 @@ +============================= +### Настройки интерфейса Davinci для ускорения монтажа. +Активируем притягивание плейхэда +Устанавливаем подходящее разрешение воспроизведения тайм линии +A — selection mode, инструмент стрелочка +B — blade mode, инструмент лезвие +CMD/CTRL + B — разрезать по указателю воспроизведения +T — trim edit mode +↑ ↓ — переместить курсор к началу следующего или предыдущего клипа +← → — переместить курсор на один кадр +SHIFT + ← → переместить курсор на 1 секунду +SHIFT + [ ] обрезать по указателю воспроизведения слева или справа +CMD/CTRL + SHIFT + [ ] обрезать по указателю воспроизведения слева или справа со сдвигом +SHIFT + BACKSPACE — удалить клип со сдвигом +DEL — удалить клип со сдвигом +Y — выделить все клипы справа от курсора на основном треке +ALT/OPT + Y — выбрать все клипы справа от курсора на всех треках +CMD/CTRL + Y — выбрать все клипы слева от курсора на основном треке +ALT/OPT +CMD/CTRL + Y — выбрать все клипы слева от курсора на всех треках +CMD/CTRL + X — вырезать клип +CMD/CTRL + SHIFT + X — вырезать клип со сдвигом +CMD/CTRL + SHIFT + V — вставить клип со сдвигом +ALT/OPT + V — вставить атрибуты +ALT/OPT + 1/2/3... — смена камеры в мультикамерной секвенции + +============================= +### Моя раскладка по горячим клавишам: + +I \ O — Входная и выходная точка на клипе в Media pool, на таймлайне и окне превью + +Alt + X — отмена входной и выходной точки на клипе + +Q \ W — подрезает начало и конец клипа на таймлайне + +D — Razor tool разрезает клип на 2 части + +2 \ 3 — просмотр вперед и назад со скоростью X2 + +P — полноэкранный режим + +R — создание прокси + +Shift + D — выделить все клипы на таймлайне справа + +N — Вкл\выкл Snapping (магнит) для перемещения клипов на таймлайне с привязкой к другим клипам + +B — Вкл\выкл один клип на таймлайне + +1;2;3;4;5;6 num pad — смена цвета клипа + +T — trim edit mode + +M — маркер + +=============================
\ No newline at end of file diff --git a/DaVinciResolve/ОПТИМИЗАЦИЯ.md b/DaVinciResolve/ОПТИМИЗАЦИЯ.md new file mode 100644 index 0000000..ac15eba --- /dev/null +++ b/DaVinciResolve/ОПТИМИЗАЦИЯ.md @@ -0,0 +1,19 @@ +============================= + +PROXY RESOLUTION -> PLAYBACK -> TIMELINE PROXY RESOLUTION = Half (Quarter) + +DAVINCI RESOLVE -> PREFERENCES -> USERS -> Playback Settings -> +Performance mode = Automatic +v = Hide UI overlays +v = Minimize interface updates during playback + +PROXY MEDIA -> выбираем медиа -> прав кн мыши = Generate Proxy Media +RENDER CACHE -> Playback -> Render Cache = Smart + +============================= + +Preferences -> Use vertical resolution (вертикальное видео) +Video -> Smart Reframe -> квадратик с точкой -> Reference Point (выбираем и наводим) - Re + + + diff --git a/ETHERNET 1Gbit - кабель.md b/ETHERNET 1Gbit - кабель.md new file mode 100644 index 0000000..0d48d69 --- /dev/null +++ b/ETHERNET 1Gbit - кабель.md @@ -0,0 +1,2 @@ +![[Снимок экрана 2024-11-01 в 01.39.59.png]] + @@ -0,0 +1,252 @@ +![[GIT_шпаргалка.jpg]] + +```sh +sudo apt install git +git config --global --edit +git config --global user.name "Name_Name" +git config --global user.email email@example.com +git config --list + +# Создание "своего чистого" репозитория: +git init --bare ~/git/RadSens.git + +git clone vlapa@178.20.46.157:~/git/...... +# По умолчанию git clone загружает всю историю изменений, что может быть медленно и требовать много места. Если история не нужна, используйте: +git clone <repo_url> --depth=1 + +git commit -a -m + -m "text_commit" + -a (заменяет git add) + --amend - заменить/исправить последний commit + +git init +git add . # добавить все файлы +git commit -m "first commit" +git branch -M main +git remote add origin vlapa@178.20.46.157:~/git/...... +git push -u origin main + +git remote set-url origin new.git.url/here + +git@89.110.92.137:/home/git/my/esp8266/8266-clock_ws2812.git + +====================== +======= FLENOV ======= +====================== + +git checkout -b имяветки база +# git checkout говорит, что мы должны переключиться на репозиторий. Ключик -b говорит, что нужно не просто переключиться, а создать новую ветку с именем =имяветки=. +# После имени бренча через пробел идет база - по документации необязательный параметр - от какой ветки вы хотите создать свою. Я рекомендую всегда указывать этот параметр. Если вы не укажите базу, то в качестве нее будет выступать текущая ветка. Чаще всего ветки нужно создавать от основной master, а не от текущей. Если только вы явно не хотите создать свою ветку от какой-то другой, команда создания будет + +git checkout -b имяветки origin/master + +# Стоп, еще немного о именовании. В данном случае я указываю origin/master и это удаленный ствол. На это указывает слово origin. Если мы создаем новую ветку fixdone/345321, то это локальная копия на нашем диске. Удаленная копия на сервере git в репозитории будет иметь имя origin/fixdone/345321. Когда мы создаем новые ветки, то желательно создавать от удаленной версии master, но у нас ее пока нет, поэтому если попытаться выполнить такую команду, то она завершиться ошибкой. + +# Да, я рекомендую всегда использовать в качестве базы удаленную версию origin/master, которая указывает на версию на сервере, но если ее нет, то придется использовать локальную – просто master. Мы пока создали только локальный репозиторий. Пока нет удаленного, придется использовать локальный. Как только появиться удаленный, всегда используйте его. О удаленных ветках мы поговорим в следующий раз. + +# Итак, пока нет удаленного мастера, давайте создадим новую ветку от локального: +git checkout -b initialwork master + +# Я назвал ветку initialwork. В результате выполнения команды вы должны увидеть что-то типа: +Switched to a new branch 'initialwork' + +# Теперь мы находимся в ветке, где мы можем исправлять баг или работать над новым функционалом. Давайте создадим файл index.html. Вы можете воспользоваться для этого любым текстовым редактором. + +git add i* # все файлы начинающиеся с =i= +git add -u # все измененные файлы + +git checkout master +git merge initialwork + +git branch # отображает все ветки созданные локально + -M # переименовать ветку + -a # все ветки: локальные и удаленные + +echo "# demo" >> README.md +git init +git add README.md +git commit -m "first commit" +# git branch -M main +git remote add origin vlapa@178.20.46.157:~/git/name.git +git push -u origin master # main + +# Работа на другом компьютере: +git clone vlapa@178.20.46.157:~/git/name.git + +# или: +git init +git remote add origin vlapa@178.20.46.157:~/git/name.git +# получить последние изменения: +git fetch + +git checkout secondone +# изменеяем файл, далее: +git diff # file_name +git add -u +git commit -m "commit" +git push origin secondone +# теперь на сервере есть 2 ветки + +# на еще одном компьютере: +git checkout -b origin/secondone +git merge origin/secondone + + + + +============== +git show +git commit --amend # дополнение и изменение коммита +git commit --amend -m "новое название" # переименовать +git checkout file_name # откатить последние изменения +git reset --hard # сбросить все последние изменения +git status + # untracked - неотслеживаемый + # modified - в рабочей директории есть более новая версия по сравнению с хранящейся в HEAD или в области подготовленных файлов (изменения не находятся в следующем коммите). + # staget - в рабочей директории и области подготовленных файлов есть более новая версия по сравнению с хранящейся в HEAD (готов к коммиту). + # Без изменений — одна версия файла во всех разделах, т. е. в последнем коммите содержится актуальная версия. + +# Файл может быть одновременно в состоянии «изменён» и «подготовлен», если версия в рабочей директории новее, чем в области подготовленных файлов, которая в свою очередь новее версии в HEAD. + +# Мы можем использовать опцию -s для команды git status, чтобы получить более компактный вывод (по строке на файл). +# Если файл не отслеживается, то будет выведено ??; если он был изменён, то его имя будет красным, а если подготовлен — зелёным. + +# Чтобы посмотреть сами изменения, а не изменённые файлы, можно использовать следующие команды: + +git diff +# — посмотреть что добавилось или сравнение рабочей директории с областью подготовленных файлов; + +git diff --staged +# — сравнение области подготовленных файлов с HEAD. + +# Если использовать аргумент <файл/папка>, то diff покажет изменения только для указанных файлов/папок, например git diff src/. + +.gitignore +# Чтобы отметить файлы, которые мы хотим игнорировать, можно использовать шаблоны поиска (считайте их упрощёнными регулярными выражениями): + +# /... — позволяет избежать рекурсивности — соответствует файлам только в текущей директории; +# .../ — соответствует всем файлам в указанной директории; +# *... — соответствует всем файлам с указанным окончанием; +# ! — игнорирование файлов, попадающих под указанный шаблон; +# [...] — соответствует любому символу из указанных в квадратных скобках; +# ? — соответствует любому символу; +# /**/ — соответствует вложенным директориям, например a/**/d соответствует a/d, a/b/d, a/b/c/d и т. д. + +# Мы даже можем использовать шаблоны поиска при указании файла/папки в других командах. Например: +git add src/*.css +# добавит все файлы .css в папке src. + +git log # - можно передать разные опции: + --oneline # короткий лог + -p # показывает изменения в каждом коммите; + --stat # показывает сокращённую статистику для коммитов, например изменённые файлы и количество добавленных/удалённых строк в каждом их них; + -n # показывает n последних коммитов; + --since=___ и --until=___ # позволяет отфильтровать коммиты по промежутку времени, например --since="2019-01-01" покажет коммиты с 1 января 2019 года; + --pretty # позволяет указать формат логов (например, --pretty=oneline), также можно использовать --pretty=format для большей кастомизации, например --pretty=format:"%h %s"; + --grep и -S # фильтруют коммиты с сообщениями/изменениями кода, которые содержат указанную строку, например, git log -S имя_функции позволяет посмотреть добавление/удаление функции; + --no-merges # пропускает коммиты со слиянием веток; +ветка1..ветка2 # позволяет посмотреть, какие коммиты из ветки 2 не находятся в ветке 1 (полезно при слиянии веток). Например, `git log master..test` покажет, каких коммитов из ветки test нет в master (о ветках поговорим чуть позже). + --left-right # ветка1...ветка2 показывает коммиты, которые есть либо в ветке 1, либо в ветке 2, но не в обеих; знак < обозначает коммиты из ветка1, а > — из ветка2. Обратите внимание: используется три точки, а не две; + -L # принимает аргумент начало,конец:файл или :функция:файл и показывает историю изменений переданного набора строк или функции в файле. + +``` + +# Свой GIT +```sh +# Server: +sudo apt install git +sudo useradd git +sudo passwd git +ssh-keygen # - ??? +mkdir repo1.git +git init --bare +git init --bare ~/git/repo1.git +#------------------------ +# Client: +sudo apt install git +ssh-copy-id git@server_adresses + +git config --global --edit +git config --global user.name "Name_Name" +git config --global _user.email email@example.com +git config --global core.editor "vim" # устанавливаем редактор по умолчанию +git config --list + +mkdir /home/user/repo1 +cd /home/user/repo +git init +git add . +git commit -m "commit1" +git remote add origin git@addresses_server:/home/git/repo1.git +git push -u origin master + +#------------------------- +# Создаем свою ветку: +git branch # смотрим ветку +git branch -a # - смотрим все ветки +git branch new_branch # - создаем ветку new_branch +git checkout -b new_branch # - так тоже можно создать +git checkout new_branch # - переключаемся на нее +git diff # - посмотреть изменения +git status + +vim .gitignore # добавляем в игнор все лишнее +# .idea/ +# .vscode/ +# .pio +git add . +git commit -m "modified .gitignore" +git reset HEAD <file> # - убрать файл из индекса +git push --set-upstream origin new_branch + # установить ветку new_branch как origin + # указывается только при первом PUSH + +git checkout <hash> # - переход на другой commit + +# Сливаемся: +git checkout master # переключаемся на ветку master +git checkout -b new-branch # создаём новую ветку +git merge new_branch # сливаемся: master/new_branch +git branch -d new_branch +git branch -r +git branch -d -r origin/new_branch +git push --set-upstream origin new-branch + +# Удалить файл удаленно и локально: +git branch -D new-branch # удаляем лишнюю ветку локально +git rm 'file name' +git rm -r 'folder name' +git commit -m 'message' +git push -u origin branch + +# Удалить файл только удаленно: +git push --delete origin new-branch # удаляем ветку в репозитории +git rm --cached 'file name' +git rm -r 'folder name' +git commit -m'message' +git push -u origin branch + +#===================================== + +git fetch # - загружает коммиты, файлы и ссылки из удаленного репозитория в ваш локальный репозиторий. + +# Извлеките данные с помощью команды fetch, если хотите увидеть, над чем работают остальные. Эта команда похожа на команду `svn update` и позволяет увидеть прогресс в центральном репозитории, но при этом не вынуждает вас выполнять слияние изменений с вашим репозиторием. + +git pull # - слить локальную и удаленную версию на локальной машине (на самом деле представляет собой комбинацию двух других команд: git fetch и git merge) + +git log --oneline # короткий LOG + +git reset --... +# -- soft HEAD~ - на 1 commit назад без удаления последних изменений +# -- mixed HEAD~ - на 1 commit назад без удаления последних изменений и не внесенными предпоследними изменениями +# -- hard HEAD~ - на 1 commit назад с удалением ВСЕХ последних изменений + +git rm file.name # - удаление и сразу git add . (если просто самому удалить файл из папки - то нужно самому git add .) + +git mv file.name new_file.name # - переименование и сразу git add . (если просто самому переименовать файл из папки - то нужно самому git add .) + +git restore file.name # - откат всех последних изменений до commit +--staged # убирает файл из зоны индекса + +``` + diff --git a/GIT_new-2.md b/GIT_new-2.md new file mode 100644 index 0000000..b7a126b --- /dev/null +++ b/GIT_new-2.md @@ -0,0 +1,218 @@ +**Шаг 1: Создайте пользователя для репозиториев (рекомендуется)** + +Для безопасности лучше не использовать вашего основного пользователя или `root`. + +bash + +# На сервере +sudo adduser git +sudo su - git +mkdir repos + +**Шаг 2: Создайте «голый» репозиторий для вашего проекта** + +bash + +#Находясь под пользователем git, в папке repos +mkdir my-project.git +cd my-project.git +git init --bare + +Поздравляю! Серверная часть готова. Путь к репозиторию: `/home/git/repos/my-project.git` + +**Шаг 3: Настройте доступ по SSH (самый простой способ)** + +Сгенерируйте SSH-ключ на вашем рабочем компьютере (если еще нет): + +bash + +#На вашем компьютере (Windows, Mac, Linux) +ssh-keygen -t ed25519 -C "your_email@example.com" + +Скопируйте публичный ключ на сервер: + +bash + +# На вашем компьютере +ssh-copy-id git@IP-вашего-сервера + +**Шаг 4: Используйте репозиторий** + +Теперь вы можете работать с ним как с удаленным репозиторием, например, GitHub. + +**Вариант А: Создать новый проект и запушить его** + +bash + +# На вашем компьютере +mkdir my-local-project +cd my-local-project +git init +echo "# Мой проект" > README.md +git add README.md +git commit -m "Первый коммит" +git remote add origin git@IP-вашего-сервера:/home/git/repos/my-project.git +git push -u origin master + +**Вариант Б: Склонировать существующий пустой репозиторий** + +bash + +git clone git@IP-вашего-сервера:/home/git/repos/my-project.git + +### 🚀 Полезные улучшения для «голого» Git + +Чтобы работа была комфортнее, добавьте несколько важных вещей. + +**1. Защита от перезаписи (очень важно для домашнего сервера!)** + +Добавьте эту настройку в «голый» репозиторий на сервере, чтобы случайно не затереть чужую работу (или свою же, но с другого компьютера): + +bash + +# На сервере, в папке /home/git/repos/my-project.git +git config receive.denyNonFastForwards true + +**2. Упростите путь к репозиториям с помощью `~/.ssh/config`** + +На вашем _локальном_ компьютере отредактируйте файл `~/.ssh/config` (создайте, если нет): + +text + +Host home-server + HostName IP-вашего-сервера + User git + Port 22 + +Теперь вместо `git@IP-вашего-сервера:/home/git/repos/my-project.git` вы можете писать: + +bash + +git remote add origin home-server:repos/my-project.git + +Намного короче и удобнее. + +**3. Быстрый просмотр содержимого (без клонирования)** + +Иногда нужно просто «посмотреть», какие коммиты и ветки есть на сервере, не клонируя весь репозиторий. Для этого есть команда: + +bash + +# На сервере, в папке с «голым» репозиторием +git log --oneline --all +git branch -a + +**4. Добавьте описание к репозиторию** + +bash + +# На сервере, в папке с «голым» репозиторием +echo "Мой супер-проект для расчетов траектории кота" > description + +Это описание будет видно, например, в GitWeb или при просмотре через `git instaweb`. + +### Как организовать несколько проектов? + +Просто создайте несколько папок в `/home/git/repos/`: + +text + +/home/git/repos/ +├── website.git +├── telegram-bot.git +├── home-automation.git +└── useful-scripts.git + +Каждый из них — это независимый «голый» репозиторий со своей историей. + +### 💾 Бэкапы для «голого» Git + +Здесь всё просто, потому что «голый» репозиторий — это просто обычная папка с файлами. Чтобы сделать бэкап всех ваших репозиториев: + +# На сервере или по SSH +tar -czf git-backup-$(date +%Y%m%d).tar.gz /home/git/repos/ + +А потом скопируйте этот архив на внешний диск или в облако (согласно правилу 3-2-1). +Для автоматизации добавьте в `crontab -e`: + +bash + +0 2 * * * tar -czf /home/git/backups/git-$(date +\%Y\%m\%d).tar.gz /home/git/repos/ + + +### Итог: когда выбирать «голый» Git? + +«Голый» Git идеален, если: +- Вы единственный разработчик (или команда из 2-3 доверенных человек) +- Вам нужен просто надежный «склад» для кода +- Сервер слабый (например, старенький Raspberry Pi или виртуалка с 256 MB RAM) +- Вы любите минимализм и контроль через командную строку + +Если позже захотите веб-интерфейс, вы всегда можете поверх этих же «голых» репозиториев поставить **cgit** (очень легкий) или просто открыть к ним доступ через **GitWeb**, не меняя саму структуру хранения. + + +### Важное замечание о бэкапах Git-репозиториев + +Для бэкапа "голых" Git-репозиториев есть даже более правильный способ, чем `tar`: + +bash + +# Клонируйте репозиторий с зеркалированием (делает полную копию) +git clone --mirror ~/repos/my.git /backups/my-backup.git +# Или для всех репозиториев +for repo in ~/repos/*.git; do + git clone --mirror "$repo" "/backups/$(basename $repo)" +done + +Этот метод лучше, потому что: +- Сохраняет все ссылки и конфигурации +- Позволяет проверить целостность +- Легче восстановить (`git clone --mirror /backups/my-backup.git`) + + +### Оптимальная структура для бэкапов + +Вот как лучше организовать бэкапы на вашем сервере: + +bash + +# На сервере (под пользователем git) +# Создайте структуру +mkdir -p ~/backups/{daily,weekly,monthly} +# Ежедневный бэкап +tar -czf ~/backups/daily/git-backup-$(date +%Y%m%d).tar.gz ~/repos/ +# Очистка старых бэкапов (например, удалять бэкапы старше 30 дней) +find ~/backups/daily -name "git-backup-*.tar.gz" -mtime +30 -delete + +### 🤖 Автоматизируйте бэкап с cron + +Добавьте в crontab, чтобы бэкап делался автоматически каждую ночь: + +bash + +# Редактируем crontab пользователя git +crontab -e +# Добавьте эту строку (делает бэкап каждый день в 2 часа ночи) +0 2 * * * tar -czf /home/git/backups/daily/git-backup-$(date +\%Y\%m\%d).tar.gz /home/git/repos/ && find /home/git/backups/daily -name "git-backup-*.tar.gz" -mtime +30 -delete + +### 📥 Как восстановиться из бэкапа? +Если что-то пойдет не так: + +# На сервере +cd ~/backups +# Распакуйте бэкап (замените дату на вашу) +tar -xzf git-backup-20260613.tar.gz -C / +# Или если хотите посмотреть содержимое бэкапа без распаковки +tar -tzf git-backup-20260613.tar.gz | head -20 + +### 💡 Важное дополнение + +Не забудьте про **правило 3-2-1**: +- У вас есть оригинал (на сервере) ✅ +- У вас есть бэкап (там же на сервере) ✅ +- **Скопируйте этот бэкап на внешний диск или в облако** (например, на ваш YandexDisk или Google Drive) ❌ пока не сделано + +# На вашем локальном компьютере (Windows) +scp git@v2670762:~/backups/git-backup-*.tar.gz "C:\Users\Villa-PC\YandexDisk\Backups\" + + diff --git a/GIT_new.md b/GIT_new.md new file mode 100644 index 0000000..b93ffa1 --- /dev/null +++ b/GIT_new.md @@ -0,0 +1,165 @@ +```sh +#========================================== +# Полный список подкоманд: +git help --all + +# Установка редактора по умолчанию: +export GIT_EDITOR=vim + +# Настройка автора фиксации: +git config --global user.name "my_name" +git config --global user.email "my_name@mail.com" + +-add # добавляет файлы в индекс +-bisect # выполняет бинарный поиск ошибки по истории фиксаций +-branch # выводит, создает, или удаляет ветки +-checkout # проверяет и переключает на ветку +-clone # клонирует репозиторий в новый каталог +-commit # записывает изменения в каталог (фиксация) +-diff # показывает изменения между фиксациями, рабочими деревьями... +-fetch # загружает объекты и ссылки из другого репозитория +-grep # выводит строки, соответствующие шаблону +-init # создает пустой репозиторий git, или переинициализирует существующий +-log # показывает журналы фиксации +-merge # объединяет две, или больше истории разработки +-mv # перемещает, или переименовывает файл, каталог, или символическую ссылку +-pull # получает изменения из удаленного репозитория и объединяет их с локальным, или локальной веткой +-push # загружет изменения из локального репозитория в удаленный +-rebase # строит ровную линию фиксаций +-reset # сбрасывает текущий HEAD в указанное состояние +-rm # удаляет файлы из рабочего дерева и из индекса +-show # показывает различные типы объектов +-status # показывает состояние рабочего дерева +-tag # создает, выводит, удаляет, или проверяет тег объекта, подписанного с GPG + + + +#========================================== +# Создание веток: +git branch name_branch [начальная фиксация] + +# Вывод имен веток в репозитории: +git branch + -r # удаленные ветки + -a # все ветки + +# Вывод детальной информации: +git show-branch + +# Переключение на другую ветку: +git checkout name_branch + +# Объединение изменений в другую ветку: +git checkout -m dev + +# Создание новой ветки и переключение на нее: +git checkout -b dev_new + +#========================================== +# Использование UNIX команды diff: +diff -u cat-1 cat-2 + +# Разница между рабочим каталогом и индексом: +git diff + +# Разница между рабочим каталогом и указанной фиксацией: +git diff фиксация + +# Разница между подготовленными в индексе изиенениями и заданной фиксацией: +git diff --cached фиксация + +#========================================== +# Исследование конфликтов слияния: +git merge alt + +# Когда есть кофликты, рабочая копия каждого проблемного файла добавляется маркерами слияния - маркеры показывают две возможные версии конфликтного участка файла: нужно просто выбрать одну из фраз, удалить маркеры конфликта и выполнить команды: +git add +git commit + +# Отмена, или перезапуск слияния: +# Перед последним git commit: +# команда восстановит рабочий каталог и индекс в состояние предшествующее git merge: +git reset --hard HEAD + +# После завершения слияния (уже есть новая фиксация): +git reset --hard ORIG_HEAD +# перед началом слияния GIT созраняет оригинальную ветку HEAD в ссылке ORIG_HEAD, чтобы можно было выполнить откат слияния + +# Вернуться к исходному состоянию конфликта, чтобы разрешить его снова: +git checkout -m + +#========================================== +# Применение изменений представленных =фиксацией= на текущей ветке: +# не изменяет существующую историю в репозитории, вместо этого она добавляет фиксацию в историю: +git cherry-pick фиксация +# обычно используется чтобы представить определенные фиксации из одной ветки в репозитории в другую ветку +``` +![[Снимок экрана 2025-02-02 в 18.06.09.png]] + +```sh +# Отменяет фиксацию использую хэш этой фиксации +# не изменяет существующую историю в репозитории +# добавляет в историю новую фиксацию: +git revert фиксация +``` + +![[Снимок экрана 2025-02-02 в 18.13.01.png]] +```sh +#========================================== +# Изменение последней фиксации: +git commit --amend + +# Изменение последовательности фиксаций: +git rebase +# требует, как минимум, имени другой ветки, в которую будут перемещены фиксации, по умолчанию перебазируются фиксации из текущей ветки, которых еще нет в целевой ветке +# типично: сохранить серию фиксаций из указанной ветки на последнюю фиксацию ветки =master= +``` +![[Снимок экрана 2025-02-02 в 18.29.43.png]] +![[Снимок экрана 2025-02-02 в 18.30.02.png]] +```sh +# использование git rebase - портирование, ветка topic была портирована в ветку master + +#========================================== +# Если нужно сохранить все и заняться чем-то другим: +git stash save "работаю над текущим проектом" +# занимаемся чем-то другим +..... +git commit -a -m "что-то другое" +git stash pop +# выполнить можно только в чистый рабочий каталог ! +#========================================== +# Запись изменений в головы веток +# показывает транзакции только для одной ссылки за один раз +git reflog show + +# журнал изменений ветки fred: +git reflog fred +# каждая строка-это отдельная транзакция из истории ссылки, нчиная с самого последнего изменения и вплоть до самого первого + +#========================================== +# Создание "чистого" (bare) репозитория: +git init --bare + +# Преобразовать репозиторий в другой чистый клон: +git clone --bare vlapa@178.20.46.157:~/git/name new_name.git + +# добавить удаленную конфигурацию +git remote add origin /путь/к/репозиторию +# команда git remote добавила новый раздел remote с названием origin + +git push origin master +# изменения ветки master отправлены удаленному репозиторию origin + +#========================================== +# Посмотреть список веток в своем репозитории: +git branch -a + +#========================================== +# Загрузка обновлений: +git pull # состоит из двух команд: + -git fetch + -git merge (rebase) - по умолчанию = merge + +#========================================== +``` + diff --git a/LINUX/AmneziaWG.md b/LINUX/AmneziaWG.md new file mode 100644 index 0000000..24e9ffa --- /dev/null +++ b/LINUX/AmneziaWG.md @@ -0,0 +1,67 @@ +AmneziaWG — это форк WireGuard с обфускацией трафика, который помогает обходить DPI-блокировки там, где обычный WireGuard режется [](https://github.com/bivlked/amneziawg-installer/blob/main/README.md)[](https://github.com/bivlked/amneziawg-installer/blob/main/README.en.md). + +Вот **три способа поднять сервер AmneziaWG** (от самого простого к более продвинутому): + +Этот скрипт установит AmneziaWG за пару минут. + +**1. Загрузите и запустите скрипт:** + +```sh +bash + +curl -O https://raw.githubusercontent.com/wiresock/amneziawg-install/main/amneziawg-install.sh +chmod +x amneziawg-install.sh +sudo ./amneziawg-install.sh +``` + +**2. Ответьте на вопросы скрипта:** + +- Укажите публичный IP вашего сервера +- Выберите порт (по умолчанию 51820, но лучше сменить) +- Скрипт сам сгенерирует ключи и параметры обфускации + +**3. Результат:** После установки готовый конфиг клиента появится в +`~/awg0-client-<имя>.conf` [](https://github.com/wiresock/amneziawg-install/blob/main/README.md) + +### Обновление и управление: +```sh + +bash + +# Добавить или удалить клиентов +sudo ./amneziawg-install.sh + +# Если нужна веб-панель (опционально) +sudo ./amneziawg-web.sh install +``` +### Добавление нового клиента + +Если вам нужен конфиг для дополнительного клиента, выполните: + +bash + +sudo ./amneziawg-install.sh --add-client ИМЯ_КЛИЕНТА + + +### Параметры обфускации + +Если у вас возникли проблемы с подключением, попробуйте изменить параметры в конфиге сервера (`/etc/amnezia/amneziawg/awg0.conf`): + +|Параметр|Назначение|Рекомендуемое значение| +|---|---|---| +|`Jc`|Количество мусорных пакетов|4-10| +|`Jmin`|Мин. размер мусора (байт)|50| +|`Jmax`|Макс. размер мусора (байт)|1000| +|`S1`|Мусор в handshake init|0-150| +|`S2`|Мусор в handshake response|0-150| +|`H1-H4`|Магические заголовки|Уникальные числа [](https://github.com/telemt/telemt/blob/main/docs/Setup_examples/VPS_DOUBLE_HOP.ru.md)[](https://github.com/link0ln/amneziawg-docker-server)| + +### Важные замечания + +- **Все клиенты** должны использовать те же параметры `Jc, Jmin, Jmax`, что и сервер [](https://github.com/link0ln/amneziawg-docker-server) + +- Рекомендуется **сменить порт** с 51820 на что-то нестандартное (например, 8443 или 585) [](https://docs.amnezia.org/ru/documentation/instructions/install-vpn-on-server/#%d0%b4%d1%80%d1%83%d0%b3%d0%b8%d0%b5-%d0%b8%d0%bd%d1%81%d1%82%d1%80%d1%83%d0%ba%d1%86%d0%b8%d0%b8) + +- AmneziaWG совместим с обычным WireGuard, но для обфускации нужны специальные параметры + + diff --git a/LINUX/BASH -курс.md b/LINUX/BASH -курс.md new file mode 100644 index 0000000..79d5707 --- /dev/null +++ b/LINUX/BASH -курс.md @@ -0,0 +1,121 @@ +=========================== +```sh +# СОЗДАЕМ СКРИПТ ДЛЯ СОЗДАНИЯ ПОЛЬЗОВАТЕЛЯ И ГРУППЫ + +#!/bin/bash # шебанг + +set -e # при ошибке скрипт прервется! + +read -p "login: " login # ввод логина +read -p "Password: " -s password # секьюрный ввод пароля! +read -p "path of ssh public key: " -e ssh_pub_key # ввод ключа (выбор через TAB) + +homedir=/home/$login +ssh_dir=$homedir/.ssh + +sudo mkdir -p "$ssh_dir" + +sudo cat "$ssh_pub_path" >> "$ssh_dir/authorized_keys" # дописываем в конец файла +sudo cp -rT /etc/skel $homedir + +sudo useradd -d $homedir -s /bin/bash $login +sudo chown -R $login:$login $homedir + +echo "$login:$password" | sudo chpasswd + +cat /etc/passwd | grep $login + + +=========================== +# Выход из пользователя: +Ctrl+d +exit + +man usermod +# поиск: +/... +/enter # перемещаемся + +cat /var/TabTab +cat /var/log/TabTab +cat /var/log | tail -20 + +=========================== +# SUDO - находятся: +sudo vim /etc/sudoers # НЕ ИЗМЕНЯТЬ !!! + +# Добавлять с проверкой: +sudo vimsudo /etc/sudoers.d/user +# в файл добавляем: +user ALL=(ALL:ALL) ALL +user ALL=(ALL:ALL) NOPASSWD:ALL + +=========================== +# Скрипт +vim ~/script/... + +#!/bin/bash + +# генерируем 3 директории с правами для всех, для группы, для юзера. Сделаем проверку, что права выставлены верно: + +set -e + +login=$1 +homedir=/home/$login +list_name_dir=(secret_for_other secret_for_group secret_for_user) + +ready_dir_list=() + +for name in ${list_name_dir[@]}; # так для массива(списка) +do + mkdir -p $homedir/${login}_${name} + #chown -R $login:$login $homedir + ready_dir_list+=($homedir/${login}_${name}) + if [[ $(echo $name | grep other) ]]; + then + chmod -R 007 $homedir/${login}_${name} + elif [[ $(echo $name | grep group) ]]; + then + chmod -R 070 $homedir/${login}_${name} + elif [[ $(echo $name | grep user) ]]; + then + chmod -R 700 $homedir/${login}_${name} + fi +done + +echo ${list_name_dir[@]} +echo ${ready_dir_list[@]} + +counter=$(ls $homedir/ | wc -l) +while [ $counter -gt 0 ]; +do + counter=$(( $counter - 1 )) + access_rules=$(ls -ld ${ready_dir_list[$counter]} | cut -d' ' -f 1) + type_of_rules=$(echo ${ready_dir_list[$counter]} | cut -d'_' -f 4) + + if [[ ${access_rules:1} == "rwx------" ]] && [[ ${type_of_rules} == "user" ]]; + then + echo ${counter} " THIS IS RULES FOR" ${type_of_rules} + echo $access_rules + echo "GOOD JOB" + elif [[ ${access_rules:1} == "---rwx---" ]] && [[ ${type_of_rules} == "group" ]]; + then + echo "THIS IS RULES FOR" ${type_of_rules} + echo $access_rules + echo "GOOD JOB" + elif [[ ${access_rules:1} == "------rwx" ]] && [[ ${type_of_rules} == "other" ]]; + then + echo "THIS IS RULES FOR" ${type_of_rules} + echo $access_rules + echo "GOOD JOB" + else + echo ---------------- + echo "YOU MADE MISTAKE FOR" ${type_of_rules} + echo $access_rules + echo ---------------- + fi + +#done + + +``` diff --git a/LINUX/BASH.md b/LINUX/BASH.md new file mode 100644 index 0000000..c8812a0 --- /dev/null +++ b/LINUX/BASH.md @@ -0,0 +1,109 @@ +```bash +mydir=$(pwd) +mydir=`pwd` +echo $mydir + +=========================== + +=========================== +-z # строка пуста +-n # строка не пуста +=, (==) # строки равны +! = # строки не равны + +! # отрицание логическоно выражения +-a, (&&) # логическое И +-o, (||) # логическое ИЛИ + +var1=(( 5 + 5)) +-eq = +-ge >= +-gt > +-le <= +-lt < +-ne != + +if [ $val - gt 5] +then +elif +else +fi + +/dev/pts/$i + +IFS=$'\n' # переназначение переменной среды на только перевод строки +IFS=: +``` + +```sh +# Где я? +mydir=`pwd` +mydir=$(pwd) +echo $mydir + +# Математика: +var1=$(( 5 + 5 )) +echo $var1 + +# IF-THEN-ELSE-ELIF +user=likegeeks +if grep $user /etc/passwd +then +echo "The user $user Exists!" +else +echo "The user $user doesn't exist" +elif grep $user /usr/bin +then +echo "jhgvhjlgvljhgljhg" +fi +``` + +```sh +# Как в Linux, так и в OS X grep возвращает код выхода, поэтому легко проверить, был ли процесс найден или нет:** + +#!/bin/bash +ps cax | grep httpd > /dev/null +if [ $? -eq 0 ]; then + echo "Process is running." +else + echo "Process is not running." +fi + + +# Кроме того, если вам нужен список PID, вы также можете легко выполнить grep для них:** + +ps cax | grep httpd | grep -o '^[ ]*[0-9]*' + +# Чей вывод одинаков в Linux и OS X:** + +3519 3521 3523 3524 + +# Вывод следующего представляет собой пустую строку, что делает этот подход безопасным для процессов, которые не запущены:** + +echo `ps cax | grep aasdfasdf | grep -o '^[ ]*[0-9]*'` + +# Этот подход подходит для написания простого теста с пустой строкой, а затем даже для перебора обнаруженных PID. + +#!/bin/bash +PROCESS=$1 +PIDS=`ps cax | grep $PROCESS | grep -o '^[ ]*[0-9]*'` +if [ -z "$PIDS" ]; then + echo "Process not running." 1>&2 + exit 1 +else + for PID in $PIDS; do + echo $PID + done +fi + +# Вы можете протестировать ее, сохранив в файл (с именем "running") с разрешениями на выполнение (chmod + x running) и выполнив его с параметром: `./running "httpd"` + +#!/bin/bash +ps cax | grep httpd +if [ $? -eq 0 ]; then + echo "Process is running." +else + echo "Process is not running." +fi +``` + diff --git a/LINUX/BASH_github/bash-guide-1.md b/LINUX/BASH_github/bash-guide-1.md new file mode 100644 index 0000000..0128e08 --- /dev/null +++ b/LINUX/BASH_github/bash-guide-1.md @@ -0,0 +1,218 @@ +Бесплатная книга-сайт на русском, полный гайд +[Advanced Bash-Scripting Guide](http://rus-linux.net/MyLDP/BOOKS/abs-guide/flat/abs-book.html) + +# [Основы BASH. Часть 1](https://habr.com/post/47163/) +## Введение + +BASH — Bourne-Again SHell (что может переводится как «перерожденный шел», или «Снова шел Борна(создатель sh)»), самый популярный командный интерпретатор в юниксоподобных системах, в особенности в GNU/Linux. Ниже приведу ряд встроенных команд, которые мы будем использовать для создания своих скриптов. + + +>`break` выход из цикла for, while или until +> `continue` выполнение следующей итерации цикла for, while или until +> `echo` вывод аргументов, разделенных пробелами, на стандартное устройство вывода +> `exit` выход из оболочки +> `export` отмечает аргументы как переменные для передачи в дочерние процессы в среде +> `hash` запоминает полные имена путей команд, указанных в качестве аргументов, чтобы не искать их при следующем обращении +> `kill` посылает сигнал завершения процессу +> `pwd` выводит текущий рабочий каталог +> `read` читает строку из ввода оболочки и использует ее для присвоения значений указанным переменным.\ +> `return` заставляет функцию оболочки выйти с указанным значением +> `shift` перемещает позиционные параметры налево +> `test` вычисляет условное выражение +> `times` выводит имя пользователя и системное время, использованное оболочкой и ее потомками +> `trap` указывает команды, которые должны выполняться при получении оболочкой сигнала +> `unset` вызывает уничтожение переменных оболочки +> `wait` ждет выхода из дочернего процесса и сообщает выходное состояние. + + +И конечно же кроме встроенных команд мы будем использовать целую кучу внешних, отдельных команд-программ, с которыми мы познакомимся уже в процессе + +## Что необходимо знать с самого начала + +1. Любой bash-скрипт должен начинаться со строки: + +> `#!/bin/bash` + +*в этой строке после #! указывается путь к bash-интерпретатору, поэтому если он у вас установлен в другом месте(где, вы можете узнать набрав `whereis bash`) поменяйте её на ваш путь.* + +2. Коментарии начинаются с символа # (кроме первой строки). +3. В bash переменные не имеют типа(о них речь пойдет ниже) + +## Переменные и параметры скрипта + +Приведу как пример небольшой пример, который мы разберем: + +> `#!/bin/bash` #указываем где у нас хранится bash-интерпретатор +> \#присваиваем переменной parametr1 значение первого параметра скрипта +> `parametr1=$1` +> \#присваиваем переменной script_name значение имени скрипта +> `script_name=$0` +> \# команда echo выводит определенную строку, обращение к переменным осуществляется через $имя_переменной. +> `echo "Вы запустили скрипт с именем $script_name и параметром $parametr1"` +> \# здесь мы видим другие кавычки, разница в том, что в одинарных кавычках не происходит подстановки переменных. +> `echo 'Вы запустили скрипт с именем $script_name и параметром $parametr1'` +> \#Выход с кодом 0 (удачное завершение работы скрипта) +> `exit 0` + + +Результат выполнения скрипта: + +> `ite@ite-desktop:~$ ./test.sh qwerty` +> Вы запустили скрипт с именем ./test.sh и параметром qwerty +> Вы запустили скрипт с именем $script_name и параметром $parametr1 + + +После того как мы познакомились как использовать переменные и передавать скрипту параметры, время познакомиться с зарезервированными переменными: + +> `$DIRSTACK` - содержимое вершины стека каталогов +> `$EDITOR` - текстовый редактор по умолчанию +> `$EUID` - Эффективный UID. Если вы использовали программу su для выполнения команд от другого пользователя, то эта переменная содержит UID этого пользователя, в то время как... +> `$UID` - ...содержит реальный идентификатор, который устанавливается только при логине. +> `$FUNCNAME` - имя текущей функции в скрипте. +> `$GROUPS` - массив групп к которым принадлежит текущий пользователь +> `$HOME` - домашний каталог пользователя +> `$HOSTNAME` - ваш hostname +> `$HOSTTYPE` - архитектура машины. +> `$LC_CTYPE` - внутренняя переменная, котороя определяет кодировку символов +> `$OLDPWD` - прежний рабочий каталог +> `$OSTYPE` - тип ОС +> `$PATH` - путь поиска программ +> `$PPID` - идентификатор родительского процесса +> `$SECONDS` - время работы скрипта(в сек.) +> `$#` - общее количество параметров переданных скрипту +> `$*` - все аргументы переданыне скрипту(выводятся в строку) +> `$@` - тоже самое, что и предыдущий, но параметры выводятся в столбик +> `$!` - PID последнего запущенного в фоне процесса +> `$$` - PID самого скрипта + +## Условия + +Условные операторы, думаю, знакомы практически каждому, кто хоть раз пытался на чем-то писать программы. В bash условия пишутся след. образом (как обычно на примере): + + +> `#!/bin/bash` +> \#в переменную source засовываем первый параметр скрипта +> `source=$1` +> \#в переменную dest засовываем второй параметр скрипта +> `dest=$2` +> +> \# в ковычках указываем имена переменных для сравнения. -eq - логическое сравнение обозначающие "равны" +> `if [[ "$source" -eq "$dest" ]]` +> \# если они действительно равны, то +> `then` +> \#выводим сообщение об ошибке, т.к. $source и $dest у нас равны +> `echo "Применик $dest и источник $source один и тот же файл!"` +> \# выходим с ошибкой (1 - код ошибки) +> `exit 1` +> \# если же они не равны +> `else` +> \# то выполняем команду cp: копируем источник в приемник +> `cp $source $dest` +> `echo "Удачное копирование!"` +> `fi` #обозначаем окончание условия. + + +Результат выполнения скрипта: + + +> `ite@ite-desktop:~$ ./primer2.sh 1 1` +> Применик 1 и источник 1 один и тот же файл! + +> `ite@ite-desktop:~$ ./primer2.sh 1 2` +> Удачное копирование! + +Структура `if-then-else` используется следующим образом: + +> `if` <команда или набор команд возвращающих код возврата(0 или 1)> +> `then` +> <если выражение после if истино, то выполняется этот блок> +> `else` +> <если выражение после if ложно, тот этот> + + +В качестве команд возвращающих код возврата могут выступать структуры `[[ , [ , test, (( ))` или любая другая(или несколько) linux-команда. + + +> `test` - используется для логического сравнения. после выражения, неоьбходима закрывающая скобка "]" +>`[` - синоним команды test +>`[[` - расширенная версия "[" (начиная с версии 2.02)(как в примере), внутри которой могут быть использованы || (или), & (и). Долна иметь закрывающуб скобку "]]" +>`(( ))` - математическое сравнение. + + +для построения многоярусных условий вида: + +> `if ...` +>`then ....` +>`else` +>`if ....` +>`then....` +>`else ....` + +для краткости и читаемости кода, можно использовать структуру: + +> `if ..` +> `then ...` +> `elif ...` +> `then ...` +> `elif ...` + + +## Условия. Множественный выбор + +Если необходимо сравнивать какоую-то одну переменную с большим количеством параметров, то целесообразней использовать оператор case. + +> `#!/bin/bash` +> `echo "Выберите редатор для запуска:"` +> `echo "1 Запуск программы nano"` +> `echo "2 Запуск программы vi"` +> `echo "3 Запуск программы emacs"` +> `echo "4 Выход"` +\#здесь мы читаем в переменную $doing со стандартного ввода +> `read doing` +> +> `case $doing in` +> `1)` +> `/usr/bin/nano` # если $doing содержит 1, то запустить nano +> `;;` +> `2)` +> `/usr/bin/vi` # если $doing содержит 2, то запустить vi +> `;;` +> `3)` +> `/usr/bin/emacs` # если $doing содержит 3, то запустить emacs +> `;;` +> `4)` +> `exit 0` +> `;;` +> `*)` #если введено с клавиатуры то, что в case не описывается, выполнять следующее: +> `echo "Введено неправильное действие"` +> +> `esac` #окончание оператора case. + +Результат работы: + +> `ite@ite-desktop:~$ ./menu2.sh` +> Выберите редатор для запуска: +> 1 Запуск программы nano +> 2 Запуск программы vi +> 3 Запуск программы emacs +> 4 Выход + +После выбор цифры и нажатия Enter запуститься тот редактор, который вы выбрали(если конечно все пути указаны правильно, и у вас установлены эти редакторы :) ) + + +Прведу список логических операторв, которые используются для конструкции if-then-else-fi: + +> `-z` # строка пуста +> `-n` # строка не пуста +> `=, (==)` # строки равны +> `!=` # строки неравны +> `-eq` # равно +> `-ne` # неравно +> `-lt,(< )` # меньше +> `-le,(<=)` # меньше или равно +> `-gt,(>)` #больше +> `-ge,(>=)` #больше или равно +> `!` #отрицание логического выражения +> `-a,(&&)` #логическое «И» +> `-o,(||)` # логическое «ИЛИ» + diff --git a/LINUX/BASH_github/bash-guide-2.md b/LINUX/BASH_github/bash-guide-2.md new file mode 100644 index 0000000..92c42fa --- /dev/null +++ b/LINUX/BASH_github/bash-guide-2.md @@ -0,0 +1,225 @@ +# [Основы BASH. Часть 2](https://habr.com/post/52871/) + +## Циклы. Цикл for-in. + +Оператор `for-in` предназначен для поочередного обращения к значениям перечисленным в списке. Каждое значение поочередно в списке присваивается переменной. +Синтаксис следующий: + +``` +for переменная in список_значений +do +команды +done +``` + +Рассмотрим небольшой пример: + +``` +#!/bin/bash +for i in 0 1 2 3 4 #переменной $i будем поочередно присваивать значения от 0 до 4 включительно +do +echo "Console number is $i" >> /dev/pts/$i #Пишем в файл /dev/pts/$i(файл виртуального терминала) строку "Console number is $i" +done #цикл окончен +exit 0 +``` + +После выполнения примера в первых 5 виртуальных консолях(терминалах) появится строка с её номером. В переменную `$i` поочередно подставляются значения из списка и в цикле идет работа со значением этой переменной + +## Циклы. Цикл while. + +Цикл `while` сложнее цикла `for-in` и используется для повторения команд, пока какое-то выражение истинно( код возврата = 0). +Синтаксис оператора следующий: + +``` +while выражение или команда возвращающая код возврата +do +команды +done +``` + +Пример работы цикла рассмотрим на следующем примере: + +``` +#!/bin/bash +again=yes #присваиваем значение "yes" переменной again +while [ "$again" = "yes" ] #Будем выполнять цикл, пока $again будет равно "yes" +do +echo "Please enter a name:" +read name +echo "The name you entered is $name" + +echo "Do you wish to continue?" +read again +done +echo "Bye-Bye" +``` + + +А теперь результат работы скрипта: +``` +ite@ite-desktop:~$ ./bash2_primer1.sh +Please enter a name: +ite +The name you entered is ite +Do you wish to continue? +yes +Please enter a name: +mihail +The name you entered is mihail +Do you wish to continue? +no +Bye-Bye +``` + +Как видим цикл выполняется до тех пор, пока мы не введем что-то отличное от «yes». Между `do` и `done` можно описывать любые структуры, операторы и т.п., все они будут выполнятся в цикле.Но следует быть осторожным с этим циклом, если вы запустите на выполнение в нём какую-либо команду, без изменения переменной выражения, вы можете попасть в бесконечный цикл. + +Теперь об условии истинности. После `while`, как и в условном операторе `if-then-else` можно вставлять любое выражение или команду, которая возвращает код возврата, и цикл будет исполнятся до тех пор, пока код возврата = 0! Оператор `[` аналог команды `test`, которая проверяет истинность условия, которое ей передали. + +Рассмотрим еще один пример, я взял его из книги Advanced Bash Scripting. Уж очень он мне понравился :), но я его немного упростил. В этом примере мы познакомимся с еще одним типом циклов **UNTIL-DO**. Эта практически полный аналог цикла **WHILE-DO**, только выполняется пока какое-то выражение ложно. +Вот пример: +``` +#!/bin/bash +echo "Введите числитель: " +read dividend +echo "Введите знаменатель: " +read divisor + +dnd=$dividend #мы будем изменять переменные dividend и divisor, +#сохраним их знания в других переменных, т.к. они нам +#понадобятся +dvs=$divisor +remainder=1 + +until [ "$remainder" -eq 0 ] +do +let "remainder = dividend % divisor" +dividend=$divisor +divisor=$remainder +done + +echo "НОД чисел $dnd и $dvs = $dividend" +``` + +Результат выполнения скрипта: +``` +ite@ite-desktop:~$ ./bash2_primer3.sh +Введите числитель: +100 +Введите знаменатель: +90 +НОД чисел 100 и 90 = 10 +``` + +## Математические операции + +Команда `let`. +Команда `let` производит арифметические операции над числами и переменными. +Рассмотрим небольшой пример, в котором мы производим некоторые вычисления над введенными числами: + +``` +#!/bin/bash +echo "Введите a: " +read a +echo "Введите b: " +read b + +let "c = a + b" #сложение +echo "a+b= $c" +let "c = a / b" #деление +echo "a/b= $c" +let "c <<= 2" #сдвигает c на 2 разряда влево +echo "c после сдвига на 2 разряда: $c" +let "c = a % b" # находит остаток от деления a на b +echo "$a / $b. остаток: $c " +``` + +Результат выполнения: +``` +ite@ite-desktop:~$ ./bash2_primer2.sh +Введите a: +123 +Введите b: +12 +a+b= 135 +a/b= 10 +c после сдвига на 2 разряда: 40 +123 / 12. остаток: 3 +``` + + +Ну вот, как видите ничего сложного, список математических операций стандартный: + +> `+` — сложение +> `—` — вычитание +> `*` — умножение +> `/` — деление +> `**` — возведение в степень +> `%` — модуль(деление по модулю), остаток от деления +> `let` позволяет использовать сокращения арифметических команд, тем самым сокращая кол-во используемых переменных. Например: `a = a+b` эквивалентно `a +=b` и т.д + +## Работа с внешними программами при написании shell-скриптов + +Для начала немного полезной теории. +### Перенаправление потоков. + +В **bash** (как и многих других оболочках) есть встроенные файловые дескрипторы: `0 (stdin)`, `1 (stdout)`, `2 (stderr)`. +`stdout` — Стандартный вывод. Сюда попадает все что выводят программы +`stdin` — Стандартный ввод. Это все что набирает юзер в консоли +`stderr` — Стандартный вывод ошибок. +Для операций с этими дескрипторами, существуют специальные символы: `>` (перенаправление вывода), `<` (перенаправление ввода). Оперировать ими не сложно. Например: + +``` +cat /dev/random > /dev/null #перенаправить вывод команды cat /dev/random в /dev/null (абсолютно бесполезная операция :)) ) +``` +или + +``` +ls -la > listing #записать в файл listing содержание текущего каталога (уже полезней) +``` + +Если есть необходимость дописывать в файл(при использовании "`>`" он заменятеся), необходимо вместо "`>`" использовать "`>>`" +``` +sudo < my_password +``` +после просьбы sudo ввести пароль, он возьмется из файла my_password, как будто вы его ввели с клавиатуры. +Если необходимо записать в файл только ошибки, которые могли возникнуть при работе программы, то можно использовать: +``` +./program_with_error 2> error_file +``` +цифра 2 перед "`>`" означает что нужно перенаправлять все что попадет в дескриптор 2(stderr). +Если необходимо заставить `stderr` писать в `stdout`, то это можно след. образом: +``` +./program_with_error 2>&1 +``` +символ "`&`" означает указатель на дескриптор `1(stdout)` +(Поумолчанию `stderr` пишет на ту консоль, в котрой работает пользователь(вренее пишет на дисплей)). + +### 2. Конвееры. + +Конвеер — очень мощный инструмент для работы с консолью Bash. Синтаксис простой: +`команда1 | команда 2` — означает, что вывод команды 1 передастся на ввод команде 2 +Конвееры можно группировать в цепочки и выводить с помощью перенаправления в файл, например: +``` +ls -la | grep «hash» |sort > sortilg_list +``` +вывод команды `ls -la` передается команде `grep`, которая отбирает все строки, в которых встретится слово *hash*, и передает команде сортировке `sort`, которая пишет результат в файл *sorting_list*. Все довольно понятно и просто. + +Чаще всего скрипты на Bash используются в качестве автоматизации каких-то рутинных операций в консоли, отсюда иногда возникает необходимость в обработке `stdout` одной команды и передача на `stdin` другой команде, при этом результат выполнения одной команды должен быть неким образом обработан. В этом разделе я постораюсь объяснить основные принципы работы с внешними командами внутри скрипта. Думаю что примеров я привел достаточно и можно теперь писать только основные моменты. + +### 1. Передача вывода в переменную. + +Для того чтобы записать в переменную вывод какой-либо команды, достаточно заключить команду в ` `` ` ковычки, например +> a = \` echo "qwerty" \` +> echo $a + +Результат работы: qwerty + +Однако если вы захотите записать в переменную список директорий, то необходимо, должным образом обработать результат для помещения данных в переменную. Рассмотрим небольшой, пример: +``` +LIST=`find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | tr '\n' ' '` +for ONE_OF_LIST in $LIST +do +svnadmin hotcopy /svn/$ONE_OF_LIST /svn/temp4backup/$ONE_OF_LIST +done +``` +Здесь мы используем цикл `for-do-done` для архивирование всех директорий в папке */svn/* с помощью команды `svnadmin hotcopy`(что в нашем случае не имеет никого значения, просто как пример). Наибольшй интерес вызывает строка: LIST=`find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | tr '\n' ' '` В ней переменной LIST присваивается выполнение команды find, обработанной командами awk, sort, uniq,tr(все эти команды мы рассматривать не будем, ибо это отдельная статья). В переменной LIST будут имена всех каталогов в папке /svn/ пгомещенных в одну строку(для того чтобы её стравить циклу.
\ No newline at end of file diff --git a/LINUX/BASH_github/Основы Bash.md b/LINUX/BASH_github/Основы Bash.md new file mode 100644 index 0000000..7100ab1 --- /dev/null +++ b/LINUX/BASH_github/Основы Bash.md @@ -0,0 +1,222 @@ +## синтаксис, переменные и команды ## + +Кодить будем в терминале Linux — он открывается сочетанием клавиш **Ctrl + Alt + T**. Если вы хотите научиться работать с Bash, но пока не пользуетесь Linux, то пишите код в одном из онлайн-терминалов — например, [Replit](https://repl.it/languages/bash). + +### **Переменные** + +Переменные в Bash объявляются просто: + +` message="Hello, World!" ` + +Теперь переменная message хранит значение — текстовое сообщение «Hello, World!». Давайте выведем его — сделать это можно с помощью команды echo: + +` echo $message # Будет выведено «Hello, World!» ` + +Обратите внимание на отсутствие пробелов. В Bash пробелы являются разделителями. Если пробелы есть, то Bash будет считать их отдельными токенами и выдавать ошибки. + +Следующий код приведёт к ошибке: + +` message = "Hello, World!" bash: message: command not found ` + +Также обратите внимание на символ $ **—** в Bash он называется символом расширения (expansion character). Поставленный перед именем переменной, он указывает на то, что нужно использовать именно значение переменной, а не одноимённую строку. + +Следующий код выведет сообщение «message»: + +` message="Hello, World!" echo message ` + +Чтобы сохранить в переменной введённое пользователем значение, используйте команду read: + +` read name # Пользователь вводит что-то — например, «Olga» echo $name # Будет выведено «Olga» ` + +Если вы хотите добавить сообщение перед пользовательским вводом, добавьте флаг -p и сообщение после команды read: + +` read -p "Enter your name: " name # Будет выведено «Enter your name: » # Пользователь вводит что-то — например, «Olga» echo $name # Будет выведено «Olga» ` + +В Bash есть несколько правил именования переменных: + +- Имя переменной должно начинаться с буквы латинского алфавита (нижнего или верхнего регистра) или символа подчёркивания _. +- Оно может включать буквы, цифры и символ подчёркивания, но не может содержать пробелы или специальные символы. +- Регистр букв в именах переменных имеет значение. Например, переменные var1 и Var1 будут рассматриваться как разные. +- Не рекомендуется использовать зарезервированные слова Bash в качестве имён переменных. + +### **Типы данных** + +Ну что же, вот мы и создали нашу первую переменную. Поздравляем! Пока эта переменная содержит всего один тип данных — строковый (string). Но в Bash он далеко не единственный — давайте разберём ещё несколько. + +**ВАЖНАЯ ОГОВОРКА** + +В Bash нет строгой системы типов, как в некоторых других языках. Тип данных зависит от содержимого переменной и контекста её использования. Поэтому термин «типы данных» в этой статье используется условно, для облегчения понимания. Простите нам это упрощение. + +**Строки (strings)** — это набор символов, заключённый в кавычки. В Bash можно писать строки в одинарных '…' или двойных "…" кавычках. Например, 'Привет' и "Мир" — это строки. + +**Числа (numbers)** — это целочисленные значения. Bash поддерживает арифметические операции с целыми числами, включая сложение, вычитание, умножение и деление. Арифметические выражения нужно предварять знаком $ и обрамлять двойными круглыми скобками: + +` a=2 b=3 echo $(($a + $b)) # Будет выведено 5 ` + +Также можно использовать команду expr: + +` echo $(expr $a + $b) # Будет выведено 5 ` + +Стандартные библиотеки Bash поддерживают следующие арифметические операции: + +- Сложение — +. +- Вычитание — -. +- Умножение — *. +- Целочисленное деление — /. +- Остаток от деления — %. +- Возведение в степень — **. + +**Примечание**: арифметические операторы в Bash не работают с числами с плавающей точкой. + +**Массивы (arrays)** — тип переменных, который может хранить несколько значений. Элементы массива перечисляются в круглых скобках через пробел: + +` my_array=(apple banana cherry) ` + +При использовании переменной массива в команде применяются фигурные скобки. Если мы хотим вывести его отдельные элементы, это можно сделать с помощью символа $ и номера индекса в квадратных скобках: + +` echo ${my_array[0]} # Выведет «apple» echo ${my_array[1]} # Выведет «banana» echo ${my_array[2]} # Выведет «cherry» ` + +А если нам нужно получить сразу все элементы массива, вместо индекса в квадратных скобках нужно поставить символ * или @: + +` echo ${my_array[*]} # Выведет «apple banana cherry» echo ${my_array[@]} # Выведет «apple banana cherry» ` + +**Логические значения (boolean)** — нужны для того, чтобы проверить, выполняется ли какое-то условие или нет. Они могут быть либо true (истина), либо false (ложь). Например, в операторах if и while мы пишем условие, и если оно true, то выполняем какое-то действие. + +В Bash логические значения представлены целыми числами. Число 0 означает true, а любое другое число означает false. Когда мы выводим логическое значение на экран, то вместо true мы видим 0. + +Также в Bash есть операторы сравнения. Например, эти нужны для строк: + +- = проверяет, одинаковы ли две строки. +- != проверяет, различаются ли две строки. + +А эти — для чисел: + +- -eq проверяет, равны ли два числа. +- -ne проверяет, различаются ли два числа. +- -gt проверяет, больше ли первое число, чем второе. +- -lt проверяет, меньше ли первое число, чем второе. +- -ge выводит true, если первое число больше или равно второму. +- -le выводит true, если первое число меньше или равно второму. + +Использовать операторы сравнения можно с помощью команды test. Команда test принимает два значения и оператор сравнения и возвращает 1 или 0 в зависимости от результата: + +` test "hello" = "hello" echo $? # Выведет 0 (true), так как строки одинаковые test "hello" != "world" echo $? # Выведет 0 (true), так как строки различаются ` + +Помимо команды test, операторы сравнения можно использовать квадратных скобках [ ]. Но для этого нам понадобится разобраться, как работать с ветвлениями. + +### **Ветвления** + +Ветвления if-then-else позволяют проверить условие и выполнить один набор команд, если условие истинно, и другой набор команд, если условие ложно. Это выглядит так: + +` if [ условие ]; then # Команды, которые нужно выполнить, если условие истинно else # Команды, которые нужно выполнить, если условие ложно fi ` + +**Обратите внимание**: открывающую и закрывающую квадратные скобки нужно отделять от операндов пробелами. + +А если в паре с основным условием нужно проверить дополнительное, используют конструкцию elif: + +` if [ условие_1 ]; then # Команды, которые нужно выполнить, если условие_1 истинно elif [ условие_2 ]; then # Команды, которые нужно выполнить, если условие_2 истинно else # Команды, которые нужно выполнить, если все условия ложны fi ` + +Существует и более короткая форма записи условной конструкции — if-then. Её используют, когда нужно проверить только одно условие: + +` if [ условие ]; then # Команды, которые нужно выполнить fi ` + +В этом случае команды будут выполнены, только если условие истинно. + +Для наглядности давайте напишем скрипт, который проверяет правильность введённого пароля: + +` #!/bin/bash # Задаём значение переменной password password="14pomTEr" # Запрашиваем ввод пароля у пользователя и сохраняем полученное значение в переменную user_input read -p "Enter the password: " user_input # Сравниваем значение переменной password с введённым пользователем значением user_input if [ "$password" = "$user_input" ]; then # Если пароли совпадают, выводим сообщение о том, что пароль введён правильно echo "Password accepted" else # Если пароли не совпадают, выводим сообщение о том, что пароль введён неправильно echo "Wrong password" fi ` + +Кроме того, в Bash есть логические операторы & & (логическое И) и **||** (логическое ИЛИ), которые позволяют комбинировать несколько условий и возвращать соответствующее булево значение. + +Например, вот как будет выглядеть наша программа, если мы добавим проверку логина: + +` #!/bin/bash username="OlgaBelova94" # Устанавливаем переменную с именем пользователя password="14pomTEr" read -p "Enter your username: " user_input_username # Запрашиваем имя пользователя read -p "Enter the password: " user_input_username # Запрашиваем пароль echo "Enter the password: " read user_input_password # Запрашиваем пароль if [ "$username" == "$user_input_username" ] && [ "$password" == "$user_input_password" ]; then echo "Password accepted" else echo "Wrong username or password" # Уточняем, что именно было неверно fi ` + +Здесь мы получим значение true и сообщение «Password accepted», только если будут выполнены оба условия. + +### **Циклы** + +Как и в большинстве языков программирования, в Bash есть циклы. Они бывают двух видов: + +- for — когда количество итераций заранее известно. +- while — когда количество итераций заранее не известно. + +Вот как выглядит сигнатура цикла for: + +` for переменная цикла in элемент1 элемент2 ... элементN do # Команды, которые будут выполняться в цикле done ` + +Количество итераций (часть между in и do) можно указать простым перечислением элементов: + +` for fruit in apple banana cherry do echo "I like $fruit" done # Будет выведено: # I like apple # I like banana # I like cherry ` + +Можно перебрать элементы массива: + +` my_array=("apple" "banana" "orange") for fruit in "${my_array[@]}" do echo "I like $fruit" done # Будет выведено: # I like apple # I like banana # I like cherry ` + +Для явного указания нужного количества итераций используют арифметические выражения: + +` for ((i=0; i<5; i++)) do echo "Iteration number $i" done # Будет выведено: # Iteration number 0 # Iteration number 1 # Iteration number 2 # Iteration number 3 # Iteration number 4 ` + +А вот так выглядит сигнатура цикла while: + +` while [ условие ] do # Команды, которые будут выполняться в цикле done ` + +**Условие** **(condition)** — это выражение, результат которого является логическим значением true или false. Команды внутри цикла будут выполняться до тех пор, пока condition возвращает true. + +Давайте усовершенствуем нашу программу проверки пароля так, чтобы она запрашивала его до тех пор, пока пользователь не введёт верный: + +` #!/bin/bash password="14pomTEr" user_input="" while [ "$user_input" != "$password" ] do read -p "Enter the password: " user_input echo "Wrong password, login denied" done echo "Password accepted, login allowed" ` + +**Что здесь происходит.** В этом примере цикл while запрашивает пароль у пользователя, а потом проверяет его на совпадение с правильным с помощью оператора ["$user_input" != "$password"]. А дальше происходит следующее: + +- Если пароль неверный, программа выводит соответствующее сообщение, а цикл продолжается. +- Если пароль верный, программа выводит сообщение о принятии пароля и завершает выполнение. + +### **Функции** + +Функции на языке Bash имеют такой вид: + +` имя_функции (аргументы) { # Тело функции } ` + +Например, вот как выглядит простая функция без аргументов: + +` say_hello () { # Создаём функцию echo hello } say_hello # Вызываем функцию, будет выведено «hello» ` + +А вот как выглядит функция, которая принимает аргументы: + +` repeat_text () { local text=$1 local reps=$2 for (( i=0; i<$reps; i++ )); do echo "$text" done } repeat_text "hello" 3 ` + +**Что здесь происходит:** + +- С помощью ключевого слова local мы создали две локальные переменные — то есть переменные, существующие только внутри функции. +- В одну из них поместили текст, а во вторую — количество повторов. +- Затем создали цикл, который берёт текст из первой переменной и повторяет его столько раз, сколько раз указано во второй. +- Вызвали функцию, передав ей значения hello и 3 — то есть приказали повторить слово «hello» три раза. + +## **Ну здравствуй, Bash! Пишем первый скрипт** + +Настало время применить знания на практике — напишем скрипт для автоматической очистки кэша браузера и DNS. + +Состоять наш скрипт будет из трёх функций: первая — для очистки кэша браузера, вторая — для очистки кэша DNS, а в третью завернём небольшое меню для выбора нужного действия. + +Начинаем, как водится, с шебанга — специальной строки, которая говорит компьютеру, с помощью какой программы нужно запускать скрипт. Так как мы хотим использовать bash, которая лежит в папке /bin, шебанг будет выглядеть так: + +` #!/bin/bash ` + +Теперь напишем функцию, которая будет очищать браузерный кэш. В комментариях — разбор синтаксиса. + +` clear_browser_cache() { # Проверяем, какой браузер установлен на компьютере if command -v google-chrome >/dev/null 2>&1; then # Если установлен Google Chrome, очищаем кэш браузера google-chrome --user-data-dir=$(mktemp -d) --disable-extensions --disable-plugins --disable-geolocation --no-default-browser-check --no-first-run --noerrdialogs --disable-session-crashed-bubble --disable-infobars --disk-cache-size=0 --media-cache-size=0 # Далее проверяем, установлен ли Firefox elif command -v firefox >/dev/null 2>&1; then # Если установлен Firefox, очищаем кэш браузера firefox --new-instance --safe-mode # Если браузер не установлен, выводим сообщение об ошибке else echo "Error: no supported browser found" fi } ` + +Далее напишем функцию для очистки кэша DNS. + +` clear_dns_cache() { # Проверяем, какая операционная система установлена на компьютере # Если установлена Linux, очищаем кэш DNS if [[ "$OSTYPE" == "linux-gnu"* ]]; then sudo systemd-resolve --flush-caches # Если установлена macOS, очищаем кэш DNS elif [[ "$OSTYPE" == "darwin"* ]]; then sudo dscacheutil -flushcache sudo killall -HUP mDNSResponder # Если операционная система не поддерживается, выводим сообщение об ошибке else echo "Error: unsupported operating system" fi } ` + +Ну а теперь дело за малым. Напишем небольшое меню, чтобы пользователь мог выбрать, кэш браузера или кэш DNS нужно очистить. + +` # Сначала выведем варианты clear_cache() { echo "Select cache to clear:" echo "1. Browser cache" echo "2. DNS cache" # Запросим у пользователя номер выбранного пункта меню read -p "Enter your choice: " choice # В зависимости от выбранного пункта меню вызываем соответствующую функцию для очистки кэша if [ "$choice" -eq 1 ]; then clear_browser_cache elif [ "$choice" -eq 2 ]; then clear_dns_cache else echo "Invalid choice" fi } ` + +Вуаля! Наш скрипт готов. Чтобы его запустить, нам нужно лишь вызвать функцию clear_cache: + +` clear_cache ` + +В теории можно сделать скрипт и ещё лучше — например, добавить больше браузеров, реализовать проверку прав администратора и другие фишки. Но на сегодня, пожалуй, достаточно.
\ No newline at end of file diff --git a/LINUX/BASH_script/15 однострочных bash-скриптов.md b/LINUX/BASH_script/15 однострочных bash-скриптов.md new file mode 100644 index 0000000..555f4ff --- /dev/null +++ b/LINUX/BASH_script/15 однострочных bash-скриптов.md @@ -0,0 +1,211 @@ +для упрощения рутинных задач + +1. Проверка использования +дискового пространства +Нехватка места на диске может привести к +сбою приложений. Эта команда выводит +список всех смонтированных разделов и +выделяет те, которые утилизированы на 80% +и более. +==`df -h | awk '$5+0 > 80 {print}' +- df -hотображает использование диска в +удобном для чтения формате. +- awk ‘$5+0 > 80 {print}’извлекает +строки, в которых утилизация превышает +80%. + +2. Поиск больших файлов, +которые занимают место +Определите 10 самых больших файлов в +системе. +==`find / -type f -exec du -h {} + | +sort -rh | head -10 +- find / -type fищет все файлы. +- du -hвычисляет размеры файлов в +удобном для чтения формате. +- sort -rhсортирует результаты по +убыванию. +- head -10ограничивает вывод 10 самыми +большими файлами. + +3. Отслеживание обновлений +лога в реальном времени +Просмотр файлов логов в режиме реального +времени, с фильтрацией сообщений об +ошибках. +==`tail -f /var/log/syslog | grep - +line-buffered "error" +- tail -fотслеживает файл журнала по +мере добавления новых записей. +- grep — line-buffered “error” +фильтрует строки, содержащие "error". + +4. Перезапуск службы в случае +сбоя +Проверка, что критически важная служба, +такая как Nginx, всегда запущена. +==`systemctl is-active - quiet nginx +|| systemctl restart nginx +- systemctl is-active — quiet +nginx проверяет состояние службы. +- Если она не запущена, systemctl +restart nginx перезапускает ее. + +5. Мониторинг использования +CPU в режиме реального +времени +Проверка загрузки CPU в реальном времени +для устранения неполадок с +производительностью. +==`top -b -n 1 | grep "Cpu(s)" +- top -b -n 1запускает «top» в пакетном +режиме для одной итерации. +- grep “Cpu(s)”извлекает сведения об +использовании CPU. + +6. Уничтожение самого +ресурсоемкого процесса +Поиск и завершение процесса, +потребляющего больше всего ресурсов CPU. +==`kill -9 $(ps -eo pid,%cpu - +sort=-%cpu | awk 'NR==2 {print +$1}') +- ps -eo pid,%cpu — sort=-%cpuвыводит +список процессов, отсортированных по +использованию CPU. +- awk ‘NR\==2 {print $1}’выбирает +процесс с наибольшим потреблением ЦП. +- kill -9принудительно завершает его. + +7. Получение публичного IP- +адреса +Быстрое получение публичного IP-адреса +сервера. +==`curl -s ifconfig.me +- Способ использует curl для запроса +текущего публичного IP-адреса из +«ifconfig.me». +- -s Подавляет ненужный вывод. + +8. Поиск всех открытых сетевых +портов +Перечисление всех активных +прослушиваемых портов на вашем +компьютере. +==`netstat -tulnp | grep LISTEN +- netstat -tulnpотображает открытые +порты и связанные с ними процессы. +- grep LISTENфильтрует результаты по +прослушиваемым портам. + +9. Копирование SSH-ключа на +удаленный сервер +Настройка беспарольной SSH- +аутентификации за секунды. +==`ssh-keygen -t rsa -b 2048 -q -N +"" && ssh-copy-id +user@remote_server +- ssh-keygen создает пару ключей RSA. +- ssh-copy-id передает открытый ключ на +удаленный сервер. + +10. Синхронизация файлов +между серверами +Использование rsync для зеркалирования +файлов с одного сервера на другой. +==`rsync -avz /local/path +user@remote:/remote/path +- -a сохраняет разрешения и временные +метки. +- -v включает подробный режим. +- -z сжимает данные для более быстрой +передачи. + +11. Планирование задач с +помощью Cron +Добавление задания cron для резервного +копирования файлов каждую ночь в 2 часа +ночи. +==`echo "0 2 * * * tar -czf / +backup.tar.gz /important/data" | +crontab - +- 0 2 * * *планирует задание на 2 часа +ночи ежедневно. +- tar -czfсжимает и создает резервную +копию "/important/data". + +12. Загрузка файла в фоновом +режиме +Использование «wget» для загрузки +больших файлов без блокировки терминала. +==`nohup wget -q https:// +example.com/bigfile.zip & +- nohup обеспечивает выполнение процесса +даже после выхода из системы. +- wget -q незаметно загружает файл. +- & запускает процесс в фоновом режиме. + +13. Поиск и удаление старых +файлов логов +Автоматическая очистка логов старше 7 +дней. +==`find /var/log -name "*.log" +-mtime +7 -exec rm -f {} \; +- -mtime +7выбирает файлы старше 7 дней. +- -exec rm -f {}удаляет их. + +14. Проверка открыт ли порт на +удаленном сервере +Проверка открыт ли определенный порт +(например, 443) на удаленном хосте. +==`nc -zv example.com 443 +- nc -zvпроверяет соединение без +отправки данных. +- Полезно для устранения неполадок +брандмауэра и подключения. + +15. Список всех запущенных +Docker-контейнеров +Удобный просмотр активных контейнеров +Docker. +==`docker ps - format "table {{.ID}} +\t {{.Image}} \t {{.Status}}" +- docker psвыводит список запущенных +контейнеров. +- — formatструктурирует вывод для лучшей +читабельности. + +========================= +Подборка полезных скриптов для опытных Linux-админов + +1) 🔍 Быстрый поиск крупных файлов (удобно перед очисткой диска) +du -ah / | sort -rh | head -n 30 + +2) 🛑 Массовое убийство зависших процессов по шаблону +pkill -f "pattern" + +3) 🧼 Очистка старых логов/кэша старше X дней +find /var/log -type f -mtime +7 -delete + +4) 🧬 Проверка файловой системы без остановки +fsck -n /dev/sdX + +5) 📦 Список пакетов, обновлённых за последние 5 дней +grep "upgrade" /var/log/dpkg.log | tail + +6) 🕵️ Слежение за открытыми портами в реальном времени +watch -n1 "ss -tulpn" + +7) 📊 Сбор top-нагружающих процессов по CPU за минуту +ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | head + +8) 🔁 Авто-рестарт службы при падении +while true; do systemctl restart myservice; sleep 5; done + +9) 🔗 Быстрый тест сетевой задержки и MTU +tracepath google.com + +10) 🔐 Сканирование слабых SSH-ключей +ssh-audit -p 22 localhost + +Такие короткие утилитарные команды экономят кучу времени в рутине админа.
\ No newline at end of file diff --git a/LINUX/BASH_script/BASH. Разное.md b/LINUX/BASH_script/BASH. Разное.md new file mode 100644 index 0000000..f76b28f --- /dev/null +++ b/LINUX/BASH_script/BASH. Разное.md @@ -0,0 +1,40 @@ +_**Использование стандартных ключей**_ + +При написании bash-скриптов вы можете выбирать любые буквы для ключей командной строки и произвольно задавать реакцию скрипта на эти ключи. Однако, в мире Linux значения некоторых ключей стали чем-то вроде стандарта, которого полезно придерживаться. Вот список этих ключей: + +``` +-a Вывести все объекты. +-c Произвести подсчёт. +-d Указать директорию. +-e Развернуть объект. + +-f Указать файл, из которого нужно прочитать данные. +-h Вывести справку по команде. +-i Игнорировать регистр символов. +-l Выполнить полноформатный вывод данных. + +-n Использовать неинтерактивный (пакетный) режим. (ввод на той же строке) +-o Позволяет указать файл, в который нужно перенаправить вывод. +-p предотвращения чтения оболочкой файлов, контролируемых пользователем +-t 5 параметр ключа задаёт время ожидания ввода в секундах +-q Выполнить скрипт в quiet-режиме. +-r Обрабатывать папки и файлы рекурсивно. +-s Выполнить скрипт в silent-режиме. команды read предотвращает отображение на экране данных, вводимых с клавиатуры +-v Выполнить многословный вывод. +-x Исключить объект. +-y Ответить «yes» на все вопросы. + +$REPLY - специальная переменная среды в которую запоминается ввод + +``` + +```sh +# простое чтение данных из файла: + +#!/bin/bash +count=1 +cat myfile | while read line do +echo "Line $count: $line" count=$(( $count + 1 )) +done +echo "Finished" +``` diff --git a/LINUX/BASH_script/BASH. Часть 1.md b/LINUX/BASH_script/BASH. Часть 1.md new file mode 100644 index 0000000..abc45f8 --- /dev/null +++ b/LINUX/BASH_script/BASH. Часть 1.md @@ -0,0 +1,221 @@ +## Введение + +BASH — Bourne-Again SHell (что может переводится как «перерожденный шел», или «Снова шел Борна(создатель sh)»), самый популярный командный интерпретатор в юниксоподобных системах, в особенности в GNU/Linux. Ниже приведу ряд встроенных команд, которые мы будем использовать для создания своих скриптов. + + +>`break` выход из цикла for, while или until +> `continue` выполнение следующей итерации цикла for, while или until +> `echo` вывод аргументов, разделенных пробелами, на стандартное устройство вывода +> `exit` выход из оболочки +> `export` отмечает аргументы как переменные для передачи в дочерние процессы в среде +> `hash` запоминает полные имена путей команд, указанных в качестве аргументов, чтобы не искать их при следующем обращении +> `kill` посылает сигнал завершения процессу +> `pwd` выводит текущий рабочий каталог +> `read` читает строку из ввода оболочки и использует ее для присвоения значений указанным переменным.\ +> `return` заставляет функцию оболочки выйти с указанным значением +> `shift` перемещает позиционные параметры налево +> `test` вычисляет условное выражение +> `times` выводит имя пользователя и системное время, использованное оболочкой и ее потомками +> `trap` указывает команды, которые должны выполняться при получении оболочкой сигнала +> `unset` вызывает уничтожение переменных оболочки +> `wait` ждет выхода из дочернего процесса и сообщает выходное состояние. + + +И конечно же кроме встроенных команд мы будем использовать целую кучу внешних, отдельных команд-программ, с которыми мы познакомимся уже в процессе + +## Что необходимо знать с самого начала + +1. Любой bash-скрипт должен начинаться со строки: + +> `#!/bin/bash` + +*в этой строке после #! указывается путь к bash-интерпретатору, поэтому если он у вас установлен в другом месте(где, вы можете узнать набрав `whereis bash`) поменяйте её на ваш путь.* + +2. Коментарии начинаются с символа # (кроме первой строки). +3. В bash переменные не имеют типа(о них речь пойдет ниже) + +## Переменные и параметры скрипта + +Приведу как пример небольшой пример, который мы разберем: + +> `#!/bin/bash` #указываем где у нас хранится bash-интерпретатор +> \#присваиваем переменной parametr1 значение первого параметра скрипта +> `parametr1=$1` +> \#присваиваем переменной script_name значение имени скрипта +> `script_name=$0` +> \# команда echo выводит определенную строку, обращение к переменным осуществляется через $имя_переменной. +> `echo "Вы запустили скрипт с именем $script_name и параметром $parametr1"` +> \# здесь мы видим другие кавычки, разница в том, что в одинарных кавычках не происходит подстановки переменных. +> `echo 'Вы запустили скрипт с именем $script_name и параметром $parametr1'` +> \#Выход с кодом 0 (удачное завершение работы скрипта) +> `exit 0` + + +Результат выполнения скрипта: + +> `ite@ite-desktop:~$ ./test.sh qwerty` +> Вы запустили скрипт с именем ./test.sh и параметром qwerty +> Вы запустили скрипт с именем $script_name и параметром $parametr1 + + +После того как мы познакомились как использовать переменные и передавать скрипту параметры, время познакомиться с зарезервированными переменными: + +> `$DIRSTACK` - содержимое вершины стека каталогов +> `$EDITOR` - текстовый редактор по умолчанию +> `$EUID` - Эффективный UID. Если вы использовали программу su для выполнения команд от другого пользователя, то эта переменная содержит UID этого пользователя, в то время как... +> `$UID` - ...содержит реальный идентификатор, который устанавливается только при логине. +> `$FUNCNAME` - имя текущей функции в скрипте. +> `$GROUPS` - массив групп к которым принадлежит текущий пользователь +> `$HOME` - домашний каталог пользователя +> `$HOSTNAME` - ваш hostname +> `$HOSTTYPE` - архитектура машины. +> `$LC_CTYPE` - внутренняя переменная, котороя определяет кодировку символов +> `$OLDPWD` - прежний рабочий каталог +> `$OSTYPE` - тип ОС +> `$PATH` - путь поиска программ +> `$PPID` - идентификатор родительского процесса +> `$SECONDS` - время работы скрипта(в сек.) +> `$#` - общее количество параметров переданных скрипту +> `$*` - все аргументы переданыне скрипту(выводятся в строку) +> `$@` - тоже самое, что и предыдущий, но параметры выводятся в столбик +> `$!` - PID последнего запущенного в фоне процесса +> `$$` - PID самого скрипта + +> IFS=$'\n' - переназначение переменной среды на 'только перевод строки' +> IFS=: - переназначение переменной среды на ' : ' + +## Условия + +Условные операторы, думаю, знакомы практически каждому, кто хоть раз пытался на чем-то писать программы. В bash условия пишутся след. образом (как обычно на примере): + + +> `#!/bin/bash` +> \#в переменную source засовываем первый параметр скрипта +> `source=$1` +> \#в переменную dest засовываем второй параметр скрипта +> `dest=$2` +> +> \# в ковычках указываем имена переменных для сравнения. -eq - логическое сравнение обозначающие "равны" +> `if [[ "$source" -eq "$dest" ]]` +> \# если они действительно равны, то +> `then` +> \#выводим сообщение об ошибке, т.к. $source и $dest у нас равны +> `echo "Применик $dest и источник $source один и тот же файл!"` +> \# выходим с ошибкой (1 - код ошибки) +> `exit 1` +> \# если же они не равны +> `else` +> \# то выполняем команду cp: копируем источник в приемник +> `cp $source $dest` +> `echo "Удачное копирование!"` +> `fi` #обозначаем окончание условия. + + +Результат выполнения скрипта: + + +> `ite@ite-desktop:~$ ./primer2.sh 1 1` +> Применик 1 и источник 1 один и тот же файл! + +> `ite@ite-desktop:~$ ./primer2.sh 1 2` +> Удачное копирование! + +Структура `if-then-else` используется следующим образом: + +> `if` <команда или набор команд возвращающих код возврата(0 или 1)> +> `then` +> <если выражение после if истино, то выполняется этот блок> +> `else` +> <если выражение после if ложно, тот этот> + + +В качестве команд возвращающих код возврата могут выступать структуры `[[ , [ , test, (( ))` или любая другая(или несколько) linux-команда. + + +> `test` - используется для логического сравнения. после выражения, неоьбходима закрывающая скобка "]" +>`[` - синоним команды test +>`[[` - расширенная версия "[" (начиная с версии 2.02)(как в примере), внутри которой могут быть использованы || (или), & (и). Долна иметь закрывающуб скобку "]]" +>`(( ))` - математическое сравнение. + + +для построения многоярусных условий вида: + +> `if ...` +>`then ....` +>`else` +>`if ....` +>`then....` +>`else ....` + +для краткости и читаемости кода, можно использовать структуру: + +> `if ..` +> `then ...` +> `elif ...` +> `then ...` +> `elif ...` + + +## Условия. Множественный выбор + +Если необходимо сравнивать какоую-то одну переменную с большим количеством параметров, то целесообразней использовать оператор case. + +> `#!/bin/bash` +> `echo "Выберите редатор для запуска:"` +> `echo "1 Запуск программы nano"` +> `echo "2 Запуск программы vi"` +> `echo "3 Запуск программы emacs"` +> `echo "4 Выход"` +\#здесь мы читаем в переменную $doing со стандартного ввода +> `read doing` +> +> `case $doing in` +> `1)` +> `/usr/bin/nano` # если $doing содержит 1, то запустить nano +> `;;` +> `2)` +> `/usr/bin/vi` # если $doing содержит 2, то запустить vi +> `;;` +> `3)` +> `/usr/bin/emacs` # если $doing содержит 3, то запустить emacs +> `;;` +> `4)` +> `exit 0` +> `;;` +> `*)` #если введено с клавиатуры то, что в case не описывается, выполнять следующее: +> `echo "Введено неправильное действие"` +> +> `esac` #окончание оператора case. + +Результат работы: + +> `ite@ite-desktop:~$ ./menu2.sh` +> Выберите редатор для запуска: +> 1 Запуск программы nano +> 2 Запуск программы vi +> 3 Запуск программы emacs +> 4 Выход + +После выбор цифры и нажатия Enter запуститься тот редактор, который вы выбрали(если конечно все пути указаны правильно, и у вас установлены эти редакторы :) ) + + +Прведу список логических операторв, которые используются для конструкции if-then-else-fi: + +> `-z` # строка пуста +> `-n` # строка не пуста +> `=, (==)` # строки равны +> `!=` # строки неравны +> +> `-eq` # равно +> `-ne` # неравно +> `-lt,(< )` # меньше +> `-le,(<=)` # меньше или равно +> `-gt,(>)` # больше +> `-ge,(>=)` # больше или равно +> `!` # отрицание логического выражения +> +> `-a,(&&)` # логическое «И» +> `-o,(||)` # логическое «ИЛИ» + + +С основами языка и условиями мы разобрались, чтобы не перегружать статью, разобью её на несколько частей(допустим на 3). Во второй части разберем операторы цикла и выполнение математических операций. diff --git a/LINUX/BASH_script/BASH. Часть 2.md b/LINUX/BASH_script/BASH. Часть 2.md new file mode 100644 index 0000000..dea8c45 --- /dev/null +++ b/LINUX/BASH_script/BASH. Часть 2.md @@ -0,0 +1,223 @@ +## Циклы. Цикл for-in. + +Оператор `for-in` предназначен для поочередного обращения к значениям перечисленным в списке. Каждое значение поочередно в списке присваивается переменной. +Синтаксис следующий: + +``` +for переменная in список_значений +do +команды +done +``` + +Рассмотрим небольшой пример: + +``` +#!/bin/bash +for i in 0 1 2 3 4 #переменной $i будем поочередно присваивать значения от 0 до 4 включительно +do +echo "Console number is $i" >> /dev/pts/$i #Пишем в файл /dev/pts/$i(файл виртуального терминала) строку "Console number is $i" +done #цикл окончен +exit 0 +``` + +После выполнения примера в первых 5 виртуальных консолях(терминалах) появится строка с её номером. В переменную `$i` поочередно подставляются значения из списка и в цикле идет работа со значением этой переменной + +## Циклы. Цикл while. + +Цикл `while` сложнее цикла `for-in` и используется для повторения команд, пока какое-то выражение истинно( код возврата = 0). +Синтаксис оператора следующий: + +``` +while выражение или команда возвращающая код возврата +do +команды +done +``` + +Пример работы цикла рассмотрим на следующем примере: + +``` +#!/bin/bash +again=yes #присваиваем значение "yes" переменной again +while [ "$again" = "yes" ] #Будем выполнять цикл, пока $again будет равно "yes" +do +echo "Please enter a name:" +read name +echo "The name you entered is $name" + +echo "Do you wish to continue?" +read again +done +echo "Bye-Bye" +``` + + +А теперь результат работы скрипта: +``` +ite@ite-desktop:~$ ./bash2_primer1.sh +Please enter a name: +ite +The name you entered is ite +Do you wish to continue? +yes +Please enter a name: +mihail +The name you entered is mihail +Do you wish to continue? +no +Bye-Bye +``` + +Как видим цикл выполняется до тех пор, пока мы не введем что-то отличное от «yes». Между `do` и `done` можно описывать любые структуры, операторы и т.п., все они будут выполнятся в цикле.Но следует быть осторожным с этим циклом, если вы запустите на выполнение в нём какую-либо команду, без изменения переменной выражения, вы можете попасть в бесконечный цикл. + +Теперь об условии истинности. После `while`, как и в условном операторе `if-then-else` можно вставлять любое выражение или команду, которая возвращает код возврата, и цикл будет исполнятся до тех пор, пока код возврата = 0! Оператор `[` аналог команды `test`, которая проверяет истинность условия, которое ей передали. + +Рассмотрим еще один пример, я взял его из книги Advanced Bash Scripting. Уж очень он мне понравился :), но я его немного упростил. В этом примере мы познакомимся с еще одним типом циклов **UNTIL-DO**. Эта практически полный аналог цикла **WHILE-DO**, только выполняется пока какое-то выражение ложно. +Вот пример: +``` +#!/bin/bash +echo "Введите числитель: " +read dividend +echo "Введите знаменатель: " +read divisor + +dnd=$dividend #мы будем изменять переменные dividend и divisor, +#сохраним их знания в других переменных, т.к. они нам +#понадобятся +dvs=$divisor +remainder=1 + +until [ "$remainder" -eq 0 ] +do +let "remainder = dividend % divisor" +dividend=$divisor +divisor=$remainder +done + +echo "НОД чисел $dnd и $dvs = $dividend" +``` + +Результат выполнения скрипта: +``` +ite@ite-desktop:~$ ./bash2_primer3.sh +Введите числитель: +100 +Введите знаменатель: +90 +НОД чисел 100 и 90 = 10 +``` + +## Математические операции + +Команда `let`. +Команда `let` производит арифметические операции над числами и переменными. +Рассмотрим небольшой пример, в котором мы производим некоторые вычисления над введенными числами: + +``` +#!/bin/bash +echo "Введите a: " +read a +echo "Введите b: " +read b + +let "c = a + b" #сложение +echo "a+b= $c" +let "c = a / b" #деление +echo "a/b= $c" +let "c <<= 2" #сдвигает c на 2 разряда влево +echo "c после сдвига на 2 разряда: $c" +let "c = a % b" # находит остаток от деления a на b +echo "$a / $b. остаток: $c " +``` + +Результат выполнения: +``` +ite@ite-desktop:~$ ./bash2_primer2.sh +Введите a: +123 +Введите b: +12 +a+b= 135 +a/b= 10 +c после сдвига на 2 разряда: 40 +123 / 12. остаток: 3 +``` + + +Ну вот, как видите ничего сложного, список математических операций стандартный: + +> `+` — сложение +> `—` — вычитание +> `*` — умножение +> `/` — деление +> `**` — возведение в степень +> `%` — модуль(деление по модулю), остаток от деления +> `let` позволяет использовать сокращения арифметических команд, тем самым сокращая кол-во используемых переменных. Например: `a = a+b` эквивалентно `a +=b` и т.д + +## Работа с внешними программами при написании shell-скриптов + +Для начала немного полезной теории. +### Перенаправление потоков. + +В **bash** (как и многих других оболочках) есть встроенные файловые дескрипторы: `0 (stdin)`, `1 (stdout)`, `2 (stderr)`. +`stdout` — Стандартный вывод. Сюда попадает все что выводят программы +`stdin` — Стандартный ввод. Это все что набирает юзер в консоли +`stderr` — Стандартный вывод ошибок. +Для операций с этими дескрипторами, существуют специальные символы: `>` (перенаправление вывода), `<` (перенаправление ввода). Оперировать ими не сложно. Например: + +``` +cat /dev/random > /dev/null #перенаправить вывод команды cat /dev/random в /dev/null (абсолютно бесполезная операция :)) ) +``` +или + +``` +ls -la > listing #записать в файл listing содержание текущего каталога (уже полезней) +``` + +Если есть необходимость дописывать в файл(при использовании "`>`" он заменятеся), необходимо вместо "`>`" использовать "`>>`" +``` +sudo < my_password +``` +после просьбы sudo ввести пароль, он возьмется из файла my_password, как будто вы его ввели с клавиатуры. +Если необходимо записать в файл только ошибки, которые могли возникнуть при работе программы, то можно использовать: +``` +./program_with_error 2> error_file +``` +цифра 2 перед "`>`" означает что нужно перенаправлять все что попадет в дескриптор 2(stderr). +Если необходимо заставить `stderr` писать в `stdout`, то это можно след. образом: +``` +./program_with_error 2>&1 +``` +символ "`&`" означает указатель на дескриптор `1(stdout)` +(Поумолчанию `stderr` пишет на ту консоль, в котрой работает пользователь(вренее пишет на дисплей)). + +### 2. Конвееры. + +Конвеер — очень мощный инструмент для работы с консолью Bash. Синтаксис простой: +`команда1 | команда 2` — означает, что вывод команды 1 передастся на ввод команде 2 +Конвееры можно группировать в цепочки и выводить с помощью перенаправления в файл, например: +``` +ls -la | grep «hash» |sort > sortilg_list +``` +вывод команды `ls -la` передается команде `grep`, которая отбирает все строки, в которых встретится слово *hash*, и передает команде сортировке `sort`, которая пишет результат в файл *sorting_list*. Все довольно понятно и просто. + +Чаще всего скрипты на Bash используются в качестве автоматизации каких-то рутинных операций в консоли, отсюда иногда возникает необходимость в обработке `stdout` одной команды и передача на `stdin` другой команде, при этом результат выполнения одной команды должен быть неким образом обработан. В этом разделе я постораюсь объяснить основные принципы работы с внешними командами внутри скрипта. Думаю что примеров я привел достаточно и можно теперь писать только основные моменты. + +### 1. Передача вывода в переменную. + +Для того чтобы записать в переменную вывод какой-либо команды, достаточно заключить команду в ` `` ` ковычки, например +> a = \` echo "qwerty" \` +> echo $a + +Результат работы: qwerty + +Однако если вы захотите записать в переменную список директорий, то необходимо, должным образом обработать результат для помещения данных в переменную. Рассмотрим небольшой, пример: +``` +LIST=`find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | tr '\n' ' '` +for ONE_OF_LIST in $LIST +do +svnadmin hotcopy /svn/$ONE_OF_LIST /svn/temp4backup/$ONE_OF_LIST +done +``` +Здесь мы используем цикл `for-do-done` для архивирование всех директорий в папке */svn/* с помощью команды `svnadmin hotcopy`(что в нашем случае не имеет никого значения, просто как пример). Наибольшй интерес вызывает строка: LIST=`find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | tr '\n' ' '` В ней переменной LIST присваивается выполнение команды find, обработанной командами awk, sort, uniq,tr(все эти команды мы рассматривать не будем, ибо это отдельная статья). В переменной LIST будут имена всех каталогов в папке /svn/ пгомещенных в одну строку(для того чтобы её стравить циклу.
\ No newline at end of file diff --git a/LINUX/BASH_script/Обновление списка пакетов и установка обновлений.md b/LINUX/BASH_script/Обновление списка пакетов и установка обновлений.md new file mode 100644 index 0000000..bfa1302 --- /dev/null +++ b/LINUX/BASH_script/Обновление списка пакетов и установка обновлений.md @@ -0,0 +1,24 @@ +```sh +#!/bin/bash + +# Обновление списка пакетов и установка обновлений +echo "----------------------------" +echo "Обновление системы." +echo "----------------------------" +sudo apt-get update +sudo apt-get upgrade -y + +# Автоматическое удаление ненужных пакетов и очистка кэша +echo "----------------------------" +echo "Очистка системы." +echo "----------------------------" +sudo apt-get autoremove -y +sudo apt-get autoclean -y +sudo apt-get clean + +# Вывод сообщения о завершении +echo "----------------------------" +echo "Система обновлена и очищена." +echo "----------------------------" +``` + diff --git a/LINUX/BASH_script/Скрипты для админов.md b/LINUX/BASH_script/Скрипты для админов.md new file mode 100644 index 0000000..ec58a6a --- /dev/null +++ b/LINUX/BASH_script/Скрипты для админов.md @@ -0,0 +1 @@ +![[СкриптыДляАдмина.jpg]]
\ No newline at end of file diff --git a/LINUX/BAT.md b/LINUX/BAT.md new file mode 100644 index 0000000..99adb16 --- /dev/null +++ b/LINUX/BAT.md @@ -0,0 +1,21 @@ +```sh +brew install bat + +sudo apt install bat + +#-------------------- + +bat <fileName> +bat -p <fileName> # открывает без строк нумерации +bat -H 14:17 <fileName> # подсветка строк 14:17 + +# bat проинтегрирован с GIT: +bat -d <fileName> # показать только изменения (diff) + +bat --list-themes # темы bat + +bat --themes dark <fileName> # установить(открыть файл) с темой + + +``` + diff --git a/LINUX/BEGIN.md b/LINUX/BEGIN.md new file mode 100644 index 0000000..d5b7615 --- /dev/null +++ b/LINUX/BEGIN.md @@ -0,0 +1,218 @@ +/boot - 500Mb - ext2 +/ 20Gb - ext4 +swap = озу +/home - остальное + +```sh +# Изменение текстового редактора по умолчанию: +sudo update-alternatives --config editor +``` + +```bash +sudo apt update && sudo apt upgrade -y +sudo apt install mc ranger nload tmux wireguard samba gpg pass smartmontools lm-sensors + +# Диски: +mkdir 1Tb-a 1Tb-temp 2Tb-a 2Tb-b +sudo blkid +lsblk +# редактируем /etc/fstab, монтируем диски и RAID по UUID: +sudo mount -a +sudo systemctl daemon-reload + +# Samba: + + + + + + +sudo apt-get install -f +# Эта команда подтянет нужные пакеты и переустановит проблемный. + + +# Какая версия установлена: +lsb_release -a +``` + +## КОНСОЛЬ клавиши## +```sh +Ctrl-a - перейти в начало +Ctrl-e - перейти в конец + +Ctrl-w - вырезать слово перед курсором +Ctrl-k - вырезать от курсора и до конца строки +Ctrl-u - вырезать от курсора и назад до начала строки +Ctrl-y - вставить вырезанное +Ctrl- <- (Alt- ) +Ctrl- -> (Alt- ) + +Ctrl-u - очищает слева от курсора +Ctrl-k - очищает справа от курсора +Ctrl-r - поиск по истории команд + +history - список всех введенных команд + +reset - исправить вывод + +mkdir 123/name/345 -p - создать папку с полным путем + +cat /var/log/syslog | grep ssh +``` +## Файловая система SWAP ( файл подкачки ) ## +```sh +# Создание swap-пространство на разделе hda3 +mkswap /dev/hda3 + +# Включить swap-пространство, расположенное на разделе hda3 +swapon /dev/hda3 + +# Активировать swap-пространства, расположенные на разделах hda2 и hdb3 +swapon /dev/hda2 /dev/hdb3 +``` +## TERMINAL ## +```bash +# добавить сторонний репозиторий: +sudo add-apt-repository ppa:name_rep/ppa + +# Обновление информации о пакетах и обновление самих пакетов +sudo apt update && sudo apt upgrade -y + +# Установка пакетов +sudo apt install -y htop curl wget build-essential git openssh-server +sudo apt install nload git tmux vim mc wireguard gpg pass + +# Редактор по умолчанию: +update-alternatives --config editor + +# Печатаем сразу в файл +echo «Hello World !» > /tmp/file.txt + + +cat /etc/*-release +whoami +whatis +whereis + +# - выводит файл в консоль +cat (-n) + +# - выводит текст задом на перед +tac + +# - поиск в тексте +grep <какой> file.txt + +# найти и вывести все что начинается с '#': +sudo grep -v ^#/etc/ssh/sshd_config + +# + убрать все пустые строки в выводе: +sudo grep -v ^#/etc/ssh/sshd_config | grep -v ^$ + +# + посчитать кол-во строк в выводе: +sudo grep -v ^#/etc/ssh/sshd_config | grep -v ^$ | wc -l + +# посчитать общее кол-во строк в файле: +sudo wc -l /etc/ssh/sshd_config + +# - если текст длинный и перемещаемся кнопками вверх и вниз если нажать "/" то откроется строка для поиска в этом файле "n" - перемещаемся в поиске вперед и "ctrl+n" - назад "q" - выход +less + +# - последние или первые 10 строк файла `"-f"` онлайн просмотр +tail / head + +# - создание каталога с путем +mkdir -p + +# - переместить/переименовать +mv + +# - просмотр сразу 2 каталогов +ls dir1 dir2 + +# - копирование `-r` - копируем каталог +cp + +# - удаление каталога +rm -r +``` +## GROUP ## +```sh +groupadd # — не выводит ничего при создании группы; лучше при использовании в скрипте, так как нет лишнего вывода на терминал + +addgroup # — выводит создаваемый <gid> при создании группы. +--gid 5555 # - можно задать при создании + +groupdel +delgroup + +# Чтобы пользователю добавить дополнительную группу можно воспользоваться командой +usermod -aG +-a #— добавляет к текущим группам пользователя дополнительные. Без этой опции группы заменяются на новые, а с этой опцией к старым группам добавляются новые; +-G #— указывает, что работать будем с дополнительными группами а не с первичной. +#(`usermod -aG testgroup alex`) +# Утилитой <usermod> нельзя удалять дополнительные группы, можно только не используя опцию **-a**, написать список групп в которые должен будет входить данный пользователь, не указав ту группу, из которой вы хотите удалить пользователя. + +# Если мы хотим поменять пользователю первичную группу, то нужно использовать опцию -g: +usermod -g testgroup alex + +id alex +# uid=1000(alex) gid=1002(testgroup) группы=1002(testgroup),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev)` + +# И вернем все как было: +usermod -g alex alex + +# Еще один способ добавить пользователя в дополнительную группу (и наверное более запоминающийся) это использовать утилиту adduser, при этом утилите вначале передается имя пользователя, затем группы: +adduser alex testgroup + +# Удалить пользователя из группы можно командой deluser: +deluser alex testgroup + +# И так, для добавления или удаления пользователя в дополнительную группу я бы рекомендовал использовать команды adduser и deluser. А для изменения первичной группы команду usermod -g. +``` +## PASSWD ## +```sh +passwd - изменить пароль текущему пользователю + +/etc/passwd +/etc/group +/etc/shadow + +# В passwd хранится информация о пользователях в системе, вот информация о пользователе alex: +grep alex /etc/passwd + +alex:x:1000:1000:Alexandr,,,:/home/alex:/bin/bash + +# То что после имени идет буква «x» означает что пароль зашифрован в файле shadow. + +# В /etc/group хранится информация о группах в системе, вот информация о группе alex и о дополнительных группах пользователя alex: +grep alex /etc/group + +cdrom:x:24:alex +floppy:x:25:alex +audio:x:29:alex +dip:x:30:alex +video:x:44:alex +plugdev:x:46:alex +netdev:x:109:alex +alex:x:1000: + +# Здесь вначале идет имя группы, потом символ «x» обозначает что пароль зашифрован, затем gid, затем пользователи Linux для которых эта группа является дополнительной. Таким образом группа alex имеет gid=1000, а пользователь alex входит в дополнительные группы cdrom, floppy и так далее. + +# В /etc/shadow хранятся соленые хэши паролей пользователей, вот например строка для alex: +grep alex /etc/shadow + +alex:$y$j9T$STsC82fbWIlhONPF0rXcm1$a.c./bIw7BUBiewrYoKnEBGBAH3Z5UGsWCFQsW3HOR8:19006:0:99999:7::: + +# Строка файла содержит поля разделённые двоеточиями, в полях находится следующая информация: + +- имя пользователя; +- хеш пароля; +- дата последней смены пароля в днях начиная с 1 января 1970 года, чем больше число, тем новее пароль; +- минимальный срок действия пароля, если 0 то неограничен; +- максимальный срок действия пароля; +- период предупреждения о необходимости сменить пароль; +- период неактивности пароля — у меня пустое поле, период неактивности отсутствует; +- дата истечения срока действия учётной записи — у меня пустое поле, учётная запись никогда не устаревает; +- зарезервированное поле для использования в будущем. +``` diff --git a/LINUX/BorgBackup.md b/LINUX/BorgBackup.md new file mode 100644 index 0000000..d737616 --- /dev/null +++ b/LINUX/BorgBackup.md @@ -0,0 +1,32 @@ +**BorgBackup: безопасные и эффективные бэкапы в Linux** + +**BorgBackup** — это инструмент для создания зашифрованных, сжатых и дедуплицированных резервных копий. Он идеально подходит как для серверов, так и для персональных машин. В этом гайде разберёмся, как его установить, настроить и использовать. + +1️⃣ **Установка BorgBackup** +В большинстве дистрибутивов Borg доступен в официальных репозиториях. +`sudo apt install borgbackup + +2️⃣ **Инициализация репозитория** +Перед созданием бэкапов необходимо инициализировать репозиторий. Это место, где будут храниться резервные копии. +`borg init --encryption=repokey /mnt/backup + +🟢 --encryption=repokey включает шифрование (ключ хранится в репозитории). +🟢 Можно использовать --encryption=none, если защита не нужна. + +3️⃣ **Создание бэкапа** +Допустим, нам нужно сделать резервную копию директорий Documents и Pictures: +`borg create --progress /mnt/backup::backup-$(date +%F) ~/Documents ~/Pictures + +🟢 /mnt/backup — путь к репозиторию. +🟢 backup-$(date +%F) — имя бэкапа с датой. +🟢 ~/Documents ~/Pictures — папки, которые сохраняем. +🟢 --progress — показывает прогресс выполнения. + +4️⃣ **Проверка и восстановление данных** +Посмотреть список всех бэкапов: +`borg list /mnt/backup + +Извлечь конкретный бэкап (например, backup-2025-01-28): +`borg extract /mnt/backup::backup-2025-01-28 --progress + +🔜 Если хотите автоматизировать создание бэкапов, можно добавить cron-задачу.
\ No newline at end of file diff --git a/LINUX/CAL.md b/LINUX/CAL.md new file mode 100644 index 0000000..0d2995f --- /dev/null +++ b/LINUX/CAL.md @@ -0,0 +1,9 @@ +```sh +sudo apt install ncal +cal # отображает календарь в терминале. + +cal # показывает текущий месяц, +cal 2025 # показывает календарь на 2025 год. + +# Идеально подходит для быстрого поиска дат! +``` diff --git a/LINUX/CERTBOT.md b/LINUX/CERTBOT.md new file mode 100644 index 0000000..c8b1b45 --- /dev/null +++ b/LINUX/CERTBOT.md @@ -0,0 +1,123 @@ +# [Создание сертификата Let’s Encrypt для Nginx в Ubuntu 20.04](https://www.8host.com/blog/sozdanie-sertifikata-lets-encrypt-dlya-nginx-v-ubuntu-20-04/ "Создание сертификата Let’s Encrypt для Nginx в Ubuntu 20.04") + +4 августа, 2020 12:20 + +Let’s Encrypt – это центр сертификации (ЦС, или Certificate Authority, CA), который предоставляет простой способ создания бесплатных сертификатов TLS/SSL, тем самым позволяя шифровать HTTPS на веб-серверах. Этот ЦС упрощает процесс создания сертификатов путём автоматизации большинства необходимых действий при помощи клиентского ПО. Процесс получения и установки сертификата полностью автоматизирован на веб-серверах Apache и Nginx. + +Данный мануал расскажет, как получить SSL-сертификат для Nginx в системе Ubuntu 20.04 с помощью Certbot и как обновлять этот сертификат. + +В мануале используется отдельный виртуальный хост Nginx, а не хост по умолчанию. Рекомендуем вам создать такой хост для каждого домена, чтобы избежать общих ошибок и иметь возможность использовать стандартные конфигурации в случае сбоя. + +### Требования + +- Сервер Ubuntu 20.04, настроенный по [этому мануалу](https://www.8host.com/blog/nachalnaya-nastrojka-servera-ubuntu-20-04/). +- Доменное имя, которое можно использовать в сертификате (в руководстве используется условный домен example.com). +- Записи А, связывающие домены example.com и www.example.com с внешним IP-адресом сервера. Они необходимы для того, чтобы Let’s Encrypt мог проверить доменное имя +- Установленный сервер Nginx и блок server для вашего домена (согласно [этому мануалу](https://www.8host.com/blog/ustanovka-nginx-v-ubuntu-20-04/)). Здесь в качестве примера используется хост /etc/nginx/sites-available/example.com. + +## 1: Установка Certbot + +Для начала нужно установить клиент Certbot на ваш сервер. + +Чтобы установить пакет certbot и плагин для Nginx с помощью стандартного пакетного менеджера, введите команду: + +`sudo apt install certbot python3-certbot-nginx` + +Теперь Certbot готов к работе, но для того, чтобы настроить SSL для Nginx, нужно проверить некоторые конфигурации. + +## 2: Проверка параметров Nginx + +Certbot должен иметь возможность найти целевой виртуальный хост (блок server) в конфигурации Nginx, чтобы автоматически настроить сертификат SSL. Для этого Certbot ищет директиву server_name – в ней указан домен, для которого предназначен запрашиваемый сертификат. + +Если вы следовали разделу по установке виртуального хоста в [мануале по установке Nginx](https://www.8host.com/blog/ustanovka-nginx-v-ubuntu-20-04/), у вас должен быть виртуальный хост для домена в ==`/etc/nginx/sites-available/example.com`==, а директива server_name в нем уже настроена соответствующим образом. + +Чтобы проверить это, откройте файл виртуального хоста, используя nano или другой текстовый редактор: + +`sudo nano /etc/nginx/sites-available/example.com` + +Найдите строку server_name: + +`... server_name example.com www.example.com; ...` + +Если она выглядит так, можете закрыть редактор. Если нет, вам нужно обновить ее. Укажите в ней свой домен, а затем сохраните и закройте файл. После этого нужно проверить синтаксис конфигурации. + +`sudo nginx -t` + +Если команда сообщила об ошибке, вам нужно снова открыть файл и исправить ее. После этого повторите проверку. + +Если ошибок в конфигурации нет, перезапустите веб-сервер. + +`sudo systemctl reload nginx` + +Теперь Certbot сможет найти правильный блок server и автоматически обновить его. + +## 3: Настройка брандмауэра + +Если во время настройки сервера вы включили брандмауэр ufw, вам нужно настроить его для поддержки соединений HTTPS. + +Во время установки Nginx регистрирует в ufw несколько профилей. Просмотреть текущие настройки можно с помощью команды: + +`sudo ufw status` + +Скорее всего, брандмауэр пока что поддерживает только трафик HTTP: + +`Status: active +To Action From +-- ------ ---- +OpenSSH ALLOW Anywhere +Nginx HTTP ALLOW Anywhere +OpenSSH (v6) ALLOW Anywhere (v6) +Nginx HTTP (v6) ALLOW Anywhere (v6)` + +Чтобы разблокировать трафик HTTPS, активируйте профиль Nginx Full и удалите профиль Nginx. + +`sudo ufw allow 'Nginx Full' sudo ufw delete allow 'Nginx HTTP'` + +Теперь параметры брандмауэра будут выглядеть так: + +`sudo ufw status +Status: active +To Action From +-- ------ ---- +OpenSSH ALLOW Anywhere +Nginx Full ALLOW Anywhere +OpenSSH (v6) ALLOW Anywhere (v6) +Nginx Full (v6) ALLOW Anywhere (v6)` + +Теперь пора запустить Certbot и получить сертификаты. + +## 4: Получение SSL-сертификата + +Certbot предлагает различные способы получения сертификатов SSL через плагины. Плагин Nginx позаботится о реконфигурации веб-сервера и при необходимости перезагрузит его. Чтобы использовать этот плагин, введите: + +`sudo certbot --nginx -d example.com -d www.example.com` + +Эта команда запустит certbot с плагином — nginx, а флаг –d определит имена, для которых предназначен сертификат. + +Если вы запускаете certbot впервые, вам будет предложено ввести адрес электронной почты и принять условия обслуживания. После этого certbot свяжется с сервером Let’s Encrypt, а затем отправит запрос, чтобы убедиться, что вы контролируете домен, для которого запрашиваете сертификат. + +Если проверка пройдет успешно, certbot спросит, как вы хотите настроить HTTPS: + +`Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: No redirect - Make no further changes to the webserver configuration. 2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate number [1-2] then [enter] (press 'c' to cancel):` + +Сделайте свой выбор, затем нажмите Enter. Конфигурация будет обновлена, и Nginx перезагрузится, чтобы использовать новые настройки. Клиент certbot выведет сообщение о том, что процесс был выполнен успешно и скажет, где хранятся ваши сертификаты: + +`IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/example.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/example.com/privkey.pem Your cert will expire on 2020-08-18. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le` + +Сертификаты будут загружены и установлены. Попробуйте перезагрузить свой сайт, указав https://, и обратите внимание на индикатор безопасности в браузере. Он должен указывать, что сайт надежно защищен (как правило, для этого используется зеленый замочек в адресной строке). Если вы протестируете свой сервер с помощью [SSL Labs Server Test](https://www.ssllabs.com/ssltest/), он получит оценку A. + +Теперь давайте протестируем процесс обновления сертификата. + +## 5: Проверка автоматического обновления сертификата + +Сертификаты Let’s Encrypt действительны только в течение девяноста дней. Потому пользователи должны автоматизировать процесс продления сертификата. Установленный вами пакет certbot позаботится об этом с помощью таймера systemd. Он запускается два раза в день и автоматически обновляет сертификат, срок действия которого истекает через тридцать дней. + +Чтобы проверить статус таймера, введите: + +`sudo systemctl status certbot.timer certbot.timer - Run certbot twice daily Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; vendor preset: enabled) Active: active (waiting) since Mon 2020-05-04 20:04:36 UTC; 2 weeks 1 days ago Trigger: Thu 2020-05-21 05:22:32 UTC; 9h left Triggers: ● certbot.service` + +Чтобы протестировать процесс обновления сертификата, запустите сухой прогон: + +`sudo certbot renew --dry-run` + +Если вы не видите ошибок, все настроено правильно. При необходимости Certbot обновит ваши сертификаты и перезагрузит Nginx, чтобы активировать изменения. Если процесс автоматического обновления не срабатывает, Let’s Encrypt отправит сообщение на указанный вами адрес электронной почты, предупредив вас о завершении срока действия вашего сертификата.
\ No newline at end of file diff --git a/LINUX/CHMOD, CHOWN.md b/LINUX/CHMOD, CHOWN.md new file mode 100644 index 0000000..97a50b8 --- /dev/null +++ b/LINUX/CHMOD, CHOWN.md @@ -0,0 +1,79 @@ +## CHMOD ## +```sh +chmod опции права /путь/к/файлу + +# Сначала рассмотрим какими бывают права доступа linux и как они устанавливаются. Пред этим рекомендую прочитать статью про права, ссылка на которую есть выше. Есть три основных вида прав: + +- r - чтение; +- w - запись; +- x - выполнение; +- s - выполнение от имени суперпользователя (дополнительный); + +# Также есть три категории пользователей, для которых вы можете установить эти права на файл linux: + +- u - владелец файла; +- g - группа файла; +- o - все остальные пользователи; + +# Синтаксис настройки прав такой: +# Группа_пользователейДействиеВид_прав + +"+" - включить или +"-" - отключить. + +- u+x - разрешить выполнение для владельца; +- ugo+x - разрешить выполнение для всех; +- ug+w - разрешить запись для владельца и группы; +- o-x - запретить выполнение для остальных пользователей; +- ugo+rwx - разрешить все для всех; + +# Но права можно записывать не только таким способом. Есть еще восьмеричный формат записи, он более сложен для понимания, но пишется короче и проще. Я не буду рассказывать как считать эти цифры, просто запомните какая цифра за что отвечает, так проще: + +# rwx = 7 | x=1 w=2 r=4 + +- 0 - никаких прав; +- 1 - только выполнение; +- 2 - только запись; +- 3 - выполнение и запись; +- 4 - только чтение; +- 5 - чтение и выполнение; +- 6 - чтение и запись; +- 7 - чтение запись и выполнение. + +# Права на папку linux такие же, как и для файла. Во время установки прав сначала укажите цифру прав для владельца, затем для группы, а потом для остальных. Например, : + +- 744 - разрешить все для владельца, а остальным только чтение; +- 755 - все для владельца, остальным только чтение и выполнение; +- 764 - все для владельца, чтение и запись для группы, и только чтение для остальных; +- 777 - всем разрешено все. + +Каждая из цифр не зависит от предыдущих, вы вбираете именно то, что вам нужно. Теперь давайте рассмотрим несколько опций команды, которые нам понадобятся во время работы: + +-c - выводить информацию обо всех изменениях; +-f - не выводить сообщения об ошибках; +-v - выводить максимум информации; +--preserve-root - не выполнять рекурсивные операции для корня "/"; +--reference - взять маску прав из указанного файла; +-R - включить поддержку рекурсии; +--version - вывести версию утилиты; +``` + +## CHOWN ## +```sh +chown пользователь:группа опции /путь/к/файлу + +# В поле пользователь надо указать пользователя, которому мы хотим передать файл. Также можно указать через двоеточие группу, например, пользователь:группа. Тогда изменится не только пользователь, но и группа. Вот основные опции, которые могут вам понадобиться: + +-c, --changes - подробный вывод всех выполняемых изменений; +-f, --silent, --quiet - минимум информации, скрыть сообщения об ошибках; +--dereference - изменять права для файла к которому ведет символическая ссылка вместо самой ссылки (поведение по умолчанию); +-h, --no-dereference - изменять права символических ссылок и не трогать файлы, к которым они ведут; +--from - изменять пользователя только для тех файлов, владельцем которых является указанный пользователь и группа; +-R, --recursive - рекурсивная обработка всех подкаталогов; +-H - если передана символическая ссылка на директорию - перейти по ней; +-L - переходить по всем символическим ссылкам на директории; +-P - не переходить по символическим ссылкам на директории (по умолчанию). +``` + + +![[2024-09-05 01.32.11.jpg]]
\ No newline at end of file diff --git a/LINUX/CIFS.md b/LINUX/CIFS.md new file mode 100644 index 0000000..163bff7 --- /dev/null +++ b/LINUX/CIFS.md @@ -0,0 +1,189 @@ +```sh +sudo apt install cifs-utils -y + +# Монтирование ресурса выглядит так: +mount.cifs {ресурс} {путь к директории в которую монтируем } + +# Автоматическое монтирование директории происходит с использованием файла fstab. Для начала создадим дополнительный файл с учетными данными пользователя для авторизации. Оправданно будет создать этот файл в директории root. +sudo vim /root/.smbselus + +# вносим в файл: +username = vlapa +password = uspass + +sudo vim /etc/fstab + +# добавляем: +//10.10.10.2/что_монтируем /куда_монтирем cifs user,rw,crdentials=/root/.smbselus 0 0 + +# ======= +cifs user 一 разрешает монтировать любому пользователю; +rw 一 предоставляет права на чтение и запись; +credentials=/root/.smbselus 一 файл с учетными данными пользователя; +_0_ используется утилитой dump 0 一 выключен; +_0_ используется утилитой fsck 0 一 выключен. + +В некоторых случаях требуется добавить директиву domain в файл .smbselus. + +#================ +# Настройка сетевой корзны: +sudo mkdir /var/recycle +sudo chmod 777 /var/recycle + +sudo vim /etc/samba/smb.conf + +[Recycle] + comment = rec_dir + path = /var/recycle + public = yes + browseable = yes + writable = yes + vfs objects = recycle + recycle:repository = .recycle/%U + recycle:keeptree = Yes + recycle:touch = Yes + recycle:version = Yes + recycle:maxsize = *.tmp, ~$* + recycle:exclude_dir = /tmp + +#============== +[Recycle] 一 имя ресурса, отображаемое клиенту при подключении + +comment = rec_dir 一 комментарий + +path = /var/recycle 一 директория, в которой размещена сетевая корзина + +public = yes 一 доступ на чтение всем авторизованным пользователям, также гостевым + +browseable = yes 一 параметр устанавливает, будет ли общий ресурс отображаться в списке доступных общих ресурсов в сетевом окружении и в списке просмотра + +writable = yes 一 право записи в сетевой ресурс + +vfs objects = recycle 一 использование подсистемы recycle + +recycle:repository = .recycle/%U 一 место хранения удаленных объектов. В этой директиве настроено попадание удаленных файлов в каталог с именем пользователя, который создается автоматически в директории .recycle + +recycle:keeptree = Yes 一 удаление с сохранением дерева каталогов + +recycle:touch = Yes 一 изменение даты при перемещении файла в сетевую корзину + +recycle:versions = Yes 一 добавление номера версии при удалении файлов с одинаковыми именами + +recycle:maxsize = 0 一 параметр отвечает за максимальный размер файла, который может быть перемещен в корзину. При текущем значении возможно переместить файл любого размера + +recycle:exclude = *.tmp, ~$* 一 исключение файлов заданного типа + +recycle:exclude_dir = /tmp 一 исключение директории заданного типа + +``` +# Как в Linux монтировать шару CIFS + +  Обновлено: 19.06.2024  Опубликовано: 18.07.2016 + +Работа с общими папками Windows происходит с использованием протокола CIFS (SMB). Все примеры в данном руководстве выполняются на Linux Ubuntu и CentOS. + +### Установка пакетов + +`apt install cifs-utils + +### Сетевые порты + +Если мы будем монтировать сетевую папку, сервер которой находится за брандмауэром, необходимо открыть следующие порты: + +- 137/UDP +- 138/UDP +- 139/TCP +- 445/TCP + +## Синтаксис + +`mount.cifs <папка на сервере> <во что монтируем> <-o опции> + +_* вместо **mount.cifs** можно написать **mount -t cifs**._ + +Пример: + +`mount.cifs //192.168.1.1/public /mnt + +_* простой пример монтирования папки **public** на сервере **192.168.1.1** в локальный каталог **/mnt**._ + +`apt install samba-client +`smbclient -L 192.168.1.1 + + или, при необходимости авторизоваться на файловом сервере:> +`smbclient -L 192.168.1.1 -U username + +## Ручное монтирование + +Теперь монтирование можно выполнить следующей командой: + +`mount.cifs //192.168.1.10/share /mnt -o user=dmosk + +_* в данном примере будет примонтирован каталог **share** на сервере **192.168.1.10** в локальную папку **/mnt** под учетной записью **dmosk**._ + +То же самое, с использованием домена: + +`mount.cifs //192.168.1.10/share /mnt -o user=dmosk,domain=dmosk.local + +## Автоматическое монтирование CIFS через fstab + +Для начала создаем файл, в котором будем хранить данные авторизации при подключении к общей папке: + +vim /root/.smbclient + +И добавляем в него данные следующего вида: + +```sh +username=dmosk +password=dPassw0rd +domain=dmosk.local +``` +_* в этом примере создана пара логин/пароль — **dmosk**/**dPassw0rd**;_ **domain** указывать не обязательно, если аутентификация выполняется без него. + +Задаем права на созданный файл, чтобы доступ был только у пользователя, скажем, root: +`chmod 600 /root/.smbclient +`chown root:root /root/.smbclient + +Теперь открываем конфигурационный файл fstab: +`vim /etc/fstab + +и добавляем в него следующее: +`//192.168.1.10/share /mnt cifs user,rw,credentials=/root/.smbclient 0 0 + +_* в данном примере выполняется монтирование общей папки **share** на сервере с IP-адресом **192.168.1.10** в каталог **/mnt**. Параметры для подключения — **user**: позволяет выполнить монтирование любому пользователю, **rw**: с правом на чтение и запись, **credentials**: файл, который мы создали на предыдущем шаге._ + +Чтобы проверить правильность настроек, вводим следующую команду: +`mount -a + +## Примеры использования опций + +### Версии SMB + +Если на стороне Windows используется старая или слишком новая версия протокола SMB, при попытке монтирования мы можем получить ошибку **mount error(112): Host is down**. Чтобы это исправить, указываем версию: +`mount.cifs //192.168.1.10/share /mnt/ -o vers=1.0 + +_* монтирование по протоколу SMB1.0_ + +### Монтирование от гостевой учетной записи + +Если сервер принимает запросы без логина и пароля, то клиент подключается, как гость: +`mount.cifs //192.168.1.10/share /mnt -o guest + +или в fstab: +`//192.168.1.10/share /mnt cifs guest 0 0 + +Для монтирования шары Windows, иногда, недостаточно указать опцию **guest**. Нужно делать так: +`mount.cifs //192.168.1.10/share /mnt -o user=guest,pass='' + +Или в fstab: +`//192.168.1.10/share /mnt cifs guest,pass 0 0 + +### Права на примонтированные каталоги + +При монтировании папки мы можем указать определенные права: +`mount.cifs //192.168.1.10/share /mnt -o file_mode=0777,dir_mode=0777 + +Для указания владельца, который будет назначен для примонтированного каталога, используем: + `mount.cifs //192.168.1.10/share /mnt -o uid=33,gid=33 + +_* чтобы посмотреть идентификаторы пользователя, вводим **id -u <имя пользователя>** и **id -g <имя группы>**._
\ No newline at end of file diff --git a/LINUX/CRON.md b/LINUX/CRON.md new file mode 100644 index 0000000..ca554dc --- /dev/null +++ b/LINUX/CRON.md @@ -0,0 +1,93 @@ +_www.crontab.guru_ + + + +```sh +systemctl status cron +crontab --help + +crontab -e # настройка для текущего пользователя +crontab -l # настройка для супер пользователя +crontab -r # удалить все задачи +``` + +**_минута_** **_час_** **_день_** **_месяц_** **_день_недели_** **_/путь/к/исполняемому/файлу_** +**обязательно нужно писать полный путь к команде** **_!_** +**Дата и время указываются с помощью цифр или символа ' * '.** +**интервал указывается «** **7-9** **»** +**несколько раз - «** **5,7,12 »** +**раз в 10 мин - « /10 »** + +****@reboot**** **- при загрузке, только один раз;** +****@yearly, @annually**** - раз год; +****@monthly**** - раз в месяц; +****@weekly**** - раз в неделю; +****@daily, @midnight**** - каждый день; +****@hourly**** - каждый час. + +команда запуска скрипта раз в час: + +`@hourly /usr/local/bin/serve` + +Если же вы собрались добавить скрипт в одну из папок, то, как я уже говорил, нужно чтобы его имя было без точек и у него были права на выполнение: + +`sudo vi /etc/corn.daily/basckup` + +`#!/bin/bash ......` + +смотрим лог cron. Иногда он находится в `/var/log/cron`, +а иногда пишется в syslog. + +`grep CRON /var/log/syslog` + +`sudo run-parts /etc/cron.daily/` + +Дальше вы увидите весь вывод, включая вывод скрипта и сможете быстро понять в чем проблема. + + +Можно еще отправлять результат работы скрипта на почту указав команду MAILTO=user@email.ru + +`tail /var/log/syslog` +-получаем "хвост", последние 10 строк лог-файла + +``` +@hourly rsync -avzhe ssh vlapa@178.20.46.157:/home/vlapa/git/ /home/vlapa/1Tb_r1-a/Backup/_git/ + +@hourly rsync -avzh /home/vlapa/300Gb/yaDisk/All-reports /home/vlapa/1Tb_r1-a/Backup/ + +@hourly rsync -avzh /home/vlapa/300Gb/yaDisk/Family /home/vlapa/1Tb_r1-a/Backup/ + +#@hourly rsync -avzh /home/vlapa/300Gb/yaDisk/_MY /home/vlapa/1Tb_r1-a/Backup/ +``` +⚙️ **Синтаксис** + +*`* * * * * команда + +Каждая звезда отвечает за время: + +• минуты +• часы +• дата +• месяц +• день недели + +`30 2 * * * /home/user/backup.sh + +Эта задача будет выполняться каждый день в 2:30. + +👀 **Просмотр и редактирование** + +• Чтобы редактировать cron-задачи, используйте: +`crontab -e + +• Чтобы увидеть текущие: +`crontab -l + +✍️ **Логирование cron-задач** +Важно отслеживать выполнение задач. Для этого можно записывать вывод задач в файл: + +`* * * * * /path/to/script.sh >> /var/log/cron.log 2>&1 + +2>&1 — это перенаправление стандартного потока ошибок (stderr) в тот же файл, что и стандартный вывод. То есть любые ошибки скрипта будут также записываться в файл cron.log + + diff --git a/LINUX/DIFFERENT.md b/LINUX/DIFFERENT.md new file mode 100644 index 0000000..f19a55f --- /dev/null +++ b/LINUX/DIFFERENT.md @@ -0,0 +1,51 @@ +============================ +`dpkg --list` +`sudo apt remove package-name` +`sudo apt purge package-name` +`sudo apt autoremove` + +`sudo apt autoclean` + +============================ +Сделать пользователя СуперПользователем: +`sudo usermod -aG sudo sampleuser` +`sudo gpasswd -a sampleuser sudo` + +проверить: +`groups sampleuser` + +удалить из группы sudo: +`sudo gpasswd -d sampleuser sudo` + +============================ +Сразу выкл: +`sudo power off` ??? +`shutdown -h now` + + +пароль, потом выкл: +`sudo poweroff` + +Запланировать выключение через: +`shutdown -h` (time in minutes) +`Shutdown +30` +`shutdown +30 "you have a meeting right now"`` + +Отменить запланированное выкл: +`shutdown -c` + +Выключение в: +`shutdown –h 20:3` + +Справка: +`poweroff --help` + +============================= +`sudo reboot` +`sudo shutdown -r` +`sudo shutdown -r +mins` +`sudo shutdown -r hh:mm` + +`init 6` + +=============================
\ No newline at end of file diff --git a/LINUX/DISK/DF, DU.md b/LINUX/DISK/DF, DU.md new file mode 100644 index 0000000..a06b473 --- /dev/null +++ b/LINUX/DISK/DF, DU.md @@ -0,0 +1,101 @@ +мгновенно создать пустой файл, не используя touch: +`>text.txt + +================================ + +команда `du` дает вам возможность исключить файлы определенного типа при расчете размера каталога + +`du -h --exclude="*.txt" tutorials + +================================ + +`df -help` +`df -h` - размер и информация о дисках +`df -i` - inode + +. du -help` +`du -h` - размер директории +`du -d1 / | sort -nr - с сортировкой по весу папки + +$ **du** **опции** **/путь/к/папке** + +А вот опции утилиты: + +_**-a, --all**_ - выводить размер для всех файлов, а не только для директорий, по умолчанию размер выводится только для папок; +_**-B, --block-size**_ - указать единицы вывода размера, доступно: K,M,G,T,P,E,Z,Y для 1024 и KB, MB и так далее для 1000; +_**-c, --total**_ - выводить в конце общий размер всех папок; +_**-d, --max-depth**_ - максимальная глубина вложенности директорий; +_**-h, --human-readable**_ - выводить размер в единицах измерения удобных для человека; +_**--inodes**_ - выводить информацию об использованию inode; +_**-L, --dereference**_ - следовать по всем символическим ссылкам; +_**-l, --count-links**_ - учитывать размер файла несколько раз для жестких ссылок; +_**-P, --no-dereference**_ - не следовать по символическим ссылкам, это поведение используется по умолчанию; +_**-S, --separate-dirs**_ - не включать размер подпапок в размер папки; +_**--si**_ - выводить размер файлов и папок в системе си, используется 1000 вместо 1024; +_**-s, --summarize**_ - выводить только общий размер; +_**-t, --threshold**_ - не учитывать файлы и папки с размером меньше указанного; +_**--time**_ - отображать время последней модификации для файла или папки, вместо времени модификации можно выводить такие метки: atime, access, use, ctime; +_**-X, --exclude**_ - исключить файлы из подсчёта; +_**-x, --one-file-system**_ - пропускать примонтированные файловые системы; +_**--version**_ - вывести версию утилиты. + +Здесь перечислены не все опции. Если вам надо больше, смотрите в: + +_**man du**_ + +## Bash скрипт мониторинга свободного места в Linux + +Рассмотрим простой bash скрипт, для мониторинга дискового пространства на сервере и отправке email уведомления администратору, если места недостаточно. + +```sh +#!/bin/bash +CURRENT=$(df /dev/sdd5 | grep / | awk '{ print $5}' | sed 's/%//g) +THRESHOLD=90 +if [ "$CURRENT" -gt "$THRESHOLD" ] ; then +echo "Осталось слишком мало дискового пространства. Используется: $CURRENT%" | mail -s 'Заканчивается дисковое пространство !' -r home@server.ru vlapa@yandex.ru +fi +``` + +При достижении заполняемости директории / до 90% к вам на почту будет отправляться уведомление. Добавьте данный скрипт в [cron](https://winitpro.ru/index.php/2020/04/21/planirovshhik-zadach-cron-v-linux/) и мониторьте вашу систему. Можно изменить скрипт, чтобы он смотрел за размером определенного раздела или каталога. + +=========================== +**du** — позволяет быстро и эффективно проанализировать использование дискового пространства, найти самые «тяжёлые» файлы и папки, чтобы освободить место. + +🟠 **Показать размер папки:** +`du -sh /path/to/folder +➡️ Выводит общий размер папки в удобном формате (МБ/ГБ). + +🟠 **Посмотреть размеры всех вложенных папок:** +`du -h /path/to/folder +➡️ Отлично подходит для поиска «тяжёлых» файлов. + +🟠 **Сортировка по размеру:** +`du -h /path/to/folder | sort -h +➡️ Самые большие файлы окажутся внизу списка. + +=========================== +**Быстрое удаление больших файлов в Linux** + +Когда на диске заканчивается место, полезно быстро находить и удалять самые "тяжелые" файлы. В Linux это можно сделать несколькими способами. + +**🟢 Найти файлы больше 1ГБ:** +find / -type f -size +1G 2>/dev/null +Отобразит файлы больше 1ГБ во всей системе. + +**🟢 Найти и удалить файлы больше 500МБ в указанной папке:** +find /var/log -type f -size +500M -delete +Удаляет файлы в /var/log, превышающие 500МБ. + +**🟢 Найти самые "тяжелые" каталоги:** +du -ah / | sort -rh | head -10 +Выведет 10 самых больших файлов и папок. + +🗣 **Полезно для очистки места на сервере, анализа логов и удаления ненужных файлов.** + +=========================== + +=========================== + +=========================== + +=========================== diff --git a/LINUX/DISK/FDISK.md b/LINUX/DISK/FDISK.md new file mode 100644 index 0000000..f034e53 --- /dev/null +++ b/LINUX/DISK/FDISK.md @@ -0,0 +1,217 @@ +## Разбить диск на разделы с помощью команды fdisk + +Выполните следующие действия, чтобы разбить диск в Linux с помощью команды `fdisk`. +#### Шаг 1. Список существующих разделов + +Выполните следующую команду, чтобы вывести список всех существующих разделов: +`sudo fdisk -l +Вывод содержит информацию о дисках и разделах хранилища: + +[](https://wiki.merionet.ru/images/kak-sozdavat-razdely-diska-v-linux/9.png) +#### Шаг 2: Выберите диск для хранения + +Выберите диск для хранения, на котором вы хотите создать разделы, выполнив следующую команду: +`sudo fdisk /dev/sdb + +Диск `/dev/sdbstorage` открыт: + +[](https://wiki.merionet.ru/images/kak-sozdavat-razdely-diska-v-linux/10.png) +Будет предложено ввести `m` для справки, вводим и смотрим какие есть возможности по работе с диском +Help: + + **GPT** + M enter protective/hybrid MBR + + **Generic** + d delete a partition + F list free unpartitioned space + l list known partition types + n add a new partition + p print the partition table + t change a partition type + v verify the partition table + i print information about a partition + + **Misc** + m print this menu + x extra functionality (experts only) + + **Script** + I load disk layout from sfdisk script file + O dump disk layout to sfdisk script file + + **Save & Exit** + w write table to disk and exit + q quit without saving changes + + **Create a new label** + g create a new empty GPT partition table + G create a new empty SGI (IRIX) partition table + o create a new empty MBR (DOS) partition table + s create a new empty Sun partition table + +#### Шаг 3: Создайте новый раздел + +1. Запустите команду `n`, чтобы создать новый раздел. +2. Выберите номер раздела, набрав номер по умолчанию (2). +3. После этого вас попросят указать начальный и конечный сектор вашего жесткого диска. Лучше всего ввести в этом разделе номер по умолчанию (3622912). +4. Последний запрос связан с размером раздела. Вы можете выбрать несколько секторов или установить размер в мегабайтах или гигабайтах. Введите `+ 2 GB`, чтобы установить размер раздела 2 ГБ. + +Появится сообщение, подтверждающее создание раздела. + +[](https://wiki.merionet.ru/images/kak-sozdavat-razdely-diska-v-linux/11.png) +#### Шаг 4: запись на диск + +Система создала раздел, но изменения не записываются на диск. +1. Чтобы записать изменения на диск, выполните команду `w`: + +[](https://wiki.merionet.ru/images/kak-sozdavat-razdely-diska-v-linux/12.png) +2. Убедитесь, что раздел создан, выполнив следующую команду: +`sudo fdisk -l +Как видите, раздел `/dev/sdb2` создан. + +### Отформатируйте раздел + +После создания раздела с помощью команды `parted` или `fdisk` отформатируйте его перед использованием. +Отформатируйте раздел, выполнив следующую команду: +`sudo mkfs -t ext4 /dev/sdb1 + +[](https://wiki.merionet.ru/images/kak-sozdavat-razdely-diska-v-linux/14.png) +#### Смонтировать раздел + +Чтобы начать взаимодействие с диском, создайте точку монтирования (mount point) и смонтируйте к ней раздел. + +1. Создайте точку монтирования, выполнив следующую команду: +`sudo mkdir -p /mt/sdb1 +2. После этого смонтируйте раздел, введя: +`sudo mount -t auto /dev/sbd1 /mt/sdb1 + +Терминал не распечатывает вывод, если команды выполнены успешно. +3. Убедитесь, что раздел смонтирован, с помощью команды `df hT`: +[](https://wiki.merionet.ru/images/kak-sozdavat-razdely-diska-v-linux/15.png) +========================================== +`lsblk` - диски со структурой + +`cat /proc/mdstat` - информация о RAID массивах в системе + +`sudo blkid` +Примерный вывод: + +`/dev/sda1: UUID="00a4ad8e-dbcc-4c32-a0b4-074b5ee2bfb4" TYPE="ext4"` +`/dev/sda2: LABEL="Win7" UUID="0E42D6D242D6BE21" TYPE="ntfs"` +`/dev/sda3: LABEL="Shared" UUID="7ECCC7F5CCC7A62D" TYPE="ntfs"` +`/dev/sda4: UUID="1050e484-896f-42a9-bdb3-49eb1255cc77" TYPE="swap"` +`/dev/sr0: LABEL="DrWebLiveCD" TYPE="iso9660"` +`/dev/sdb1: LABEL="TRENDNET8GB" UUID="355B-FE37" TYPE="vfat"` + +Предположим, мы хотим подключить раздел «Shared» (NTFS-раздел, находящийся на основном диске компьютера) и «TRENDNET8GB» (флэшка, отформатированная в FAT32). + +Бонус использования команды blkid по сравнению с инспекцией каталога `/dev/disk/by-uuid/` в том,что вы сразу видите тип файловой системы на блочном устройстве, если ядру удалось его определить. + +- Создаём каталоги для монтирования: + + `sudo mkdir /mnt/Shared /mnt/TrendNet8Gb` + +- Открываем **от имени администратора** файл _/etc/fstab_, содержащий информацию о всех монтируемых при загрузке разделах: + +`sudo nano /etc/fstab + +Добавляем в конец файла, +для NTFS: + +`UUID="7ECCC7F5CCC7A62D" /mnt/Shared ntfs rw,nls=utf8,gid=plugdev,umask=0002 0 0` + +для FAT и FAT32: + +`UUID="355B-FE37" /mnt/TrendNet8Gb vfat rw,exec,codepage=866,nls=utf8,gid=plugdev,umask=0002,nofail,users 0 0` + +Монтируем только что вписанные разделы диска: + +`sudo mount -a` + +`fdisk -u -l /dev/sd?` +`sudo mkdir /mnt/raid0` +`sudo mount /dev/md0 /mnt/raid0 -t auto -o rw,nls=utf8,noatime` + +`$ mount` + +_**....**_ + +`/dev/md0 on /mnt/raid0 type ext3 (rw,noatime,nls=utf8)` + + +`$ mount опции -t файловая_система -o опции_монтирования файл_устройства папка_назначения` + +Опции задают различные дополнительные особенности работы утилиты. Опция -t необязательна, но она позволяет задать файловую систему, которая будет использована и иногда это очень полезно. С помощью опции -o вы можете задать различные параметры монтирования, например, монтировать только для чтения и т д. Последних два параметра - это файл устройства, например, /dev/sda1 и папка назначения, например, /mnt. + +Перед тем как перейти к рассмотрению примеров работы утилитой, давайте рассмотрим ее основные опции: + +- **-V** - вывести версию утилиты; +- **-h** - вывести справку; +- **-v** - подробный режим; +- **-a, --all** - примонтировать все устройства, описанные в fstab; +- **-F, --fork** - создавать отдельный экземпляр mount для каждого отдельного раздела; +- **-f, --fake** - не выполнять никаких действий, а только посмотреть что собирается делать утилита; +- **-n, --no-mtab** - не записывать данные о монтировании в /etc/mtab; +- **-l, --show-labels** - добавить метку диска к точке монтирования; +- **-c** - использовать только абсолютные пути; +- **-r, --read-only** - монтировать раздел только для чтения; +- **-w, --rw** - монтировать для чтения и записи; +- **-L, --label** - монтировать раздел по метке; +- **-U, --uuid** - монтировать раздел по UUID; +- **-T, --fstab** - использовать альтернативный fstab; +- **-B, --bind** - монтировать локальную папку; +- **-R, --rbind** - перемонтировать локальную папку. + +`sudo umount /home/vlapa/2Tb_ext` + +`lsof -w /mnt` + +`sudo umount -l /mnt` +-жесткое размонтирование + + +`lsblk ( -f )` + + +****N********AME**** ****-**** имя устройства +****M********AJ********:M********IN**** ****-**** Гланый и Минимальные Номера устройств +****RM**** - Является ли устройство съемным (1) или не (0) +****S********IZE**** - Объем устройства +****RO -**** устройство, только для чтения (1) или не (0) +****TYPE**** - типа устройства, т.е., если это - диск или разделы и т.д. +****M********OUNT POINT**** - точка монтирования устройства (если применимо). + +`sudo fdisk -l` +`sudo parted -l` + + +`sudo mkdir /mnt/name_folder` +`sudo mount /dev/имя_раздела /точка/монтирования` +`sudo chmod 0777 /mnt/name_folder` +`sudo umount /dev/имя_раздела` +`sudo umount /точка_монтирования` + +`sudo nano /etc/fstab` + +`/etc/fstab` +: static file system information. + + +Use =='blkid'== to print the universally unique identifier for a +device; this may be used with UUID= as a more robust way to name devices that works even if disks are added and removed. See fstab(5). + +<file system> <mount point> <type> <options> <dump> <pass> + + +/dev/disk/by-uuid/c1ca47b1-6f02-4cbb-a917-0bf76c7beae6 none swap sw 0 0 +# / was on /dev/sda3 during curtin installation +/dev/disk/by-uuid/2b69c1d7-9e7a-48bb-91f5-28cce8b4a0e2 / ext4 defaults 0 1 +# /home was on /dev/sda4 during curtin installation +/dev/disk/by-uuid/0c68c1b7-3621-454b-8a4a-02a5c3ce5e92 /home ext4 defaults 0 1 +# /home/1Tb +/dev/disk/by-uuid/1c513d3e-3aa1-4f67-b815-e62369251830 /home/vlapa/1Tb ext4 defaults 0 1 +# /home/2Tb-a +/dev/disk/by-uuid/caa5ad3b-4f1c-45ae-9cd9-d8011ad024e5 /home/vlapa/2Tb-a ext4 defaults 0 1 +# /home/2Tb-b +/dev/disk/by-uuid/e800aa0b-5217-42ad-bac0-5a3f07a748bf /home/vlapa/2Tb-b ext4 defaults 0 1 diff --git a/LINUX/DISK/FSTAB.md b/LINUX/DISK/FSTAB.md new file mode 100644 index 0000000..3d4d30f --- /dev/null +++ b/LINUX/DISK/FSTAB.md @@ -0,0 +1,100 @@ +```sh +# При загрузке Linux на определенном этапе выполняется чтение файла /etc/fstab, в котором описаны правила монтирования разделов. Рассмотрим синтаксис его заполнения. + +# Каждый раздел, который должен быть смонтирован записывается с новой строки: + +# <device> <dir> <type> <options> <dump> <fsck> + +# device — накопитель, который мы монтируем. Это может быть путь до устройства или его идентификатор. +# dir — каталог, в который выполняем монтирование **device**. +# type — файловая система. +# options — опции монтирования, например, владелец каталога или параметры аутентификации. +# dump — указываем, нужно ли создать резервную копию с помощью утилиты dump. +# fsck — нужно ли проверять файловую систему при помощи fsck. Стоит очень аккуратно относится к данному параметру, так как не со всеми файловыми системами утилита fsck корректно работает. Если нет уверенности, ставим 0. Доступны значения: + # 0 — не выполнять проверку. + # 1 — проверять с высоким приоритетом. Рекомендовано только для корневого раздела. + # 2 — проверять с низким приоритетом. + +# Пример файла fstab: + +# <file system> <mount point> <type> <options> <dump> <pass> +/dev/pve/root / ext4 errors=remount-ro 0 1 +/dev/pve/swap none swap sw 0 0 + +# в данном примере будут смонтированы 2 раздела: + +# /dev/pve/root — в корневой раздел с файловой системой ext4. +# /dev/pve/swap — файл подкачки. + +``` +## Монтирование по имени диска +```sh +vi /etc/fstab + +# добавляем в него следующую строчку: + +/dev/sda1 /db xfs defaults 0 0 + +# Где: +# /dev/sda1 — диск, который мы монтируем; +# /db — каталог, в который монтируем диск; +# xfs — файловая система; defaults — стандартные опции; +# 0 0 — первый отключает создание резервных копий при помощи утилиты dump, второй отключает проверку диска. + +# создаем каталог, в который монтируем диск (в данном примере, /db): +mkdir /db + +# И монтируем вручную диск, чтобы проверить правильность настроек fstab: +mount /db + +# Должен появиться новый диск. Это можно проверить следующей командой +mount +``` + +## Монтирование по идентификатору +```sh +# Данный способ не сильно отличается от монтирования по имени диска — вместо имени мы указываем идентификатор. Такой подход считается более надежным, так как при переподключении дисков к другим портам материнской платы может поменяться приоритет и, как следствие, имя устройства — это может привести к проблемам монтирования. UUID диска не меняется и поэтому, данная проблема при таком монтировании не страшна. +blkid + +# Мы получим ответ на подобие: +/dev/sdb: UUID="3e53fc7e-a417-4fa5-b034-b227bea2474c" BLOCK_SIZE="4096" TYPE="ext4" +/dev/sda1: UUID="358f032e-3efb-42ab-b3ba-05ddc82fedfd" BLOCK_SIZE="512" TYPE="xfs" PARTUUID="0beca126-01" + +# допустим, диск который мы хотим примонтировать — sda1. Его идентификатор: 358f032e-3efb-42ab-b3ba-05ddc82fedfd + +vim /etc/fstab +# добавляем в него следующую строчку: +UUID=358f032e-3efb-42ab-b3ba-05ddc82fedfd /db xfs defaults 0 0 + +# Монтирование можно проверить командой: +mount -a + +# Пример монтирования сетевого диска + +192.168.0.1/network /mnt cifs user,rw,noauto,credentials=/root/.smbclient 0 0 + +# выполняется монтировние общей папки *network* на сервере с IP-адресом 192.168.0.1 в каталог /mnt. В качестве сетевой файловой системы используется *cifs (протокол SMB: сервер samba или общая папка Windows). Параметры для подключения: +# user - позволяет выполнить монтирование любому пользователю +# rw - с правом на чтение и запись +# noauto - не монтировать автоматически при старте системы +# credentials - файл, в котором написаны логин и пароль для подключения к общей папке + +# Теперь создадим файл аутентификации (credentials): +vim /root/.smbclient + +# и приведем его к следующему виду: +username=backup +password=backup + +# username - имя пользоватля +# password - пароль (Само собой, в вашем случае )указываются свои данные) + +# Теперь вводим следующую команду: +mount /mnt + +# Должна примонтироваться сетевая папка //192.168.0.1/network +# Это можно проверить следующей командой: + +mount +``` + diff --git a/LINUX/DISK/GPT-диск в Linux.md b/LINUX/DISK/GPT-диск в Linux.md new file mode 100644 index 0000000..8c313f4 --- /dev/null +++ b/LINUX/DISK/GPT-диск в Linux.md @@ -0,0 +1,68 @@ + (носитель более 2 ТБ) + +  Обновлено: 24.05.2024  Опубликовано: 17.01.2017 + +C [GPT](https://www.dmosk.ru/terminus.php?object=gpt) в системах [Linux](https://www.dmosk.ru/terminus.php?object=linux) работает [утилита](https://www.dmosk.ru/terminus.php?object=utility) **parted**. + +Чтобы узнать, какой именно тип диска (GPT или [MBR](https://www.dmosk.ru/terminus.php?object=mbr)) настроен вводим команду: + +fdisk /dev/sdb -l + +_* где sdb — наш [жесткий диск](https://www.dmosk.ru/terminus.php?object=hdd)._ + +Либо будет выведено предупреждение **WARNING: GPT (GUID Partition Table) detected**, значит используется GPT, либо мы увидим: + +Тип метки диска: gpt + +Также, для определения диска можно использовать **gdisk**: + +gdisk /dev/sdb + +Мы должны увидеть строку на подобие: + +Found valid GPT with protective MBR; using GPT. + +## Создаем GPT + +Открываем наш диск утилитой parted: + +parted /dev/sdb + +Откроется интерактивное меню утилиты — нажимаем **p:** + +(parted) p + +На экран будет выведена информация о разделах. Если они есть, удаляем командой **rm:** + +(parted) rm 1 + +_* где **1** — номер раздела. Команду нужно повторить для всех разделов. Их список мы смотрели на предыдущем шаге командой **p**. +* если на разделе есть важная информация, ее нужно скопировать на другой носитель._ + +Создаем разметку GPT: + +(parted) mklabel gpt + +И создаем новый раздел: + +(parted) mkpart primary 0 0 + +_* где **primary** указывает, что раздел является основным; **0** и **0** — использовать весь диск для создания раздела, но можно использовать часть диска, например, 0 2048GB._ + +Выходим из parted: + +(parted) q + +И форматируем диск: + +mkfs.ext4 /dev/sdb + +_* где в качестве файловой системы выбрана **ext4**._ + +Для проверки, монтируем отформатированный диск в директорию **/mnt**: + +mount /dev/sdb /mnt + +И выводим список дисков: + +df -h
\ No newline at end of file diff --git a/LINUX/DISK/GeeseFS ( Yandex Cloud диск ).md b/LINUX/DISK/GeeseFS ( Yandex Cloud диск ).md new file mode 100644 index 0000000..1539cc5 --- /dev/null +++ b/LINUX/DISK/GeeseFS ( Yandex Cloud диск ).md @@ -0,0 +1,73 @@ +### Установка компонентов + +Сначала устанавливаем утилиты fuse и wget. В зависимости + +а) Для Deb (Debian / Ubuntu): + +apt update + +apt install fuse wget + +б) Для RPM (Rocky / CentOS): + +yum install fuse fuse3 wget + +Скачиваем geesefs и сохраняем ее в /usr/bin/geesefs: + +wget https://github.com/yandex-cloud/geesefs/releases/latest/download/geesefs-linux-amd64 -O /usr/bin/geesefs + +Выставляем разрешение утилиты geesefs на запуск: + +chmod a+x /usr/bin/geesefs + +Проверяем работу утилиты, посмотрев ее версию: + +geesefs -v + +Мы должны увидеть что-то на подобие: + +geesefs version 0.36.2 + +### Авторизация + +Yandex Cloud для монтирования диска требует прохождение аутентификации с помощью токена. Его мы получаем в личном кабинете хостера. + +Для настройки доступа создаем каталог **.aws** в домашней директории пользователя, под которым будем монтировать диск. В моем случае, пользователь root: + +mkdir /root/.aws + +После создаем файл с токеном: + +vi /root/.aws/credentials + +[default] +aws_access_key_id = H.....D +aws_secret_access_key = H......7 + +### Ручное монтирование + +Для монтирования используем команду geesefs: + +geesefs <имя бакета> <точка монтирования> + +Например: + +geesefs testbacket /mnt + +### Автоматическое монтирование + +Чтобы раздел автоматически монтировался при перезапуске компьютера, открываем на редактирование файл fstab: + +vi /etc/fstab + +Добавляем строку: + +<имя бакета> <точка монтирования> fuse.geesefs _netdev,allow_other,--file-mode=0666,--dir-mode=0777 0 0 + +> Если мы хотим, чтобы точке монтирования назвачался определенный владелец и группы, указываем опции **uid** и **gid**: +> +> <имя бакета> <точка монтирования> fuse.geesefs _netdev,allow_other,--file-mode=0666,--dir-mode=0777,--uid=1024,--gid=1024 0 0 + +Чтобы применить настройку и проверить ее, вводим: + +mount -a
\ No newline at end of file diff --git a/LINUX/DISK/HDPARM скорость диска.md b/LINUX/DISK/HDPARM скорость диска.md new file mode 100644 index 0000000..866f5be --- /dev/null +++ b/LINUX/DISK/HDPARM скорость диска.md @@ -0,0 +1,133 @@ +```sh +hdparm -tT /dev/sda + +``` + +/dev/sda: + Timing cached reads: 24138 MB in 1.98 seconds = 12174.75 MB/sec + + Timing buffered disk reads: 1006 MB in 3.00 seconds = 335.09 MB/sec + + +`$ hdparm опция значение /dev/имя_файла_диска` + +Если опцию передать без значения, то как правило, она просто выводит текущее значения параметра, если передать значение, то оно устанавливается вместо старого. Давайте сначала рассмотрим основные опции утилиты, которые мы будем сегодня использовать: + +- ****-a**** - позволяет включить режим read-ahead, когда чтение выполняется в первую очередь. Этот режим позволяет улучшить производительность чтения больших объемов данных; + +- ****-A**** - позволяет включить режим read-look-ahead, когда диск просматривается перед чтением. По умолчанию включена; +- ****-b**** - остановить жесткий диск до следующего к нему обращения; +- ****-B**** - настройка управления питанием Advanced Power Management (APM), чем ниже значение, тем лучше энергосбережение. Чтобы отключить режим полностью установите значение в 255. +- ****-c**** - включает 32-битный режим передачи для IDE; +- ****-C**** - позволяет посмотреть текущий режим питания IDE; +- ****-d**** - включает режим DMA; +- ****-D**** - включает обнаружение дефектных областей на диске; +- ****-E**** - позволяет установить скорость для CD/DVD привода; +- ****-f**** - позволяет записать кэш диска на носитель; +- ****-i**** - выводит информацию о диске; +- ****-I**** - тоже выводит информацию о диске, но в более подробной форме; +- ****-m**** - позволяет указать количество секторов для многосекторного ввода/вывода. Чтобы отключить эту опцию установите её в 0; +- -M - позволяет управлять уровнем шума диска с помощью Automatic Acoustic Management (AAM); +- ****-n**** - позволяет включить флаг ****ignore write errors**** для игнорирования ошибок записи; +- ****-r**** - включает флаг read only. После этого с устройства можно только читать данные, но не записывать; +- ****-S**** - устанавливает время неактивности диска для перехода его в режим сна. Если установлено в 0, то диск сразу же отключается; +- ****-T**** - выполняет тест скорости чтения из кэша; +- ****-t**** - выполняет тест скорости чтения с диска без кэширования; +- ****-v**** - выводит все настройки; +- ****-w**** - выполняет сброс диска; +- ****-W**** - включает или отключает кэш для записи данных на IDE; +- ****-y**** - переводит IDE диск в режим низкого энергопотребления, замедляя скорость вращения диска; +- ****-Y**** - переводит IDE диск в режим низкого энергопотребления останавливая его; +- ****-z**** - просит ядро перечитать таблицу разделов для этого диска; +- ****-Z**** - отключает режим энергосбережения диска. + +### Hdparm + +`hdparm` - программа для получения/установки параметров устройств SATA/IDE - из одноимённого пакета, которая поддерживает управление временем автопарковки: + +Проверить, что установлено (`hdparm` работает от имени **root**): + +$ su - +`hdparm -J /dev/sdX + +Выключить: + +`hdparm -J0 --please-destroy-my-drive /dev/sdX + +Настройка диска (команда idle3ctl из пакета idle3-tools) + +1. idle3ctl -s 900 /dev/sda +2. idle3ctl -d /dev/sda + +- [https://forum.altlinux.org/index.php?topic=29692.105](https://forum.altlinux.org/index.php?topic=29692.105) +- [https://bugs.etersoft.ru/show_bug.cgi?id=9438](https://bugs.etersoft.ru/show_bug.cgi?id=9438) + +Если указанный выше способ не сработал, можно отключить парковку головок жесткого диска через функцию **APM (расширенного управления питанием)** жесткого диска. Сначала нужно узнать, какой уровень управления энергопитанием установлен: + +$ su - +`hdparm -B /dev/sdX +где sdX - имя нужного жесткого диска в системе. + +Во время питания ноутбука от сети значение APM равно 254. При работе от батареи можно установить его на уровне 128, а при одном-двух запущенных приложениях — 96. Запуска веб-браузера в этом случае стоит избегать. + +$ sudo hdparm -B 96 -S 0 /dev/sda + +При частой работе за ноутбуком от аккумулятора, вероятно, лучшим решением будет использование твердотельного накопителя — SSD: он очевидно превосходит HDD в экономии энергии. Возможно также комбинировать два варианта, когда на SDD установлена сама операционная система, а HDD используется для хранения данных. + +Возможные значения варьируются от 1 до 254. Наивысшая степень управления питанием достигается при значении 1, а наивысшая производительность ввода-вывода - при значении 254. Значение 255 указывает `hdparm` на полное отключение расширенного управления питанием на диске (не все диски поддерживают его отключение, но большинство поддерживают). + + +Для настройки APM при загрузке — создаем исполняемый файл `[rc.local](https://www.altlinux.org/Rc.local "Rc.local")`: + +$ su - +`echo '#!/bin/sh' > /etc/rc.d/rc.local +`chmod +x /etc/rc.d/rc.local + +В этот файл добавляем строчку: + +hdparm -B 254 /dev/sda + +Для настройки APM при выходе из ждущего режима — создаем файл `/etc/systemd/system/root-resume.service`: + +`touch /etc/systemd/system/root-resume.service + +В этот файл добавляем строчки: + +[Unit] +Description=Local system resume actions +After=suspend.target + +[Service] +Type=simple +ExecStart=/sbin/hdparm -B 254 /dev/sda + +[Install] +WantedBy=suspend.target + +И выполняем команду: + +`systemctl enable root-resume + +Для настройки APM при выходе из спящего режима — создаем файл `/etc/systemd/system/root-resume-sleep.service`: + +`touch /etc/systemd/system/root-resume-sleep.service + +В этот файл добавляем строчки: + +[Unit] +Description=Local system resume actions +After=hibernate.target + +[Service] +Type=simple +ExecStart=/sbin/hdparm -B 254 /dev/sda + +[Install] +WantedBy=hibernate.target + +И выполняем команду: + +`systemctl enable root-resume-sleep + + + diff --git a/LINUX/DISK/MDADM организация RAID.md b/LINUX/DISK/MDADM организация RAID.md new file mode 100644 index 0000000..7a50f40 --- /dev/null +++ b/LINUX/DISK/MDADM организация RAID.md @@ -0,0 +1,535 @@ + Обновлено: 28.09.2023  Опубликовано: 08.03.2019 + +mdadm — утилита для работы с программными RAID-массивами различных уровней. В данной инструкции рассмотрим примеры ее использования. + +[Установка утилиты mdadm](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#install) +[Создание RAID](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#create) + [Подготовка носителей](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#create-prepare) + [Сборка отказоустойчивого массива](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#create-raid) +[Файл mdadm.conf](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#conf) +[Создание файловой системы и монтирование](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#fs-mount) +[Получение информации о RAID](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#info) +[Проверка целостности массива](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#check) +[Восстановление RAID](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#restore) + [Заменой диска](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#restore-disk) + [Пересборка массива](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#restore-assemble) +[Использование Hot Spare](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#spare) +[Добавление диска к массиву](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#add-to-raid) +[Удаление массива](https://www.dmosk.ru/miniinstruktions.php?mini=mdadm#remove) + +## Установка mdadm + +`apt-get install mdadm + +## Сборка RAID + +Перед сборкой, стоит подготовить наши носители. Затем можно приступать к созданию рейд-массива. + +### Подготовка носителей + +Сначала необходимо занулить суперблоки на дисках, которые мы будем использовать для построения RAID (если диски ранее использовались, их суперблоки могут содержать служебную информацию о других RAID): + +`mdadm --zero-superblock --force /dev/sd{b,c} + +_* в данном примере мы зануляем суперблоки для дисков **sdb** и **sdc**._ + +> Посмотреть, какие в вашей системе есть устройства можно командой: +> +> `lsblk + +Если мы получили ответ: + +`mdadm: Unrecognised md component device - /dev/sdb +`mdadm: Unrecognised md component device - /dev/sdc + +... то значит, что диски не использовались ранее для RAID. Просто продолжаем настройку. + +Далее нужно удалить старые метаданные и подпись на дисках: + +`wipefs --all --force /dev/sd{b,c} + +### Создание рейда + +Для сборки избыточного массива применяем следующую команду: + +`mdadm --create --verbose /dev/md0 -l 1 -n 2 /dev/sd{b,c} + +_* где:_ +- _**/dev/md0** — устройство RAID, которое появится после сборки;_ +- _**-l 1** — уровень RAID;_ +- _**-n 2** — количество дисков, из которых собирается массив;_ +- _**/dev/sd{b,c}** — сборка выполняется из дисков sdb и sdc._ + +Мы должны увидеть что-то на подобие: + +`mdadm: Note: this array has metadata at the start and + `may not be suitable as a boot device. If you plan to + `store '/boot' on this device please ensure that + `your boot-loader understands md/v1.x metadata, or use + `--metadata=0.90 +`mdadm: size set to 1046528K + +Также система задаст контрольный вопрос, хотим ли мы продолжить и создать RAID — нужно ответить **y**: + +`Continue creating array? y + +Мы увидим что-то на подобие: + +`mdadm: Defaulting to version 1.2 metadata +`mdadm: array /dev/md0 started. + +Вводим команду: + +`lsblk + +... и находим информацию о том, что у наших дисков sdb и sdc появился раздел md0, например: +... +sdb 8:16 0 2G 0 disk +??md0 9:0 0 2G 0 raid1 +sdc 8:32 0 2G 0 disk +??md0 9:0 0 2G 0 raid1 +... + +_* в примере мы видим собранный raid1 из дисков sdb и sdc._ + +## Создание файла mdadm.conf + +В файле mdadm.conf находится информация о RAID-массивах и компонентах, которые в них входят. Для его создания выполняем следующие команды: + +`mkdir /etc/mdadm + +`echo "DEVICE partitions" > /etc/mdadm/mdadm.conf + +`mdadm --detail --scan --verbose | awk '/ARRAY/ {print}' >> /etc/mdadm/mdadm.conf + +Пример содержимого: + +`DEVICE partitions +`ARRAY /dev/md0 level=raid1 num-devices=2 metadata=1.2 name=proxy.dmosk.local:0 UUID=411f9848:0fae25f9:85736344:ff18e41d + +_* в данном примере хранится информация о массиве **/dev/md0** — его уровень 1, он собирается из 2-х дисков._ + +## Создание файловой системы и монтирование массива + +Создание файловой системы для массива выполняется также, как для раздела, например: + +`mkfs.ext4 /dev/md0 + +_* данной командой мы создаем на **md0** файловую систему **ext4**._ +или: + +`mkfs.xfs /dev/md0 + +_* для файловой системы **xfs**._ + +Примонтировать раздел можно командой: + +`mount /dev/md0 /mnt + +_* в данном случае мы примонтировали наш массив в каталог **/mnt**._ + +Чтобы данный раздел также монтировался при загрузке системы, добавляем в fstab. + +Сначала смотрим идентификатор раздела: + +`blkid + +Мы увидим что-то на подобие: + +`/dev/md0: UUID="ffa7c082-7613-4dbc-ae62-3f5508652cf6" TYPE="ext4" + +_* в моем примере мы будем использовать идентификатор **ffa7c082-7613-4dbc-ae62-3f5508652cf6**._ + +Открываем теперь fstab и добавляем строку: + +`vim /etc/fstab + +`UUID="ffa7c082-7613-4dbc-ae62-3f5508652cf6" /mnt ext4 defaults 0 0 + +Если ранее мы монтировали раздел в каталог /mnt, вводим: + +`umount /mnt + +И после: + +`mount -a + +Мы должны увидеть примонтированный раздел md, например: + +`df -h + +`/dev/md0 990M 2,6M 921M 1% /mnt + +## Информация о RAID + +Посмотреть состояние всех RAID можно командой: + +`cat /proc/mdstat + +В ответ мы получим что-то на подобие: + +`md0 : active raid1 sdc[1] sdb[0] + `1046528 blocks super 1.2 [2/2] [UU] + +_* где **md0** — имя RAID устройства; **raid1 sdc[1] sdb[0]** — уровень избыточности и из каких дисков собран; **1046528 blocks** — размер массива; **[2/2] [UU]** — количество юнитов, которые на данный момент используются. +** мы можем увидеть строку **md0 : active(auto-read-only)** — это означает, что после монтирования массива, он не использовался для записи._ + +Подробную информацию о конкретном массиве можно посмотреть командой: + +`mdadm -D /dev/md0 + +_* где **/dev/md0** — имя RAID устройства._ + +Пример ответа: + + Version : 1.2 + Creation Time : Wed Mar 6 09:41:06 2019 + Raid Level : raid1 + Array Size : 1046528 (1022.00 MiB 1071.64 MB) + Used Dev Size : 1046528 (1022.00 MiB 1071.64 MB) + Raid Devices : 2 + Total Devices : 2 + Persistence : Superblock is persistent + + Update Time : Wed Mar 6 09:41:26 2019 + State : clean + Active Devices : 2 + Working Devices : 2 + Failed Devices : 0 + Spare Devices : 0 + +Consistency Policy : resync + + Name : proxy.dmosk.local:0 (local to host proxy.dmosk.local) + UUID : 304ad447:a04cda4a:90457d04:d9a4e884 + Events : 17 + + Number Major Minor RaidDevice State + 0 8 16 0 active sync /dev/sdb + 1 8 32 1 active sync /dev/sdc + +_* где:_ + +- _**Version —** версия метаданных._ +- _**Creation Time —** дата в время создания массива._ +- _**Raid Level —** уровень RAID._ +- _**Array Size —** объем дискового пространства для RAID._ +- _**Used Dev Size —** используемый объем для устройств. Для каждого уровня будет индивидуальный расчет: RAID1 — равен половине общего размера дисков, RAID5 — равен размеру, используемому для контроля четности._ +- _**Raid Devices —** количество используемых устройств для RAID._ +- _**Total Devices —** количество добавленных в RAID устройств._ +- _**Update Time —** дата и время последнего изменения массива._ +- _**State —** текущее состояние. clean — все в порядке._ +- _**Active Devices —** количество работающих в массиве устройств._ +- _**Working Devices —** количество добавленных в массив устройств в рабочем состоянии._ +- _**Failed Devices —** количество сбойных устройств._ +- _**Spare Devices —** количество запасных устройств._ +- _**Consistency Policy —** политика согласованности активного массива (при неожиданном сбое). По умолчанию используется **resync** — полная ресинхронизация после восстановления. Также могут быть bitmap, journal, ppl._ +- _**Name —** имя компьютера._ +- _**UUID —** идентификатор для массива._ +- _**Events —** количество событий обновления._ +- **Chunk Size** (для RAID5) — размер блока в килобайтах, который пишется на разные диски. + +Подробнее про каждый параметр можно прочитать в мануале для mdadm: + +`man mdadm + +Также, информацию о разделах и дисковом пространстве массива можно посмотреть командой fdisk: + +`fdisk -l /dev/md0 + +## Проверка целостности + +Для проверки целостности вводим: + +`echo 'check' > /sys/block/md0/md/sync_action + +Результат проверки смотрим командой: + +`cat /sys/block/md0/md/mismatch_cnt + +_* если команда возвращает **0**, то с массивом все в порядке._ + +Остановка проверки: + +`echo 'idle' > /sys/block/md0/md/sync_action + +## Восстановление RAID + +Рассмотрим два варианта восстановлении массива. + +### Замена диска + +В случае выхода из строя одного из дисков массива, команда cat /proc/mdstat покажет следующее: + +`cat /proc/mdstat + +Personalities : [raid1] +md0 : active raid1 sdb[0] + 1046528 blocks super 1.2 [2/1] [U_] + +_* о наличии проблемы нам говорит нижнее подчеркивание вместо U — **[U_]** вместо **[UU]**._ + +Или: + +`mdadm -D /dev/md0 + +... + Update Time : Thu Mar 7 20:20:40 2019 + State : clean, degraded +... + +_* статус **degraded** говорит о проблемах с RAID._ + +Для восстановления, сначала удалим сбойный диск, например: + +`mdadm /dev/md0 --remove /dev/sdc + +> Если нам нужно заменить диск, но при этом он в mdadm имеет рабочее состояние, мы получим ошибку: +> +> ... device or resource busy +> +> Сначала, нам нужно пометить диск как сбойный: +> +> `mdadm /dev/md0 --manage --fail /dev/sdc +> +> _* в данном примере диск **sdb** массива **md0** будет помечен сбойным._ +> +> Теперь удаляем: +> +> `mdadm /dev/md0 --remove /dev/sdc + +Теперь добавим новый: + +`mdadm /dev/md0 --add /dev/sde + +Смотрим состояние массива: + +`mdadm -D /dev/md0 + +... + Update Time : Thu Mar 7 20:57:13 2019 + State : clean, degraded, recovering +... + Rebuild Status : 40% complete +... + +_* **recovering** говорит, что RAID восстанавливается; **Rebuild Status** — текущее состояние восстановления массива (в данном примере он восстановлен на **40%**)._ + +Если синхронизация выполняется слишком медленно, можно увеличить ее скорость. Для изменения скорости синхронизации вводим: + +`echo '10000' > /proc/sys/dev/raid/speed_limit_min + +_* по умолчанию скорость **speed_limit_min** = 1000 Кб, **speed_limit_max** — 200000 Кб. Для изменения скорости, можно поменять только минимальную._ + +### Пересборка массива + +Если нам нужно вернуть ранее разобранный или развалившийся массив из дисков, которые уже входили в состав RAID, вводим: + +`mdadm --assemble --scan + +_* данная команда сама найдет необходимую конфигурацию и восстановит RAID._ + +Также, мы можем указать, из каких дисков пересобрать массив: + +`mdadm --assemble /dev/md0 /dev/sdb /dev/sdc + +=============================================== + +**Чтобы вытащить данные из одного диска RAID-1 в Ubuntu, можно попробовать следующее:** + +При подключении одного жесткого диска к моей машине Ubuntu `fdisk -l`отчеты: + +``` +Disk /dev/sdb: 1.8 TiB, 2000398934016 bytes, 3907029168 sectors + Units: sectors of 1 * 512 = 512 bytes +Sector size (logical/physical): 512 bytes / 4096 bytes +I/O size (minimum/optimal): 4096 bytes / 4096 bytes +Disklabel type: gpt +Disk identifier: D1C4E003-11FD-4223-AA98-61CB9C6322BD + +Device Start End Sectors Size Type +/dev/sdb1 2048 1050623 1048576 512M Linux swap +/dev/sdb2 3147776 3904929899 3901782124 1.8T Microsoft basic data +/dev/sdb3 3904931840 3907029134 2097295 1G Microsoft basic data +/dev/sdb4 1050624 3147775 2097152 1G Microsoft basic data +``` + +Я попробовал шаги из похожего вопроса на этом сайте: + +``` +root@host:/mnt# mount /dev/sdb2 /mnt/old_nas/ +mount: /mnt/old_nas: unknown filesystem type 'linux_raid_member'. + +root@host:/mnt# mdadm --assemble --run /dev/md0 /dev/sdb2 +mdadm: failed to RUN_ARRAY /dev/md0: Invalid argument +mdadm: Not enough devices to start the array. +``` + +Предположим, /deb/sdb2 — это старый участник raid 1, который вы хотите восстановить. + +**Первый вариант** : попробуйте заново собрать RAID 1 со старым диском: + +Вам необходимо собрать деградировавший RAID-массив на новой машине, используя что-то вроде этого: + +``` +mdadm --force --assemble --run /dev/md0 /dev/sdb2 +``` + +Конечно, выберите номер помимо md0, если он уже используется. Затем вы можете смонтировать /dev/md0, где захотите. + +Другой вариант: + +``` +mdadm --force --run --assemble --scan +``` + +Вы получите что-то вроде этого: + +``` +mdadm: /dev/md/0 has been started with 1 drive (out of 2) +``` + +Затем смонтируйте и просмотрите свои файлы: + +``` +cd /mnt && mkdir md0 && mount /dev/md0 md0 +ls -la md0 +``` + +Наконец, вы можете добавить второй диск в массив: + +``` +mdadm /dev/md0 --manage --add /dev/sdc1 +``` + +**Второй вариант** : использование циклического устройства только для чтения + +Получите смещение данных для старого диска: + +``` +mdadm --examine /dev/sdb2 | grep -i offset +``` + +Создайте петлевое устройство (представим, что смещение данных равно 2048): + +``` +losetup --find --show --read-only --offset $((2048*512)) /dev/sdb2 +``` + +Смонтируйте печатное устройство петли, полученное в результате предыдущей команды. + +``` +mount -o ro /dev/loo1 /mnt/ +``` + +Mount может сделать это напрямую с помощью опций -o ro,loop,offset=: + +``` +mount -o ro,loop,offset=$((2048*512)) /dev/sdb2 /mnt +``` + +Насколько я понимаю, проблема, с которой вы столкнулись, может быть связана с тем, что mdadm считает ваш массив RAID 1 "чистым", что не так. Перед тем, как извлечь диск из массива, вы должны явно сообщить mdadm, что ваш второй диск (sdb2) вышел из строя: + +``` +mdadm --manage /dev/md0 --fail /dev/sdb2 +mdadm --manage /dev/md0 --remove /dev/sdb2 +``` + +=============================================== +## Запасной диск (Hot Spare) + +Если в массиве будет запасной диск для горячей замены, при выходе из строя одного из основных дисков, его место займет запасной. + +Диском Hot Spare станет тот, который просто будет добавлен к массиву: + +`mdadm /dev/md0 --add /dev/sdd + +Информация о массиве изменится, например: + +`mdadm -D /dev/md0 + +... + Number Major Minor RaidDevice State + 0 8 16 0 active sync /dev/sdb + 2 8 48 1 active sync /dev/sdc + + 3 8 32 - spare /dev/sdd + +Проверить работоспособность резерва можно вручную, симулировав выход из строя одного из дисков: + +`mdadm /dev/md0 --fail /dev/sdb + +И смотрим состояние: + +`mdadm -D /dev/md0 + +... + Rebuild Status : 37% complete +... + Number Major Minor RaidDevice State + 3 8 32 0 spare rebuilding /dev/sdd + 2 8 48 1 active sync /dev/sdc + + 0 8 16 - faulty /dev/sdb +... + +_* как видим, начинается ребилд. На замену вышедшему из строя **sdb** встал hot-spare **sdd**._ + +## Добавить диск к массиву + +В данном примере рассмотрим вариант добавления активного диска к RAID, который будет использоваться для работы, а не в качестве запасного. + +Добавляем диск к массиву: + +`mdadm /dev/md0 --add /dev/sde + +Новый диск мы увидим в качестве spare: + +4 8 16 - spare /dev/sde + +Теперь расширяем RAID: + +`mdadm -G /dev/md0 --raid-devices=3 + +_* в данном примере подразумевается, что у нас RAID 1 и мы добавили к нему 3-й диск._ + +## Удаление массива + +При удалении массива внимательнее смотрите на имена массива и дисков и подставляйте свои значения. + +Если нам нужно полностью разобрать RAID, сначала размонтируем и остановим его: + +`umount /mnt + +_* где **/mnt** — каталог монтирования нашего RAID._ + +> Если мы получим ошибку **umount: /mnt: target is busy**, то смотрим пользовательские процессы, которые мешают отмонтировать раздел: +> +> `fuser -vm /mnt +> +> И останавливаем их. + +`mdadm -S /dev/md0 + +_* где **/dev/md0** — массив, который мы хотим разобрать._ + +> Если мы получим ошибку **mdadm: fail to stop array /dev/md0: Device or resource busy**, с помощью команды +> +> `lsof -f -- /dev/md0 +> +> ... смотрим процессы, которые используют раздел и останавливаем их. + +Затем очищаем суперблоки на всех дисках, из которых он был собран: + +`mdadm --zero-superblock /dev/sdb + +`mdadm --zero-superblock /dev/sdc + +`mdadm --zero-superblock /dev/sdd + +_* где диски **/dev/sdb**, **/dev/sdc**, **/dev/sdd** были частью массива md0._ + +А также удаляем метаданные и подпись: + +`wipefs --all --force /dev/sd{b,c,d} + diff --git a/LINUX/DISK/NTFS.md b/LINUX/DISK/NTFS.md new file mode 100644 index 0000000..c813187 --- /dev/null +++ b/LINUX/DISK/NTFS.md @@ -0,0 +1,57 @@ +### Установка компонентов + +Для начала необходимо установить пакет ntfs-3g. В противном случае, при попытке запустить команду на монтирование раздела NTFS мы получим ошибку **mount: unknown filesystem type 'ntfs'.** + +В зависимости от дистрибутива Linux, наши действия будут отличаться. + +**а) Linux Deb (Debian / Ubuntu / Astra):** + +apt update + +apt install ntfs-3g + +**б) Linux RPM (Rocky / РЕД ОС / CentOS):** + +yum install epel-release + +yum install ntfs-3g + +После установки данного пакета в системе появятся дополнительные команды **mount.ntfs-fuse** и **mount.ntfs-3g**., а также команда mount распознает опцию -t ntfs. + +### Ручное монтирование + +Монтирование NTFS на чтение и запись выполняется любой из трех команд: + +mount -t ntfs /dev/sdb1 /mnt + +или: + +mount -t ntfs-3g /dev/sdb1 /mnt + +или + +mount.ntfs-3g /dev/sdb1 /mnt + +_* в данном примере, примотировано устройство **/dev/sdb1** в раздел **/mnt**. Понять, какой устройство нужно монтировать можно при помощи команды **fdisk -l**_ + +### Автоматическое монтирование + +Чтобы раздел автоматически монтировался при перезапуске компьютера, открываем на редактирование файл: + +vi /etc/fstab + +Добавляем строку: + +/dev/sdb1 /mnt ntfs defaults 0 0 + +_* в данном примере мы раздел /dev/sdb1 будем монтировать в каталог /mnt._ + +Чтобы применить настройку и проверить ее, вводим: + +mount -a + +Посмотреть примонтированные разделы и файловые системы можно командой: + +df -hT + + diff --git a/LINUX/DISK/SMARTMONTOOLS.md b/LINUX/DISK/SMARTMONTOOLS.md new file mode 100644 index 0000000..4101b02 --- /dev/null +++ b/LINUX/DISK/SMARTMONTOOLS.md @@ -0,0 +1,85 @@ +```sh +sudo apt install smartmontools + +# узнаём какие диски подключены: +ls -l /dev | grep -E 'sd|hd' +# или: +smartctl --scan +lsblk + +# отображение информации о диске: +smartctl --info /dev/sda + +# запустить проверку: +smartctl -s on -a /dev/sda + +# Здесь опция -s включает флаг SMART на указном устройстве. Вы можете его убрать если поддержка SMART уже включена. Информация о диске разделена на несколько разделов, В разделе **READ SMART DATA** находится общая информация о здоровье жесткого диска. + +`=== START OF READ SMART DATA SECTION === SMART overall-health self-assessment rest result: PASSED` + +# Этот тест может быть пройден (PASSED) или нет (FAILED). В последнем случае сбой неизбежен, начинайте резервное копирование данных с этого диска. +``` +Следующая вещь которую можно посмотреть, когда выполняется диагностика HDD в linux, это таблица SMART атрибутов. + +[](https://losst.pro/wp-content/uploads/2015/08/Snimok-ekrana-ot-2020-05-14-14-20-12.png) + В SMART таблице записаны параметры, определенные для конкретного диска разработчиком, а также порог отказа для этих параметров. Таблица заполняется автоматически и обновляется на основе прошивки диска. + +- **ID #** - идентификатор атрибута, как правило, десятичное число между 1 и 255; +- **ATTRIBUTE_NAME** - название атрибута; +- **FLAG** - флаг обработки атрибута; +- **VALUE** - это поле представляет нормальное значение для состояния данного атрибута в диапазоне от 1 до 253, 253 - лучшее состояние, 1 - худшее. В зависимости от свойств, начальное значение может быть от 100 до 200; +- **WORST** - худшее значение value за все время; +- **THRESH** - самое низкое значение value, после перехода за которое нужно сообщить что диск непригоден для эксплуатации; +- **TYPE** - тип атрибута, может быть Pre-fail или Old_age. Все атрибуты по умолчанию считаются критическими, то-есть если диск не прошел проверку по одному из атрибутов, то он уже считается не пригодным (FAILED) но атрибуты old_age не критичны; +- **UPDATED** - показывает частоту обновления атрибута; +- **WHEN_FAILED** - будет установлено в **FAILING_NOW** если значение атрибута меньше или равно THRESH, или в "**-**" если выше. В случае **FAILING_NOW**, лучше как можно скорее выполнить резервное копирование, особенно если тип атрибута **pre-fail**. +- **RAW_VALUE** - значение, определенное производителем. + + +## Автоматическая диагностика в smartd + +Автоматическая диагностика HDD в Linux настраивается очень просто. Сначала отредактируйте файл конфигурации smartd - /etc/smartd.conf. Добавьте следующую строку: + + `nano /etc/smartd.conf` + +`/dev/sda -m myemail@mydomain.com -M test` + +Здесь: + +- **-m <email адрес>** - адрес электронной почты для отправки результатов проверки. Это может быть адрес локального пользователя, суперпользователя или внешний адрес, если настроен сервер для отправки электронной почты; +- **-M** - частота отправки писем. once - отправлять только одно сообщение о проблемах с диском. **daily** - отправлять сообщения каждый день если была обнаружена проблема. **diminishing** - отправлять сообщения через день если была обнаружена проблема. **test** - отправлять тестовое сообщение при запуске smartd. **exec** - выполняет указанную программу в место отправки почты. + +Сохраните изменения и перезапустите **smartd**: + +`sudo systemctl restart smartd` + +Вы должны получить на электронную почту письмо о том, что программа была запущена успешно. Это будет работать только если на компьютере настроен почтовый сервер. + +[](https://losst.pro/wp-content/uploads/2015/08/Snimok-ekrana-ot-2020-05-14-15-27-39.png) + +Также можно запланировать тесты по своему графику, для этого используйте опцию **-s** и регулярное выражение типа **T/MM/ДД/ДН/ЧЧ**, где: + +Здесь **T** - тип теста: + +- **L** - длинный тест; +- **S** - короткий тест; +- **C** - тест перемещения (ATA); +- **O** - оффлайн тест. + +Остальные символы определяют дату и время теста: + +- **ММ** - месяц в году; +- **ДД** - день месяца; +- **ЧЧ** - час дня; +- **ДН** - день недели (от 1 - понедельник 7 - воскресенье; +- **MM, ДД и ЧЧ** - указываются с двух десятичных цифр. + +Точка означает все возможные значения, выражение в скобках (A|B|C) - означает один из трех вариантов, выражение в квадратных скобках [1-5] означает диапазон (от 1 до 5). + +Например, чтобы выполнять полную проверку жесткого диска linux каждый рабочий день в час дня добавьте опцию **-s** в строчку конфигурации вашего устройства: + +`/dev/sda -m myemail@mydomain.com -M once -s (L /../../[1-5]/13)` + +Если вы хотите чтобы утилита сканировала и проверяла все устройства, которые есть в системе используйте вместо имени устройства директиву DEVICESCAN: + +`DEVICESCAN -m myemail@mydomain.com -M once -s (L /../../[1-5]/13)`
\ No newline at end of file diff --git a/LINUX/DISK/SOFT RAID1 (mdadm) горячая замена диска.md b/LINUX/DISK/SOFT RAID1 (mdadm) горячая замена диска.md new file mode 100644 index 0000000..b8935d7 --- /dev/null +++ b/LINUX/DISK/SOFT RAID1 (mdadm) горячая замена диска.md @@ -0,0 +1,26 @@ +```sh +# Смотрим диски: +cat /proc/mdstat +lsblk + +# Смотрим SMART: +sudo smartctl -a /dev/sda +# смотрим: -Rellocated Sector_ct +# -Current_Pending_Sector + +# Копируем и сохраняем Serial Number плохого диска + +# Открываем в разных окнах: +watch n1 cat /proc/mdstat + +# Говорим что sdc1 в md0 это fail: +sudo mdadm /dev/md0 --fail /dev/sdc1 + +# Удаляем sdc1 из md0: +sudo mdadm /dev/md0 --remove /dev/sdc1 + +sudo vim /etc/mdadm.conf +# В файле нужно указать опцию: +MAILADDR vlapa@ya.ru +``` + diff --git a/LINUX/DISK/WD - автопарковка.md b/LINUX/DISK/WD - автопарковка.md new file mode 100644 index 0000000..43da641 --- /dev/null +++ b/LINUX/DISK/WD - автопарковка.md @@ -0,0 +1,51 @@ +```sh +sudo apt install idle3-tools + +# Чтобы получить текущее значение времени парковки нужно выполнить полученный в процессе сборки файл idle3ctl со следующим параметром: +idle3ctl -g /dev/sda + +# Отключить idle3 таймер: +idle3ctl -d /dev/sda + +# Выставить таймер на 10 секунд: +idle3ctl -s 100 /dev/sda +``` +Дополнительные опции указаны на сайте программы и доступны по ключу -h. + +Чтобы новые значения вступили в действие нужно выключить и включить диск, по-этому после установки нового значения таймера программа попросит вас перезагрузиться. + +Проверено на двух 2TB WD20EARX. Счетчик количества парковок перестал так быстро расти, интенсивность уменьшилась до нескольких парковок в час. + +========================================== +#### Western Digital - отключение автопарковки в дисках серии EARS + +Заглянув в очередной раз в параметры SMART диска Western Digital(WD) серии Green (WD15EARS) увидел аномально высокое значение Load_Cycle_Count: + +manumba ~ # smartctl -A /dev/sdb +... +193 Load_Cycle_Count 0x0032 194 194 000 Old_age Always - 18713 +... + +Для сравнения параметры SMART диска WD серии BLACK: + +manumba ~ # smartctl -A /dev/sdc +... +193 Load_Cycle_Count 0x0032 200 200 000 Old_age Always - 18 +... + +Load_Cycle_Count - показывает количество парковок диска. Почитав информацию в интернете узнал что диски WD серии Green паркуются с целью энергосбережения. +Понаблюдав за параметром Load_Cycle_Count диска WD15EARS обнаружил что он увеличивается на 200 каждые сутки. Это примерно 72000 парковок в год. Согласно данным производителя, предел количества парковок для дисков WD Green находится на отметке около 600 000 парковок, т.е. при данном темпе парковок диску отмерено около 8-9 лет. С одной стороны за это время диск морально устареет, или (все может быть) выйдет из строя по другим причинам. С другой стороны взбредет что то прошивке диска, изменится характер нагрузки на диск, и вдруг диск начнет парковаться с скоростью 1000 парковок в день? Поскольку энергосбережение 10-15 ватт для домашнего компьютера мало значит, думаю выгодней отключить эту странную функцию. + +### Отключение автопарковки + +Для отключения автопарковки нужно использовать утилиту wdidle3 от WD. Утилита для DOS, поэтому лучше скачать какой нибудь LiveCD содержащий эту утилиту, для того что бы удобно запустить оную. Например [HirenBootCD](http://rutracker.org/forum/viewtopic.php?t=3419055 "http://rutracker.org/forum/viewtopic.php?t=3419055"). Итак: + +- Скачиваем [HirenBootCD](http://rutracker.org/forum/viewtopic.php?t=3419055 "http://rutracker.org/forum/viewtopic.php?t=3419055"). +- Выключаем компьютер. +- Отключаем все диски кроме WD15EARS, на котором мы будем отключать автопарковку (это рекомендовано WD и сторонними источниками в интернете). +- Загружаемся в HirenBootCD и выбираем из Hard Disk tools: wdidle3. +- В командной строке набираем wdidle3.exe /D (Эта команда отключит функцию автопарковки) +- Выключаем компьютер. +- Подключаем все остальные диски обратно. +- Включаем BIOS, восстанавливаем порядок дисков в BIOS (иначе первым диском по версии BIOS может оказаться диск на котором отключалась автопарковка, что в случае если порядок дисков был раньше другой, может стать удивительным открытием для GRUB) +- Загружаемся и радуемся отключенной автопарковке.
\ No newline at end of file diff --git a/LINUX/DISK/Автоматическое монтирование USB накопителя в заданную папку.md b/LINUX/DISK/Автоматическое монтирование USB накопителя в заданную папку.md new file mode 100644 index 0000000..0f2b882 --- /dev/null +++ b/LINUX/DISK/Автоматическое монтирование USB накопителя в заданную папку.md @@ -0,0 +1,34 @@ +Этот скрипт автоматически монтирует USB-накопитель в указанную директорию при подключении. Полезен для серверов, киосков и систем с минимальным GUI. + +```sh +#!/bin/bash + +# Точка монтирования +MOUNT_DIR="$HOME/usb_mount" + +# Создаём папку, если её нет +mkdir -p "$MOUNT_DIR" + +# Ищем новое USB-устройство +DEVICE=$(lsblk -o NAME,TYPE,HOTPLUG | awk '$2 == "disk" && $3 == 1 {print $1}' | tail -n1) + +# Если устройство найдено +if [ -n "$DEVICE" ]; then + # Получаем раздел (например, sdb1) + PARTITION="/dev/${DEVICE}1" + + # Монтируем раздел + sudo mount "$PARTITION" "$MOUNT_DIR" + + echo "✅ Устройство $PARTITION смонтировано в $MOUNT_DIR" +else + echo "❌ USB-устройство не найдено" +fi +``` + +🟢 Использует lsblk для поиска подключённых устройств +🟢 Автоматически монтирует первый раздел USB-устройства +🟢 Создаёт директорию монтирования, если она отсутствует. + +➡️ **Скрипт можно запускать вручную или автоматически через** udev**-триггеры.** + diff --git a/LINUX/DISK/Программный raid в Linux.md b/LINUX/DISK/Программный raid в Linux.md new file mode 100644 index 0000000..af8e621 --- /dev/null +++ b/LINUX/DISK/Программный raid в Linux.md @@ -0,0 +1,169 @@ +### Доступны такие режимы: + +- **RAID 0** - позволяет увеличить скорость записи. Все диски массива будут использоваться для записи данных, поэтому их скорости будут складываться. Например, если у вас есть три диска объемом 512 Гб и скоростью записи 200 Мб в секунду, то объединив их в RAID 0 вы получите виртуальный диск объемом 1,5 Тб и максимальной скоростью записи 600 Мб в секунду. +- **RAID 1** - этот режим увеличивает сохранность данных. Во время записи одни и те же данные пишутся параллельно на подключённые диски. В результате у вас будет несколько копий одних и тех же данных. Если один из дисков массива выйдет из строя, система продолжит работать, так как данные есть ещё и на другом диске. Например, если вы объедините два диска по 1 Тб в RAID 1, то получите один виртуальный диск с объемом 1 Тб. +- **RAID 10** - объединяет в себе два предыдущих варианта. Он может состоять как минимум из четырех дисков. В таком случае сначала создаются два массива RAID 1, а поверх них создается массив RAID 0 для увеличения производительности. + +Конечно, существуют и другие режимы работы, но эти самые популярные. В сегодняшней статье мы рассмотрим как создать программный RAID уровней 0 и 1. + +## Создание программного RAID в Linux + +### Шаг 1. Установка mdadm + +Для управления программными RAID массивами в Linux используется утилита mdadm. Для того чтобы установить её в Ubuntu или Debian выполните такую команду: + +`sudo apt install mdadm` + +[](https://losst.pro/wp-content/uploads/2021/06/raid4.png) + +Для установки утилиты в CentOS/Fedora/RedHat необходимо выполнить: + +`sudo yum install mdadm` + +### Шаг 2. Подготовка дисков + +Посмотреть список дисков, подключённых к системе можно с помощью команды lsblk: + +`lsblk` + +[](https://losst.pro/wp-content/uploads/2021/06/raid.png) + +В этой статье я покажу как объединить три диска в RAID на примере дисков **/dev/sda**, **/dev/sdb** и **/dev/sdc**. Сначала необходимо определиться стоит ли размещать RAID непосредственно на диски или на разделы. Лучше выбрать разделы, так как это дает больше гибкости и безопасности. Во первых, операционная система может перезаписать суперблок RAID если он размещён прямо на диске. Во вторых, если вы выделяете весь диск под RAID, то у вас могут возникнуть проблемы при замене диска. Диски одинакового объема, обычно, немного отличаются у разных производителей. Поэтому для замены вам придется искать точно такой же диск с точно таким же реальным объемом. Если же у вас будет раздел, вы просто сможете создать раздел нужного объема. + +Сначала нужно создать таблицу разделов на всех выбранных дисках: + +`sudo parted /dev/sda mklabel msdos` +`sudo parted /dev/sdb mklabel msdos` +`sudo parted /dev/sdc mklabel msdos` + +[](https://losst.pro/wp-content/uploads/2021/06/raid1.png) + +Если на диске уже существует таблица разделов программа предупредит о том, что создание новой сотрёт все данные с диска. После создания таблицы разделов следует создать по разделу на каждом диске. Например, создадим разделы размером 460 гигабайт. Для этого можно воспользоваться той же командой parted: + +`sudo parted /dev/sda mkpart primary ext4 2048 460Gb` +`sudo parted /dev/sdb mkpart primary ext4 2048 460Gb` +`sudo parted /dev/sdc mkpart primary ext4 2048 460Gb` + +[](https://losst.pro/wp-content/uploads/2021/06/raid2.png) + +Теперь диски готовы к размещению на них RAID: + +[](https://losst.pro/wp-content/uploads/2021/06/raid3.png) + +### Шаг 3. Создание RAID 0 + +Для создания RAID массива надо выполнить команду mdadm с опцией --create, указать режим работы массива, количество дисков и сами диски. Синтаксис команды такой: + +**$ sudo mdadm --create /dev/имя_массива --level=режим_работы --raid-devices=количество_устройств список устройств** + +Например: + +`sudo mdadm --create /dev/md0 --level=0 --raid-devices=3 /dev/sda1 /dev/sdb1 /dev/sdc1` + +[](https://losst.pro/wp-content/uploads/2021/06/raid5.png) + +После выполнения этой команды вы увидите раздел raid в lsblk. С этим разделом можно работать как с любым обычным разделом в вашей системе. + +### Шаг 4. Тестирование RAID 0 + +Давайте для примера отформатируем полученный раздел в файловую систему Ext4, смонтируем и попробуем записывать туда файлы: + +`sudo mkfs -t ext4 /dev/md0` +`sudo mount /dev/md0 /mnt` + +Затем можно тестировать скорость с помощью dd: + +`sudo dd if=/dev/zero of=/mnt/file bs=1G count=5` + +[](https://losst.pro/wp-content/uploads/2021/06/raid8.png) + +Как видите, при записи 5 Гб данных мы получаем скорость 400 Мб/сек, это уже на уровне обычного SSD. + +### Шаг 5. Информация о RAID + +Найти информацию обо всех созданных в системе RAID массивах вы можете в файле **/proc/mdstat:** + +`cat /proc/mdstat` + +[](https://losst.pro/wp-content/uploads/2021/06/raid9.png) + +Именно так можно посмотреть RAID Linux. Посмотреть более детальную информацию о массиве /dev/md0 можно с помощью самой утилиты mdadm: + +`sudo mdadm --detail /dev/md0` + +[](https://losst.pro/wp-content/uploads/2021/06/raid10.png) + +Здесь в том числе отображается состояние RAID Linux. Посмотреть детальную информацию о каждом устройстве, которое входит в RAID можно с помощью опции **--examine**: + +`sudo mdadm --examine /dev/sda1 /dev/sdb1 /dev/sdc1` + +[](https://losst.pro/wp-content/uploads/2021/06/raid11.png) + +### Шаг 6. Сохранение RAID массива + +В принципе, уже сейчас RAID массив работает и продолжит работать после перезагрузки, потому что mdadm просканирует все диски, найдёт метаданные массива и построит его. Но неизвестно какое имя программа присвоит полученному массиву и неизвестно все ли параметры будут восстановлены верно. Поэтому конфигурацию массива лучше сохранить. Для этого используйте такую команду: + +`sudo mdadm --detail --scan --verbose | sudo tee -a /etc/mdadm/mdadm.conf` + +[](https://losst.pro/wp-content/uploads/2021/06/raid12.png) + +Затем нужно пересоздать initramfs с поддержкой этого массива: + +`sudo update-initramfs -u` + +С полученным массивом можно обращаться как с обычным разделом диска. Например, для того чтобы автоматически монтировать его в систему добавьте такую строчку в /etc/fstab: + +`sudo vi /etc/fstab` + +`/dev/md0 /mnt/ ext4 defaults 0 0` + +На этом создание raid массива linux завершено. + +### Шаг 7. Переименование RAID массива + +Если вы не выполните предыдущий пункт и перезагрузите компьютер, то можете получить RAID массив с именем md127 вместо md0, такое имя также может быть присвоено второму RAID массиву. Для того чтобы переименовать массив, его придется пересобрать. Для этого сначала остановите существующий массив: + +`sudo mdadm --stop /dev/md127` + +Затем выполните команду переименования. Синтаксис у неё такой: + +**$ sudo mdadm --assemble --update=name --name=номер /dev/md_номер список устройств** + +Например: + +`sudo mdadm --assemble --update=name --name=0 /dev/md0 /dev/sda1 /dev/sdb1 /dev/sdc1` + +После этого следует повторить предыдущий шаг для уже правильного сохранения RAID устройства. + +### Шаг 8. Удаление RAID массива + +Если вы не хотите чтобы ваши диски и дальше были объединены в RAID, его можно удалить. Для этого выполните такую команду: + +`sudo mdadm --remove /dev/md0` + +Она удалит все метаданные с дисков /dev/md0. Дальше останется только удалить или закомментировать секцию данного RAID массива в /etc/mdadm/mdadm.conf + +`sudo vi /etc/mdadm/mdadm.conf` + +[](https://losst.pro/wp-content/uploads/2021/06/raid13.png) +### Шаг 10. Создание массива RAID 1 + +Теперь вы знаете как создать raid linux на примере RAID 0. Давайте ещё разберемся с RAID 1. Для создания RAID 1 используется такая же команда как и для RAID 0, но указывается другой уровень работы массива: + +`sudo mdadm --create /dev/md0 --level=1 --raid-devices=3 /dev/sda1 /dev/sdb1 /dev/sdc1` + +Затем вы можете убедится что RAID создан посмотрев информацию о нём: + +`sudo mdadm --detail /dev/md0` + +Ну и с помощью lsblk можно оценить размер устройства: + +`lsblk` + +[](https://losst.pro/wp-content/uploads/2021/06/raid14.png) + +Как и ожидалось, размер не увеличился, поскольку копии данных будут записываться на все три диска. Теперь давайте посмотрим на скорость: + +[](https://losst.pro/wp-content/uploads/2021/06/raid15.png) + +Скорость записи данных такая же как и у одного диска. Это цена сохранности данных. Если вы отключите один из дисков, то все данные всё равно будут вам доступны.
\ No newline at end of file diff --git a/LINUX/DOCKER.md b/LINUX/DOCKER.md new file mode 100644 index 0000000..0f6c942 --- /dev/null +++ b/LINUX/DOCKER.md @@ -0,0 +1,87 @@ +**Собираем минимальный Docker образ** + +**Docker** — это инструмент для запуска приложений в изолированных контейнерах. Он делает разработку и развертывание проще, быстрее и безопаснее. + +**Нужен он для:** изоляции приложений и зависимостей, переносимости (софтина работает одинаково на любой системе) и DevOps-процессов. + +**Плюсы докера:** простота развёртывания, экономия ресурсов по сравнению с виртуальными машинами, стандартизация окружения. + +**Минусы докера:** не подходит для задач, требующих полной виртуализации, не оптимален для GUI-приложений, требует грамотной настройки безопасности. + +Минимальный пример запуска первого **Docker-контейнера** с **Python** (**FastAPI**): + +1. Для развертывания для начала создадим новый проект и установим зависимости. Если не имели раньше дела с питухоном питоном и докером, также скачиваем все необходимое: +```sh +sudo apt install python3 python3-pip -y +sudo apt install docker.io -y +``` +После установки создаем рабочую папку: + +```sh +mkdir fastapi_backend && cd fastapi_backend +``` + +Активируем виртуальное окружение, чтобы изолировать зависимости проекта и с помощью команды **pip freeze** автоматически сформировать **requirements.txt**, не заполняя его вручную: + +```sh +python3 -m venv venv +source venv/bin/activate +``` +Cкачиваем необходимые для создания API зависимости с помощью пакетного менеджера pip и сохраняем их в **requirements.txt**: + +```sh +pip install fastapi uvicorn +pip freeze > requirements.txt +``` +2. Создадим в каталоге проекта файл "**main.py**" с простым **API**: + +```sh +touch main.py +``` + +```python +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/") +def read_root(): + return {"Hello": "LinuxCamp"} +``` +3. Ну и наконец добавим в локальный каталог сам **Dockerfile** - это инструкция по созданию **docker** контейнера. Cейчас приведу общий пример, в следующих постах подробнее разберем его структуру: +```docker +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install -r requirements.txt + +COPY . . + +CMD ["uvicorn", "main:app", "--host", "127.0.0.1", "--port", "8000"] +``` + +4. На этом все! Мы уже можем собрать свой **Docker-образ** и запустить его, после чего приложение будет доступно по адресу ниже: + +```sh +sudo docker build -t myapp . +sudo docker run -d -p 8000:8000 myapp +``` + +```sh +curl -L http://127.0.0.1:8000 +{"Hello":"LinuxCamp"} +``` +http://127.0.0.1:8000 + +Также можно посмотреть наличие нашего docker контейнера в списке запущенных контейнеров с помощью команды **docker ps**: + +```sh +docker ps + +CONTAINER ID IMAGE +COMMAND +640d6f4d1acb fastapi_backend +"uvicorn main:app --…" +``` diff --git a/LINUX/ENVIRONMENT.md b/LINUX/ENVIRONMENT.md new file mode 100644 index 0000000..85cfb8d --- /dev/null +++ b/LINUX/ENVIRONMENT.md @@ -0,0 +1,30 @@ +Системные переменные окружения: +`/etc/enviroments` +==`echo $PATH`== + +Сессионные переменные пользователя: +`.bashrc` +`export WORKON_HOME = ~/.envs` + +Системный bashrc (только локально): +`/etc/bash.bashrc` + +Системный profile (только для ВСЕХ удаленных): +`/etc/profile` + +Переменные SSH сессии: +`.bash_profile` + +После каждого изменения: +`source file_name` +или выход/вход в сессию + + +Создание с помощью файла: +`vim setenv.sh` +`export VAR1=234` +`export VAR2=hdhdy` +`export VAR3=7564888` + +запускаем файл: +==`./setenv.sh`== diff --git a/LINUX/EXIFTOOL.md b/LINUX/EXIFTOOL.md new file mode 100644 index 0000000..e68a0f3 --- /dev/null +++ b/LINUX/EXIFTOOL.md @@ -0,0 +1,41 @@ +🔍 **ExifTool — универсальный инструмент для анализа и правки метаданных** + +Если ты работаешь с фото, видео, документами или архивами — exiftool поможет тебе **увидеть, изменить или удалить метаданные** в любых файлах. + +💡 **Поддерживает более 130 форматов, включая:** +✔️ JPG, PNG, TIFF +✔️ PDF, DOCX, XLSX +✔️ MP4, MOV, AVI +✔️ ZIP, RAR, ISO +✔️ даже .exe, .torrent и .psd + +📌 **Установка (Debian/Ubuntu):** +`sudo apt install libimage-exiftool-perl + +🔧 **Основные команды:** + +📖 Посмотреть все метаданные файла: +`exiftool файл.jpg + +✏️ Удалить все метаданные: +`exiftool -all= файл.jpg + +🔁 Изменить дату создания: +`exiftool -CreateDate="2024:01:01 12:00:00" файл.jpg + +📝 Переименовать фото по дате съёмки: +`exiftool '-FileName<CreateDate' -d '%Y-%m-%d_%H-%M-%S%%-c.%%e' файл.jpg + +📂 Работа с папкой: +`exiftool -all= -r /путь/к/папке + +👍 **Фишки:** +➡️ Работает не только с медиа: +- вычистит авторов из .docx, +- даты изменения из .pdf, +- геометки из .mp4. +➡️ Умеет извлекать превью, GPS-координаты, версию редактора и многое другое. +➡️ Делает резервную копию автоматически (можно отключить -overwrite_original). + +⚠️ **Важно знать:** +==`ExifTool`== может найти даже те данные, которые не показывает система. Отлично подходит для аудита, тестирования, автоматизации задач.
\ No newline at end of file diff --git a/LINUX/FIND.md b/LINUX/FIND.md new file mode 100644 index 0000000..8ae6c17 --- /dev/null +++ b/LINUX/FIND.md @@ -0,0 +1,156 @@ +```sh +find . # find .. + +find /var/log | less + +# ищем файлы по маске: +find . -name "*.json" +find . -name "f*.json" + +# ищем файлы по маске только с 1 уровнем вложенности: +find . -maxdepth 1 -name "*.json" + +# ищем только файлы: +find . -type f -name "*first*" + +# ищем только папки: +find . -type d -nameo "*first*" + +# ищем файл с исключением папки: +find . -type d -path ./<folderName> -prune -o -name "*.json" - print + +find . -type d -name "*<folderName*"> -prune -o -name "*.json" - print + +# поиск без учета регистра: +find . -iname "*.JSoN" + +# поиск файлов которые были изменены ДО: +find . -type f -atime 0 # =0= суток + +# или ПОСЛЕ: +find . -type f -mtime 0 # =0= суток + +# найти файлы доступ к которому был позднее чем: +find . -type f -anewer <fileName> + +# найти файлы измененные позднее чем: +find . -type f -cnewer <fileName> + +# ищем файлы размером: +find . -type f -size 140M + +# ищем файлы размером больше чем: +find . -type f -size +140M + +# ищем файлы размером меньше чем: +find . -type f -size -140M + +# ищем файлы размером между: +find . -type f -size -140M -a -type f -size +120M + +# отображаем размер файлов с сортировкой по размеру: +find . -type f -exec du -sh {} \; | sort -h | tail -3 # или | head -3 + +# вывести содержимое всех файлов: +find . -type f -exec {} \; + +# вывести содержимое всех файлов с разделением: +find . -type f -exec ls -la {} \; -exec cat {} \; -exec echo "" \; +``` +======================================= +💡 Архивируйте и удаляйте логи старше 7 дней с помощью этой команды: + +`find /var/log -type f -mtime +7 -exec tar -rvf old_logs.tar {} \; -exec rm {} \; + +🌟 Вы также можете запланировать это в cron, чтобы автоматизировать очистку! + +======================================= +⚙️ **Поиск последних измененных файлов в Linux** + +Когда нужно найти недавно измененные файлы, например, для отладки или мониторинга активности, можно использовать команду find. + +**📌 Поиск файлов по времени изменения** + +**🟢 Найти файлы, измененные за последние 24 часа:** + +find /path/to/dir -type f -mtime -1 + +Выведет файлы, измененные за последние сутки. + +**🟢 Найти файлы, измененные за последние 10 минут:** + +find /var/log -type f -mmin -10 + +Полезно для отслеживания активных логов. + +**🟢 Вывести 10 последних измененных файлов:** + +ls -lt | head -10 + +Сортирует файлы по времени изменения и показывает 10 самых свежих. + +🗣 **Эти команды помогают быстро находить файлы, с которыми недавно работали, а также анализировать активность в системе.** + +======================================= +⚙️ **Поиск и удаление пустых файлов и папок в Linux** + +Со временем в системе накапливаются пустые файлы и каталоги, занимая место и создавая беспорядок. Эти команды помогут быстро их найти и удалить. + +**📌 Поиск и очистка пустых файлов и папок** + +**🟢 Найти все пустые файлы в каталоге:** + +find /path/to/dir -type f -empty + +Выведет список всех пустых файлов в указанной директории. + + +**🟢 Удалить все пустые файлы:** + +find /path/to/dir -type f -empty -delete + +Автоматически удалит все найденные пустые файлы. + + +**🟢 Найти все пустые папки:** + +find /path/to/dir -type d -empty + +Выведет список пустых каталогов. + + +**🟢 Удалить пустые папки:** + +find /path/to/dir -type d -empty -delete + +Очищает систему от ненужных пустых директорий. + +➡️ **Автоматическая проверка битых символических ссылок в Linux** +```sh +#!/bin/bash + +# Целевая директория +TARGET_DIR="$HOME" + +# Поиск и удаление битых симлинков +find "$TARGET_DIR" -xtype l -print -delete + +echo "✅ Все битые символические ссылки удалены из $TARGET_DIR" +``` +🟢 Использует find с флагом -xtype l для поиска битых ссылок +🟢 Удаляет только символические ссылки, указывающие на несуществующие файлы. + +➡️ **Можно добавить в crontab для автоматической очистки раз в месяц:** +`0 5 1 * * /path/to/script.sh + +🗣 **Эти команды помогают поддерживать порядок в системе и освобождать место на диске.** + +#========================= +🖥 Команда **find** очень мощная, но многие пользователи не осознают, насколько гибкой может быть опция -exec! + +🌟 Так можно найти и удалить все файлы .log, которым больше 7 дней: +`find /path/to/logs -type f -name "*.log" -mtime +7 -exec rm {} \; + +🌟 А вот так найти и сжать все файлы .txt: +`find /path/to/files -type f -name "*.txt" -exec gzip {} \; + diff --git a/LINUX/Fail2ban - Основы защиты серверов.md b/LINUX/Fail2ban - Основы защиты серверов.md new file mode 100644 index 0000000..6f7e476 --- /dev/null +++ b/LINUX/Fail2ban - Основы защиты серверов.md @@ -0,0 +1,42 @@ + +**Fail2ban** — это инструмент, который автоматически блокирует IP-адреса, совершающие подозрительную активность (например, попытки подбора паролей). Он анализирует логи и применяет временные баны через брандмауэр. + +`sudo apt install fail2ban + +⚙️ **Базовая настройка** + +1. Создадим копию конфига: +`sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local + +2. Отредактируем `jail.local`. Он поделён на секции, так называемые «изоляторы» (jails), каждая секция отвечает за определённый сервис и тип атаки. Добавим сервис, который нужно защищать, например SSH: +```sh +[DEFAULT] +ignoreip = 127.0.0.1/8 +bantime = 600 +maxretry = 3 +banaction = iptables-multiport + +[ssh] +enabled = true +port = ssh +filter = sshd +logpath = /var/log/auth.log +maxretry = 6 +``` +• maxretry: количество неудачных попыток входа. +• bantime: время блокировки IP. + +Секция [ssh] отвечает за защиту SSH от повторяющихся неудачных попыток авторизации на SSH–сервере, проще говоря, «brute–force». + +Параметры из секции [DEFAULT] применяются ко всем остальным секциям, если не будут переопределены. + +3. После настройки, запускаем службу: +`sudo systemctl start fail2ban +`sudo systemctl enable fail2ban + +🔐 **Проверка работы** +• Посмотреть заблокированные IP: +`fail2ban-client status + +• Увидеть детали конкретного сервиса: +`fail2ban-client status ssh diff --git a/LINUX/GPG, PASS.md b/LINUX/GPG, PASS.md new file mode 100644 index 0000000..3720f0f --- /dev/null +++ b/LINUX/GPG, PASS.md @@ -0,0 +1,276 @@ +```sh +sudo su +apt install gpg + +gpg —full-gen-key + +# ставим 4096 бит +# 0 - ключ бессрочный +# y +# Real name: vlapa +# Email address: vlapa@ya.ru +# Comment: My Keys ! +# o +# Passphrase: `xxxxxxxxxx` +# мышь, клавиатура +``` + +```sh +echo "keyid-format 0xlong +> throw-keyids +> no-emit-version +> no-comments" > ~/.gnupg/gpg.conf + +gpg -k - публичный ключ +gpg -K - приватный ключ + +cd /tmp +echo "Hello World !" > /tmp/file.txt +ls +file.txt +cat file.txt +Hello World ! + + +# шифруем: +gpg -e -a -r vlapa@ya.ru file.txt + +# расшифровываем: +gpg -d -o file.txt file.txt.asc + +======================================== +# экспортируем: +gpg —export -a vlapa@ya.ru > pubkey.gpg +gpg —export-secret-key -a vlapa@ya.ru > seckey.gpg + +# Удаляем: +gpg —delete-secret-keys vlapa@ya.ru +gpg —delete-keys vlapa@ya.ru + +# импортируем: +gpg —import pubkey.gpg +gpg —import seckey.gpg + +# делаем доверенным: +gpg --edit-key <email_при_создании_ключа> +# - trust +# - 5 +# - Y +# - quit + +# Если нужно изменить пароль GPG-ключа (не содержимое записи) + +# Если вы сменили пароль самого GPG-ключа, но `pass` по-прежнему запрашивает старый, выполните: + +gpg --edit-key "Ваше_имя_или_email" + +# Затем в интерактивном меню введите: +passwd +save + +# Где найти имя ключа? +# Посмотрите в файле: +~/.password-store/.gpg-id +# там указано имя или ID ключа, используемого pass. + +======================================== +``` + +```sh +sudo apt install pass +which pass +pass init vlapa@ya.ru + +cd /root/.password-store/ +ls -la ~/.password-store/ + +# Добавить запись: +pass insert -m Email/mail.ru +конец - < ctrl+d > + +# Удалить запись: +pass rm Email/mail.ru + +# Редактировать запись: +pass edit Email/mail.ru + +# Система откроет файл в текстовом редакторе (по умолчанию заданный в переменной окружения $EDITOR или $VISUAL). + + +# Посмотреть запись: +pass Email/ya.ru + +# Помощь: +pass —-help | less + +# Сгенерировать: +pass generate email/vlapa.ru 25 + +# Получить пароль: +pass -c Email/ya.ru +# Будет предложено ввести мастер пароль Pass. Пароль будет находиться в буфере в течении 45 секунд. После чего буфер обмена будет очищен. + +# Если просто необходимо отобразить сохраненный пароль, просто удалите `-c` + +# Изменить данные пароля: +pass edit Email/ya.ru + +# =============================== + +pass git init +pass git remote add origin git@10.10.10.2:~/git/pass +pass git push +# commit делать не надо! +``` + +```sh +# Заменить первую строку в записи pass новым сгенерированным паролем, оказывается, можно так: + +pass generate -i Email/my@email.com 20 +``` + +============================================ +## Шифрование файлов при помощи GPG + +GNU Privacy Guard – это и есть GPG – свободная реализация шифрования данных для их защиты. Это очень актуально, ведь информацию, если она персональная, конфиденциальная или просто секретная необходимо защищать. Технология GPG основана на защите данных при помощи ключей шифрования, которые изначально должны быть созданы, а также соответствующим образом распространены между участниками обмена защищаемыми данными. + +Как работает защита данных с помощью ключей GPG + +Для того, чтобы стороны могли передавать друг другу зашифрованные данные, должен быть выполнен ряд условий: + +- наличие ключей шифрования GPG, которые представляют собой пару ключей — открытый и закрытый; + +- обе стороны должны обменяться открытыми ключами; + +- передающая сторона должна зашифровать данные с помощью + + открытого ключа, полученного от принимающей стороны при обмене + + этими ключами; + +- принимающая сторона при получении шифрованных данный должна + + расшифровать их своим закрытым ключом, открытой частью которого (т. е. открытым ключом) были зашифрованы данные передающей стороной. + + Отсюда вытекают два важных момента: + + +- расшифровать данные можно только закрытым ключом, который должен храниться у его владельца в надёжном и недоступном для посторонних месте; + +- данные всегда шифруются именно ключом адресата, т. е. того, кому нужно передать зашифрованные данные. + + Открытые ключи распространяются свободно. Ими можно обмениваться непосредственно, либо загружать на специальные сервера для хранения и обслуживания ключей GPG. + + Также необходимо иметь специальный сертификат для отзыва открытых ключей, чтобы сделать их недействительными. Кроме того механизм защиты данных по ключам позволяет подписывать отправляемые данные закрытым ключом, т. е. гарантировать, что эти данные отправлены именно тем, кто указан как отправитель. А соответствующий этому закрытому ключу открытый ключ, имеющийся у принимающей стороны может это подтвердить. Таким образом и достигается достоверная идентификация пользователей и надёжная защита передаваемых данных. + + Установка системы GPG в Linux + + Практически во всех популярных дистрибутивах Linux установить систему GPG позволяет система управления пакетами (СУП) этого дистрибутива. Например, для Ubuntu: + + `$ sudo apt install gnupg + + Наименования пакетов также могут различаться в зависимости от используемого дистрибутива, так для CentOS: + + `$ sudo yum install gnupg2 + + Теперь можно приступить к созданию самих ключей. + + Генерация и подпись ключей + + Для генерации и управления ключами системы GPG предназначена команда gpg. Более подробно с ней можно ознакомиться на страницах интерактивного руководства, используя команду man gpg. Для создания же, собственно, пары (открытый и закрытый) ключей нужно выполнить команду gpg с опцией —gen-key: + + +`$ gpg --gen-key + +В ходе выполнения команды у пользователя будут запрошены некоторые персональные данные, такие как полное имя и e-mail, необходимо их ввести, после чего начнётся генерация ключей, которая может занять некоторое время. После чего будет выведена информация о созданных ключах: + +`gpg (GnuPG) 2.2.4; Copyright (C) 2017 Free Software Foundation, Inc. +`This is free software: you are free to change and redistribute it. +`There is NO WARRANTY, to the extent permitted by law. +Замечание: "gpg --full-generate-key" вызывает полнофункциональный диалог создания ключа. + +GnuPG должен составить идентификатор пользователя для идентификации ключа. +Ваше полное имя: John Silver +Адрес электронной почты: john@silver.ru + +Вы выбрали следующий идентификатор пользователя: +"John Silver <john@silver.ru>" +Сменить (N)Имя, (E)Адрес; (O)Принять/(Q)Выход? O +Необходимо получить много случайных чисел. Желательно, чтобы Вы +в процессе генерации выполняли какие-то другие действия (печать +на клавиатуре, движения мыши, обращения к дискам); это даст генератору случайных чисел больше возможностей получить достаточное количество энтропии. + +Необходимо получить много случайных чисел. Желательно, чтобы Вы +в процессе генерации выполняли какие-то другие действия (печать +на клавиатуре, движения мыши, обращения к дискам); это даст генератору случайных чисел больше возможностей получить достаточное количество энтропии. +gpg: ключ 49192168EGELF50S помечен как абсолютно доверенный +gpg: создан каталог '/home/john/.gnupg/openpgp-revocs.d' +gpg: сертификат отзыва записан в '/home/john/.gnupg/openpgp- revocs.d/FFBD0C66CBA22A8739ACB07949192168EGELF50S.rev'. +открытый и секретный ключи созданы и подписаны. + +`pub rsa3072 2019-03-27 [SC] [годен до: 2021-03-26] FFBD0C66CBA22A8739ACB07949192168EGELF50S uid John Silver <john@silver.ru> +`sub rsa3072 2019-03-27 [E] [годен до: 2021-03-26] + +Здесь pub – это открытый ключ, значение «FFBD0C66CBA22A8739ACB07949192168EGELF50S» — его отпечаток, uid – идентификатор, состоящий из полей имени и e-mail, пользователя-владельца ключа. Этот идентификатор нужно использовать при указании ключа, а также + +для обозначения адресата, которому предназначается сообщение (данные), зашифрованное этим ключом. Можно указывать любое из полей. В нижеследующих примерах будет указываться email. + +Для созданных ключей также автоматически будет сгенерирован и сертификат отзыва. Либо это можно сделать вручную: + +`$ gpg --output ~/revocation.crt --gen-revoke your_email@address.ru + +Созданный сертификат revocation.crt нужно хранить также надёжно, как и закрытые ключи. При создании сертификата отзыва нужно указать возможную причину отзыва ключа, а также комментарий к отзыву. Эту информацию будут видеть другие пользователи систем управления ключами GPG. Опция —output служит для вывода сертификата отзыва в файл, в данном случае revocation.crt. + +Очень важно также подписывать ключи, которые были получены от других пользователей, если достоверно известно, что это именно те пользователи, за которых они себя выдают и ключи принадлежат действительно им. Это часто происходит при импорте открытых ключей других пользователей. После проверки их (и ключей и их пользователей-владельцев) очень желательно эти ключи подписать: + +`$ gpg --sign-key other_user@address.ru + +После подписи импортированного ключа нужно дать знать об этом его владельцу, чтобы он мог использовать этот ключ (ведь он подписанный) как аргумент для других пользователей, которые буду видеть, что его уже кто-то «одобрил» и доверяет ему. Для этого подписанный ключ экспортируется в файл и отправляется обратно его владельцу. + +Импорт и экспорт ключей + +Для того, чтобы экспортировать, например подписанный (в предыдущей главе) ключ нужно выполнить команду: + +`$ gpg –-output ~/signed.key –-export –-armor other_user@address.ru + +Опция —armor создаёт вывод в виде символов ASCII, т. е. в виде текста. По- умолчанию (если эту опцию не указывать) gpg создаст двоичный вывод. Теперь полученный файл с подписанным ключом можно отправлять его владельцу. Он в свою очередь может его импортировать себе в GPG, а также внести некоторую информацию о подписи: + +`$ gpg --import ~/signed.key + +Таким образом происходит, собственно обмен открытыми и подписанными ключами между пользователями. + +Если нужно перенести свой секретный ключ куда-либо (для использования на других компьютерах), то нужно использовать опцию —export-secrert-key: + +`$ gpg --output mygpgkey_private.txt --armor --export-secret-key my_email.address.ru + +Чтобы теперь импортировать этот секретный ключ на другом компьютере, нужно воспользоваться опцией —allow-secret-key-import: + +`$ gpg --allow-secret-key-import --import mygpgkey_private.txt + +Шифрование и дешифровка данных + +Чтобы зашифровать данные в GPG используется опция —encrypt: + +`$ gpg --recipient other_user@address.ru --encrypt somefile + +Здесь какой-либо файл somefile зашифруется открытым ключом пользователя, идентифицируемого с помощью его e-mail (other_user@address.ru) и только он сможет расшифровать этот файл своим закрытым ключом: + +`$ gpg --decrypt-files somefile.gpg + +Как можно видеть, при шифровке на выходе получаются файлы с расширением *.gpg. + +Проверка подлинности ключа пользователя + +Каким образом можно узнать, что пользователь, предоставивший свой открытый ключ действительно тот, за кого себя выдаёт, т. е. подлинный владелец соответствующего закрытого ключа? + +Для этого существует отпечаток ключа, который можно получить с помощью опции —fingerprint: + +`$ gpg --fingerprint john@silver.ru +`pub rsa3072 2019-03-27 [SC] [годен до: 2021-03-26] FFBD 0C66 CBA2 2A87 39AC B079 4919 2168 EGEL F50S uid [ абсолютно ] John Silver <john@silver.ru> +`sub rsa3072 2019-03-27 [E] [годен до: 2021-03-26] + +Значение «FFBD 0C66 CBA2 2A87 39AC B079 4919 2168 EGEL F50S» нужно сверять с соответствующим значением, полученным таким же образом, но на стороне пользователя, приславшего этот ключ. Значения должны быть идентичны. + +Заключение + +В заключение необходимо отметить некоторую сложность для быстрого понимания самого механизма защиты GPG. Которая главным образом заключается в предварительном обмене ключами между взаимодействующими сторонами. Механизм GPG становится довольно просто использовать на практике. Если хотя бы пару раз выполнить обмен шифрованными данными, тем более, что это очень надёжно.
\ No newline at end of file diff --git a/LINUX/GREP.md b/LINUX/GREP.md new file mode 100644 index 0000000..47619ef --- /dev/null +++ b/LINUX/GREP.md @@ -0,0 +1,392 @@ +```sh +# Найти точное соответствие +Для того чтобы искать соответствия только целым словам, используйте флаг "-w": +ls +'test script.sh' test test_1 test_2 +ls | grep -w test +test script.sh +test + + +# Аналогичного результата можно добиться через регулярное выражение: +ls | grep "\<test\>" +test script.sh +test + + +# "\<" — начало слова. +# "test" — искомый текст. +# "\>" — конец слова. + +# Угловые скобки должны экранироваться, иначе они будут интерпретироваться как простые символы. + +# Игнорировать регистр + +# По умолчанию, grep чувствительна к регистру, что немного мешает поиску. Проблема решается путем добавления ключа "-i" к команде: +ls | grep -i new +newFile +NewFile + + +#Вывод файлов с совпадениями. Используйте параметр "-l", чтобы вывести не сами строки, а только пути к файлам, в которых они содержатся: +grep -r -l "libnma" /usr/share +/usr/share/doc/libnma-gtk4-0/copyright + + +# Параметр "-r" отвечает за рекурсивный поиск - grep прошерстит не только 1 уровень каталога, а пойдет вглубь. + +# Поиск по нескольким параметрам. Для того, чтобы передать команде несколько вводных данных для определения совпадений, используется флаг "-e" перед каждым объектом поиска: +ls | grep -i -e new -e old +NewFile +newFile +oldFile +# Эту команду система понимает, как "или-или" и выводит все вхождения указанных слов. + +# Использование регулярных выражений. +# Настоящая сила grep проявляется, когда вы переходите от сопоставления простых строк к сопоставлению шаблонов - регулярных выражений. + +# Выражения бывают 2 типов: basic и extended. Одна из особенностей второго типа заключается в том, что некоторые символы требуют экранирования для сохранности фич: ?, +, {, |, (, ), <, >. + +# Но можно обойтись просто добавлением флага "-E": +ls | grep -E "script|test" + + +# Квадратные скобки "[]" используются, чтобы провести проверку на соответствие одному из указанных символов: +ls | grep "test[23]" +test2 +test3 + + +# Можем указать "-" для того, чтобы использовать диапазон значений: +ls | grep "test[1-3]" +test2 +test3 + + +# Каретка "^" используется для поиска строк, которые начинаются с указанного шаблона. Давайте найдем совпадение всех строк, начинающихся с заглавной буквы: +grep "^[A-Z]" myfile +A lot of text +Hello world + + +# Знак доллара "$" означает конец строки. В результат входят только те элементы, конец которых подходит под шаблон: +grep "world$" myfile +Hello world +hello world + + +# Точка "." обозначает любой одиночный символ, кроме новой строки. Регулярное выражение "....." обозначает любую строку, содержащую минимум 5 символов: +grep '.....' myfile + + +# Вертикальная черта "|" выполняет роль логического оператора "ИЛИ": +ls | grep "script\|test" +script +test + + +# В общем, регулярные выражения прекрасны и многообразны. Детальнее ознакомиться с ними можете по ссылке. + +# Экранирование +# Иногда выражения приводят к неожиданным результатам. +# Предположим, вы хотите найти в файле "myfile" только те строки, которые заканчиваются на ".sh". + +# Следующая команда выдаст неверный результат, поскольку точка — это регулярное выражение, означающее «любой символ»: +grep ".sh" myfile +script_name.sh +script_namesh + + +# Чтобы обойти эту проблему, требуется экранировать спецсимвол: +grep "\.sh" myfile +script_name.sh + + +# Такое решение иногда становится громоздким. К счастью, можно заставить grep забыть о регулярных выражениях и искать буквально то, что мы указываем. + +# Для этого используется параметр (fixed) "-F": +grep -F ".sh" myfile +script_name.sh + +``` + +**Полезные опции для поиска файлов через grep** + +Иногда нужно найти один/несколько файлов, содержащих определённую строку в: логах, конфигах или исходном коде. Для этого очень часто под рукой оказывается утилита **grep**. Для сводки, интересные флаги и регулярные выражения для grep мы рассматривали тута. + +По умолчанию команда фильтрует один файл. Для того чтобы выполнять поиск по каталогу, нужно добавить флаг "**-r**": + +```sh +grep -r "root" /etc/ +/etc/mdadm/mdadm.conf:MAILADDR root +``` + +Если хотим подсветить вхождения цветом, используем опцию "**--color=always**": +```sh +grep --color=always -r "root" /etc/ +``` + +Опции "-**C, -B, -A**" вообще имбовые, использую их целую неделю) Вы можете включить отображение не только текущей строки, в которой было найдено вхождение, но и **n** строк **до/после**: + +1) **-C**: выводит n строк до и после вхождения; + +```sh +grep -C2 -r --color=always "Hello3" ./ +./hello-Hello1 +./hello-Hello2 +./hello:Hello3 +./hello-Hello4 +./hello-Hello5 +``` + +1) **-B**: выводит n строк перед вхождением; +```sh +grep -B2 -r --color=always "Hello3" ./ +./hello-Hello1 +./hello-Hello2 +./hello:Hello3 +``` + +1) **-A**: выводит n строк после вхождения; + +```sh +grep -A2 -r --color=always "Hello3" ./ +./hello:Hello3 +./hello-Hello4 +./hello-Hello5 +``` + +Если наш шаблон поиска походит на регулярное выражения, нужно использовать флаг "**-F**". Таким образом мы говорим команде искать ровно то, что мы запросили - без скрытых подтекстов. + +Без флага мы получим строки, в которых присутствует хотя бы 1 символ из набора (**H, e, l, o**). С флагом получим строки с буквальным совпадением: + +```sh +grep "[Hello]" hello +Help me please +Helloup +[Hello] +``` + +```sh +grep -F "[Hello]" hello +[Hello] +``` +==================================================== +Команды `grep`, `egrep`и `fgrep`являются мощными утилитами для текстового поиска в Linux. Они позволяют пользователям выполнять поиск в файлах или выводе по шаблонам текста с использованием регулярных выражений (regex). В статье мы углубимся в эти команды, их расширенное использование и то, чем они отличаются. + +**grep** расшифровывается как **Global Regular Expression Print** . Утилита ищет шаблоны в файлах или входных данных и печатает строки, соответствующие шаблону. + +### Начнем с простого: базовый синтаксис и флаги grep: + +`grep [OPTIONS] PATTERN [FILE...] + +### Популярные опции: + +- `-i`: Поиск без учета регистра. +- `-v`: Обратное соответствие (показать строки, не соответствующие шаблону). +- `-r`: Рекурсивный поиск файлов в каталогах. +- `-l`: Показывать только те имена файлов, которые содержат совпадения. +- `-c`: Показать количество совпадающих строк. +- `-n`: Показывать номера строк с соответствующими строками. +- `--color`: Выделение соответствующих шаблонов в выходных данных. +- `-f`опция считывает шаблоны из указанного файла +- `-F`пропускает механизм регулярных выражений, это быстрее, чем использование обычного`grep` + +**Примеры:** + +Поиск без учета регистра: поиск "error" в файле, игнорируя регистр +`grep -i "error" /var/log/syslog + +Рекурсивный поиск в каталоге: поиск шаблона "disk" во всех файлах в /etc/, включая подкаталоги: +`grep -r "disk" /etc/ + +Отображение номеров строк: отображение строк, содержащих "bash" в ~/.bashrc, включая номера строк +`grep -n "bash" ~/.bashrc + +Обратное совпадение: отображение всех строк, не содержащих "root", в /etc/passwd +`grep -v "root" /etc/passwd + +Поиск по нескольким шаблонам с -E: поиск строк, содержащих либо "warn", либо "error" +`grep -E "warn|error" /var/log/syslog + +`egrep`: расширенный `grep`с увеличенной мощностью + +**egrep** является сокращением от **Extended GREP** . По сути, это то же самое, что и `grep -E`, то есть утилита использует расширенные регулярные выражения, которые поддерживают более продвинутые функции регулярных выражений, такие как `+`, `?`, `|`, и скобки без необходимости их экранирования. + +Найти строки, содержащие "error" или "fail" без экранирования | +`egrep "error|fail" /var/log/syslog + +Эквивалентно: +`grep -E "error|fail" /var/ log /syslog + +Найти строки с "color" или "colour" +`egrep "colou?r" file.txt + +Расширенные регулярные выражения — шаблоны соответствия: найти строки, где появляется "log", за которым следует 1 или более цифр +`egrep "log[0-9]+" logfiles.txt + +Расширенные шаблоны grep: находим то, что другие не могут найти: + +1. Мониторинг системных журналов в режиме реального времени + +Вы можете использовать `grep` совместно с`tail`для динамического мониторинга журналов и фильтрации определенных шаблонов: + +`tail -f /var/log/syslog | grep --color=auto -E "error|warn|critical" + +- **Пояснение:** в примере выше выполняется отслеживание `/var/log/syslog`в реальном времени и фильтрует строки, содержащие «error», «warn» или «critical». Опция `--color`выделяет совпадения. + +2. Поиск и замена шаблонов в нескольких файлах + +Используйте `grep`совместно с `sed`для замены шаблонов в файлах. Это полезно для массовых обновлений в нескольких файлах конфигурации. + +`grep -rl "old-string" /path/to/directory | xargs sed -i 's/old-string/new-string/g' + +- **Пояснение**: в примере выше выполняется рекурсивный поиск ( `-r`) всех файлов, содержащих «old-string», и передача их для `sed`замены на «new-string» ( `-i`для редактирования файлов на месте). + +3. Извлечение определенных данных из больших логов + +Для извлечения определенных типов информации, например IP-адресов или временных меток из файла лога, можно использовать расширенные регулярные выражения. + +**Пример:** извлечение всех IP-адресов из файла лога: + +`grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" /var/log/apache2/access.log + +- **Пояснение**: в примере выше используется `-oE`для сопоставления IP-адресов (формат адреса IPv4, соответствующий регулярному выражению) и их вывода. + +**Пример:** извлечение всех временных меток из файлов журнала: + +`grep -oE "\[([0-9]{2}/[A-Za-z]+/[0-9]{4}):([0-9]{2}:[0-9]{2}:[0-9]{2})" /var/log/apache2/access.log + +- **Пояснение** : в примере выше извлекаются временные метки лога Apache в формате `[dd/Mon/yyyy:hh:mm:ss]`. + +4. Аудит прав доступа к файлам + +Если вам нужно выполнить аудит безопасности, вы можете использовать `grep`для поиска файлов с определенными разрешениями. + +**Пример:** найти все файлы с разрешением `777` в каталоге и его подкаталогах: + +`find / -type f -perm 777 2>/dev/null | grep --color=auto "^" + +- **Пояснение** в примере выше утилита`find`ищет файлы с разрешениями `777`и направляет вывод в `grep`для их дополнительного выделения цветом. + +5. Поиск файлов на основе метаданных (размер, время, владелец) + +`grep`может использоваться в сочетании с `find`для поиска файлов на основе более конкретных метаданных, таких как время изменения файла, размер и владельцы. + +**Пример:** найти и отобразить файлы размером более 100 МБ со словом «ERROR»: + +`find /var/log/ - type f -size +100M - exec grep -H "ERROR" {} \; + +- **Пояснение**: этот метод использует `find`для поиска файлов размером более 100 МБ в `/var/log/`,а `-exec`запускает `grep`для поиска строк, содержащих «ERROR». + +**Пример:** найти файлы, измененные за последние 7 дней, и выполнить поиск по слову «failure»: + +`find /etc/ -type f -mtime -7 -exec grep -H "failure" {} \; + +- **Пояснение**: `find`ищет файлы, измененные за последние 7 дней, и передает их `grep`для поиска «failure». + +6. Параллельный grep для повышения производительности + +При поиске в больших каталогах с большим количеством файлов параллельный запуск `grep` может значительно повысить производительность. + +**Пример:** параллельный поиск строки «timeout» в большом каталоге: + +`find /path/to/dir -type f | xargs -P 4 grep "timeout" + +- **Пояснение**: `xargs -P 4`запускает параллельно 4 процесса `grep`, ускоряя поиск. + +7. Поиск множественных сложных шаблонов + +Вы можете использовать `grep`для поиска несколько сложных шаблонов, используя расширенные регулярные выражения. + +**Пример:** поиск строк, содержащих «error» с последующим числом или любую строку с «timeout»: + +`grep -E "error[[:digit:]]+|timeout" /var/log/syslog + +- **Пояснение**: этот подход позволяет найти строки, в которых после слова «error» следует одна или несколько цифр, или строки, содержащие слово «timeout». + +**Пример:** найти в логах строки, содержащие либо «failed login», либо «successful login»: + +`grep -E "(failed|successful) login" /var/log/auth.log + +- **Пояснение**: здесь используется расширенное регулярное выражение для сопоставления «failed login» или «successful login». + +8. Поиск в двоичных файлах и игнорирование непечатаемых символов + +`grep`может использоваться для поиска строк в двоичных файлах, игнорируя непечатаемые символы. + +**Пример:** поиск строки «password» во всех двоичных файлах: + +`grep -a "password" /usr/bin/* + +- **Пояснение**: опция `-a` обрабатывает двоичные файлы как текстовые файлы, позволяя искать понятные человеку строки внутри двоичных файлов. + +9. Фильтрация и подсчет уникальных вхождений + +Вы можете использовать `grep` совместно с утилитами`sort`и `uniq`для фильтрации и подсчета уникальных шаблонов, таких как IP-адреса, имена пользователей или ошибки. + +**Пример:** подсчет уникальных IP-адресов, обращающихся к веб-серверу: + +`grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" /var/log/apache2/access.log | sort | uniq -c | sort -nr + +- **Пояснение**: эта команда извлекает IP-адреса из access-лога, сортирует их, подсчитывает уникальные вхождения, а затем сортирует их по частоте. + +**Пример:** найти наиболее частые типы ошибок в файле лога: + +`grep "ERROR" /var/log/syslog | awk '{print $5}' | sort | uniq -c | sort -nr + +- **Пояснение**: команда выше извлекает типы ошибок (предполагая, что 5-е поле — это тип ошибки), подсчитывает уникальные ошибки и сортирует их по частоте. + +### Повышение производительности: ускорьте поиск с помощью оптимизации grep + +10. Объедините grep с другими инструментами автоматизации + +**Пример:** отправка оповещения, если в журналах появляется определенная ошибка + +Вы можете создать автоматизированную систему оповещений на основе определенных шаблонов в логах, используя `grep`. + +`grep -i "critical error" /var/log/syslog | mail -s "Critical Error Found" admin@example.com + +- **Пояснение**: здесь выполняется поиск «critical error» в системном логе и, если она найдена, администратору отправляется электронное письмо с соответствующими строками. + +11. Извлечение определенных данных из диапазона строк + +Используя `grep`в сочетании с `sed`или `awk`, вы можете извлечь определенные данные из диапазона строк. + +**Пример:** извлечение линий между двумя шаблонами: + +`sed -n '/START_PATTERN/,/END_PATTERN/p' file.txt | grep "important_string" + +- **Пояснение** : в примере выше извлекаются строки между `START_PATTERN`и `END_PATTERN`и передаются в`grep`для поиска `important_string`. + +12. grep с удаленных серверов с использованием SSH + +Вы можете удаленно выполнять поиск в журналах или файлах на другом сервере, используя SSH и `grep`. + +**Пример:** Поиск «error» в удаленных журналах: + +`ssh user@remote_server 'grep "error" /var/log/syslog' + +- **Пояснение**: в примере выше выполняется команда `grep` на удаленном сервере через SSH. + +13. Найти выполненные команды в истории Bash + +Чтобы отследить конкретную команду, использовавшуюся пользователями в прошлом, вы можете выполнить поиск в файлах истории bash. + +**Пример:** поиск всех выполненных команд `mount` в истории всех пользователей: + +`grep "mount" /home/*/.bash_history + +- **Пояснение**: команда выше проверяет файлы истории bash всех пользователей на предмет выполнения ими команды `mount`. + +14. Расширенное сопоставление с образцом с отрицательным просмотром + +`grep`не поддерживает просмотр вперед напрямую, но вы можете добиться похожего поведения с помощью креативного регулярного выражения. + +**Пример:** сопоставьте строки, содержащие «error», но не содержащие «network error»: + +`grep -E "error" file.txt | grep -v "network error" + +- **Пояснение** : сначала команда находит все строки, содержащие «error», а затем исключают строки, содержащие «network error». + +Эти расширенные варианты использования демонстрируют универсальность `grep`, `egrep`, и `fgrep`при применении в сложных случаях. Независимо от того, анализируете ли вы логи, ищете в больших наборах данных или автоматизируете административные задачи, эти мощные инструменты помогут вам эффективно найти именно то, что вам нужно.
\ No newline at end of file diff --git a/LINUX/GRUB и параметры ядра в Linux.md b/LINUX/GRUB и параметры ядра в Linux.md new file mode 100644 index 0000000..1b8ace7 --- /dev/null +++ b/LINUX/GRUB и параметры ядра в Linux.md @@ -0,0 +1,147 @@ +GRUB нужен, если: +- Нужно загрузить систему с особыми параметрами. +- Нужно добавить поддержку нескольких ОС в меню загрузки. +- Надо включить/выключить режим отладки ядра. +- Любите жить опасно и копаетесь в экспериментальных функциях Linux. +### Структура конфигурации GRUB: + +**Основной конфиг:** `/etc/default/grub` +Этот файл — первая остановка при настройке GRUB. Открываем: +```sh +sudo nano /etc/default/grub +``` +Пример стандартного конфига: +``` +GRUB_DEFAULT=0GRUB_TIMEOUT=5GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"GRUB_CMDLINE_LINUX="" +``` + +Обозначение параметров: +- `GRUB_DEFAULT=0` — загружается первая запись в меню GRUB. +- `GRUB_TIMEOUT=5` — пауза в секундах перед автозагрузкой. +- `GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"` — параметры, которые передаются ядру (по умолчанию он загружается без лишнего вывода). +- `GRUB_CMDLINE_LINUX` — дополнительные параметры, которые работают во всех режимах. + +Это далеко не все параметры. Ниже будет спойлер со всеми параметрами. +Изменения в файле **не вступят в силу сами по себе**. После редактирования надо обновить конфиг: +```sh +sudo update-grub +``` +Бывает, что система не загружается, но есть время на спасение. В такие моменты жмём `e` в меню GRUB, находим строку вида: +``` +linux /boot/vmlinuz-5.15.0-52-generic root=UUID=xxxx-xxxx-xxxx-xxxx ro quiet splash +``` +И меняем параметры ядра прямо в загрузчике. Например, если система зависает из‑за ACPI, можно добавить: +``` +acpi=off +``` +После чего грузимся с помощью `Ctrl + X` или `F10`. Эти изменения действуют **только для текущей загрузки**. + +Все параметры GRUB: +Если уже загрузились и хотите узнать, какие параметры применились: +``` +cat /proc/cmdline +``` +### Применение + +Несколько вариантов применения: +Если провайдер не поддерживает IPv6, а система продолжает упрямо пытаться его использовать: +``` +GRUB_CMDLINE_LINUX_DEFAULT="quiet splash ipv6.disable=1" +``` +Обновляем GRUB, перезагружаем систему. + +Если вам нужно больше информации при загрузке системы: +``` +GRUB_CMDLINE_LINUX_DEFAULT="debug ignore_loglevel" +``` + +После этого получите максимально подробный лог загрузки. +Если у вас простая система, можно попробовать грузиться без `initrd`: +1. В `/etc/default/grub` добавляем: + ``` + GRUB_CMDLINE_LINUX_DEFAULT="noinitrd" + ``` +2. Пересобираем загрузочный образ: + ``` + sudo update-initramfs -u + ``` +3. Обновляем GRUB и перезагружаемся. +### Безопасность GRUB + +Если не хотите, чтобы кто‑то случайно (или специально) изменил параметры ядра: +1. Создаём хеш пароля: + ``` + sudo grub-mkpasswd-pbkdf2 + ``` +2. Открываем файл `/etc/grub.d/40_custom`: + ``` + sudo nano /etc/grub.d/40_custom + ``` +3. Добавляем: + ``` + set superusers="admin"password_pbkdf2 admin grub.pbkdf2.sha512.10000.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + ``` +4. Обновляем GRUB: + ``` + sudo update-grub + ``` +Теперь при попытке редактирования параметров загрузки GRUB попросит пароль. +### GRUB Modules + +GRUB умеет очень многое, но многие даже не подозревают, что можно подгружать **модули** прямо в загрузчике. + +Загрузка по сети через PXE: +``` +insmod pxeinsmod netinsmod tftpset net_pxe_ifname=eth0set net_pxe_ip=192.168.1.100set net_pxe_server=192.168.1.1set net_pxe_gateway=192.168.1.1set root=(tftp,192.168.1.1)/ +``` +Можно добавить другие полезные модули: +- `lvm` — для работы с логическими томами. +- `cryptodisk` — для расшифровки зашифрованных дисков перед загрузкой. +- `usb` — если грузимся с USB‑диска. +### Кастомизация экрана загрузки GRUB + +Стандартное меню GRUB скучное? +1. Кладём красивую картинку в `/boot/grub/` (лучше PNG 1024×768). +2. Открываем `/etc/default/grub` и добавляем: + ``` + GRUB_BACKGROUND="/boot/grub/background.png" + ``` +3. Обновляем конфиг: + ``` + sudo update-grub + ``` + +Теперь при загрузке будет фон вместо унылого чёрного экрана. +Можно поменять цвета текста в файле `/etc/grub.d/05_debian_theme`: +``` +set menu_color_normal=white/blackset menu_color_highlight=yellow/blue +``` +(меняем белый текст на чёрном фоне на жёлтый текст на синем фоне) +### Если что-то пошло не так + +Сломали GRUB? +1. Загружаемся с LiveCD или флешки с Linux. +2. Открываем терминал и монтируем системный раздел: + ``` + sudo mount /dev/sdXn /mntsudo mount --bind /dev /mnt/devsudo mount --bind /proc /mnt/procsudo mount --bind /sys /mnt/sys + ``` +3. Заходим в chroot‑окружение: + ``` + sudo chroot /mnt + ``` +4. Переустанавливаем GRUB: + ``` + grub-install /dev/sdXupdate-grub + ``` +5. Перезагружаемся. Система снова жива. + +Если GRUB сломался окончательно, можно загрузить ядро вручную из UEFI: +1. Загружаемся в UEFI Shell или LiveCD. +2. Выполняем: + ``` + linux /boot/vmlinuz-5.15.0-52-generic root=/dev/sda2 roinitrd /boot/initrd.img-5.15.0-52-genericboot + ``` +3. Если сработало — переустанавливаем GRUB: + ``` + sudo grub-install /dev/sdasudo update-grub + ``` diff --git a/LINUX/HOME_SERVER/5 малоизвестных команд для Linux.md b/LINUX/HOME_SERVER/5 малоизвестных команд для Linux.md new file mode 100644 index 0000000..36ec72f --- /dev/null +++ b/LINUX/HOME_SERVER/5 малоизвестных команд для Linux.md @@ -0,0 +1,48 @@ +### tldr + +Если набрать, например, man tar, вы получите труднопонятную простыню текста. В отличие, от man, результат вывода команды ниже более понятен. + +tldr [command] + +Например: +tldr tar +Create a .tar archive +tar cf archive.tar file1 file2 + +Extract a .tar.gz archive +tar xzf archive.tar.gz + +List contents of a tar file +tar tf archive.tar + +### timeout + +Команда timeout позволяет выполнять команду в течение определенного времени. Если время истечет, тайм-аут убьет процесс, так что скрипт не будет выполняться вечно. Пара примеров: +timeout 5s ping google.com +timeout 30s ./myscript.sh + +### ncdu + +Инструмент ncdu похож на du, но с текстовым интерактивным интерфейсом, который позволяет проверять использование диска и удалять мусор на месте. +ncdu +--- /home/user ------------------------ +. 3.1 GiB [##########] Downloads + 1.2 GiB [###.......] Videos + 800.0 MiB [##........] Projects + 450.0 MiB [#.........] Documents + +### fd + +Это удобная альтернатива find с простым синтаксисом (больше никаких длинных флагов или странных шаблонов). Несколько примеров: +fd notes.txt +fd -e py +fd config /etc + +### trash + +Если вы быстро набирали rm и удаляли не тот файл (или, что еще хуже, целую папку), вы знаете, как жесток Linux в отношении удаления файлов. Кнопки «отменить» нет. Если что-то исчезло, значит, оно действительно исчезло. + +Вот тут-то и пригодится команда trash. Она работает так же, как rm, но вместо того, чтобы навсегда удалять файлы, она отправляет их в корзину, откуда их можно легко восстановить позже. Несколько примеров: +trash filename.txt +trash file1.txt file2.txt folder/ +trash-restore
\ No newline at end of file diff --git a/LINUX/HOME_SERVER/Caddy - замена Nginx.md b/LINUX/HOME_SERVER/Caddy - замена Nginx.md new file mode 100644 index 0000000..3b4cfd6 --- /dev/null +++ b/LINUX/HOME_SERVER/Caddy - замена Nginx.md @@ -0,0 +1,54 @@ +Caddy - замена Nginx с авто-TLS и минимальной настройкой + +Ты наверняка используешь Nginx как прокси-сервер или для выдачи статики. Но есть более свежий и удивительно простой веб-сервер - Caddy. Он сам получит HTTPS, сам перезапустится при изменении конфига и вообще просит к себе минимум внимания. Разбираемся, чем он крут. + +Caddy — это современный веб-сервер, написанный на Go. Его главная фишка — всё работает из коробки: + +1) Автоматический HTTPS от Let's Encrypt +2) Перезапуск при изменении конфига +3) Встроенный reverse proxy +4) Простая конфигурация (Caddyfile) + +Установка: + + +sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https + +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy.gpg + +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list + +sudo apt update +sudo apt install caddy + + +Минимальный пример Caddyfile: + + +example.com { + reverse_proxy localhost:3000 +} + +- Заменяешь "example.com" на свой домен +- localhost:3000 (твой сервис); + +Запуск сервера + +Caddy работает как systemd-сервис. Чтобы запустить или перезапустить: + + +sudo systemctl restart caddy + +После этого caddy сам проверит домен, получит HTTPS-сертификат от Let's Encrypt, настроит прокси и всё заработает) + +Почему это удобно + +- Не нужно возиться с SSL — HTTPS работает из коробки +- Конфигурация проще, чем у Nginx +- Отлично подходит для pet-проектов, демо, VPS-серверов + +Когда лучше остаться с Nginx + +1) Если у тебя уже сложная конфигурация с множеством rewrite, map, ssl_params +2) Если нужна кастомная сборка модулей +3) Если используешь stream (TCP/UDP proxy)
\ No newline at end of file diff --git a/LINUX/HOME_SERVER/watch.md b/LINUX/HOME_SERVER/watch.md new file mode 100644 index 0000000..8c9409a --- /dev/null +++ b/LINUX/HOME_SERVER/watch.md @@ -0,0 +1,48 @@ +Команда watch: живой взгляд на процессы и метрики + +watch - утилита, которая повторно запускает любую команду через заданный интервал (по‑умолчанию 2 сек) и отображает её вывод полноэкранно. Подходит, когда: + +1) нужно увидеть изменение метрики прямо сейчас; +2) нет времени поднимать полноценный мониторинг; +3) требуется «подкараулить» событие и выйти, как только оно случится; +4) работает везде: на Debian, BusyBox в Alpine, внутри Docker‑контейнера; + +Базовый синтаксис + + +watch [-n <сек>] [-d] [-g] [-t] [-p] [-e] [-x] <команда> + +Ключевые опции + +"-n 5” — устанавливает интервал в 5 с. Хорош для медленных команд и экономии CPU; +-d — подсвечивает изменения относительно предыдущего вывода. Удобно отслеживать «скачки»; +-g — автоматически выходит, как только вывод изменится; полезно, когда ждёте окончания бэкапа; +-e — завершает работу при ненулевом exit‑коде команды. Превращает watch в health‑check; +-t — убирает заголовок watch. Идеален для чистых скриншотов; +-p — синхронизирует запуск с системными секундами. Это важно при очень частых опросах, например "-n 0.2"; +-x — запускает команду без обёртки в shell. Это помогает, если команда содержит символы вроде $ или *, которые интерпретируются bash'ем; + +Практические примеры + +Топ прожорливых процессов + + +watch -n 1 -d 'ps -eo pid,cmd,%cpu --sort=-%cpu | head' + +Ждём появления строки ERROR в логе и выходим + + +watch -g "grep -q ERROR /var/log/app.log" +echo 'Нашли ERROR!' + +Мини‑график latency + + +watch -n 0.2 -p 'ping -c1 1.1.1.1 | awk -F"=" "/time=/{print $4}"' + +Лайфхаки + +"-d" подсвечивает изменения — удобно, когда хочешь быстро понять, что именно изменилось. Добавь "--differences=permanent", чтобы подсветка не исчезала между обновлениями. +"-t" убирает заголовок watch — идеально для чистых логов или скриншотов. +"-p" даёт ровный ритм (1 Hz и выше), полезно для прецизионного мониторинга. +"-g" или "-e" + немного shell'а — и у тебя мини-скрипт, который реагирует на событие.
\ No newline at end of file diff --git a/LINUX/HOME_SERVER/Очистка системы.md b/LINUX/HOME_SERVER/Очистка системы.md new file mode 100644 index 0000000..82c3ed6 --- /dev/null +++ b/LINUX/HOME_SERVER/Очистка системы.md @@ -0,0 +1,36 @@ +```sh +# удалить старые версии установленных пакетов и очистить кэш пакетов от ненужных файлов: +sudo apt autoclean +# очистить кэш пакетов от ненужных файлов: +sudo apt clean + +sudo dpkg --remove <package_name> +sudo dpkg --purge <package_name> + +# удалить старое и неиспользуемое ядро: +sudo apt --purge remove linux-image-<version> +# текущее работающее ядро: +uname -r + +snap list +sudo snap remove <snap_name> + +# удалить файлы журнала сталше 7 дней: +sudo journalctl --vacuum-time=7d +# удалить файлы пока общий размер журналов не станет меньше 100Мб: +sudo journalctl --vacuum-size=100M + +# увидеть использование диска домашним каталогом (для дальнейшей очистки): +du -h --max-depth=1 ~ + +# очистка кэшированных миниатюр в системе (графической): +rm -rf ~/.cache/thumbnails/* +``` +## Очистка кэша миниатюр + +Ubuntu создает миниатюры для предварительного просмотра изображений, видео и документов для быстрого доступа. Со временем эти миниатюры могут накапливаться и занимать место. +Чтобы удалить их, используйте следующую команду: + +==`rm -rf ~/.cache/thumbnails/* + +Это удалит все кэшированные миниатюры в вашей системе.
\ No newline at end of file diff --git a/LINUX/HOME_SERVER/Приложение в SYSTEMD.md b/LINUX/HOME_SERVER/Приложение в SYSTEMD.md new file mode 100644 index 0000000..d0c989c --- /dev/null +++ b/LINUX/HOME_SERVER/Приложение в SYSTEMD.md @@ -0,0 +1,141 @@ +```sh +sudo systemctl edit --force --full mqtt_log.service +# vim /etc/systemd/system/mqtt_log.service + +#================================== +[Unit] +Description=mqtt_log +After=network.target + +[Service] +WorkingDirectory=/home/vlapa/mqtt_log +StandardError=append:/var/log/myapp/mqtt_log_error.log +User=vlapa +Group=vlapa +Type=simple +Restart=always +RestartSec=5 +ExecStart=/home/vlapa/mqtt/.venv/bin/python3 mqtt_log.py +Environment="PATH=/home/vlapa/mqtt/.venv/bin:/usr/bin:/bin" + + +[Install] +WantedBy=multi-user.target + +#================================== +# Создаем папку и файл +mkdir /var/log/myapp +touch /var/log/myapp/mqtt_log_error.log + +#================================== +sudo systemctl daemon-reload +sudo systemctl enable myapp +sudo systemctl start myapp +sudo systemctl status myapp + +# Логи сервиса с последнего запуска +journalctl -u mqtt_log.service -b + +``` +============ +sudo nano /etc/systemd/system/myapp.service +``` +# Пример конфигурации unit-файла + +[Unit] +Description=My Python Application +After=network.target + +[Service] +Type=simple +# Если используете виртуальное окружение: +ExecStart=/path/to/venv/bin/python /path/to/your/app.py +# Или без виртуального окружения: +# ExecStart=/usr/bin/python3 /path/to/your/app.py + +# Рабочая директория +WorkingDirectory=/path/to/your/app/directory + +# Пользователь для запуска (опционально) +User=username +Group=groupname + +# Перезапуск при ошибке +Restart=always +RestartSec=5 + +# Переменные окружения +Environment="PYTHONPATH=/path/to/your/app" +Environment="DATABASE_URL=mysql://user:pass@localhost/db" + +[Install] +WantedBy=multi-user.target + + +# Для приложений с зависимостями (виртуальное окружение) + +[Service] +Type=simple +ExecStart=/home/user/myapp/venv/bin/python /home/user/myapp/main.py +WorkingDirectory=/home/user/myapp +Environment="PATH=/home/user/myapp/venv/bin" + + +# Основные команды для управления + + +# Загрузить изменения конфигурации +sudo systemctl daemon-reload + +# Запустить сервис +sudo systemctl start myapp + +# Остановить сервис +sudo systemctl stop myapp + +# Включить автозапуск при загрузке +sudo systemctl enable myapp + +# Проверить статус +sudo systemctl status myapp + +# Просмотреть логи +sudo journalctl -u myapp -f + + +# Полезные опции для Python-приложений + +- **`Restart`** - политика перезапуска: + + - `no` - не перезапускать + + - `on-success` - только при успешном завершении + + - `on-failure` - при ошибке (рекомендуется) + + - `always` - всегда перезапускать + +- **`StandardOutput`/`StandardError`** - куда перенаправлять вывод: + + ini + + StandardOutput=journal + StandardError=journal + # Или в файл: + StandardOutput=file:/var/log/myapp.log + + +# Проверка и отладка + +bash + +# Проверить синтаксис конфигурации +sudo systemd-analyze verify /etc/systemd/system/myapp.service + +# Тестовый запуск без systemd +sudo -u username /path/to/python /path/to/app.py + +# Подробные логи +sudo journalctl -u myapp -n 100 -f + +```
\ No newline at end of file diff --git a/LINUX/HOME_SERVER/Усиление безопасности Linux.md b/LINUX/HOME_SERVER/Усиление безопасности Linux.md new file mode 100644 index 0000000..5d4ada7 --- /dev/null +++ b/LINUX/HOME_SERVER/Усиление безопасности Linux.md @@ -0,0 +1,178 @@ +### 10 bash-скриптов + +## 1. Включение автоматических обновлений безопасности + +Одной из важнейших практик поддержания безопасности систем является поддержание программного обеспечения в актуальном состоянии. Этот скрипт включит автоматические обновления безопасности в системе Linux, гарантируя, что система всегда будет защищена от уязвимостей. +```sh +#!/bin/bash +# Автоматическое обновление и установка исправлений безопасности +sudo apt-get update -y +sudo apt-get upgrade -y +sudo apt-get dist-upgrade -y +# Установка unattended-upgrades, если они не установлены +sudo apt-get install unattended-upgrades -y +# Включение автоматических обновлений +sudo dpkg-reconfigure --priority=low unattended-upgrades +``` +- `apt-get update -y` обновляет, а `apt-get upgrade -y`модернизирует все системные пакеты. +- `unattended-upgrades`обеспечивает применение автоматических обновлений безопасности. +- `dpkg-reconfigure`используется для настройки автоматических обновлений в системе. + +## 2. Отключение ненужных служб + +Во многих системах ненужные службы запущены по умолчанию. Отключение этих служб уменьшает количество целей для атаки сервера. Этот скрипт выведет список и отключит ненужные службы. +```sh +#!/bin/bash +# Список всех активных служб +echo "Список активных служб:" +sudo systemctl list-units --type=service --state=active +# Отключение ненужных служб +echo "Отключение ненужных служб..." +sudo systemctl stop apache2 +sudo systemctl disable apache2 +sudo systemctl stop mysql +sudo systemctl disable mysql +``` +- `systemctl list-units`показывает все запущенные службы. +- `systemctl stop`останавливает службу и `systemctl disable`отключает ее запуск при загрузке. +- Этот скрипт отключает Apache2 и MySQL в качестве примера. + +## 3. Внедрение политики надежных паролей + +Обеспечение соблюдения пользователями политики надежных паролей необходимо для безопасности системы. Этот скрипт настроит политику паролей системы для обеспечения надежных паролей. +```sh +#!/bin/bash +# Установите пакет libpam-pwquality для настройки политик паролей +sudo apt-get install libpam-pwquality -y +# Установите требования к сложности пароля +sudo sed -i 's/# minlen = 8/minlen = 12/' /etc/security/pwquality.conf +sudo sed -i 's/# minclass = 1/minclass = 4/' /etc/security/pwquality.conf +# Включите политику паролей в конфигурации PAM +sudo echo "password tribute pam_pwquality.so retry=3" >> /etc/pam.d/common-password +``` +- пакет `libpam-pwqualityп` предоставляет инструменты для соблюдения политики надежных паролей. +- параметр`minlen` устанавливает минимальную длину пароля, `minclass`задает количество требуемых классов символов (заглавные, строчные, цифры, специальные символы). + +## 4. Отключение входа root по SSH + +Разрешение входа root через SSH представляет собой существенный риск безопасности. Этот скрипт отключает вход root SSH путем редактирования конфигурации SSH. +```sh +#!/bin/bash +# Отключение вход root через SSH +sudo sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config +# Перезапуск службу SSH, чтобы применить изменения +sudo systemctl restart sshd +``` +- директива `PermitRootLogin` в конфигурации SSH контролирует, разрешен ли вход root. Установка значения «no» отключает его. +- `systemctl restart sshd`перезапускает службу SSH для применения изменений. + +## 5. Обеспечение прав доступа к конфиденциальным файлам + +Обеспечение правильных разрешений для конфиденциальных файлов важно для безопасности системы. Этот скрипт проверяет и корректирует разрешения для важных файлов, таких как `/etc/passwd`и `/etc/shadow`. +```sh +#!/bin/bash +# Убедитесь, что у /etc/passwd и /etc/shadow правильные права доступа +sudo chmod 644 /etc/passwd +sudo chmod 000 /etc/shadow +# Проверьте права доступа +echo "Права доступа для /etc/passwd:" +ls -l /etc/passwd +echo "Права доступа для /etc/shadow:" +ls -l /etc/shadow +``` +- `chmod 644`устанавливает разрешения на чтение и запись для владельца файла и только на чтение для остальных. +- `chmod 000`гарантирует, что никто не сможет читать или записывать в файл`/etc/shadow`, содержащий информацию о пароле пользователя. + +## 6. Настройка брандмауэра (UFW) + +Использование брандмауэра — важный шаг в защите сервера. Этот скрипт настраивает простой брандмауэр (UFW) для блокировки ненужных портов и разрешения только необходимых служб. +```sh +##!/bin/bash +# Включить UFW +sudo ufw enable +# Разрешить SSH (порт 22) и HTTP (порт 80) +sudo ufw allow 22 +sudo ufw allow 80 +# Запретить все остальные входящие соединения +sudo ufw default deny incoming +sudo ufw default allow outgoing +# Показать статус UFW +sudo ufw status +``` +- `ufw enable`включает несложный брандмауэр (UFW). +- команды «allow» разрешают соединения SSH и HTTP. +- «default deny incoming» и «default allow outgoing» устанавливают правила по умолчанию для блокировки входящего трафика и разрешения исходящего трафика. + +## 7. Регулярная ротация логов + +Чтобы избежать заполнения диска логами, следует настроить регулярную ротацию логов. Этот скрипт настраивает ротацию логов на ежедневной основе. +```sh +#!/bin/bash +# Установить logrotate, если он не установлен +sudo apt-get install logrotate -y +# Создать пользовательский файл конфигурации logrotate +echo "/var/log/*.log { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + create 0640 root root +}" | sudo tee /etc/logrotate.d/custom_logs +# Проверить конфигурацию logrotate +sudo logrotate --debug /etc/logrotate.conf +``` +- `logrotate`управляет ротацией файлов журналов. +- файл конфигурации указывает, что журналы должны ротироваться ежедневно, сохранять 7 старых журналов и сжимать старые журналы для экономии места. +- флаг `--debug` проверяет правильность конфигурации logrotate. + +## 8. Блокировка учетных записей пользователей после неудачных попыток входа в систему + +Блокировка учетных записей пользователей после определенного количества неудачных попыток входа в систему является распространенной и важной функцией безопасности. Этот скрипт настраивает блокировку учетных записей пользователей после 5 неудачных попыток входа в систему. +```sh +#!/bin/bash +# Установить pam_tally2, если он не установлен +sudo apt-get install libpam-modules -y +# Установить политику блокировки для блокировки учетных записей пользователей после 5 неудачных попыток +echo "auth required pam_tally2.so onerr=fail deny=5" | sudo tee -a /etc/pam.d/common-auth +# Сбросить счетчик неудачных попыток входа для всех пользователей +sudo pam_tally2 --user * --reset +``` +- модуль`pam_tally2` отслеживает попытки входа в систему и блокирует учетную запись после указанного количества неудачных попыток ( `deny=5`). +- флаг `--reset` сбрасывает счетчик неудачных попыток входа для всех пользователей. + +## 9. Обнаружение и удаление файлов SUID/SGID + +Файлы SUID и SGID предоставляют пользователям повышенные привилегии и могут представлять угрозу безопасности. Этот скрипт проверяет и удаляет файлы SUID/SGID из системы. +```sh +#!/bin/bash +# Поиск файлов SUID и SGID +echo "Поиск файлов SUID и SGID..." +find / - type f -executable -perm /4000 -o -perm /2000 +# При желании удалите их (закомментируйте эту строку, если не хотите удалять файлы) +# find / -type f -executable -perm /4000 -o -perm /2000 -exec rm -f {} \; +``` +- `find / -type f -executable -perm /4000`ищет файлы с установленным битом SUID (Set User ID). +- `find / -type f -executable -perm /2000`ищет файлы с установленным битом SGID (Set Group ID). +- Скрипт выводит список файлов, вы можете удалить их, раскомментировав последнюю строку. + +## 10. Обеспечение надлежащих прав доступа пользователей к критически важным файлам + +Этот скрипт проверяет и гарантирует, что конфиденциальные файлы принадлежат пользователю root и доступны только ему. +```sh +#!/bin/bash +# Установка владельца и разрешений для критических системных файлов +sudo chown root:root /etc/passwd +sudo chmod 644 /etc/passwd +sudo chown root:root /etc/shadow +sudo chmod 600 /etc/shadow +# Проверка изменений +ls -l /etc/passwd /etc/shadow +``` +- `chown root:root`гарантирует, что файлами владеет пользователь root. +- «chmod 644» гарантирует, что файл `/etc/passwd`доступен для чтения другим пользователям, но доступен для записи только пользователю root. +- «chmod 600» ограничивает доступ `/etc/shadow`только пользователю root. + + +На этом все! Спасибо за внимание! Если статья была интересна, подпишитесь на телеграм-канал [usr_bin](https://t.me/usr_bin_linux), где будет еще больше полезной информации.
\ No newline at end of file diff --git a/LINUX/HTOP.md b/LINUX/HTOP.md new file mode 100644 index 0000000..b16c7fe --- /dev/null +++ b/LINUX/HTOP.md @@ -0,0 +1,13 @@ +`F5` `t` - дерево каталогов +`N P M T` - сортировка по: PID, CPU%, MEM%, TIME +`F3 / +name` - поиск процесса +`F4 \ +name` - фильтр по имени +`\ ESC` - сброс фильтра +`u + выбор пользователя` - процессы 1 пользователя +`F9 k + выбор` - убить процесс +`l` - какие файлы открыты процессом +отображение всех переменных процесса: +выбираем процесс + е +`пробел` - выделение +`U` - отмена +`F2` - настройки diff --git a/LINUX/IPTABLES, NFTABLES.md b/LINUX/IPTABLES, NFTABLES.md new file mode 100644 index 0000000..2ab407c --- /dev/null +++ b/LINUX/IPTABLES, NFTABLES.md @@ -0,0 +1,101 @@ +**iptables** — это утилита командной строки для межсетевого экрана, которая позволяет настраивать правила управления трафиком в Linux. + +## Установка iptables + +Большинство дистрибутивов Linux включают iptables по умолчанию. Чтобы установить или обновить его: + +- **Debian/Ubuntu**: + +`sudo apt update sudo apt install iptables + +## Основные команды iptables + +- **Просмотреть текущие правила**: +`sudo iptables -L -v -n + +- **Разрешить входящий трафик SSH**: +`sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT + +- **Заблокировать IP-адрес**: +`sudo iptables -A INPUT -s [IP-адрес] -j DROP + +## **Сохранить правила**: + +- **Debian/Ubuntu:** +`sudo iptables-save > /etc/iptables/rules.v4 + +## Пример конфигурации: основные правила брандмауэра + +Чтобы настроить простой брандмауэр с помощью iptables, вы можете определить правила, разрешающие трафик SSH и HTTP, блокируя весь остальной входящий трафик: + +```sh +sudo iptables -P INPUT DROP +sudo iptables -P FORWARD DROP +sudo iptables -P OUTPUT ACCEPT +sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT +sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT +sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT +sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT +``` + +### Практические советы по настройке брандмауэра Linux + +1. **Всегда используйте политику сброса по умолчанию**: установите политику по умолчанию для сброса всего входящего трафика, разрешая только доверенные соединения. +2. **Разрешайте только определенные службы**: избегайте широких разрешений и указывайте только необходимые службы (например, SSH, HTTP). +3. **Ограничьте доступ по SSH**: ограничьте доступ по SSH, разрешив только доверенные IP-адреса и отключив вход с правами root. +4. **Используйте ведение журнала с осторожностью**: включите ведение журнала для отклоненных подключений, но ограничьте частоту, чтобы избежать переполнения журналов. + + +# nftables: будущее межсетевых экранов Linux + +**nftables** — это современная замена **iptables** , которая обеспечивает повышение производительности, более простой синтаксис и большую гибкость. Она работает с **netfilter** и предназначена для объединения функций брандмауэра, NAT и искажения пакетов. + +## Установка nftables + +В большинстве последних дистрибутивов Linux nftables предустановлен. Вы можете установить или обновить его с помощью: + +- **Debian/Ubuntu:** +`sudo apt install nftables + +## Базовые команды nftables + +- **Запуск и включение nftables**: +`sudo systemctl start nftables +`sudo systemctl enable nftables + +- **Просмотр активных правил:** +`sudo nft list ruleset + +- **Сброс всех правил**: +`sudo nft flush ruleset + +## Пример конфигурации nftables + +Вот как можно настроить nftables для разрешения трафика SSH, HTTP и HTTPS: + +1. **Откройте файл конфигурации nftables**: +`sudo nano /etc/nftables.conf + +**2. Добавьте набор правил**: + +```sh +table inet filter { + chain input { + type filter hook input priority 0; policy drop; + ct state established,related accept + tcp dport { 22, 80, 443 } accept + } + chain forward { + type filter hook forward priority 0; policy drop; + } + chain output { + type filter hook output priority 0; policy accept; + } +} +``` + +**3. Загрузите наборы правил**: + +`sudo nft -f /etc/nftables.conf + +Эта конфигурация по умолчанию сбрасывает все входящие соединения, но разрешает установленные соединения и новые соединения через порты SSH, HTTP и HTTPS.
\ No newline at end of file diff --git a/LINUX/JOURNALCTL.md b/LINUX/JOURNALCTL.md new file mode 100644 index 0000000..f3ba698 --- /dev/null +++ b/LINUX/JOURNALCTL.md @@ -0,0 +1,160 @@ +## Синтаксис и опции journalctl + +Если утилита не выводит ничего, выполните её от имени суперпользователя: + +**journalctl опции** + +- **--full, -l** - отображать все доступные поля; +- **--all, -a** - отображать все поля в выводе full, даже если они содержат непечатаемые символы или слишком длинные; +- **--pager-end, -e** - отобразить только последние сообщения из журнала; +- **--lines, -n** - количество строк, которые нужно отображать на одном экране, по умолчанию 10; +- **--no-tail** - отображать все строки доступные строки; +- **--reverse, -r** - отображать новые события в начале списка; +- **--output, -o** - настраивает формат вывода лога; +- **--output-fields** - поля, которые нужно выводить; +- **--catalog, -x** - добавить к информации об ошибках пояснения, ссылки на документацию или форумы там, где это возможно; +- **--quiet, -q** - не показывать все информационные сообщения; +- **--merge, -m** - показывать сообщения из всех доступных журналов; +- **--boot, -b** - показать сообщения с момента определенной загрузки системы. По умолчанию используется последняя загрузка; +- **--list-boots** - показать список сохраненных загрузок системы; +- **--dmesg, -k** - показывает сообщения только от ядра. Аналог вызова команды dmesg; +- **--identifier, -t** - показать сообщения с выбранным идентификатором; +- **--unit, -u** - показать сообщения от выбранного сервиса; +- **--user-unit** - фильтровать сообщения от выбранной сессии; +- **--priority, -p** - фильтровать сообщения по их приоритету. Есть восемь уровней приоритета, от 0 до 7; +- **--grep, -g** - фильтрация по тексту сообщения; +- **--cursor, -c** - начать просмотр сообщений с указанного места; +- **--since, -S, --until, -U** - фильтрация по дате и времени; +- **--field, -F** - вывести все данные из выбранного поля; +- **--fields, -N** - вывести все доступные поля; +- **--system** - выводить только системные сообщения; +- **--user** - выводить только сообщения пользователя; +- **--machine, -M** - выводить сообщения от определенного контейнера; +- **--header** - выводить заголовки полей при выводе журнала; +- **--disk-usage** - вывести общий размер лог файлов на диске; +- **--list-catalog** - вывести все доступные подсказки для ошибок; +- **--sync** - синхронизировать все не сохраненные журналы с файловой системой; +- **--flush** - перенести все данные из каталога /run/log/journal в /var/log/journal; +- **--rotate** - запустить ротацию логов; +- **--no-pager** - выводить информацию из журнала без возможности листать страницы; +- **-f** - выводить новые сообщения в реальном времени, как в команде tail; +- **--vacuum-time** - очистить логи, давностью больше указанного периода; +- **--vacuum-size** - очистить логи, чтобы размер хранилища соответствовал указанному. + +## Горячие клавиши journalctl + +По умолчанию информация лога выводится в формате, в котором её можно листать. Давайте разберем горячие клавиши, которые вы можете для этого использовать: + +- **Стрелка вниз, Enter, e** или **j** - переместиться вниз на одну строку; +- **Стрелка вверх, y** или **k** - переместиться на одну строку вверх; +- **Пробел** - переместиться на одну страницу вниз; +- **b** - переместиться на одну страницу вверх; +- **Стрелка вправо, стрелка влево** - горизонтальна прокрутка; +- **g** - перейти на первую строку; +- **G** - перейти на последнюю строку; +- **p** - перейти на позицию нужного процента сообщений. Например, 50p перенесет курсор на середину вывода; +- **/** - поиск по журналу; +- **n** - найти следующее вхождение; +- **N** - предыдущее вхождение; +- **q** - выйти. + +## Шпаргалка по journalctl + +Чтобы найти именно то, что вам нужно, необходимо научится перемещаться по этому списку. Формат вывода лога довольно простой: + +**янв 13 20:55:55 sergiy-pc kernel: Linux version 4.15.0-43-generic** + +- **янв 13 20:55:55** - дата и время события; +- **sergiy-pc** - хост, на котором произошло событие; +- **kernel** - источник события, обычно это программа или сервис. В данном случае ядро; +- **Linux version 4.15.0-43-generic** - само сообщение. + +### 1. Просмотр логов сервисов + +Самый частый случай использования journalctl - это когда вы пытаетесь запустить какой-либо сервис с помощью systemd, он не запускается и systemd выдает вам такое сообщение подобного содержания: Failed to start service use journalctl -xe for details. Система сама предлагает вам какую команду надо выполнить: + +`sudo journalctl -xe` + +Как вы помните из опций, эта команда отображает последние сообщения в журнале и добавляет к ним дополнительную информацию, если она есть. Учитывая, что последнее, что мы делали - был наш сервис, то здесь будут сообщения от него и вы быстро сможете понять почему он не запускается. + +Чтобы отфильтровать сообщения только от определенного сервиса можно использовать опцию -u. Например: + +`sudo journalctl -eu apache2.service` + +### 2. Просмотр логов в режиме tail + +С помощью опции -f можно указать утилите, что необходимо выводить новые сообщения в реальном времени: + +`sudo journalctl -f` + +В этом режиме less не поддерживается, поэтому для выхода нажмите сочетание клавиш **Ctrl+C**. + +### 3. Просмотр логов загрузки + +В логе journalctl содержатся все логи, в том числе и логи загрузки. Для того чтобы открыть лог последней загрузки используйте опцию -b: + +`sudo journalctl -b` + +Посмотреть список всех сохраненных загрузок можно командой: + +`sudo journalctl -list-boots` + +Теперь, чтобы посмотреть сообщения для нужной загрузки используйте её идентификатор: + +`sudo journalctl -b 37d5c906c9c6404682f029b2c34ec9dc` + +### 4. Фильтрация по дате + +С помощью опции **--since** вы можете указать дату и время, начиная с которой нужно отображать логи: + +`sudo journalctl --since "2019-01-20 15:10:10"` + +Опция **--until** помогает указать по какую дату вы хотите получить информацию: + +`sudo journalctl -e --until "2019-01-20 15:05:50"` + +Или сразу скомбинируем две эти опции чтобы получить логи за нужный период: + +`sudo journalctl --since "2019-01-20 15:10:10" --until "2019-01-20 15:05:50"` + +Кроме даты в формате YYYY-MM-DD в этих опциях можно использовать такие слова, как yesterday, today, и tomorrow. Также допустимы конструкции **1 day ago** (один день назад) или **3 hours ago** (три часа назад). Ещё можно использовать знаки + и -. Например **-1h30min** будет означать полтора часа назад. + +### 5. Журнал ядра + +Если вы хотите посмотреть только сообщения ядра используйте опцию -k: + +`sudo journalctl -ek` + +### 6. Настройка формата вывода + +По умолчанию journalctl выводит информацию с помощью утилиты less, в которой вы можете её удобно листать и просматривать. Но формат вывода можно изменить: + +- **short** - используется по умолчанию; +- **verbose** - также, как и short, только выводится намного больше информации; +- **json** - вывод в формате json, одна строка лога в одной строке вывода; +- **json-pretty** - форматированный вывод json для более удобного восприятия; +- **cat** - отображать только сообщения, без метаданных. + +Чтобы указать нужный формат используйте опцию -o. Например: + +`sudo journalctl -o json-pretty` + +Или: + +`sudo journalctl -eo json-pretty` + +### 7. Очистка логов + +Сначала нужно посмотреть сколько ваши логи занимают на диске. Для этого используйте такую команду: + +`sudo journalctl --disk-usage` + +Чтобы уменьшить размер лога можно использовать опцию --vacuum-size. Например, если вы хотите, чтобы ваши файлы журналов занимали на диске не более 2 Гб, выполните команду: + +`sudo journalctl --vacuum-size=2G` + +Теперь старые логи будут удалены, пока общий объем хранилища не будет составлять 2 гигабайта. + +Также можно удалять логи по времени. Для этого используется опция --vacuum-time. Например, оставим только логи за последний год: + +`journalctl --vacuum-time=1years`
\ No newline at end of file diff --git a/LINUX/KILL, PKILL.md b/LINUX/KILL, PKILL.md new file mode 100644 index 0000000..41443b2 --- /dev/null +++ b/LINUX/KILL, PKILL.md @@ -0,0 +1,16 @@ +Иногда процессы зависают или потребляют слишком много ресурсов. В Linux есть несколько удобных инструментов для их управления. + +📌 Основные команды: + +🟢 kill – завершение процесса по PID: + +ps aux | grep firefox # Найти PID процесса +kill 12345 # Завершить процесс + +🟢 pkill – завершение по имени процесса: + +pkill -9 firefox # Принудительно завершить Firefox + +🟢 htop – интерактивный мониторинг: + +htop # Открыть интерфейс для управления процессами
\ No newline at end of file diff --git a/LINUX/LEMP.md b/LINUX/LEMP.md new file mode 100644 index 0000000..ed55edc --- /dev/null +++ b/LINUX/LEMP.md @@ -0,0 +1,119 @@ +`Sudo apt update && sudo` +`apt install nginx` +`sudo ufw app list` +`sudo ufw allow 'Nginx HTTP'` +`sudo ufw status` +`ip addr show` +`hostname -I` +`curl -4 icanhazip.com` + +## MySQL : + +`sudo apt install mysql-server` +`sudo mysql_secure_installation` + +Вам будет задан вопрос, хотите ли вы настроить `**VALIDATE PASSWORD PLUGIN**`**.** +Ответьте `Y`«да» или что-нибудь еще, чтобы продолжить без включения, + +Если вы ответите «да», вам будет предложено выбрать уровень проверки пароля. Имейте в виду, что если вы войдете `2`на самый сильный уровень, вы получите ошибки при попытке установить любой пароль, не содержащий цифр, прописных и строчных букв и специальных символов: + +Независимо от того, решили ли вы настроить `**VALIDATE PASSWORD PLUGIN**`, ваш сервер попросит вас выбрать и подтвердить пароль для пользователя **root** MySQL . Его не следует путать с **корнем системы**. Пользователь **root базы данных** — это пользователь-администратор с полными привилегиями в системе базы данных. Несмотря на то, что метод аутентификации по умолчанию для пользователя root MySQL не требует использования пароля, **даже если он установлен**, вам следует определить здесь надежный пароль в качестве дополнительной меры безопасности. + +Чтобы ответить на остальные вопросы, нажмите `Y`и ударьте `ENTER`клавишу при каждом подсказке. Это приведет к удалению некоторых анонимных пользователей и тестовой базы данных, отключению удаленного входа в систему с правами root и загрузке этих новых правил, чтобы MySQL немедленно принял внесенные вами изменения. + +Когда вы закончите, проверьте, можете ли вы войти в консоль MySQL: + +`sudo mysql` +`sudo mysql -**_``_**u root -p**_` + + +## PHP : + + +'sudo apt install php8.1-fpm php-mysql' +'sudo mkdir /var/www/your_domain' +`sudo chown -R $USER:$USER /var/www/` +`sudo vim /etc/nginx/sites-available/` + +`_server {_` + `_listen 80;_` + `_server_name your_domain www.your_domain;_` + `_root /var/www/your_domain;_` + + `_index index.html index.htm index.php;_` + + + +`_location / {_` +`_try_files $uri $uri/ =404;_` +`_}_` + +`_location ~ \.php$ {_` +`_include snippets/fastcgi-php.conf;_` +`_fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;_` +`_}_` + +`_location ~ /\.ht {_` +`_deny all;_` +`_}_` + +`_}_` + + +`listen` — Определяет, какой порт будет прослушивать Nginx. + +`root` — Корень документа, в котором хранятся файлы, обслуживаемые этим веб-сайтом. + +`Index` — Определяет, в каком порядке Nginx будет отдавать приоритет индексным файлам для этого веб-сайта. Обычной практикой является перечисление `index.html` файлов с более высоким приоритетом, чем `index.php` у файлов, чтобы можно было быстро настроить целевую страницу обслуживания в приложениях PHP. Вы можете настроить эти параметры, чтобы они лучше соответствовали потребностям вашего приложения. + +`server_name` — Определяет, за какие доменные имена и/или IP-адреса должен отвечать этот блок серверов. +**Укажите в этой директиве доменное имя или общедоступный IP-адрес вашего сервера.** + +`Location /` — Первый блок местоположения включает `try_files` директиву, которая проверяет наличие файлов или каталогов, соответствующих запросу URL. Если Nginx не сможет найти подходящий ресурс, он вернет ошибку 404. + +`location ~ \.php$` — Этот блок местоположения управляет фактической обработкой PHP, указывая Nginx на `fastcgi-php.conf` файл конфигурации и `php8.1-fpm.sock` файл, который объявляет, какой сокет связан с `php8.1-fpm`. + +`location ~ /\.ht` — Последний блок _**location**_ касается `.htaccess` файлов, которые Nginx не обрабатывает. Благодаря добавлению `deny all` директивы, если какие-либо `.htaccess` файлы попадут в корень документа, они не будут доставлены посетителям. + + +Активируйте свою конфигурацию, связавшись с файлом конфигурации из `sites-enabled` каталога Nginx: + +`sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/` + +Затем отсоедините файл конфигурации по умолчанию от `/sites-enabled/` каталога: + +`sudo unlink /etc/nginx/sites-enabled/default` + +`sudo nginx -t` +- проверить конфигурацию` + +`sudo systemctl reload nginx` + +`nano /var/www//index.html` + +`<html>` +`<head>` +`<title>your_domain website</title>` +`</head>` +`<body>` +`<h1>Hello World!</h1>` + +`<p>This is the landing page of <strong>your_domain</strong>.</p>` +`</body>` +`</html>` + +***/etc/_nginx/nginx.conf*** +***nginx -s*** - сигнал + +_Где сигнал может быть одним из нижеследующих:_ + +_**stop** — быстрое завершение_ +_**quit** — плавное завершение_ +_**reload** — перезагрузка конфигурационного файла_ +_**reopen** — переоткрытие лог-файлов_ + + +_Директивы, помещённые в конфигурационном файле вне любого контекста, считаются находящимися в контексте main. Директивы events и http располагаются в контексте main, server — в http, а location — в server._ + + +_В случае если что-то работает не как ожидалось, можно попытаться выяснить причину с помощью файлов access.log и error.log из каталога ==/var/log/nginx ( /usr/local/nginx/logs )==
\ No newline at end of file diff --git a/LINUX/LESS и SPLIT.md b/LINUX/LESS и SPLIT.md new file mode 100644 index 0000000..0b0704f --- /dev/null +++ b/LINUX/LESS и SPLIT.md @@ -0,0 +1,39 @@ +**Работа с большими файлами в терминале: + +Работа с объемными файлами может быть затруднительна, однако есть два инструмента, которые несколько облегчают процесс. В этом посте кратко рассмотрим их. + +**less: Просмотр больших файлов** + +less — это инструмент для постраничного просмотра больших файлов без их полной загрузки в память. + +💬 **Открытие файла:** +`less bigfile.log + +Используйте клавиши Space для перехода вниз и b для возврата вверх. + +💬 **Поиск текста:** +Нажмите / и введите текст для поиска: +`/ошибка + +Используйте n для перехода к следующему совпадению. + +💬 **Вывод с подсветкой:** +Для удобства можно использовать less с подсветкой: + +`grep "ошибка" bigfile.log | less -R + +2️⃣ **split: Разделение больших файлов** + +Когда файл слишком большой для обработки, split поможет разбить его на части. + +💬 **Разделение файла по строкам:** +`split -l 1000 bigfile.log part_ + +**Результат:** файлы part_aa, part_ab, part_ac и т. д. + +💬 **Разделение по размеру:** +Например, каждая часть по 50 МБ: +`split -b 50M bigfile.log part_ + +💬 **Собрать файл обратно:** +`cat part_* > mergedfile.log
\ No newline at end of file diff --git a/LINUX/LN.md b/LINUX/LN.md new file mode 100644 index 0000000..6273b79 --- /dev/null +++ b/LINUX/LN.md @@ -0,0 +1,15 @@ +ln -s <на какой существующий объект будет вести> <создаваемый симлинк> + +В системах на базе Linux (например, Ubuntu или [CentOS](https://www.dmosk.ru/terminus.php?object=centos)) и [FreeBSD](https://www.dmosk.ru/terminus.php?object=freebsd) симлинк для каталога и файла создаются одинаково: + +ln -s /usr/share/nginx/html/index.php /home/dmosk/ + +ln -s /usr/share/nginx/html /home/dmosk/ + +* в первом примере создана символьная ссылка в домашней директории пользователя **dmosk** на файл **index.php**; во втором — на каталог **/usr/share/nginx/html**. + +### Удаление + +Также используется одна команда: + +rm /home/dmosk/index.php
\ No newline at end of file diff --git a/LINUX/LSOF.md b/LINUX/LSOF.md new file mode 100644 index 0000000..34ca676 --- /dev/null +++ b/LINUX/LSOF.md @@ -0,0 +1,87 @@ +👩💻 **Как использовать lsof для поиска процессов, блокирующих файлы** + +`lsof` — позволяет просматривать информацию об открытых файлах и связанных с ними процессах. Особенно полезно, если вы сталкиваетесь с ошибками доступа к файлам, например, при монтировании, удалении или использовании разделов. + +⚙️ **Примеры использования** +🟢 К примеру, если мы не можем удалить файл или получить к нему доступ, выполним: + +`lsof /путь/к/файлу + +В результате получим список процессов, которые используют файл, с подробной информацией: PID, пользователь, команда и доступ. + +🟢 Чтобы просмотреть все файлы, которые были открыты в определенном каталоге, используйте следующую команду: + +`lsof +D /директория + +➕ **Дополнительно:** + +➡️ `lsof -u username` — покажет файлы, открытые процессами указанного пользователя. + +➡️ `lsof -p PID` — отображает все файлы, которые использует процесс с заданным PID. + +➡️ `lsof -i :PORT` — покажет процессы, использующие определённый сетевой порт. + +=================================== +⚙️ **Проверка использования порта в Linux** + +Иногда нужно узнать, какой процесс использует определённый порт. В Linux это можно сделать несколькими способами. + +**📌 Просмотр процессов, занявших порт** + +**🟢 Определение процесса по порту:** +`sudo lsof -i :80` +Покажет процесс, использующий порт 80. + +**🟢 Вывести PID процесса:** +`sudo netstat -tulnp | grep :80` + +Альтернативный способ проверки. + +**🟢 Завершение процесса по PID:** +`sudo kill -9 <PID>` +Останавливает процесс, если он мешает запуску сервиса. + +**Полезно при отладке серверов, проверки конфликтов между сервисами и устранения проблем с сетью.** + + +======================= + +**Как исправить "rm: cannot remove ‘file’: Device or Resource Busy"? + +Бывало же у вас такое - заканчиваешь работать с каким-то ресурсом (файлом/каталогом/устройством), хочешь его удалить и видишь ошибку о том, что он чем-то занят (используется системой либо каким-то процессом). + +`rm: cannot remove 'file-or-directory': Device or resource busy + +Тут нужно понять, в чем конкретная причина и чем он задействован: то ли процессом, то ли это, быть может, точка монтирования для какой-то FS… + +Распространенных решения вижу 2: +1. Использование команд "**lsof + fuser**" для определения занятых ресурсов и завершения процессов. Если вы не знаете, какая программа мешает выполнить операцию, используйте **lsof**: + +`$ lsof +D Sources/App/ + +COMMAND PID USER FD NAME +server 22331 xoadmin txt Sources/App/server +client 22332 xoadmin txt Sources/App/client + +Если у нас ресурс занимают 2 процесса, можно и через **kill** все грохнуть. Если их много, можно более автоматизированно - через **fuser**. Эта команда завершит все процессы, которые что-то используют в целевом каталоге: + +`$ fuser -k Sources/App/ + +После выполнение мы вырубим сразу оба процесса **22331** и **22332**. +2. Определение точки монтирования. Это полезно, когда какая-нибудь флешка или внешний диск монтирован в целевой каталог. Узнать, так ли это, можно через команду **mount**. Допустим, у меня не получается удалить **test**: + +`$ rm -rf test + +`rm: cannot remove 'test/tmpfsdir': Device or resource busy + +Проверяем, является ли **tmpfsdir** точкой монтирования: + +`$ mount | grep test/tmpfsdir + +`none on /home/u/test/tmpfsdir type tmpfs (rw,relatime,inode64) + +Вывод говорит о том, что в каталог **tmpfsdir** примонтирована FS **tmpfs**. Для успешного выполнения операции нужно размонтировать каталог: + +`$ sudo umount test/tmpfsdir + +`$ rm -rf test
\ No newline at end of file diff --git a/LINUX/Let's Encrypt.md b/LINUX/Let's Encrypt.md new file mode 100644 index 0000000..43e6f4f --- /dev/null +++ b/LINUX/Let's Encrypt.md @@ -0,0 +1,33 @@ +**Установка и автоматизация SSL-сертификатов с Let's Encrypt** + +**Let's Encrypt** — это бесплатный сервис для генерации SSL/TLS-сертификатов. С его помощью можно легко защитить свои сайты HTTPS-соединением. Для автоматизации работы используется инструмент Certbot. + +1️⃣ **Установка Let's Encrypt (Certbot)** +Пример для дистрибутивов на базе Ubuntu/Debian: +`sudo apt update +`sudo apt install certbot python3-certbot-nginx + +Если используется другой веб-сервер (например, Apache), замените nginx на apache2. + +2️⃣ **Получение сертификата** +Выполните команду для автоматического выпуска и настройки сертификата для Nginx: +`sudo certbot --nginx + +Certbot автоматически обнаружит конфигурацию вашего веб-сервера и предложит выбрать домен. Далее сертификат будет установлен и активирован. + +3️⃣ **Автоматическое продление сертификатов** +Let's Encrypt выдает сертификаты сроком на 90 дней. Для их автоматического обновления настройте cron-задание: + +— Откройте редактор Cron: +`crontab -e + +— Добавьте следующую строку для проверки и продления сертификатов каждые 12 часов: +`0 */12 * * * certbot renew --quiet + +Certbot автоматически перезапустит веб-сервер, если сертификат обновится. +4️⃣ **Проверка работы** + +Убедитесь, что сертификат активен, открыв сайт через HTTPS. Также проверьте успешность автоматизации: +`sudo certbot renew --dry-run + +Теперь ваш сайт защищен, а обслуживание сертификатов полностью автоматизировано 👍
\ No newline at end of file diff --git a/LINUX/MAILutils.md b/LINUX/MAILutils.md new file mode 100644 index 0000000..029fd6c --- /dev/null +++ b/LINUX/MAILutils.md @@ -0,0 +1,104 @@ +```sh +sudo apt update && sudo apt upgrade -y +sudo apt install postfix mailutils +``` +### Настройка Postfix + +Данный раздел покажет, как настроить Postfix для отправки сообщений с сервера, на котором он установлен (то есть, с локального хоста). + +Для этого Postfix должен быть настроен на прослушивание только интерфейса внутренней петли (loopback interface) – это виртуальный сетевой интерфейс, который используется сервером для внутреннего взаимодействия. Откройте конфигурационный файл Postfix с помощью редактора nano: + +`sudo nano /etc/postfix/main.cf` + +Найдите в нем следующий блок кода: + +`mailbox_size_limit = 0 +`recipient_delimiter = + +`inet_interfaces = all` + +Замените строку +`inet_interfaces = all +строкой +`inet_interfaces = loopback-only +Теперь этот блок выглядит так: + +`mailbox_size_limit = 0 +`recipient_delimiter = + +`inet_interfaces = loopback-only` + +Вместо loopback-only можно также использовать localhost: + +`mailbox_size_limit = 0 +`recipient_delimiter = + +`inet_interfaces = localhost` + +Завершив редактирование файла, сохраните изменения и закройте его (CTRL+X, затем Y и ENTER). После этого перезапустите Postfix: + +`sudo service postfix restart` +`sudo systemctl restart postfix` + +### 1. Синтаксис + +(В ТЕМЕ И ТЕКСТЕ СООБЩЕНИЯ ИСПОЛЬЗОВАТЬ ТОЛЬКО ENG !!!) +Можно отправить сообщение следующей командой: + +`echo "Test text" | mail -s "Test title" master@dmosk.ru + +_* в данном примере будет отправлено письмо на электронный адрес **master@dmosk.ru** с темой **Test title** и телом письма — **Test text**._ + +Если при отправке возникли проблемы, можно проверить логи следующей командой: + +`tail /var/log/mail.log + +### 2. Отправка письма с вложением +`echo 'Attachment' | mail -s 'Subject attachment message' -a /var/log/maillog -a /var/log/maillog2 master@dmosk.ru + +... или: + +`echo 'Attachment' | mail -s 'Subject attachment message' -A /var/log/maillog -A /var/log/maillog2 master@dmosk.ru + +_* где **/var/log/maillog** и **/var/log/maillog2** — файлы, которые будут прикреплены к письму. Обратите внимание, что обе команды отличаются по регистру опции **a** и **A** — все зависит от версии и сборки Linux (в каких-то нужно использовать маленькую, в каких-то — большую). Также обратите внимание, что в примере для Ubuntu мы отправим 2 файла — для этого просто добавляем к команде еще одну опцию прикрепления файла._ + +### 3. Несколько получателей + +Для отправки письма нескольким получателям, просто перечисляем их через запятую: + +`echo "Test text" | mail -s "Test title" master@dmosk.ru,shmaster@dmosk.ru + +_* в данном примере мы отправил письмо на ящики **master@dmosk.ru** и **shmaster@dmosk.ru**._ + +### 4. Отправка с копией + +Отправить копию на адрес master2@dmosk.ru: + +`echo "Test copy" | mail -s "Test copy title" master@dmosk.ru -c master2@dmosk.ru + +Отправить скрытую копию на адрес master3@dmosk.ru: + +`echo "Test hidden copy" | mail -s "Test hidden copy title" master@dmosk.ru -b master3@dmosk.ru + +### 5. Указать отправителя + +`echo "Test text" | mail -s "Test title" master@dmosk.ru -aFrom:postmaster@dmosk.ru + +### 6. Отправка через внешний SMTP сервер + +**а) Для Ubuntu / Debian:** + +`echo "Test text" | mail -s "Test title" -a "Smtp: smtp.mail.ru:25" -a "From: postmaster@dmosk.ru" -a "Return-path: postmaster@dmosk.ru" master@dmosk.ru + +Однако, если сторонний почтовый сервер работает по шифрованному каналу и требует аутентификацию, необходимо ввести следующее: + +`echo "Test text" | mail -v -s "Test title" -S smtp="smtp.dmosk.ru:587" -S smtp-use-starttls -S smtp-auth=login -S smtp-auth-user="master@dmosk.ru" -S smtp-auth-password="password" -S ssl-verify=ignore -S nss-config-dir=/etc/pki/nssdb -S from=postmaster@dmosk.ru master@dmosk.ru + +* где: + +- **smtp.dmosk.ru** — сервер smtp; +- **587** — порт для подключения к серверу отправки; +- **smtp-use-starttls** указывает на использование шифрования через TLS; +- **smtp-auth=login** задает аутентификацию с использованием логина и пароля; +- **smtp-auth-user** и **smtp-auth-password** — соответственно логин и пароль; +- **ssl-verify=ignore** отключает проверку подлинности сертификата безопасности; +- **nss-config-dir** указывает на каталог с базами nss; +- **from** задает поле FROM + diff --git a/LINUX/MC.md b/LINUX/MC.md new file mode 100644 index 0000000..a1b6351 --- /dev/null +++ b/LINUX/MC.md @@ -0,0 +1,124 @@ +Чтобы открывать по f4 свой редактор, снимаем в настройках (в верхнем меню Настройки-Конфигурация) крестик с пункта Встроенный редактор. +Далее в .bashrc добавляем: + +`alias ss='subl'` +`alias mc='EDITOR=subl mc'` + +**Общее** +Ctrl+x,j — Показ фоновых задач +Сtrl-X+A — текущие VFS (ftp и пр.) +ESC+цифра — тоже что и F{цифра} (ESC+5 = F5) + +**F1-F12** +F1 — Помощь +F2 — Сохранить изменения в файл при редактировании +F3 — Просмотр файла +F3 — (Во время редактирования) Начать выделение текста. Повторное нажатие F3 закончит выделение +F4 — Редактирование файла +F5 — Скопировать выделенное +F6 — Переместить выделенное +F8 — Удалить выделенное +Shift+F1 — вызывается меню быстрого перехода между точками монтирования на левой панели +Shift+F2 — вызывается меню быстрого перехода между точками монтирования на правой панели +Shift+F5 — скопировать файл в этот же каталог +Shift+F6 — переместить/переименовать файл в этот же каталог + +**Поиск** +ctrl-s — Быстрый поиск файла или каталога +alt-shift-? — Вызывается меню расширенного поиска файлов + +**Командная строка** +ctrl-a — курсор в начало +ctrl-e — курсор в конец +esc-! — выполнить команду с параметрами +ctrl-x, p или alt+a — вставить имя каталога +esc, enter; ctrl-enter — послать текущий объект в командную строку +ctrl-x,t — Вставка всех выделенных объектов в командную строку +esc, tab — дополнение команды +alt-a — вставить текущий каталог в командную строку +Alt-TAB (ESC+TAB) — Крутейшая комбинация для поиска загружаемых файлов (например, вы забыли как правильно пишется: netcfg или netconfig. Достаточно набрать в командной строке net и нажать Alt-TAB — вам сразу покажут что можно запустить, которое начинается со слова net) нажимать два раза! +alt-shift-! — вывод результата команды +ctrl-shift-v — вставка из буфера обмена + +**Операции над файлами** +Shift+F4 — Создает новый файл +shift-f6 — переименовать файл +Ctrl+x,i — Быстрый просмотр информации о текущем объекте на второй панели +Ctrl+x,q — Быстрый просмотр содержимого файла на второй панели +alt-. — показать скрытые файлы +ctrl-x, c — права на файл +ctrl-x, o — владелец файла +Shift-F3 — просмотр файла (raw/ без учета расширения) +Ctrl-X+L — создать ссылку на файл +Ctrl-X+S — создать символическую ссылку на файл +Ctrl + x v — создать относительную символическую ссылку +trl+x,Ctrl+s — Редактировать символическую ссылку +F12 — Save as +ctrl-t — отметить файл либо снять отметку ++ — выбрать (отметить) группу файлов по регулярному выражению +\ — снять отметку с группы файлов +ctrl-x, ctrl-d — сравнить файлы +ctrl-t или Insert — Выделение текущего объекта + +**Работа с каталогами** +alt-o — открыть каталог под курсором в соседней панели +alt-i — открыть текущий каталог в соседней панели +сtrl-\ — показать «Справочник каталогов» +ctrl-x, h — добавить ссылку на текущий каталог в меню быстрого доступа +alt-shift-h — Отобразить историю каталогов +Ctrl+l — Перерисовать экран +Ctrl+r — перечитать содержимое каталога +Ctrl+Space — Показать размер текущего каталога +Ctrl-X+D — сравнить каталоги +ESC+C — быстро сменить каталог +alt-c -окно «Быстрая смена каталога» + +**Работа с панелями** +Ctrl-O — погасить панели и посмотреть что под ними (работает ТОЛЬКО в linux!) здесь же можно поработать в shell’e. +alt-, — Переключить режим отображения панелей (вертикально/горизонтально) +alt-t переключение подробного режима отображения панели +Meta+c — Вызывается меню быстрой смены текущего каталога на активной панели +ctrl-u — поменять панели местами +alt-g — отмечает первый файл или каталог в отображаемом списке на панели (по сути как клавиша Home, поднимает курсор вверх) +alt-e — поменять кодировку панели +Ctrl + x i — переводит пассивную панель в режим «Информация» +Ctrl + x q — переводит пассивную панель в режим быстрого просмотра +ctrl-x ! — меню → «Критерий панелизации» (git) + +**История** +alt+h; esc, h — история командной строки +alt-p, alt-n — вернуть предыдущую (следущую) командную строку (строки) +alt-y. Перемещение к предыдущему каталогу из истории перемещения по каталогам; эквивалентно нажатию мышкой на символ ‘<‘ в верхнем углу панели. +alt-u. Перемещение к следующему каталогу из истории перемещения по каталогам; эквивалентно нажатию мышкой на символ ‘>’. + +**Редактор** +F4 (в редакторе) — поиск с заменой +F6 (в редакторе) — поиск с помощью регулярного выражения +Shift-Del (в редакторе) — удалить блок (и положить его в clipboard) +Shift-Ins (в редакторе) — вставить блок из буфера (работает даже в другой консоли!!!) Можно редактировать два и более файлов на разных консолях и гонять блоки между ними. +Shift+F3 — Начать выделение блока текста. Повторное нажатие F3 закончит выделение +Shift+F5 — Вставка текста из внутреннего буфера обмена mc (прочитать внешний файл) +Meta+i — Переключение режима «Автовыравнивание возвратом каретки», удобно при вставке отформатированного текста из буфера обмена +Meta+l — Переход к строке по её номеру +Meta+q — Вставка литерала (непечатного символа).См. таблицу ниже +Meta+t — Сортировка строк выделенного текста +Meta+u — Выполнить внешнюю команду и вставить в позицию под курсором её вывод +Ctrl+f — Занести выделенный фрагмент во внутренний буфер обмена mc (записать во внешний файл) +Ctrl+k — Удалить часть строки до конца строки +Ctrl+n — Создать новый файл +Ctrl+s — Включить или выключить подсветку синтаксиса +Ctrl+t — Выбрать кодировку текста +Ctrl+u — Отменить действия +Ctrl+x — Перейти в конец следующего +Ctrl+y — Удалить строку +Ctrl+z — Перейти на начало предыдущего слова +Meta+Enter — Диалог перехода к определению функции +Meta+- — Возврат после перехода к определению функции +Meta++ — Переход вперед к определению функции +Meta+n — Включение/отключение отображения номеров строк +tab Отодвигает вправо выделенный текст, если выключена опция «Постоянные блоки» +Meta-tab Отодвигает влево выделенный текст, если выключена опция «Постоянные блоки» +Shift+Стрелки Выделение текста +Meta+Стрелки Выделение вертикального блока +Meta+Shift+- Переключение режима отображения табуляций и пробелов +Meta+Shift++ Переключение режима «Автовыравнивание возвратом каретки»
\ No newline at end of file diff --git a/LINUX/MOSQUITTO.md b/LINUX/MOSQUITTO.md new file mode 100644 index 0000000..94efb8f --- /dev/null +++ b/LINUX/MOSQUITTO.md @@ -0,0 +1,121 @@ +```bash +sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa +sudo apt-get update +sudo apt install mosquitto mosquitto-clients + +sudo service mosquitto status +# mosquitto start/running, process 16614 + +sudo mosquitto_passwd -c /etc/mosquitto/mosquitto.pwd user_name +sudo mosquitto_passwd /etc/mosquitto/mosquitto.pwd user_name + +sudo vim /etc/mosquitto/mosquitto.conf + +# Добавляем в конец файла, чем говорим MQTT где искать наш файл с паролями: +allow_anonymous false +password_file /etc/mosquitto/mosquitto.pwd + +vim /etc/mosquitto/mosquitto.pwd +# добавляем клиента: +user_name-1:EXAMPLE_PASSWORD +user_name-2:STRONG_PASSWORD + +# Зашифруйте открытые пароли пользователей с помощью `mosquitto_passwd`утилиты: +sudo mosquitto_passwd -U /etc/mosquitto/mosquitto.pwd +# посмотрим: +sudo cat /etc/mosquitto/mosquitto.pwd + +sudo chmod 0644 /etc/mosquitto/mosquitto.pwd + +sudo service mosquitto restart + +``` + +Подпишитесь на `/home/sensor/temperature`тему, используя действительный логин и пароль. Например, `john_doe`и `EXAMPLE_PASSWORD`: +```sh +mosquitto_sub -u home -P 1234 -t test + +mosquitto_sub -u john_doe -P EXAMPLE_PASSWORD -t "/home/sensor/temperature" +``` + +Опубликуйте новое сообщение в `/home/sensor/temperature`теме: +```sh +mosquitto_pub -t "test" -m "hello world" -u home -P 1234 + +mosquitto_pub -u john_doe -P EXAMPLE_PASSWORD -t "home/sensor/temperature" -m "22.5" -q 1 -r +``` + +Вывод: +```sh +22.5 +``` + +Просмотрите все активные фоновые задания и запишите `mosquitto_sub`идентификатор задания: +```sh +jobs +``` + +Вывод: +```sh +[1] Running mosquitto_sub -u john_doe -P EXAMPLE_PASSWORD -t "home/lights/sitting_room" & +``` + +Остановите тему, указав идентификатор задания: +```sh +kill %1 +``` + +Вывод: +```sh +[1] Done mosquitto_sub -u john_doe -P EXAMPLE_PASSWORD -t "home/lights/sitting_room" & +``` + +Попробуйте подписаться на `/home/sensor/temperature`тему, используя неверные учетные данные +```sh +mosquitto_pub -u john_doe -P VERY_WRONG_PASSWORD -t "home/sensor/temperature" -m "22.5" -q 1 -r +``` + +Вывод: +```sh +Connection error: Connection Refused: not authorised. +Error: The connection was refused. +``` + +Вышеприведенный вывод показывает, что соединение не устанавливается, поскольку Mosquitto не может авторизовать пользователя. + + +**Настройка** + +После установки вы получите mqtt-брокера, слушающего только локальные интерфейсы. Причем, он об этом вам не скажет. У меня ушло довольно много времени, чтобы понять что не так, вы же получите эти знания сразу ) +Поправим конфигурационный файл: + +**/etc/mosquitto/mosquitto.conf** +```sh + +# пишем в логи всё (если не нужно, то потом можно отключить) +log_type error +log_type warning +log_type notice +log_type information + +# разрешаем подключаться без пароля (мне это было надо) +allow_anonymous true + +# те самые "слушатели", без них москит будет слушать только локальные интерфейсы +listener 1883 192.168.1.2 +listener 1883 192.168.3.2 + ``` + + В принципе, это все, по крайней мере, что было нужно мне. + Обратите внимание, что у **listener** вначале идет порт и только потом ip. Если ip не указывать, записать listener 1883, то будут доступны все интерфейсы, как внутренние, так и внешние. + + **Эксплуатация** + +Новые настройки сами себя не применят, поэтому сервис нужно перезагрузить: +`systemctl restart mosquitto + +Логи москит пишет в **/var/log/mosquitto/mosquitto.log** + +Помните, мы вместе с mosquitto поставили mosquitto-clients? При помощи клиента, например, можно смотреть сообщения в брокере: + + `mosquitto_sub -d -t '#' diff --git a/LINUX/NC.md b/LINUX/NC.md new file mode 100644 index 0000000..b51c5ae --- /dev/null +++ b/LINUX/NC.md @@ -0,0 +1,33 @@ +Эффективно копируем гигабайтные файлы по сети в Linux / Unix / Mac OS + +```sh +# Передатчик: +nc -lp 8888 -q 1 < file_name.ext + +# приемник: +nc vlapa@178.20.46.157 > file_name.ext + +watch "du -h file_name.ext" + +#=============================== +# Проверка <адрес порт> : +nc localhost 22 + +# Проверка портов: +nc -z -v localhost 1-30 + + +#=============================== +# Передача файлов через сеть (с ожиданием и не защищенно): + +# запускаем сервер: +nc -lp 8888 -q 1 > file.name + +# скачиваем удаленно: +nc 192.168.88.61 8888 < file.name + + +du -h file.name + +watch «du -h file.name» +```
\ No newline at end of file diff --git a/LINUX/NCDU, BAT, FD, EXA, HTTPIE.md b/LINUX/NCDU, BAT, FD, EXA, HTTPIE.md new file mode 100644 index 0000000..3d581b1 --- /dev/null +++ b/LINUX/NCDU, BAT, FD, EXA, HTTPIE.md @@ -0,0 +1,6 @@ +NCDU - замена <DU> размер в интерактивном режиме +BAT - замена <CAT> с подсветкой синтаксиса +FD - замена <FIND> поиск +EXA - замена <LS> с подсветкой +`exa --tree --level=2 --long` + diff --git a/LINUX/NCDU.md b/LINUX/NCDU.md new file mode 100644 index 0000000..eb81446 --- /dev/null +++ b/LINUX/NCDU.md @@ -0,0 +1,20 @@ +— лучший способ быстро очистить место на сервере + +Когда на сервере внезапно заканчивается место, первое, что хочется — понять, что его съело. Вместо того, чтобы долго ковыряться в du и find, просто поставь ncdu. + +Это интерактивная утилита для анализа диска в терминале. Работает быстро, показывает размеры папок/файлов и позволяет удалять ненужное прямо из интерфейса: + +`sudo apt install ncdu + +Для сканирования нужно просто ввести команду и передать целевой каталог. Чтобы промониторить корень: + +`sudo ncdu / + +Нужные кнопочки: + +Навигация - стрелками вверх/вниз. +Удаление - клавиша d. +Обновить - g. +Выйти - q. + +В отличие от "du -sh *", ncdu не только показывает вес директорий, но и позволяет разобраться в них быстрее. Особенно полезно, если забит логами /var/log, кто-то накатил 100500 .tar.gz или забыли почистить кэш в home. Утилита очень выручает, когда каждая секунда важна. Установил - посмотрел - удалил - освободил.
\ No newline at end of file diff --git a/LINUX/NETWORK/DNS.md b/LINUX/NETWORK/DNS.md new file mode 100644 index 0000000..4931e61 --- /dev/null +++ b/LINUX/NETWORK/DNS.md @@ -0,0 +1,36 @@ +```sh +sudo vim /etc/resolv.conf +#======== +nameserver 8.8.8.8 +nameserver 8.8.4.4 + + +sudo resolvectl status + +# Через NETPLAN + +network: + version: 2 + renderer: networkd + ethernets: + enp0s3: + dhcp4: no + addresses: [192.168.157.140/24] + gateway4: 192.168.157.140 + nameservers: + addresses: [8.8.8.8,8.8.4.4] +``` + +В части сервера имен этого примера указано, что система должна разрешать имена хостов с помощью публичных DNS-серверов Google ( `8.8.8.8`и `8.8.4.4`). Кроме того, у машины есть IP-адрес `192.168.157.140`и маска подсети `24`. `192.168.157.140`— это шлюз, через который проходит трафик. + +После внесения изменений в файл конфигурации выполните следующую команду, чтобы изменения вступили в силу: + +```sh +sudo netplan --debug generate +sudo netplan try +sudo netplan apply +``` + +Сделав это, вы сможете использовать серверы имен, перечисленные в файле конфигурации, и обновлять настройки DNS системы. + +```
\ No newline at end of file diff --git a/LINUX/NETWORK/IPERF3.md b/LINUX/NETWORK/IPERF3.md new file mode 100644 index 0000000..869d8f6 --- /dev/null +++ b/LINUX/NETWORK/IPERF3.md @@ -0,0 +1,33 @@ +```bash +# Проверка скорости подключения + +sudo apt install iperf3 + +# Порт по умолчанию = 5201, открываем его: +sudo ufw allow 5201 + +# Запуск клиента: +iperf3 -c адрес_сервера -f m + +# Запуск сервера: +iperf3 -s -f m + +iperf3 -h # help +-p, --port - указать порт для сервера или к какому порту надо подключаться; +-f, --format - формат выводимых данных, доступны значения: k,m,g,t,K,M,G,T; +-i, --interval - интервал времени между выводами результата тестирования; +-F, --file - использовать данные из файла вместо генерации случайных данных; +-V, --verbose - вывод максимально подробной информации; +-J, --json - вывод в формате JSON; +--logfile - записывать информацию в лог файл; +--forceflush - очищать историю вывода перед выводом следующего результата тестирования; +-v, --version - версия программы. + +k - килобиты; +m - мегабиты; +g - гигабиты; +K - килобайты; +M - мегабайты; +G - гигабайты; +T - терабайты. +``` diff --git a/LINUX/NETWORK/NETPLAN.md b/LINUX/NETWORK/NETPLAN.md new file mode 100644 index 0000000..e556960 --- /dev/null +++ b/LINUX/NETWORK/NETPLAN.md @@ -0,0 +1,230 @@ +```bash +sudo systemctl --type=service | grep net +sudo netplan apply +networkctl status +journalctl -f -u systemd-networkd.service + +#=============================== +ip a +sudo vim /etc/netplan/file_name.yaml + +#=============================== + +network: + renderer: networkd + ethernets: + enp2s0: + dhcp4: no # true + addresses: + - 192.168.88.2/24 + routes: + - to: default + via: 192.168.88.1 + nameservers: + addresses: + - 192.168.88.1 + - 8.8.8.8 + - 8.8.4.4 + version: 2 + +#=============================== + +network: + version: 2 + renderer: networkd + ethernets: + enp2s0: + dhcp4: no + addresses: + - 192.168.88.40/24 + routes: + - to: default + via: 192.168.88.1 + nameservers: + addresses: + - 192.168.88.1 + - 77.88.8.8 + - 77.88.8.1 + - 8.8.8.8 + - 8.8.4.4 + +#=============================== +network: + version: 2 + renderer: networkd + ethernets: + enp6s0: + dhcp4: no + addresses: [192.168.88.2/24] + nameservers: + addresses: [192.168.88.1,77.88.8.8,77.88.8.1] + routes: + - to: default + via: 192.168.88.1 + wifis: + wlp4s0: + dhcp4: yes + # addresses: [192.168.88.3/24] + nameservers: + addresses: [192.168.88.1,77.88.8.8,77.88.8.1] + access-points: + "link": + password: "dkfgf#*12091997" + # routes: + # - to: default + # via: 192.168.88.1 +#=============================== + +chmod 600 /etc/netplan/your_config_file.yaml + +sudo netplan --debug generate +sudo netplan try +sudo netplan -d apply +sudo netplan apply + +sudo systemctl restart network-manager +sudo systemctl restart system-networkd +``` + +Для каждого сетевого интерфейса добавляем блоки **routes** и **routing-policy**. Итоговая запись для 1 интерфейса будет выглядеть так: + +```sh +network: + version: 2 + ethernets: + enp0s5: + addresses: + - 78.89.90.12/24 + gateway4: 78.89.90.1 + match: + macaddress: 54:43:32:21:10:09 + mtu: 1500 + nameservers: + addresses: + - 8.8.8.8 + - 1.1.1.1 + - 8.8.4.4 + search: [] + set-name: enp0s5 + routes: + - to: 0.0.0.0/0 + via: 78.89.90.1 + table: 700 + routing-policy: + - from: 78.89.90.12 + table: 700 + priority: 300 +``` + +Для других интерфейсов запись будет аналогична, начиная с имени интерфейса (**enp0s5**), но у других должен отсутствовать параметр **gateway4**, т.к. шлюз по умолчанию должен быть один. Настройки **routes** и **routing-policy**: + +- to – адрес назначения пакета. Для наших целей – любой (0.0.0.0/0) +- via – адрес шлюза для данного интерфейса +- table – имя таблицы маршрутизации. Должны быть указаны одинаковые значения в **routes** и **routing-policy** для одного интерфейса, но быть различными от интерфейса к интерфейсу. Данные таблицы мы создали в файле **rt_tables** +- from – адрес источника пакета. Для целей данного руководства – это IP адрес интерфейса +- priority – обязательное числовое значение + +После внесения настроек и сохранения файла проверим конфигурацию на ошибки: + +`netplan generate` + +============================================== +Пожалуйста, добавьте эту конфигурацию: + +``` +network: + renderer: networkd + ethernets: + ens18: + addresses: [1.2.3.4/24] + gateway4: 1.2.3.254 + dhcp4: no + dhcp6: no + nameservers: + search: [domain.tld] + addresses: [123.456.789.101] + routes: + - to: 1.2.3.254/32 + via: 0.0.0.0 + scope: link + ens19: + dhcp4: true + dhcp6: no + version: 2 +``` + +и нет необходимости перезагружать просто + +``` +sudo netplan --debug apply +``` + +```sh +Computer 1 +network: + version: 2 + renderer: networkd + ethernets: + enp1s0: + dhcp4: true + enp4s0: + addresses: + - xxx.xxx.xxx.xxx/24 + routes: + - to: default + via: 10.0.0.1 + tunnels: + wg0: + mode: wireguard + port: 51820 + key: XXX + addresses: + - 10.0.0.1/24 + peers: + - allowed-ips: [10.0.0.0/24] + endpoint: xxx.xxx.xxx.xxx:51820 + keys: + public: XXX + vxlan0: + mode: vxlan + id: 100 + local: 10.8.0.1 + remote: 10.8.0.2 + port: 4789 + link: enp4s0 + +Computer 2 + +network: + version: 2 + renderer: networkd + ethernets: + enp1s0: + dhcp4: true + enp4s0: + addresses: + - xxx.xxx.xxx.xxx/24 + routes: + - to: default + via: 10.0.0.2 + tunnels: + wg0: + mode: wireguard + port: 51820 + key: XXX + addresses: + - 10.0.0.1/24 + peers: + - allowed-ips: [10.0.0.0/24] + endpoint: xxx.xxx.xxx.xxx:51820 + keys: + public: XXXX + vxlan0: + mode: vxlan + id: 100 + local: 10.8.0.2 + remote: 10.8.0.1 + port: 4789 + link: enp4s0 + ``` + diff --git a/LINUX/NETWORK/NETWORK.md b/LINUX/NETWORK/NETWORK.md new file mode 100644 index 0000000..38dd456 --- /dev/null +++ b/LINUX/NETWORK/NETWORK.md @@ -0,0 +1,162 @@ +```sh +# Проверка нагрузки на сеть в Linux +# Иногда необходимо узнать, какие процессы активно используют сеть и сколько трафика они потребляют. + +# Просмотр сетевой активности. Вывести активные соединения:** +netstat -tunapl +# Отобразит все активные соединения и их состояние. + +# Проверить процессы, использующие сеть:** +sudo lsof -i -P -n +# Покажет, какие программы используют сетевые порты. + +# Мониторинг трафика в реальном времени:** +iftop +# Позволяет в реальном времени отслеживать трафик по интерфейсам. + +# Полезно для диагностики сетевых проблем, выявления утечек трафика и мониторинга серверов. + +#============================= +# +netstat -tlpn + +# кто слушает TCP порт? +sudo ss -tlpn + +# кто слушает UDP порт? +sudo ss -ulpn + +# Просмотр всех узлов в сети: +arp -n + +#============================= +# Сетевое соединение: +hostnamectl + +# отображение всех socket: +sudo ss -tlpn + +sudo apt-get install nload bmon nethogs + +ifconfig +ip a +ls /sys/class/net + +nc localhost 22 + +sudo lshw -C network + +sudo lspci | grep Network +lspci -knn | grep Net -A2 + +sudo ethtool enp2s0 + +ip link show +ip -br link show + +wg-quick up wg0 +wg-quick down wg0 + + +# получить IP: +curl ifconfig.me + + +sudo apt install networking-tool +sudo iwconfig +sudo iwlist wlan0 scan + + +# - поиск беспроводных pci-карт +lspci | grep -i wireless + +# - вывод этой команды будет содержать все сетевые интерфейсы +ip link list + +# раскрасить и отобразить все IP-адреса и интерфейсы в Linux в табличном формате для лучшей читаемости: +ip -br -c addr show +ip -br -c link show + +# - показать устройства wi-fi +iw dev + +# - включить устройство wlp2s0 +ip link set wlp2s0 up + +# !!! Если у вас возникнет ошибка: +# !!! RTNETLINK answers: Operation not possible due to RF-kill +# !!! Можно установить утилиту rfkill и с ее помощью решить эту проблему + +# - присвоить SSID "MyNet" +iwconfig wlan0 essid "MyNet" + +# - установит канал 3 +iwconfig wlan0 channel 3 + +# - сменить режим Managed на Ad-Hoc +iwconfig wlan0 mode Ad-Hoc + +# - использовать любую точку доступа +iwconfig wlan0 ap any + +# - установить чувствительность -50 +iwconfig wlan0 sens -50 + +# - установить значение 20 для повторной передачи MAC +iwconfig wlan0 retry 20 + +# - установить пароль 1234-pass-123 +ifconfig wlan0 key 1234-pass-123 + +# - подключение к незащищенной сети +iwconfig wlp4s0 essid ваш_essid + +# - подключение с задающим каналом сети +iwconfig wlp4s0 essid ваш_essid freq 2432M + +# -подключение к WEP зашифрованному каналу сети, ключа ASCII +iwconfig wlp4s0 essid ваш_essid key s:ваш_ключ + +# - включение режима экономии питания +iwconfig wlp4s0 power on + +# - показать статус соединения +iw dev wlp4s0 link iwconfig wlp4s0 + +# -сканирование сети на точки доступа +iw dev wlp4s0 scan iwlist wlp4s0 scan + +# - настройка режима работы ad-hoc +iw dev wlp4s0 set type ibss + +# - подключение к незащищенной сети +iw dev wlp4s0 connect ваш_essid + +# - подключение с задающим каналом сети +iw dev wlp4s0 connect ваш_essid 2432 + +# - -подключение к WEP зашифрованному каналу сети, ключа ASCII +iw dev wlp4s0 connect ваш_essid key 0:ваш_ключ + +# - включение режима экономии питания +iw dev wlp4s0 set power_save on + +# - сканируем сеть +iw dev wlp4s0 scan | less + +#================================== +# Изменить скорость сетевой карты + +sudo ethtool enp2s0 + +sudo ethtool -s eth0 duplex full speed 100 autoneg off + +sudo ethtool -s eth0 duplex full speed 1000 autoneg off + +# как то же самое сделать не имея ethtool? +mii-tool -F 100baseTx-FD eth0 + +# вариант совсем для гиков, на «чистом» линуксе :): +ifconfig eth0 media 100baseTX mediaopt full-duplex + +#================================== diff --git a/LINUX/NETWORK/Автоматическое обнаружение и уведомление о новых устройствах в сети.md b/LINUX/NETWORK/Автоматическое обнаружение и уведомление о новых устройствах в сети.md new file mode 100644 index 0000000..677a0b3 --- /dev/null +++ b/LINUX/NETWORK/Автоматическое обнаружение и уведомление о новых устройствах в сети.md @@ -0,0 +1,49 @@ +```sh +#!/bin/bash + +# Укажите диапазон сети (пример: 192.168.1.0/24) +NETWORK="192.168.1.0/24" + +# Файл для хранения предыдущего списка устройств +KNOWN_DEVICES="$HOME/known_devices.txt" + +# Файл для временного списка +CURRENT_DEVICES="$HOME/current_devices.txt" + +# Сканируем сеть и сохраняем MAC-адреса активных устройств +arp-scan --localnet | awk '/:/{print $2}' | sort > "$CURRENT_DEVICES" + +# Если это первый запуск, просто сохраняем список +if [ ! -f "$KNOWN_DEVICES" ]; then + cp "$CURRENT_DEVICES" "$KNOWN_DEVICES" + echo "🔍 Список известных устройств сохранён." + exit 0 +fi + +# Поиск новых устройств +NEW_DEVICES=$(comm -13 "$KNOWN_DEVICES" "$CURRENT_DEVICES") + +if [ -n "$NEW_DEVICES" ]; then + echo "🚨 Обнаружены новые устройства в сети:" + echo "$NEW_DEVICES" + notify-send "Новые устройства в сети" "$NEW_DEVICES" + echo "$NEW_DEVICES" >> "$KNOWN_DEVICES" +else + echo "✅ Новых устройств не обнаружено." +fi + +# Обновляем список известных устройств +mv "$CURRENT_DEVICES" "$KNOWN_DEVICES" +``` + + Использует arp-scan для поиска активных устройств. + +🟢 Сохраняет список известных устройств и сравнивает его с новыми данными. + +🟢 Отправляет уведомление, если в сети появилось новое устройство. + +🟢 Можно добавить в crontab для автоматического мониторинга: + +`*/5 * * * * /path/to/script.sh` + +Этот скрипт поможет отслеживать неизвестные подключения и контролировать безопасность сети.
\ No newline at end of file diff --git a/LINUX/NETWORK/Внешний IP из консоли.md b/LINUX/NETWORK/Внешний IP из консоли.md new file mode 100644 index 0000000..d1a5d4f --- /dev/null +++ b/LINUX/NETWORK/Внешний IP из консоли.md @@ -0,0 +1,17 @@ +```sh +# Сетевое соединение: +hostnamectl + +wget -qO- eth0.me + +wget -qO- ipecho.net/plain +curl ifconfig.me +wget -O - -q ifconfig.me/ip + + +curl https://icanhazip.com +curl https://ipinfo.io/ip +curl https://ifconfig.me + + +``` diff --git a/LINUX/NETWORK/СКОРОСТЬ СЕТЕВОЙ КАРТЫ.md b/LINUX/NETWORK/СКОРОСТЬ СЕТЕВОЙ КАРТЫ.md new file mode 100644 index 0000000..66ad165 --- /dev/null +++ b/LINUX/NETWORK/СКОРОСТЬ СЕТЕВОЙ КАРТЫ.md @@ -0,0 +1,10 @@ +```sh +# Сетевое соединение: +hostnamectl + +ip a + +cat /sys/class/net/<interface>/speed + +sudo ethtool <interface> | grep Speed +``` diff --git a/LINUX/NGINX.md b/LINUX/NGINX.md new file mode 100644 index 0000000..21e9380 --- /dev/null +++ b/LINUX/NGINX.md @@ -0,0 +1,538 @@ +## Настройка перенаправлений + +Настройки необходимо вносить в файлах конфигураций виртуальных доменов. В [Linux](https://www.dmosk.ru/terminus.php?object=linux) на основе RPM (CentOS, Red Hat), как правило, они расположены в директории /etc/nginx/conf.d/. В Linux на основе Deb (Ubuntu, Debian) — в директории /etc/nginx/sites-enabled/. Во [FreeBSD](https://www.dmosk.ru/terminus.php?object=freebsd) все в одном файле — /usr/local/etc/nginx/nginx.conf. + +Саму настройку на перенаправление в NGINX можно прописать несколькими способами. + +1. Первый: + +rewrite ^ https://$host$request_uri? <флаг>; + +_* **$host** — имя хоста из запроса, если отсутствует — имя в поле «Host» заголовка, если тоже отсутствует — имя сервера; **$request_uri** — первоначальный запрос с аргументами (все, что идет после доменного имени). +** где флаги могут быть следующие:_ + +- _permanent — перенаправление с кодом 301._ +- _redirect — перенаправить с кодом 302._ +- _last — закончить обработку с переходом в новый location._ +- _break — закончить обработку и остаться в текущем location._ + +2. Второй: + +return <код> https://$host$request_uri; + +_* где коды могут использоваться любые, но чаще всего — 301, 302, 404._ + +Есть различные мнения, какой из методов лучше и безопаснее, поэтому каким воспользоваться — решать по ситуации. В данных примерах используются оба варианта. + +После внесения изменений, необходимо проверить их корректность: + +nginx -t + +И для их применения перезапустить веб-сервер: + +systemctl restart nginx + +service nginx restart + +_* в первом примере перезапуск выполняется на новых системах Linux. Второй пример — на устаревших или FreeBSD._ + +Проверяя редиректы в [браузере](https://www.dmosk.ru/terminus.php?object=brouser), следует учесть, что настройки могут [кэшироваться](https://www.dmosk.ru/terminus.php?object=cache). Для обновления кэша используйте комбинацию Ctrl + F5. Если и это не помогает, закрывайте вкладку и открывайте новую. + +## С HTTP на HTTPS (другой порт) + +Пример конфигурации для перенаправления запросов на другой порт — с 80 (http) на 443 (https): + +server { + listen 80; + server_name domain.ru www.domain.ru; + return 301 https://$host$request_uri; +} + +_* в данном примере для всех обращений к сайту domain.ru по 80 порту ([http](https://www.dmosk.ru/terminus.php?object=http)) будет работать редирект на 443 порт ([https](https://www.dmosk.ru/terminus.php?object=https)) с кодом 301 (для склеивания [доменов](https://www.dmosk.ru/terminus.php?object=domain))._ + +Также мы можем добавить условие, чтобы не перенаправлять на https для определенных ссылок, например: + +server { + listen 80; + server_name domain.ru www.domain.ru; + if ($uri !~ /page.html){ + return 301 https://$host$request_uri; + } +} + +_* в данном примере запрос на страницу **/page.html** будет открыт по http._ + +Есть еще способ настройки директивы **server** сразу для http и https. При этом, если зайти по 80 порты, нас перекинет на 443: + +server { + listen 80; + listen 443 ssl; + server_name domain.ru www.domain.ru; + + if ($scheme = 'http') { + return 301 https://$host$request_uri; + } + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/cert.key; + + ... +} + +_* данный способ удобен, чтобы не создавать несколько секций server со своими настройками._ + +## С одного домена на другой + +server { + ... + server_name domain1.ru; + return 302 http://domain2.ru$request_uri; +} + +## C домена без www на домен с www + +server { + ... + server_name domain.ru; + return 301 http://www.$host$request_uri; +} + +## С www на без www + +Конкретный домен: + +server { + ... + server_name www.domain.ru; + return 301 $scheme://domain.ru$request_uri; +} + +Все домены, которые обслуживает nginx: + +server { + ... + server_name "~^www\.(.*)$"; + return 301 $scheme://$1$request_uri; +} + +Или с использованием if: + +server { + ... + if ($host ~ "^www\.(.*)$") { + return 301 $scheme://$1$request_uri; + } + ... +} + +## C index.php на / (корень) + +Данная настройка позволит перевести все запросы с /index.php на корневой адрес /: + +server { + ... + if ($request_uri ~ "^(.*)index\.(?:php|html)") { + return 301 $1; + } +} + +## Перенаправление запросов для отсутствующих доменов (перенаправление по умолчанию) + +Если обращение к веб-серверу идет по [IP-адресу](https://www.dmosk.ru/terminus.php?object=ipaddr) или домену, который не прописан в конфигурационном файле, можно перенаправить весь трафик на домен по умолчанию: + +server { + listen 80 default_server; + return 302 https://welcome.domain.ru$request_uri; +} + +или независимо от протокола: + +server { + listen 80 default_server; + return 302 $scheme://welcome.domain.ru$request_uri; +} + +server { + listen 443 ssl default_server; + return 302 $scheme://welcome.domain.ru$request_uri; + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/cert.key; +} + +_* **$scheme** позволяет перевести запрос на тот же протокол (http или https), по которому он был инициирован. +* если nginx должен слушать и обрабатывать запросы по https, необходимо указывать в настройках пути к сертификатам._ + +## С IP-адреса на домен + +В данном случае мы переводим все запросы по IP-адресу на конкретный домен: + +server { + listen 80; + server_name 192.168.1.15; + return 301 http://site.ru$request_uri; +} + +_* при отправке http-запроса на сервер **192.168.1.15** по IP-адресу, он будет переведен на домен **site.ru**._ + +## Редирект домена и всех его поддоменов + +server { + ... + server_name domain domain.*; + return 301 https://$host$request_uri; +} + +## На другой файл + +Это скорее не перенаправление, а алиас или rewrite. Позволяет по запросу одного из файлов, отдать другой: + +server { + ... + location = /robots.txt { + rewrite ^/robots.txt$ /robots2.txt; + } +} + +_* в данном примере по запросу **robots.txt**, сервер отдаст содержимое **robots2.txt**._ + +## Часть url на другой сервер + +Перенаправить запрос на другой сервер при обращении по url page1: + +server { + ... + server_name domain1.ru; + location ~ ^/page1/(.*)$ { + return 301 $scheme://domain2.ru/$1; + } +} + +_* в данном примере для всех запросов, начинающихся на **/page1/...** будет работать перенаправление на другой домен **domain2.ru**._ + +## Редирект со слешем + +**1. Убрать слеш в конце url** + +server { + ... + if (!-f $request_filename ) { + rewrite ^/(.*)/$ /$1 permanent; + } +} + +**2. Добавить слеш в конце url** + +server { + ... + if (!-f $request_filename ) { + rewrite ^(.*[^/])$ $1/ permanent; + } +} + +## Удаляем расширение + +Для перенаправления запроса, где в URL есть полное название файла скрипта (с расширением) используем конфигурацию: + +server { + ... + if ($request_uri ~ "^(.*)\.(?:php|html)") { + return 301 $1; + } +} + +_* в данном случае все запросы, которые заканчиваются на **.php** или **.html**, будут перенаправляться на страницы без данных расширений._ + +Для примера, запрос http://site-example.ru/page.php будет переведен на http://site-example.ru/page. + +## На другую страницу + +Нам может понадобиться перенаправлять запросы с одной страницы сайта на другую. Приведем примеры, как это сделать с помощью return и rewrite. + +а) с помощью rewrite: + +server { + ... + rewrite ^/page1$ /page2 permanent; +} + +б) с помощью return: + +server { + ... + location = /page1 { + return 301 /page2; + } +} + +## Удалить часть URL + +Иногда нужно удалять часть url. Это можно сделать следующими способами: + +server { + ... + rewrite /deleted-url/(.*) /$1 permanent; +} + +или: + +server { + ... + if ($request_uri ~ "/deleted-url/(.*)") { + return 301 $1; + } +} + +_* в данном примере из url мы удалим **deleted-url/**._ + +## Перенаправить запрос в случае обращения к несуществующим файлам + +Предположим, что нам нужно обращаться к скриптам на сервере, но без прописывания в URL .php на конце. Запрос будет выглядеть, примерно, http://url/. Чтобы nginx перекинул запрос на url.php вставляем следующее: + +server { + ... + location / { + root /var/www; + try_files $uri $uri/ $uri.php $uri.html /404.php =404; + } +} + +_* в данном примере мы проверим наличие скрипта по пути **$uri**, затем **$uri/**. И уже после мы добавим к запросу **.php** и **.html**. Если ни одного скрипта не будет найдено, запрос будет перенаправлен на страницу **404.php**, которая вернет код 404._ + +Также можно решить задачу с помощью **if** и **rewrite**: + +server { + ... + if (!-e $request_filename){ + rewrite ^(.*)$ /$1.php; + } +} + +_* в данном примере мы проверяем наличие файла, к которому идет обращение. И если его нет, то происходит замена адреса на такое же имя файла с .php на конце._ + +## Перевод запросов, если файла не существует + +Данное действие не является редиректом, но близко по смыслу — NGINX проверяет наличие файла скрипта, к которому идет обращение, и если его нет, переводит запрос на другой файл. Как правило, это используется для того, чтобы перевести все обращения на файл index.php. + +server { + ... + location / { + try_files $uri $uri/ /index.php?$query_string; + } + ... +} + +_* в данном примере мы скажем веб-серверу сначала проверить наличие скрипта по пути **$uri**, затем **$uri/** и если ответ будет 404, запросы будут обрабатываться с помощью файла **index.php** в корневой директории._ + +## Проксирование + +Проксирование, в отличие от редиректа, не передает инструкции браузеру перейти на другой url — NGINX сам выполняет http-запрос по другому адресу и возвращает готовый ответ. Эта возможность может применяться для внутреннего распределения серверных ресурсов. + +Хоть это и не совсем редирект, рассмотрим примеры его настройки, так как очень часто нужно не перенаправление, а, как раз, обратное проксирование. + +### 1. На другой сервер + +Пример внутреннего перенаправления http-запроса на другой веб-сервер: + +location / { + proxy_pass http://192.168.0.15:8080/; + proxy_redirect off; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto http; +} + +* в данном случае, принимать запросы от браузера и отвечать на них будет NGINX, а сама обработка будет выполняться на сервере с IP-адресом 192.168.0.15 на порту 8080. Рассмотрим все директивы: + +- **proxy_pass —** схема, узел и порт. По сути, это как и куда будет перенаправлен запрос. +- **proxy_redirect —** позволяет отловить редиректы или определенные адреса и заменить их на другие. Пример работы с данной директивой [описан ниже](https://www.dmosk.ru/miniinstruktions.php?mini=nginx-redirects#proxypass-redirects). +- **proxy_http_version —** задает версию протокола http. По умолчанию имеет значение 1.0. +- **proxy_set_header —** добавляет http-заголовки: + - **Host —** сообщяет принимающей стороне, какой виртуальный хост использовать. Важный заголовок, если данная принимающая сторона обслуживает разные домены и требуется точная передача имени. + - **Upgrade —** действует только для http версии 1.1. Позволяет обновить протокол или его версию для уже созданного соединения. Например, с HTTP 1.1 до HTTP 2.0 или в WebSocket. + - **Connection —** определяет, останется ли сетевое соединение открытым после завершения текущей транзакции. + - **X-Real-IP —** позволяет передать принимающей стороне реальный IP-адрес посетителя. В противном случае, принимающая сторона получить адрес самого nginx. + - **X-Forwarded-For —** второй заголовок, с помощью которого передается реальный IP-адрес посетителя. + - **X-Forwarded-Proto —** определяет протокол, по которому клиент подключился к nginx. По возможности, стоит использовать https, но все зависит от принимающей стороны — она может отказаться принимать https или наоборот, требовать только его. + +Использование NGINX в качестве http-прокси: + +server { + ... + server_name site1.ru www.site1.ru; + location / { + proxy_pass http://192.168.1.21/; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} + +server { + ... + server_name site2.ru www.site2.ru; + location / { + proxy_pass http://192.168.1.22/; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} + +_* в данном примере запросы на **site1.ru** будут перекинуты на сервер **192.168.1.21**, а запросы на **site2.ru** — **192.168.1.22**._ + +HTTP proxy с авторизацией (если удаленный веб-сервер требует аутентификации): + +server { + ... + location / { + proxy_pass http://10.10.10.10/page/; + proxy_set_header Authorization "Basic dGVzdDp0ZXN0"; + ... + } +} + +_* где **10.10.10.10/page** — страница, на которую будут перекинуты запросы; **dGVzdDp0ZXN0** — логин:пароль test:test, закодированные в формате base64._ + +### 2. Часть url на другой сервер + +Выше мы рассмотрели пример перенаправления запроса по части веб-адреса. По схожему сценарию мы можем делать проксирование: + +server { + ... + location ~ ^/page1/(.*)$ { + proxy_pass $scheme://10.10.10.10/$1$is_args$args; + } +} + +_* и так, в данном примере при обращении по адресу **site.ru/page1/<что-то еще>**, nginx сделает внутренний запрос на сервер **10.10.10.10** по адресу **10.10.10.10/<что-то еще>** и вернет готовый ответ. +** обратите внимание, что мы добавили **$is_args$args —** если строка запроса имеет аргументы, то их также нужно передать._ + +### 3. На другой сайт + +Мы можем сделать так, что при переходе по одному адресу у нас будет открываться совершенно другой сайт: + +server { + ... + server_name dmosk.local; + location / { + proxy_pass https://www.dmosk.ru; + proxy_set_header Host www.dmosk.ru; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} + +_* в данном случае мы при обращении к нашему серверу (до доменному имени **dmosk.local**) будем попадать на сайт **https://www.dmosk.ru**. Обратите внимание, что в **proxy_set_header** мы передаем хосту его имя — в противном случае, как правило, другой сервер вернет ошибку. Также мы не указываем **proxy_redirect**, иначе, nginx будет переводить запросы на реальный сайт (отправлять инструкции браузеру перейти на него), а не тот, что мы используем за http-прокси._ + +### 4. На другой сайт по части URL + +Если нам нужно настроить проксирование на другой сайт при обращении к определенной странице сайта, настраиваем NGINX так: + +server { + ... + server_name www.dmosk.ru; + location /page { + rewrite /page/(.*) /$1 break; + proxy_pass https://www.site.ru; + proxy_set_header Host www.site.ru; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} + +_* в данном примере, если мы перейдем на страницу **www.dmosk.ru/page**, то мы попадем на сайт **www.site.ru**._ + +### 5. Редиректы при проксировании + +Если при проксировании хост возвращает инструкцию браузеру для выполнения редиректа, обозреватель может сменить адрес сайта. Это особенно не удобно, когда проксирование мы выполняем на другой сайт. Чтобы отловить редиректы и заменить их своими значениями, мы должны воспользоваться опцией **proxy_redirect**. Рассмотрим ее применение для предыдущего примера, когда мы проксировали запрос на сайт www.dmosk.ru: + +server { + listen 80; + server_name dmosk.local www.dmosk.local; + location / { + proxy_pass https://www.dmosk.ru; + proxy_set_header Host www.dmosk.ru; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect https://www.dmosk.ru/url1 http://dmosk.local/url2; + proxy_redirect https://www.dmosk.ru/ http://dmosk.local/; + } +} + +_* в конкретном случае мы проксируем запросы **http://dmosk.local** на сайт **www.dmosk.ru**, но если он вернет инструкцию для редиректа **https://www.dmosk.ru/url1**, в браузере он должен быть заменен на **http://dmosk.local/url2**. А также любое перенаправление для **https://www.dmosk.ru/** будет заменено на **http://dmosk.local/**._ + +### 6. Таймауты + +Сервер может генерировать, довольно, долго ответ. Если за определенное время nginx не получит ответ, он вернет ошибку с кодом 504. Мы можем задать свои значения таймаутов, если приложение должно отвечать долго, например: + + location / { + ... + proxy_connect_timeout 30; + proxy_send_timeout 30; + proxy_read_timeout 300; + ... + } + +* где: + +- **proxy_connect_timeout —** время соединения с удаленным сервером. +- **proxy_send_timeout —** время ожидания для отправки запроса на сервер. +- **proxy_read_timeout —** время ожидания ответа от сервера. + +** время указывается в секундах. + +## Работа с location + +Обратите внимание, что мы часто использовали в качестве условия запроса его URN (Unifrorm Resource Name или унифицированное имя ресурса). Поэтому важно понимать, как мы можем работать с ними при помощи nginx, когда будем обрабатывать запросы для редиректов или проксирований. + +### Типы location + +Выше мы применяли различные ваианты написания location. Рассмотрим их чуть подробнее: + +- **= —** явное указание на точное название адреса запроса. +- **^~ —** регулярное выражение. Требует совпадения в начале строки. +- **/ или /path —** строковое выражение. Требует совпадения в начале строки. Сильно напоминает **^~**, но не позволяет применять регулярные выражения. +- **~ —** регулярное выражение. С учётом регистра. +- **~* —** регулярное выражение. Без учёта регистра. + +### Приоритеты + +В конфигурации nginx мы можем создать много конструкций на подобие: + +location /api { +} + +location /news { +} + +location /newstest { +} + +Они начнут обрабатываться сверху вниз, пока не будет найден первый подходящий вариант. Таким образом, в нашем примере, содержимое страницы **newstest** не сможет загрузиться, так как будет загружено содержимое страницы **news** (ее location выше в конфигурации). + +Приоритеты при выборе, какой location будет грузиться раньше, работают не только по принципу "сверху вниз". Также учитывается тип обработки (выше мы уже их рассмотрели): + +**1. location = /path.** Точное совпадение. Самый высокий приоритет. Сначала будут обрабатываться все локейшены данного типа. + +**2. location ^~ /path.** Регулярное выражение с совпадением в начале строки. + +**3. location ~ /path/ , location ~* .(api|test).** Регулярные выражения. + +**4. location /path.** Строковое выражение. Самый низкий приоритет. + +## Немного о 301 и 302 + +В чем принципиальная разница между ответом с кодом 301 и 302? Для обычного посетителя сайта разницы нет. А вот для поискового робота разница огромная. + +301-й редирект говорит о склеивании страниц. Это означает для поисковика то, что старая и новая страницы — это одно и то же. Таким образом, результаты ранжирования необходимо сохранить для новой страницы. + +302-о перенаправление просто говорит о том, что нужно перейти по другому адресу. Поисковый робот не сохраняет результат выдачи для новой страницы, индексируя его с нуля.
\ No newline at end of file diff --git a/LINUX/PLEX.md b/LINUX/PLEX.md new file mode 100644 index 0000000..5e178d8 --- /dev/null +++ b/LINUX/PLEX.md @@ -0,0 +1,33 @@ +Plex Media Server — это ключевое решение для энтузиастов потоковой передачи, которые стремятся организовать и получить доступ к своим медиабиблиотекам без проблем с различных устройств. Его способность перекодировать медиафайлы на лету обеспечивает совместимость на разных платформах, что делает его незаменимым для унифицированного потокового опыта. + +Ключевые особенности Plex Media Server включают в себя: + +- **Централизованная медиабиблиотека:** объединяет ваши видео, музыку и фотографии на одной простой в навигации платформе. +- **Транскодирование «на лету»:** преобразует медиаданные в оптимальный формат для каждого устройства, улучшая качество воспроизведения. +- **Удаленный доступ:** транслируйте свой контент из любой точки мира, на любое устройство с доступом в Интернет. +- **Удобный интерфейс:** обеспечивает визуально привлекательную и интуитивно понятную навигацию. +- **Настраиваемые плейлисты и библиотеки:** адаптируйте свои впечатления от просмотра и прослушивания, создавая персонализированные коллекции. +- **Расширенная поддержка устройств:** совместимость с различными устройствами, от смартфонов до смарт-телевизоров. +- **Безопасный обмен:** безопасно делитесь выбранным контентом с друзьями и семьей. +- **Регулярные обновления:** постоянно совершенствуется за счет новых функций и улучшений. + +```sh +sudo apt update && sudo apt upgrade -y +sudo apt install gpg ca-certificates software-properties-common apt-transport-https curl -y +curl -fsSL https://downloads.plex.tv/plex-keys/PlexSign.key | sudo gpg --dearmor -o /usr/share/keyrings/plex.gpg +echo "deb [signed-by=/usr/share/keyrings/plex.gpg] https://downloads.plex.tv/repo/deb public main" | sudo tee /etc/apt/sources.list.d/plexmediaserver.list + +sudo apt update +sudo apt install plexmediaserver + +systemctl status plexmediaserver + +sudo systemctl start plexmediaserver +sudo systemctl enable plexmediaserver +sudo systemctl restart plexmediaserver + + +sudo ufw enable +sudo ufw allow 32400 + +```
\ No newline at end of file diff --git a/LINUX/PROGRAMS_bash.md b/LINUX/PROGRAMS_bash.md new file mode 100644 index 0000000..e406fbe --- /dev/null +++ b/LINUX/PROGRAMS_bash.md @@ -0,0 +1,311 @@ +Волшебная строчка, которую нужно добавить после `#!/bin/bash`, чтоб скрипт можно было отлаживать по шагам +```bash +#!/bin/bash +trap 'echo "# $BASH_COMMAND";read' DEBUG + +echo line1 +echo line2 +echo line3 +``` + +**Процесс отладки** + +Запускаем скрипт, перед выполнением каждой команды выводится то, что будет исполняться, затем интерпретатор начинает ожидать нажатия клавиши `<ENTER>`. + +Если понимаем, что что-то пошло не так, нажимаем Ctrl+C и выходим из отладки. + +**Ингредиенты** + +команда trap, которая умеет перехватывать разные сигналы и в нашем случае она перехватывает сигнал DEBUG, посылаемый перед выполнением команды + +команда read, которая умеет ожидать ввода чего-нибудь с клавиатуры (в данном случае нам нужно только либо ENTER либо Ctrl+C + +переменная окружения ==`$BASH_COMMAND`==, валидная внутри обработчика команды trap. + +=============================================== +**Автоматическое выключение компьютера при низком заряде батареи** + +```bash +#!/bin/bash + +# Порог заряда батареи в процентах +THRESHOLD=15 + +# Получение текущего заряда батареи +BATTERY_LEVEL=$(acpi -b | grep -P -o '[0-9]+(?=%)') + +# Проверка уровня заряда и выключение, если ниже порога +if [ "$BATTERY_LEVEL" -lt "$THRESHOLD" ]; then + echo "Низкий уровень заряда батареи: $BATTERY_LEVEL%. Выключение..." + shutdown -h now +fi +``` + **Скрипт проверяет уровень заряда батареи и автоматически выключает компьютер, если заряд ниже указанного порога.** + +Особого внимания заслуживает использование команды acpi для получения уровня заряда батареи. Скрипт полезен для защиты ноутбуков от аварийного выключения при низком заряде. + +=============================================== +### Автоматическое обновление IP-адреса в DNS-записи ### +```bash +#!/bin/bash + +# Получение текущего внешнего IP-адреса +CURRENT_IP=$(curl -s http://ifconfig.me) + +# Путь к файлу с последним IP-адресом +IP_FILE="$HOME/current_ip.txt" + +# Чтение последнего сохраненного IP-адреса +if [ -f "$IP_FILE" ]; then + LAST_IP=$(cat "$IP_FILE") +else + LAST_IP="" +fi + +# Если IP-адрес изменился, обновить DNS-запись +if [ "$CURRENT_IP" != "$LAST_IP" ]; then + echo "IP изменился на $CURRENT_IP, обновление DNS..." + + # Обновление DNS-записи (пример с использованием Cloudflare API) + curl -X PUT "https://api.cloudflare.com/client/v4/zones/your_zone_id/dns_records/your_record_id" \ + -H "Authorization: Bearer your_api_token" \ + -H "Content-Type: application/json" \ + --data "{\"type\":\"A\",\"name\":\"yourdomain.com\",\"content\":\"$CURRENT_IP\",\"ttl\":120,\"proxied\":false}" + + # Сохранение нового IP-адреса + echo "$CURRENT_IP" > "$IP_FILE" +else + echo "IP не изменился, обновление не требуется." +fi +``` +**Скрипт автоматически проверяет, изменился ли внешний IP-адрес, и при изменении обновляет DNS-запись с использованием Cloudflare API.** + +Особого внимания заслуживает логика проверки текущего и предыдущего IP-адреса, что позволяет избежать ненужных запросов к DNS-серверу. В случае изменения IP-адреса, скрипт использует API Cloudflare для обновления DNS-записи. Такой скрипт полезен для пользователей с динамическим IP-адресом, которые хотят поддерживать актуальную DNS-запись для своего домена. + +=============================================== +### Автоматическое резервное копирование файлов с указанием даты ### +```bash +#!/bin/sh +cp -r /path/to/source /path/to/backup/backup_$(date +%Y%m%d) +``` +Скрипт создает резервную копию указанной директории, добавляя текущую дату в имя папки для каждой копии. + +=============================================== +### Работа с логами в Linux с помощью journalctl ### +```bash +# Вывод всех системных логов +journalctl +``` +выводит полный журнал событий системы. Вывод может содержать сообщения ядра, системные службы и другие системные события. + +```bash +# Фильтрация логов по дате: +# Чтобы отфильтровать логи по дате и времени, можно использовать параметры +journalctl --since +journalctl --until + +# Логи за определённый промежуток времени +journalctl --since "2023-08-01 10:00:00" --until "2023-08-01 12:00:00" +``` +С помощью этих параметров можно просматривать логи за конкретный период, что упрощает поиск информации о системных событиях, произошедших в определённое время. +```bash +# Логи службы nginx +journalctl -u nginx +``` +С помощью флага -u можно выводить логи, связанные с конкретной системой или службой, например, nginx. Это полезно для диагностики и устранения проблем, связанных с отдельными компонентами системы. +```bash +# Отслеживание логов в реальном времени: +# Функция реального времени позволяет наблюдать за логами по мере их поступления в систему. + +journalctl -f +``` +Команда ==`journalctl -f`== выводит логи в реальном времени, аналогично команде ==`tail -f`== для текстовых файлов. Это особенно полезно при мониторинге работы системы или приложений в режиме реального времени. + +Использование `journalctl` позволяет легко анализировать системные логи, отфильтровывать нужную информацию и отслеживать проблемы в работе системы или сервисов. + +================================================ +## Отчет о системной информации и отправка его по электронной почте ## +```bash +#!/bin/bash + +# Создание файла отчета +report_file="$HOME/system_report.txt" + +# Запись заголовка в файл отчета +echo "Отчет о системной информации - $(date)" > "$report_file" +echo "========================================" >> "$report_file" + +# Системная информация +echo -e "\nСистемная информация:" >> "$report_file" +uname -a >> "$report_file" + +# Информация о загрузке системы +echo -e "\nИнформация о загрузке системы:" >> "$report_file" +uptime >> "$report_file" + +# Использование дискового пространства +echo -e "\nИспользование дискового пространства:" >> "$report_file" +df -h >> "$report_file" + +# Использование памяти +echo -e "\nИспользование памяти:" >> "$report_file" +free -h >> "$report_file" + +# Список работающих процессов +echo -e "\nСписок работающих процессов:" >> "$report_file" +ps aux --sort=-%mem | head -n 10 >> "$report_file" + +# Сетевые подключения +echo -e "\nСетевые подключения:" >> "$report_file" +ss -tuln >> "$report_file" + +# Отправка отчета по электронной почте +recipient="user@example.com" +mail -s "Отчет о системной информации" "$recipient" < "$report_file" + +echo "Отчет о системной информации создан и отправлен на $recipient" +``` +Этот скрипт полезен для системных администраторов и может быть настроен для автоматического выполнения с помощью cron, чтобы регулярно получать отчеты о состоянии системы. + +================================= +### Найдите все неработающие симлинки (символьные ссылки) в вашей системе с помощью команды: ### + +```bash +find . -xtype l +``` +================================= +### Обновление и очистка системы ### +```bash +#!/bin/bash + +# Обновление списка пакетов и установка обновлений +sudo apt-get update +sudo apt-get upgrade -y + +# Автоматическое удаление ненужных пакетов и очистка кэша +sudo apt-get autoremove -y +sudo apt-get autoclean -y +sudo apt-get clean + +# Вывод сообщения о завершении +echo "Система обновлена и очищена." +``` +================================= +### Синхронизация директорий с помощью rsync ### +```bash +#!/bin/bash + +# Исходная и целевая директории +source_directory="$HOME/source_directory" +target_directory="$HOME/target_directory" + +# Лог-файл +log_file="$HOME/rsync_log.txt" + +# Запуск синхронизации +rsync -av --delete "$source_directory/" "$target_directory/" >> "$log_file" 2>&1 + +# Проверка результата синхронизации +if [ $? -eq 0 ]; then + echo "Синхронизация завершена успешно: $(date)" >> "$log_file" +else + echo "Ошибка синхронизации: $(date)" >> "$log_file" +fi + +echo "Синхронизация завершена. Подробности в лог-файле $log_file" +``` +Скрипт синхронизирует содержимое исходной директории с целевой директорией, создавая резервную копию и записывая результаты в лог-файл. + +================================= +### Функция для создания резервной копии ### +```bash +#!/bin/bash + +# Путь к отслеживаемому каталогу +WATCHED_DIR="/path/to/watched/directory" + +# Путь для сохранения резервных копий +BACKUP_DIR="/path/to/backup/directory" + +# Функция для создания резервной копии +backup() { + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + tar -czf "$BACKUP_DIR/backup_$TIMESTAMP.tar.gz" -C "$WATCHED_DIR" . + echo "Backup created at $BACKUP_DIR/backup_$TIMESTAMP.tar.gz" +} + +# Использование inotifywait для мониторинга изменений в каталоге +inotifywait -m -r -e modify -e move -e create -e delete "$WATCHED_DIR" --format '%T %w %f %e' --timefmt '%Y-%m-%d %H:%M:%S' | +while read date time dir file event +do + echo "Detected $event on $file at $date $time" + backup +done +``` +================================= +### Создание резервной копии базы данных ### +```bash +#!/bin/bash + +# Параметры базы данных +db_name="mydatabase" +db_user="myuser" +db_password="mypassword" +backup_dir="$HOME/db_backups" +backup_file="$backup_dir/${db_name}_$(date +%Y%m%d%H%M%S).sql" + +# Создание каталога для резервных копий, если он не существует +mkdir -p "$backup_dir" + +# Создание резервной копии базы данных +mysqldump -u "$db_user" -p"$db_password" "$db_name" > "$backup_file" + +# Проверка результата резервного копирования +if [ $? -eq 0 ]; then + echo "Резервное копирование базы данных завершено успешно: $(date)" >> "$backup_dir/backup_log.txt" +else + echo "Ошибка резервного копирования базы данных: $(date)" >> "$backup_dir/backup_log.txt" +fi + +echo "Резервная копия базы данных создана: $backup_file" +``` +================================= +### Создание резервной копии базы данных 2 ### +```bash +#!/bin/bash +# Установите желаемую длину пароля +password_length=12 +# Генерация пароля +password=$(tr -dc 'A-Za-z0-9_!@#$%^&*' < /dev/urandom | head -c $password_length) +------ +echo "Сгенерированный пароль: $password +------ +#!/bin/bash +# Настройки для подключения к базе данных +DB_USER="yourusername" +DB_PASSWORD="yourpassword" +DB_NAME="yourdbname" +BACKUP_PATH="/path/to/your/backup/directory" +DATE=$(date +%Y%m%d%H%M) + +# Создание дампа базы данных +mysqldump -u $DB_USER -p$DB_PASSWORD $DB_NAME > $BACKUP_PATH/${DB_NAME}_$DATE.sql + +# Опционально: удаление старых резервных копий старше 30 дней +find $BACKUP_PATH -name "*.sql" -type f -mtime +30 -exec rm {} \; + +echo "✔️ Резервная копия базы данных $DB_NAME создана успешно." +``` +Скрипт создает резервную копию указанной MySQL базы данных и сохраняет её в заданный каталог с меткой текущей даты и времени в имени файла. + +================================= +### Архив tar.gz текущего каталога ### +```bash +#!/bin/sh +tar -czf "../${PWD##*/}.tar.gz" . +``` +Скрипт создает архив tar.gz текущего каталога. + +Особого внимания здесь заслуживает конструкция ==`${PWD##*/}`==, которая берет полный путь до текущего каталога (переменная $PWD) и удаляет из него первую часть вплоть до последнего слеша, оставляя, таким образом, только имя самого каталога. Далее к нему добавляется расширение tar.gz. + +================================= diff --git a/LINUX/PS.md b/LINUX/PS.md new file mode 100644 index 0000000..c333381 --- /dev/null +++ b/LINUX/PS.md @@ -0,0 +1,34 @@ +```sh +# отправить программу в фон и поставить на паузу: +ctrl + z (cmd + z) + +[1] + 68920 suspended vim . + +ps | grep vim +... +... + +# Посмотреть свернутые процессы: +jobs + +[1] + 68920 suspended vim . + +# Восстановить: +fg %2 (fg # если одна) + +# Запустить исполнение в фоне: +bg :2 (bg # если один) + +# Можно запускать программы уже в бекграунде используя амперсанд: +python app.py & + +kill -9 68920 + +# Запущенная в консоли программа: +ctrl + z (cmd + z) + +tail -f /tmp/log + + +``` + diff --git a/LINUX/PostgreSQL.md b/LINUX/PostgreSQL.md new file mode 100644 index 0000000..ce3ebf8 --- /dev/null +++ b/LINUX/PostgreSQL.md @@ -0,0 +1,165 @@ +# Как настроить сетевое подключение + +  Обновлено: 11.12.2024  Опубликовано: 11.10.2016 + +По умолчанию, сервер баз данных pgsql разрешает подключение только с локального компьютера. Данная инструкция показывает, как разрешить подключения с удаленных систем. + +Для настройки нам нужно будет внести изменения в два файла: +## Основной конфигурационный файл postgresql.conf + +Для начала посмотрим путь расположения конфигурационного файла **postgresql.conf**: + +su - postgres -c "psql -c 'SHOW config_file;'" + +Команда выдаст, примерно, следующее: + +----------------------------------------- + /var/lib/pgsql/data/postgresql.conf +(1 row) + +_* нас интересует строчка **/var/lib/pgsql/data/postgresql.conf** — это путь расположения нужного нам конфигурационного файла._ + +Теперь открываем на редактирование основной файл конфигурации PostgreSQL по найденному пути: + +vi /var/lib/pgsql/data/postgresql.conf + +Находим и редактируем следующее: + +listen_addresses = '*' + +_* по умолчанию, параметр закомментирован и настроен на прослушивание запросов только с локального сетевого интерфейса. В данном примере мы разрешили прослушивание запросов на всех IP-адресах **(*)**, но, если требуется более безопасная настройка, можно просто перечислить последние через пробел._ + +Чтобы изменения вступили в силу, перезапускаем службу postgresql: + +systemctl restart postgresql + +_* данное имя службы (**postgresql**) может сильно отличаться, в зависимости от системы. Иногда, необходимо добавить версию postgresql или использовать другое имя. Стоит обратиться к документации, чтобы выяснить точное название сервиса._ + +Проверим, что настройка применилась: + +ss -tunlp | grep 5432 + +_* где **5432 —** порт, на котором работает postgresql. Данный номер используется по умолчанию, но в вашей системе он может быть другим._ + +В ответ мы должны получить строку, содержащую что-то на подобие: + +... 0.0.0.0:5432 ... + +Первые **0.0.0.0** говорят о том, что PostgreSQL слушает сетевые запросы на всех сетевых интерфейсах. Если мы ограничили прослушивание, настраивая опцию **listen_addresses**, то вместо нулей мы увидим конкретный IP, на котором разрешили работать службе. + +## Файл host-based authentication (pg_hba.conf) + +Смотрим путь расположения конфигурационного файла **pg_hba.conf**: + +su - postgres -c "psql -c 'SHOW hba_file;'" + +Команда выдаст, примерно, следующее: + +--------------------------------- + /var/lib/pgsql/data/pg_hba.conf +(1 row) + +Открываем на редактирование файл, путь до которого вернула команда выше: + +vi /var/lib/pgsql/data/pg_hba.conf + +Находим: + +# IPv4 local connections: + +и сразу под ним добавляем строку на подобие: + +host all all 192.168.0.10/32 md5 + +* в данном примере мы: + +- разрешаем удаленные подключения к серверу с компьютера **192.168.0.10**. +- доступ предоставляется всем базам (первое **all**). +- доступ предоставляется всем учетным записям (второе **all**). +- обратите внимание на **md5** — способ хэширования пароля. В зависимости от версии postgresql может потребоваться использовать другой вариант. + +** При желании, вместо all можно указать конкретные данные для повышения безопасности. + +Чтобы изменения вступили в силу, перезапускаем службу postgresql: + +systemctl restart postgresql + +## Проверка подключения + +Проверить подключение можно с удаленного компьютера следующей командой: + +psql -h 192.168.0.15 -U usersql + +_* где **192.168.0.15** — IP-адрес сервера баз данных; **usersql** — имя учетной записи, от которой идет подключение._ + +================================================== +```sh + +# Установка POSTGRES из исходников: +curl ............ + +sudo tar xzvf postgresql.tar.gz +cd postgresql + +./configure --prefix=/var/lib/postgresql +make world +sudo make install world +sudo chown -R vlapa:vlapa /var/lib/postgresql + +export PGDATA=/var/lib/postgresql + +./bin/pg_ctl initdb +./bin/pg_ctl -D /var/lib/postgresql/main/ start +./bin/psql -h localhost -U vlapa postgres + \d + create database some_temp_db; + \c some_temp_db + + +#============================= +sudo tar xzvf file_name*.tgz -C /opt/ +cd /opt/file_name + +./install + +tar -xvzf имя_файла + +# Для архивов сжатых с помощью BZIP (bz, bz2 и т.д.) выполняем: +tar -xvjf имя_файла + +#Команды Tar: + +-x — извлечь файлы из архива; +-v — подробный вывод информации на экран; +-f — Обязательная опция. Если не указать, Tar будет пытаться использовать магнитную ленту; +-z — обработать архив сжатый gzip’ом; +-j — обработать архив сжатый bzip’ом. + +vim `~/.local/share/applications/PyCharm.desktop +``` + +```bash +[Desktop Entry] +Version=1.1 +Type=Application +Name=nsnake +GenericName=Classic snake game on the terminal +NoDisplay=false//отображать в меню +Icon=nsnake +Exec=nsnake +Terminal=true//запускать в терминале +Actions= +Categories=ActionGame;Game; +Keywords=snake;ncurses;textual;terminal; +``` + +```bash +[Desktop Entry] //desktop-файл всегда начинается с этого +Encoding=UTF-8 //кодировка +Type=Application //тип +Name=Eclipse C/C++IDE //имя +Comment=Eclipse C/C++IDE //комментарий в лаунчере +Terminal=false +Exec=/home/kofed/eclipse/eclipse //путь к исполняемому файлу +Icon=/home/kofed/eclipse/icon.xpm //путь к иконке +``` diff --git a/LINUX/RANGER.md b/LINUX/RANGER.md new file mode 100644 index 0000000..4886c13 --- /dev/null +++ b/LINUX/RANGER.md @@ -0,0 +1,91 @@ +Откройте `ranger`, нажмите S, чтобы открыть `ranger`оболочку. +Затем введите `select-editor`, появится пронумерованный список доступных опций, и вам нужно будет ввести номер нужной опции. + +***Навигация:*** +``` +j/Down # - переместиться вниз +k/Up # - переместиться вверх +h # - перейти в родительский каталог +gg # - перейти в начало списка +G # - перейти в конец списка +<ctrl>-f # - листать вниз +<ctrl>-b # - листать вверх +J # - листать вниз на 1/2 страницы +K # - листать вверх на 1/2 страницы +H # - вернуться назад через историю навигации +L # - переместиться вперед по истории навигации +``` + +***Переключение директории:*** +``` +gh = cd ~ +ge = cd / etc +gu = cd / usr +gd = cd / dev +go = cd / opt +gv = cd / var +gm = cd / media +gM = cd / mnt +gs = cd / srv +gr = cd / +gR = cd для глобальной конфигурации +``` + +***Работа с файлами:*** +``` +i: показать файл (позволяет просто просмотреть текстовый файл в пейджере вместо редактирования) +l или E: открыть файл (открывает файл в файловом обработчике по умолчанию) +r: открыть файл с помощью… (позволяет выбрать программу, с помощью которой будет открыт файл) +z: изменить настройки (часто используемые тумблеры) +zh: просмотреть скрытые файлы +<space>: выбрать текущий файл +t: отметить файл +cw: переименовать текущий файл +/: поиск файлов +n: перейти к следующему совпадению +N: вернуться к предыдущему совпадению +yy: скопировать файл +dd: отметить файл, чтобы вырезать +pp: вставить файл +<delete>: удалить выбранный файл. +``` + +***Вкладки:*** +``` +<ctrl>-n = Создать новую вкладку +<ctrl>-w = Закрыть текущую вкладку +<tab> = Следующая вкладка +<shift> <tab> = Предыдущая вкладка +``` + +***Общие команды:*** +``` +?: просмотр мануала Ranger +1?: просмотреть комбинации клавиш +2?: справка по командам +3?: справка по настройкам +R: обновить текущий каталог +Q: выход. + +! – выполнить команду из оболочки (не-ranger команду) +: — выполнить команду ranger +du – измерить использование диска текущего каталога +chmod – изменить права на текущий файл. +``` + +Также есть множество флагов, которые можно использовать для изменения поведения команды. Их нужно использовать с тире после символа восклицательного знака (для команд оболочки) или двоеточия (для команд ranger). +``` +-s: # игнорировать вывод текущей команды +-d: # отключить процесс и работать в фоновом режиме +-p: # вернуть вывод в пейджер Ranger +-w: # Не возвращать сразу после выполнения команды. +-c: # выполнить команду на текущий файл (не на выбранные файлы). +``` + +Также Ranger позволяет объединять файлы в группы для выполнения замены. Это делается при помощи заполнителей: +``` +%f: # заменить выделенный файл +%d: # заменить текущий каталог +%s: # заменить выбранные файлы +%t: # заменить текущие отмеченные файлы. +``` diff --git a/LINUX/REPOSITORIES свой репозиторий с пакетами.md b/LINUX/REPOSITORIES свой репозиторий с пакетами.md new file mode 100644 index 0000000..172b126 --- /dev/null +++ b/LINUX/REPOSITORIES свой репозиторий с пакетами.md @@ -0,0 +1,33 @@ +Если вам нужно централизованно управлять программным обеспечением в сети или хранить свои пакеты, можно создать локальный репозиторий. Это полезно для DevOps, админов и разработчиков. + +⚙️ **Настройка локального репозитория (Debian/Ubuntu)** +```sh +# Создаем каталог для пакетов:** + +mkdir -p /var/repo/{conf,incoming} + +# Генерируем конфигурацию репозитория** + +# В /var/repo/conf/distributions добавляем: +Origin: MyRepo +Label: My Custom Repository +Suite: stable +Codename: custom +Components: main +Architectures: amd64 +Description: My private Debian repository + +# Добавляем пакеты в репозиторий:** +cp mypackage.deb /var/repo/incoming/ + +# Индексируем репозиторий: +cd /var/repo && dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz + +# Добавляем в систему (клиент) + +# Редактируем /etc/apt/sources.list: +deb [trusted=yes] file:///var/repo custom main + +sudo apt update + +``` diff --git a/LINUX/RM.md b/LINUX/RM.md new file mode 100644 index 0000000..78e1612 --- /dev/null +++ b/LINUX/RM.md @@ -0,0 +1,10 @@ +Как **удалить** непустую папку **Linux**? + +Если хотите **удалить** пустой каталог, задействуйте опцию - d +`rm -d dirname + +Если хотим **удалить** **непустой** каталог и все файлы, которые в нём находятся, поступаем следующим образом: +`rm -r dirname + +Опять же, в случае наличия защиты от записи, система **Linux** спросит пользователя, стоит ли выполнять **удаление**. Если мы хотим **удалить** файлы и **непустые** каталоги без лишних вопросов, делаем так: +`rm -rf dirname
\ No newline at end of file diff --git a/LINUX/ROOT.md b/LINUX/ROOT.md new file mode 100644 index 0000000..47a70dd --- /dev/null +++ b/LINUX/ROOT.md @@ -0,0 +1 @@ +sudo passwd root diff --git a/LINUX/RSYNC.md b/LINUX/RSYNC.md new file mode 100644 index 0000000..8ced185 --- /dev/null +++ b/LINUX/RSYNC.md @@ -0,0 +1,373 @@ +```sh +sudo apt-get install rsync + +rsync_ _опции_ _источник_ _приемник_ + +rsync -avzhe ssh vlapa@178.20.46.157:~/git/ ~/1Tb_r1-a/Backup/_git/ +``` + +****-v**** - Выводить подробную информацию о процессе копирования;** +-****-q**** - Минимум информации; +****-c**** - Проверка контрольных сумм для файлов; +****-a**** - Режим архивирования, когда сохраняются все атрибуты оригинальных файлов; +****-R**** - Относительные пути; +****-b**** - Создание резервной копии; +****-u**** - Не перезаписывать более новые файлы; +****-l**** - Копировать символьные ссылки; +****-L**** - Копировать содержимое ссылок; +****-H**** - Копировать жесткие ссылки; +****-p**** - Сохранять права для файлов; +****-g**** - Сохранять группу; +****-t**** - Сохранять время модификации; +****-x**** - Работать только в этой файловой системе; +****-e**** - Использовать другой транспорт, например, ssh; +****-z**** - Сжимать файлы перед передачей; +****--delete**** - Удалять файлы которых нет в источнике; +****--exclude**** - Исключить файлы по шаблону; +****--recursive**** - Перебирать директории рекурсивно; +****--no-recursive**** - Отключить рекурсию; +****--progress**** - Выводить прогресс передачи файла; +****--stat**** - Показать статистику передачи; +****--********max-size**** ****—**** максимальный размер файла для передачи; +****--********bwlimit**** ****—**** ограничение скорости для передачи файлов; +****--version**** - Версия утилиты. + + +```sh +sudo vim /etc/rsyncd.conf` + +pid file = /var/run/rsyncd.pid +lock file = /var/run/rsync.lock` +log file = /var/log/rsync.log` +[share] path = /tmp/share/` +hosts allow = 192.168.56.1` +hosts deny = * list = true` +uid = root gid = root` +read only = false` +``` + +**Здесь мы задаем путь к нашей папке для синхронизации, разрешаем доступ к серверу только с ip адреса (192.168.56.1) и запрещаем все остальные подключения. Параметры uid и gid указывают пользователя и группу, от которых будет запущен демон. Лучше не использовать root, а указать пользователя nobody и выдать ему права на ту папку, в которую будет выполняться синхронизация каталогов rsync.** + +Настройка rsync завершена, остается сохранить файл, запустить сервер rsync и добавить его в автозагрузку: + +```sh +sudo systemctl start rsync +sudo systemctl enable rsync + +sudo service cron reload +``` + +**Копирование и синхронизация файлов на локальном компьютере + +`rsync -zvh file /tmp/backups/` + +**Указав опцию** `--progress` **вы можете видеть сколько процентов уже скопировано, а сколько еще осталось:** + +`rsync -zvh --progress file /tmp/backups/` + +**Синхронизация папок на локальной машине** + +`rsync -azvh /home/user/documents /tmp/backups/` + + +**Синхронизация с удаленным сервером** + +`rsync -avz /home/sergiy/tmp/ root@192.168.56.102:/home/` + + +**По умолчанию rsync попытается использовать транспорт ssh. Если вы хотите использовать ранее созданный сервер rsync, нужно указать это явно:** + +`rsync -avz /home/sergiy/tmp/ rsync://192.168.56.102:/share` + +Точно также можно синхронизировать файлы с rsync из удаленного сервера: + +`rsync -avz root@192.168.56.102:/home/ /home/sergiy/tmp/` + +Адрес удаленного сервера записывается в таком формате: + +`имя_пользователя@адрес_машины/папка/на/удаленной_машине` + +Синхронизация папок rsync будет выполняться на стандартном порту. + + +### _**Синхронизация файлов по SSH**_ + +Чтобы задать протокол подключения используется опция -e. При использовании SSH все передаваемые данные шифруются и передаются по защищенному каналу, таким образом, чтобы никто не мог их перехватить. Для использования SSH вам нужно знать пароль пользователя в системе. + +Синхронизация файлов rsync с удаленного сервера по ssh будет выглядеть вот так: + +`rsync -avzhe ssh root@192.168.56.102:/root/install.log /tmp/` + +Если вы используете другой порт для ssh, то здесь его можно указать: + +`rsync -avzhe "ssh -p 22" root@192.168.56.102:/root/install.log /tmp/` + +А теперь передадим данные на тот же сервер: + +`rsync -avzhe ssh backup.tar root@192.168.0.101:/backups/` + +**Просмотр прогресса при синхронизации** + +Для просмотра прогресса копирования файла с одной машины на другую используется опция progress: + +`rsync -avzhe ssh --progress /home/user/documents root@192.168.56.102:/root/documents` + +**Синхронизация не всех файлов в rsync** + +Опции include и exclude позволяют указать какие файлы нужно синхронизировать, а какие исключить. Опции работают не только с файлами но и с директориями. + +Например, скопируем все файлы, начинающиеся на букву R: + +`rsync -avze ssh --include 'R*' --exclude '*'` +`root@192.168.56.102:/root/documents/ /root/documents` + +**Удаление при синхронизации** + +Во время синхронизации можно удалять файлы, которых нет на машине откуда идет rsync синхронизация, для этого используется опция --delete. + +Например: + +`rsync -avz --delete root@192.168.56.102:/documents/ /tmp/documents/` + +Если перед выполнением этой команды создать в папке файл которого нет на удаленном сервере, то он будет удален. + +**Максимальный размер файлов** + +Вы можете указать максимальный размер файлов, которые нужно синхронизировать. Для этого используется опция --max-size. Например, будем синхронизировать только файлы меньше 200 килобайт: + +`rsync -avzhe ssh --max-size='200k' /user/documents/` +`root@192.168.56.102:/root/documents` + +**Удаление исходных файлов** + +Есть возможность удалять исходные файлы после завершения синхронизации с удаленным сервером: + +`rsync --remove-source-files -zvh backup.tar /tmp/backups/` + +Таким образом, файл backup.tar будет удален после завершения копирования в папку /tmp/backups. + +**Режим симуляции rsync** + +Если вы новичок, и еще не использовали rsync, то возможно захотите посмотреть как отработает команда без применения реальных действий в файловой системе. Для этого есть опция dry-run. Команда только выведет все выполняемые действия в терминал, без выполнения реальных изменений: + +`rsync --dry-run --remove-source-files -zvh backup.tar /tmp/backups/` + + +**Ограничить скорость передачи** + +Вы можете ограничить использование пропускной способности сети с помощью опции ==`--bwlimit`==: + +`rsync --bwlimit=100 -avzhe ssh /user/home/documents/ root@192.168.56.102:/root/documents/` + +Как я уже писал выше, rsync синхронизирует только части файла, если вы хотите синхронизировать файл целиком используйте опцию ==-W==: + +`rsync -zvhW backup.tar /tmp/backups/backup.tar backup.tar` + +**Автоматическая синхронизация папок rsync** + +Можно расписать автоматическую синхронизацию с помощью cron. Но в случае доступа к серверу по SSH необходимо будет создать ключ и загрузить его на сервер, чтобы аутентификация проходила без запроса пароля. + +Создаем ключ: + +`ssh-keygen -t rsa` + +Загружаем ключ на сервер к с которым собираемся синхронизироваться: + +`ssh-copy-id -i /home/sk/.ssh/id_rsa.pub sk@192.168.1.250` + +Теперь можем переходить к настройке расписания cron. Будем запускать синхронизацию каждый день: + +`crontab -e` + +`00 05 * * * rsync -azvre ssh /home/user/Downloads/ 192.168.56.102:/share` + +rsync синхронизация каталогов будет выполняться каждый день в пять утра. + +`@hourly rsync -avzhe ssh vlapa@178.20.46.157:/home/vlapa/git/ /home/vlapa/1Tb_r1-a/Backup/_git/` + +rsync синхронизация каталогов будет выполняться каждый час. + +============================== + +**Автоматическая синхронизация файлов с удалённым сервером** +```bash +#!/bin/bash + +# Локальная директория для синхронизации +LOCAL_DIR="/path/to/local/dir" + +# Удалённый сервер и директория +REMOTE_USER="user" +REMOTE_SERVER="server.com" +REMOTE_DIR="/path/to/remote/dir" + +# Синхронизация с удалённым сервером +rsync -avz $LOCAL_DIR $REMOTE_USER@$REMOTE_SERVER:$REMOTE_DIR + +# Вывод сообщения о завершении +echo "Синхронизация завершена!" +``` +✔️ **Скрипт синхронизирует файлы из локальной директории на удалённый сервер с помощью** rsync**.** + +======================================= +# Утилита rcync: синхронизация директорий и backup-ы + +15.12.2019 + +Теги: [CLI](https://tokmakov.msk.ru/blog/tags/15) • [Linux](https://tokmakov.msk.ru/blog/tags/1) • [Директория](https://tokmakov.msk.ru/blog/tags/37) • [Команда](https://tokmakov.msk.ru/blog/tags/2) • [Конфигурация](https://tokmakov.msk.ru/blog/tags/11) • [Настройка](https://tokmakov.msk.ru/blog/tags/266) • [Сервер](https://tokmakov.msk.ru/blog/tags/301) • [Файл](https://tokmakov.msk.ru/blog/tags/35) + +Утилита rsync — это программное обеспечение с открытым исходным кодом, которое можно использовать для синхронизации файлов и директорий с локального компьютера на удаленный и наоборот. Примечательная особенность rsync — возможность передавать файлы с шифрованием, используя ssh в качестве транспорта. + +Синтаксис команды + +$ rsync опции источник приемникКопировать + +## Примеры использования + +Перед тем, как копировать файлы, всегда стоит сделать пробный прогон (ключ `-n`) в режиме с показом подробностей (`-v`): + +`rsync -avn /path/to/src/ user@example.com:/path/to/dstКопировать + +В этом режиме `rsync` покажет список файлов, которые он скопирует. После этого можно запускать настоящее копирование: + +`rsync -av /path/to/src/ user@example.com:/path/to/dstКопировать + +В этой команде ключ `-a` подразумевает рекурсивное копирование всех файлов и каталогов включая их атрибуты: владелец, группа, дата создания и изменения. Ключ `-v` даст подробный отчет о работе по мере выполнения и по окончании. + +Обратите внимание на слэш в конце первого аргумента — без него файлы источника `/path/to/src/файлы` будут размещены в `/path/to/dst/src/файлы`. А при наличии слэша в конце первого аргумента — файлы будут в `/path/to/dst/файлы`, что скорее всего и нужно. + +По умолчанию `rsync` попытается использовать транспорт ssh. При использовании другого порта для ssh, его нужно указать: + +`rsync -av -e "ssh -p 2222" /path/to/src/ user@example.com:/path/to/dstКопировать + +Точно также можно скопировать файлы из удаленного сервера на локальную машину: + +`rsync -av user@example.com:/remote/machine/path/ /local/machine/pathКопировать + +Обратите внимание, что источник всегда является первым аргументом, а приемник — вторым. + +Для автоматического копирования, например по `cron`, необходимо настроить авторизацию с использованием ключей. + +## Основные опции + +**Опция** `-a` или `--archive` — рекурсивно копирует файлы, включая разыменованные символьные ссылки, с сохранением связанных с файлами сведений об их владельцах, групповых и индивидуальных правах и времени последнего изменения. Использование этого ключа равносильно указанию ключей `--group`, `--links`, `--owner`, `--perms`, `--recursive` и `--times`. + +**Опция** `-b` или `--backup` — переименовывает файлы, которые в противном случае должны быть удалены или перезаписаны, добавляя к их существующим именам знак тильды (~). Эта опция обычно используется вместе с `--backup-dir` — вместо переименования `rsync` перемещает эти файлы в указанный каталог. + +**Опция** `--backup-dir=dir` — используется вместе с `--backup` и перемещает в директорию `dir` те файлы, которые в противном случае должны быть удалены или перезаписаны. После перемещения старой версии файла в каталог `dir` новая версия из источника копируется в приемник. + +Если `dir` указан с использованием относительного пути, то это путь относительно приемника. + +**Опция** `--copy-unsafe-links` — для каждого файла, являющегося символьной ссылкой на файл за пределами той иерархии, которой принадлежит источник, производится копирование файла, на который указывает ссылка, а не символьной ссылки на него. + +**Опция** `--delete` заставляет `rsync` удалять файлы в приемнике, которые отсутствуют в источнике. + +**Опция** `-n` или `--dry-run` — запуск утилиты без записи на диск. С опцией `--verbose` позволяет посмотреть, что будет сделано, если запустить rsync без `--dry-run`. Особенно полезно запускать перед использованием `--delete`. + +**Опция** `-g` или `--group` — сохранение групповых связей копируемых файлов. + +**Опция** `-l` или `--links` — для каждого файла, являющегося символьной ссылкой, копирование символьной ссылки, а не файла, на который указывает ссылка, даже если файл, на который указывает ссылка, не присутствует в источнике. + +**Опция** `--link-dest=dir` — если файл существует в источнике, но его нет в приемнике, rsync ищет в каталоге `dir` файл с точно таким же именем. Если точная копия файла будет найдена, rsync создает из файла в `dir` жесткую ссылку на него в приемнике. Если точная копия не будет найдена, rsync копирует файл в приемник. + +**Опция** `-o` или `--owner` — сохранение данных о владельце копируемых файлов (только для пользователей с root-правами). + +**Опция** `-p` или `--perms` — сохранение данных о правах доступа копируемых файлов. + +**Опция** `-r` или `--recursive` — рекурсивный спуск по каталогу-источнику и копирование всех файлов в иерархии. + +**Опция** `-t` или `--times` — сохранение данных о времени последнего изменения копируемых файлов. + +**Опция** `-u` или `--update` — пропускать файл, если в приемнике оказался файл новее, чем в источнике. + +**Опция** `-v` или `--verbose` — вывод информации о том, что в данный момент делает rsync. + +**Опция** `-z` или `--compress` — сжатие файлов в процессе копирования. + +## Зеркальное копирование + +Утилиту rsync можно использовать для создания зеркальной копии каталога. На удаленной системе должен быть установлен ssh-сервер (к нему можно подключиться с использованием утилиты ssh). Если нужно запускать копирование по расписанию, следует настроить авторизацию с использованием ключей. + +`rsync --archive --update --delete /path/to/mirror/ user@example.com:/path/to/mirrorКопировать + +Опция `--update` заставляет rsync не перезаписывать самые последние версии файлов на удаленной системе более старыми версиями таких же файлов, взятых из локальной системы. Опция `--delete` заставляет rsync удалять на удаленном сервере файлы, отсутствующие на локальной системе. + +## Резервное копирование + +После проведения исходного резервного копирования rsync может эффективно осуществлять дополнительное резервное копирование, экономя место на диске и время копирования. При дополнительном (инкрементном) копировании сохраняются только те файлы, которые претерпели изменения со времени последнего резервного копирования. + +С помощью опции `--link-dest=dir` можно превращать каждое дополняющее резервное копирование в полноценное, путем создания жестких ссылок между дополняющими резервными копиями и неизменившимися файлами, имеющимися в исходной полноценной резервной копии. + +Если `dir` задается как отосительный путь, то он задается относительно файла-приемника. + +`rsync --archive --link-dest=../backup /path/to/src/ user@example.com:/path/to/dstКопировать + +При запуске этой команды rsync проходит сверху вниз по содержимому каталога `/path/to/src`, проверяя каждый найденный файл. Для каждого файла в каталоге `/path/to/src` утилита ищет в каталоге `/path/to/dst` точную копию этого файла. + +- Если она находит точную копию в каталоге `/path/to/dst` — переходит к следующему файлу +- Если она не находит точную копию в каталоге `/path/to/dst` — ищет точную копию в каталоге `backup` + - Если она находит точную копию файла в каталоге `backup`, утилита создает жесткую ссылку из файла в каталоге `backup` на файл в каталоге `/path/to/dst` + - Если она не находит точную копию файла в каталоге `backup`, утилита копирует файл из каталога `/path/to/src` в каталог `/path/to/dst` + +Небольшой bash-скрипт, демонстрирующий создание полноценной и дополняющей резервной копии: + +```bash +#!/bin/bash +rm -rf backup-two-days-ago +mv backup-one-day-ago backup-two-days-ago +mv backup-latest-state backup-one-day-ago +rsync --archive --link-dest=../backup-one-day-ago /some/path/data/ backup-latest-state +``` + +Можно немного доработать этот скрипт, чтобы хранить не три, а тридцать резервных копий: + +```bash +#!/bin/bash + +SOURCE='/home/evgeniy/data' +BACKUP='/home/evgeniy/backup' +COUNT=30 # кол-во бэкапов + +rm -rf ${BACKUP}/backup-${COUNT} +for (( i = COUNT; i > 1; i-- )); do + mv ${BACKUP}/backup-$((i - 1)) ${BACKUP}/backup-${i} +done + +rsync --archive --link-dest=../backup-2 ${SOURCE}/ ${BACKUP}/backup-1 +``` + +========================================== +Шпора по флагам rsync + +![[2025-06-20_22-34-47.png]] + +Rsync - программка, которую можно использовать для синхронизации файлов и каталогов с локального компьютера на удаленный и наоборот. Чем-то напоминает scp. + +`rsync file1 file2 ... user@host:dest + +`rsync -a dir host:dest_dir + +Взаимодействием с сервером возможности утилиты не ограничиваются. Все операции можно выполнять и локально. По умолчанию rsync копирует ресурсы без учета предыдущего содержимого целевого каталога. + +Например, если вы перенесли каталог d, содержащий файлы a и b, на машину, на которой был файл d/c, то после копирования сервер будет содержать d/a, d/b и d/c. + +Чтобы создать точную копию исходного каталога, необходимо из целевого каталога удалить не существующие в исходном каталоге файлы, например d/c в примере: + + +`rsync -a --delete dir host:dest_dir + +Посмотрим на еще 1 пример: + +`rsync -avh --del --progress dir1 dir2 dir3 backup + +В результате выполнения, каталоги "dir1, dir2 и dir3", вместе со всем их содержимым, будут рекурсивно скопированы в каталог backup. + +Если какие-то файлы уже есть в каталоге backup, и не были изменены, они будут пропущены. + +Исключение файлов и каталогов + +Еще одной значительной фичей rsync является способность исключать файлы и каталоги из процесса передачи. + +Например, ты хочешь перенести локальный каталог src на хост и при этом исключить все данные с именем .git. Можно сделать следующим образом: + +`rsync -a --exclude=.git src host: diff --git a/LINUX/SAMBA.md b/LINUX/SAMBA.md new file mode 100644 index 0000000..357b5e4 --- /dev/null +++ b/LINUX/SAMBA.md @@ -0,0 +1,262 @@ +```shell +sudo su +apt install -y samba samba-client +systemctl enable smbd +systemctl start smbd +systemctl status smbd +systemctl stop smbd +systemctl restart smbd + +systemctl disable smbd +systemctl reload smbd + +ufw allow Samba +ufw allow 445 +ufw allow 137 +ufw allow 138 +ufw allow 139 + +#=================================== +smbclient -L 192.168.1.1 +smbclient -L 192.168.1.1 -U username +#=================================== + +### Сброс кэша сетевых подключений Windows: +# (если не дает подключиться) +# cmd + +net use * /delete +ipconfig /flushdns +nbtstat -R + +#=================================== + +sudo cp /etc/samba/smb.conf /etc/samba/res_smb.conf + +# оставим в файле конфигурации только те строки, которые используются в работе и не являются комментарием: +grep -v '^ *#\|^ *$' /etc/samba/smb.conf | sudo tee /etc/samba/smb.conf + +sudo vim /etc/samba/smb.conf + +#============================ +[global] +workgroup = WORKGROUP +security = user +map to guest = bad user +wins support = no +dns proxy = no + +[150Gb] +path = /home/media/150Gb +public = yes +writable = yes +read only = no +guest ok = yes +create mask = 0775 +directory mask = 0775 +force create mode = 0775 +force directory mode = 0775 + +[private] +path = /media/samba/private +public = no +writable = yes +read only = no +guest ok = no +create mask = 0775 +directory mask = 0775 +force create mode = 0775 +force directory mode = 0775 +#============================ + +testparm -s +systemctl restart smbd nmbd + +# Доступ по учетным данным: +# Создаем каталог для новой папки: +mkdir /data/staff + +# Задаем права на созданный каталог: +chmod 775 /data/staff + +sudo adduser samba # add a regular Linux user +sudo smbpasswd -a samba # add Samba user called `samba` +sudo smbpasswd -e samba # enable - may not be needed + +# Создаем пользователя в системе Linux: +useradd staff1 + +# где_ staff1 _— имя пользователя +# Задаем пароль для пользователя: +passwd staff1 + +# Теперь создадим пользователя в samba: +smbpasswd -a staff1 + +# Перезапускаем samba: +systemctl restart smbd nmbd + +# Посмотреть пользователей samba: +sudo pdbedit -L -v +-L - to list users. +-v - to be verbose. + +# Доступ для пользователей и групп:*** +sudo mkdir /var/acc_gr_and_user +sudo chmod 777 /var/acc_gr_and_user +``` + +```bash +[acc_gr_and_user] + comment = Private Folder + path = /var/acc_gr_and_user + public = no + writable = no + read only = yes + guest ok = no + valid users = admin, staff2, staff3, @privateusers + write list = admin, staff2 + create mask = 0775 + directory mask = 0775 + force create mode = 0775 + force directory mode = 0775 + inherit owner = yes + + +systemctl restart smbd nmbd +``` + +*стоит обратить внимание на следующие настройки: + +- ***path = /data/private*** — используем новый путь до папки +- ***writable = no*** +- ***read only = yes*** +- в данном примере мы разрешим запись в каталог только некоторым пользователям. Поэтому общие настройки, разрешающие запись в папку, должны быть запрещены +- ***valid users*** - список пользователей, которым разрешено подключаться к каталогу. В данном примере разрешения работают для пользователей +- **_admin_**__,__ **_staff2_** __и__ **_staff3_**, а также для всех, кто входим в группу ***@privateusers*** +- ***write list*** - список пользователей, которые имеют доступ к папке на чтение и запись. В данном примере мы разрешаем это только для пользователей. ***admin*** и ***staff2*** +- ***inherit owner*** - опция позволяем включить наследование владельца при создании папок и файлов + +*если мы хотим, чтобы доступ к каталогу был полный у определенных пользователей (без разделения на тех, кто может только читать и тех, кто может также писать в папку), то опцию +***write list*** можно не указывать, а опции ***writable*** и ***read only*** оставить как в примерах выше + +***Вывести список сетевых ресурсов samba:*** +`smbclient -L localhost -U%` + +***Подключение к ресурсу из Linux***: +```sh +sudo apt install cifs-utils -y +mount.cifs {ресурс} {путь к директории в которую монтируем } {-o опции} +mount.cifs //10.8.0.1/anonimus_share /mnt +``` + +Автоматическое монтирование директории происходит с использованием файла **fstab**. Для начала создадим дополнительный файл с учетными данными пользователя для авторизации. Оправданно будет создать этот файл в директории **root**. + +`sudo nano /root/.smbselus` + +Внесем в файл следующие директивы: + +`username=selectel password=uspass` + +Сохраняем и переходим к конфигурационному файлу **fstab**. + +`sudo nano /etc/fstab` + +Добавляем в него следующий блок инструкций: + +`//10.8.0.1/acc_gr_and_user /mnt cifs user,rw,credentials=/root/.smbselus 0 0` + +Конкретизируем каждый пункт: + +**_//10.8.0.1/acc_gr_and_user_** 一 директория, которую необходимо смонтировать; +**_/mnt_** 一 директория, в которую необходимо смонтировать; +**_cifs user_** 一 разрешает монтировать любому пользователю; +**_rw_** 一 предоставляет права на чтение и запись; +**_credentials=/root/.smbselus_** 一 файл с учетными данными пользователя; +_0_ используется утилитой dump 0 一 выключен; +_0_ используется утилитой fsck 0 一 выключен. + +В некоторых случаях требуется добавить директиву **domain** в файл **.smbselus.** +## Настройка сетевой корзины + +По умолчанию файлы из директории удаляются совсем. При определенных условиях необходимо иметь возможность восстановить эти файлы. В Samba данная возможность реализована с использованием сетевой корзины. Алгоритм работы следующий: настраивается скрытый каталог, в который при удалении перемещаются файлы. + +Первым этапом создадим директорию для корзины и назначим ей права: +```sh +sudo mkdir /var/recycle | sudo chmod 777 /var/recycle + +# Внесем блок директив в конфигурационный файл: + +sudo vim /etc/samba/smb.conf + +[Recycle] +comment = rec_dir +path = /var/recycle +public = yes +browseable = yes +writable = yes +vfs objects = recycle +recycle:repository = .recycle/%U +recycle:keeptree = Yes +recycle:touch = Yes +recycle:versions = Yes +recycle:maxsize = 0 +recycle:exclude = *.tmp, ~$* +recycle:exclude_dir = /tmp +``` + +Значения директив: + +**_[Recycle]_** 一 имя ресурса, отображаемое клиенту при подключении; +**_comment = rec_dir_** 一 комментарий; +**_path = /var/recycle_** 一 директория, в которой размещена сетевая корзина. +**_public = yes_** 一 доступ на чтение всем авторизованным пользователям, также гостевым; +**_browseable = yes_** 一 параметр устанавливает, будет ли общий ресурс отображаться в списке доступных общих ресурсов в сетевом окружении и в списке просмотра; +**_writable = yes_** 一 право записи в сетевой ресурс; +**_vfs objects = recycle_** 一 использование подсистемы recycle; +**_recycle:repository = .recycle/%U_** 一 место хранения удаленных объектов. В этой директиве настроено попадание удаленных файлов в каталог с именем пользователя, который создается автоматически в директории .**recycle;** +**_recycle:keeptree = Yes_** 一 удаление с сохранением дерева каталогов; +**_recycle:touch = Yes_** 一 изменение даты при перемещении файла в сетевую корзину; +**_recycle:versions = Yes_** 一 добавление номера версии при удалении файлов с одинаковыми именами; +**_recycle:maxsize = 0_** 一 параметр отвечает за максимальный размер файла, который может быть перемещен в корзину. При текущем значении возможно переместить файл любого размера; +**_recycle:exclude = *.tmp, ~$*_** 一 исключение файлов заданного типа; +**_recycle:exclude_dir = /tmp_** 一 исключение директории заданного типа. + +```sh +mkdir /data/recycle +chmod 777 /data/recycle + +# Перезапускаем самбу: + +systemctl restart smbd + +# Пробуем зайти в сетевой каталог Recycle и создать, а после удалить файл. Он должен оказаться к скрытой папке .recycle. + +# Для автоматической чистки сетевой корзины можно создать скрипт: + +mkdir /scripts +vim /scripts/cleanrecycle.sh + +#!/bin/bash + +recyclePath="/data/recycle/.recycle" +maxStoreDays="30" + +/usr/bin/find $recyclePath -name "*" -ctime +$maxStoreDays -exec rm {} \; + +# в данном скрипте мы ищем все файлы в каталоге /data/recycle/.recycle, которые старше 30 дней и удаляем их. + +# Разрешаем запуск скрипта: + +chmod +x /scripts/cleanrecycle.sh + +# Создаем задание в планировщике: + +crontab -e + +0 5 * * * /scripts/cleanrecycle.sh + +# в данном примере мы будем запускать скрипт по очистке сетевой корзины каждый день в 05:00. +``` + + diff --git a/LINUX/SCP, SFTP.md b/LINUX/SCP, SFTP.md new file mode 100644 index 0000000..e133e5f --- /dev/null +++ b/LINUX/SCP, SFTP.md @@ -0,0 +1,70 @@ +Скопируем файл с удаленного хоста в локальный каталог: +$ scp user@host:/path/to/file /local/dir + +Скопируем файлы с локального компьютера на удаленный хост: +$ scp /local/dir/file user@host:/remote/dir + +Скопируем файл с одного удаленного хоста на другой: +$ scp user1@host1:/remote/file user2@host2:/remote/dir + +======================================== +Прога **sftp** не требует постоянно указывать адрес **ssh** сервера. Вместо этого мы подключаемся к нему один раз и используем интерфейс **get** и **put**: + +$ sftp user@host +sftp> get /remote/dir/file /local/dir +sftp> put /local/dir/file /remote/dir + +Для передачи каталогов используем флаг "**-r**": +$ scp -r /local/dir user@host:/remote/dir +$ sftp> put -r /local/dir /remote/dir + +***Также хочется отметить, что **sftp** имеет важное преимущество перед обычным **ftp** — она не требует, чтобы на удаленном узле работал сервер **FTP**. Ей необходим только **SSH**. + +======================================== +```sh +mkdir bot +scp /полный/путь/к файлу/скрипта/bot.py vlapa@10.10.10.2:~/work_prog + +# Копируем файл на сервер (копирум = что -> куда): + +scp -r source_file_name username@destination_host:destination_folder +# параметр: -r – рекурсивное копирование папок (включая подкаталоги) + +scp /etc/wireguard/wg0.conf vlapa@10.0.0.1:/home/vlapa/wg000.conf +scp vlapa@10.0.0.1:/home/vlapa/wg0.conf log.txt +scp path/myfile user@8.8.8.8:/full/path/to/new/location/ + +# Копируем файл с сервера: + +scp user@8.8.8.8:/full/path/to/file /path/to/put/here +``` + +``` +-r - рекурсивное копирование +-p - сохраняет временные метки и права доступа на файлы +-q - тихий режим. Подавляет все сообщения, кроме ошибок +-C - включает сжатие данных +-P 1345 - указывает порт подключения к удаленному хосту +``` +💻 **Передача файлов с помощью scp** +Когда нам необходимо быстро передать файл на сервер или наоборот, скопировать файл с сервера на локалку, мы можем воспользоваться утилитой — **scp (Secure Copy Protocol)**. + +SCP — это утилита командной строки в Linux, используемая для безопасной передачи файлов между локальным и удаленным узлами или между двумя удаленными узлами. Она использует протокол SSH (Secure Shell) для передачи данных, обеспечивая безопасное копирование файлов с использованием тех же мер аутентификации и безопасности, что и SSH. + +⚙️ **Примеры использования** +• Копирование файла на удалённый сервер: + +`scp file.txt user@remote_host:/path/to/destination + +• Скачивание файла с сервера: +`scp user@remote_host:/path/to/file.txt ./ + +• Передача директории: +`scp -r папка user@remote_host:/path/to/destination +`-r — ключ для копирования директории + + • Указание порта SSH: +Если вдруг используется нестандартный порт, мы можем его передать установив флаг -P: +`scp -P 2222 file.txt user@remote_host:/path/to/destination + +➡️ Для оптимизации передачи больших файлов, можем добавить флаг -C для сжатия файлов. А также, для проверки удачной передачи, включаем подробный вывод -v.
\ No newline at end of file diff --git a/LINUX/SERVER - мониторинг.md b/LINUX/SERVER - мониторинг.md new file mode 100644 index 0000000..6875dd6 --- /dev/null +++ b/LINUX/SERVER - мониторинг.md @@ -0,0 +1,280 @@ +```sh +# Топ процессов по загрузке CPU +ps -eo pid,comm,%cpu --sort=-%cpu | head + +# Запуск интерактивного мониторинга +top +htop # Более удобный интерфейс (если установлен) + +# Убить процесс с высокой нагрузкой по PID +kill -9 <PID> + +# Ограничить нагрузку от процесса (через nice) +nice -n 10 командa + +🟢 ps + sort быстро выявляют топовые процессы +🟢 top и htop позволяют следить в реальном времени +🟢 nice — задаёт приоритет и снижает нагрузку + +#================================================= +# Просмотр истории команд пользователя +history + +# Узнаем кто зарегистрирован в системе и сколько времени в ней находится: +who + +# список всех пользователей, которые сейчас зарегистрированы в системе: +users + +# Подробная информация о сессиях пользователей +w + +# когда и сколько времени определенный пользователь находился в системе: +last user_name + +# выдаст информацию о последних пяти входах: +last -n 5 user_name + +# вывод на экран перечня всех пользователей с датами их последнего подключения к системе: +lastlog +# удобно контролировать системные записи. У них дата последнего входа должна быть = Never logged in =, потому что под ними нельзя войти в систему (в качестве командной оболочки установлены /bin/false, /dev/null, /sbin/nologin и др.). Если вы заметили, что кто-либо проник в систему через одну из этих учетных записей, то это значит, что хакер использует ее, изменив настройки. Простая замена командной оболочки в файле /etc/passwd может открыть хакеру потайную дверь, и администратор не заметит этой трансформации. Но после выполнения команды lastlog все неявное становится явным. + +# Обращайте внимание на тип подключения и адрес. Если что-то вызывает подозрение, то можно выявить атаку на этапе ее созревания. + +# Просмотр логов SSH-подключений +sudo cat /var/log/auth.log | grep "ssh" + +# определить, какие файлы и какими пользователями открыты в данный момент: +lsof + +# Файлы, открытые определённым пользователем +lsof -u username + +# Определить, кто использует файл +lsof /path/to/file + +# Какие файлы открыты на определённом порту +sudo lsof -i :80 + +# Удалённые, но занятые файлы +lsof | grep deleted + +# узнать, какие процессы используют интернет прямо сейчас? + +lsof -i -P -n + +# Расшифровка: +# - lsof — список открытых файлов (в Linux всё — файл) +# -i — показывает сетевые соединения +# -P — отображает порты в числовом виде (без разрешения имён) +# -n — не резолвит имена хостов (быстрее) + +# Это отличный способ отследить: какие приложения слушают порты, кто подключен к твоему серверу, утечки сетевой активности. Полезно при аудитах, отладке и настройке брандмауэров. + + +# запустить программу на выполнение в фоновом режиме,программа запускается на выполнение, но не блокирует консоль. Да, вы видите вывод на экране, но в любой момент может забрать консоль себе, нажав Enter. Вы можете закрывать консоль, и стартующая программа не прервет свою работу. +start-olap-server.sh & + +# список программ работающих в фоновом режиме +jobs +# Число, которое вы видите в квадратных скобках – это номер выполняемой задачи. В фон можно отправить команду нажатием Ctrl+Z. В этом случае команда уходит в фон, но перестает выполняться. + +# Псевдонимы +alias ll="ls -l" + +# Перенаправление ввода/вывода +ls > outfile.txt +# В результате выполнения этой команды, результат (т.е. содержимое каталога) не будет выведено на экран, а будет сохранено в файл. + +# Если отобразить содержимое файла: +cat outfile.txt +# То вы увидите то, что должно было быть на экране еще после выполнения той команды. + +# получать параметры из файла. Например, можно выполнить команду: +cat < infile.txt +# Команде cat в качестве параметров будут передано содержимое infile.txt. + +# Pipe +lastlog | sort + +# Посмотреть общее время загрузки системы +systemd-analyze + +# Показать список сервисов и их время загрузки +systemd-analyze blame + +# Проверить, какие службы запускаются при старте +systemctl list-unit-files --state=enabled + +# Отключить ненужный сервис +sudo systemctl disable имя_сервиса + +# Установить задержку старта для тяжёлого сервиса +sudo systemctl edit имя_сервиса +# В открывшемся файле добавить: +# [Service] +# ExecStartPre=/bin/sleep 30 + +🟢 systemd-analyze показывает время загрузки системы +🟢 systemctl позволяет отключать или откладывать запуск лишних служб +🟢 Оптимизация автозагрузки ускоряет старт системы + +# Регулярная проверка и настройка автозагрузки особенно важна для серверов и рабочих станций с ограниченными ресурсами. + +#================================================== +# информация о процессоре: +less /proc/cpuinfo +lscpu +lshw +nproc +sudo dmidecode -t 4 + + +I #================================================== +# === Изменение имени сервера ===: +hostnamectl set-hostname hostname +# перезагрузка не потребуется — новое имя начнет использоваться сразу ??? +# Если, по каким-либо причинам, новое имя не начнет использоваться, вводим: +systemctl restart systemd-hostnamed +# Подробнее, статус можно посмотреть командой: +hostnamectl status +# Также, можно задать дополнительное для описания имя (прелестное): +hostnamectl set-hostname "Description Name" --pretty + +#================================================== +# === Диски ===: +df -h +du -h +df -i + +lsblk +smartctl -a /dev/sda +smartctl -a /dev/nvme1n1 +cat /proc/mdstat + +iostat +iotop + +#================================================== +# === Процессы / память ===: +top +uptime +htop + +vmstat +free -m +cat /proc/meminfo + +ps aux +kill 123456 +systemctl status nginx +strace -p 1234567 + +#================================================== +# === Сеть ===: +# Быстрое отображение всех прослушиваемых портов +ss -tuln +# Показать процессы, связанные с портами +ss -tulnp +# Сканировать локальный хост с помощью nmap +nmap -sT -p- 127.0.0.1 +# Сканировать удалённый сервер на стандартные порты +nmap example.com +🟢 ss — встроенный инструмент, заменяющий netstat +🟢 nmap — мощный сканер безопасности и сетевых портов +🟢 Позволяет быстро увидеть, какие порты открыты и кем используются. + +ip a +ifconfig +netstat -rn +ip r +ping 8.8.8.8 +traceroute 8.8.8.8 +mtr 8.8.8.8 +dig vlapa.ru +dig @8.8.8.8 vlapa.ru +dig -t mx vlapa.ru +curl -Lv vlapa.ru +curl -v telnet://127.0.0.1:22 +curl --unix-socket /var/run/docker.sock http:/images/json +tcpdump -i any port 9100 -nn + +curl ifconfig.me + +#================================================== +# === Логи ===: + +cd /var/log +ls -lah +tail -f -n50 /var/log/syslog +ls -lah /var/log/auth.log +ls -lah /var/log/kern.log +dmesq -T +journalctl -xeu nginx + +#================================================== +# === Температура ===: +sudo apt install lm-sensors +sudo sensors-detect + +watch -n 1 sensors +watch -d -n 1 sensors + +#================================================== +# === Стресс тест ===: +sudo apt-get install stress-ng +# CPU: +stress-ng --cpu 32 --sequential 8 --timeout 60s --metrics-brief +# Memory: +stress-ng --class memory --sequential 8 --timeout 60s --metrics-brief +# Disk IO: +stress-ng --class io --sequential 8 --timeout 60s --metrics-brief +# File System: +stress-ng --class filesystem --sequential 8 --timeout 60s --metrics-brief +# All: +# Если необходимо провести комплексное стресс-тестирование, можно задействовать работу нескольких основных подсистем вместе одной командой: + +stress-ng --cpu 4 --io 4 --vm 1 --vm-bytes 1G --timeout 60s --metrics-brief + +# Эта команда запустит тест для CPU в 4 потока, тест виртуальной памяти с размещением в ней одного гигабайта данных, а также 4 потока для тестирования операций ввода/вывода. + +# Что бы запустить тестирование всего «железа», используется команда: +stress-ng --sequential 0 --timeout 60s --metrics-brief + +# Эта команда запустит все тесты. После выполнения результат будет выведен в консоль. Во время выполнения команды сервер не нагружать другой работой. + +#================================================== +# Автоматическое уведомление при высокой загрузке CPU + +# Этот скрипт следит за загрузкой процессора и отправляет уведомление, если нагрузка превышает критическое значение. Полезен для серверов и рабочих станций, чтобы избежать перегрева и зависаний. + +#!/bin/bash +# Пороговое значение загрузки CPU в процентах +THRESHOLD=80 +# Лог-файл +LOG_FILE="$HOME/cpu_usage.log" +# Получаем среднюю загрузку CPU +CPU_LOAD=$(awk '{print $1}' < /proc/loadavg | awk '{print $1*100}') +if (( ${CPU_LOAD%.*} >= THRESHOLD )); then + MESSAGE="⚠️ Высокая загрузка CPU: $CPU_LOAD%" + echo "$MESSAGE" | tee -a "$LOG_FILE" + notify-send "CPU Alert" "$MESSAGE" +fi + +# Проверяет загрузку процессора по /proc/loadavg. +# Если нагрузка превышает установленный порог, отправляет уведомление через notify-send. +# Записывает события в лог-файл для анализа. + +# Автоматический мониторинг: +# Добавьте скрипт в crontab для проверки каждые 5 минут + +*/5 * * * * /path/to/script.sh + +# Этот скрипт поможет отслеживать пиковые нагрузки и предотвратить зависания системы. + +#================================================== + + + +``` + diff --git a/LINUX/SHELL команды.md b/LINUX/SHELL команды.md new file mode 100644 index 0000000..b348b41 --- /dev/null +++ b/LINUX/SHELL команды.md @@ -0,0 +1,63 @@ +```sh +pwd +ls ; ls . ; ls -1 ; ls -la +cd ; cd .. ; cd ~ ; cd - + +mkdir name_dir ; mkdir -p /name1/name2 +touch test.txt ; > test.txt +echo "test" > test.txt + +cp test.txt test1.txt +cp -r dir1 dir2 +mv test.txt test2.txt +mv test2.txt dir + +rm test.txt +rm /путь/к/файлам/*.txt +rm -r dir_name +rm -i /путь/к/файлам/*.txt +rm -f /путь/к/файлам/*.txt + +ln file1 file2 + +chmod xxxx dir +chmod -R xxxx dir + +cat test.txt +cat test.txt | head -2 +cat test.txt | tail -1 + +grep 'string' test.txt + +find ./ -name 'test' +find ~/ -name 'test*' +find ~/ -name '*.php*' + +tar -cf arhive_name.tar dir_name +tar -xvf arhive.tar + +history +clear +exit + +Ctrl-C +Ctrl-D +Ctrl-U +Ctrl-Z +Ctrl-W +!! +Ctrl-Shift_c +Ctrl-Shift-V + +fg ; bg + +==================================== +sodu mkdir demo +^sodu^sudo + +# Это заменит «sodu» на «sudo» и повторно выполнит команду. + +# Это особенно полезно, если вы допустили ошибку в длинной команде, так как избавляет вас от необходимости повторно вводить команду. + +``` + diff --git a/LINUX/SHUTDOWN, HALT, POWEROFF, REBOOT.md b/LINUX/SHUTDOWN, HALT, POWEROFF, REBOOT.md new file mode 100644 index 0000000..57192ec --- /dev/null +++ b/LINUX/SHUTDOWN, HALT, POWEROFF, REBOOT.md @@ -0,0 +1,56 @@ +========= +**shutdown +```sh +shutdown # Schedule a shutdown +shutdown now # Shutdown immediately +shutdown 13:20 # Shutdown at 1:20 PM +shutdown -p now # Power off the machine +shutdown -H now # Halt the machine +shutdown -r 09:35 # Reboot at 9:35 AM + +shutdown -c # отменить +``` +- `-r`: Перезагрузка после выключения. +- `-h`: Остановка или выключение питания (решает система). +- `-H`: Остановите систему. +- `-P`: Выключите систему. +- `-c`: Отменить запущенное завершение работы. +- `-k`: Только отправлять предупреждающие сообщения (без выключения). + +**Примечание по исправлению** : распространенной ошибкой является использование `shutdown -p`, что недопустимо. Правильный вариант выключения — `shutdown -P` + +========= +**halt +```sh +halt # Остановить машину +halt -p # Выключить машину +halt --reboot # Перезагрузить машину +``` + +========= +**poweroff +```sh +poweroff # Выключить машину +poweroff --halt # Остановить машину +poweroff --reboot # Перезагрузить машину +``` + +========= +**reboot +```sh +reboot # Перезагрузить машину +reboot --halt # Остановить машину +reboot -p # Выключить машину +``` + +========= +**systemctl +```sh +sudo systemctl poweroff # Выключить систему +sudo systemctl reboot # Перезагрузить систему +sudo systemctl halt # Остановить систему +``` + +Эти альтернативы работают надежно и считаются лучшими в системах на базе systemd. + + diff --git a/LINUX/SSH.md b/LINUX/SSH.md new file mode 100644 index 0000000..a169457 --- /dev/null +++ b/LINUX/SSH.md @@ -0,0 +1,302 @@ +#### SSH alias !!! #### +```sh +ssh-keygen --help # -h + +vim ~/.ssh/config + +UseRoaming no + +Host <newHostName> +Hostname 178.20.46.157 # или vlapa.ru +User vlapa +Port 22 + +##########################: +# заходим на сервер +ssh newHostName +``` +#### Проброс портов SSH: #### +```sh +ssh -L 9000:localhost:8000 server2 + +ssh -L [локальный_адрес:]порт:удалённый_адрес:порт [user@]hostname + +# Если не указывать [локальный_адрес:], соединение по умолчанию будет привязано к интерфейсу localhost. +``` + +#### SSH #### +```sh +ssh-keygen -t rsa +cat ~/.ssh/id_rsa.pub + +ssh-keygen -t ed25519 +ssh-keygen -t ed25519 -f ~/.ssh/key2 -C "new key" + +# -сменить пароль на ключ +ssh-keygen -p + +# -открытый ключ. копируют на сервера +~/.ssh/id_rsa.pub + +# -закрытый ключ (у нас) +~/.ssh/id_rsa + + +~/.ssh/authorized_keys +chown -R vlapa:vlapa /home/vlapa/.ssh +# Права обязательно !!! +chmod 700 ~/.ssh +chmod 644 ~/.ssh/* + +chmod 600 authorized_keys + +ssh -i ~/.ssh/key2 vlapa@192.168.88.2 + +cd /etc/ssh +ll + +vim /etc/sshd_config + + Port 22 + PermitRootLogin no + PubkeyAuthentication yes + PasswordAuthentication no + +systemctl restart sshd +systemctl status sshd + +ssh -i ~/.ssh/key2 vlapa@192.168.88.2 + +vim .ssh/config + +Host \*.com + StrictHostKeyChecking no + User etoosamoe + ForwardAgent yes + IdentityFile /Users/username/.ssh/id_rsa + IdentitiesOnly yes + UserKnownHostsFile=/dev/null + UseKeychain yes + AddKeysToAgent yes + ServerAliveInterval 60 + ServerAliveCountMax 1200 +Host std-\* + StrictHostKeyChecking no + User ci-user + ForwardAgent yes + IdentityFile /Users/username/.ssh/id_rsa_second + IdentitiesOnly yes + UserKnownHostsFile=/dev/null + UseKeychain yes + AddKeysToAgent yes + ServerAliveInterval 60 + ServerAliveCountMax 1200 +``` + +```sh +# В каталоге пользователя, под которым вы хотите зайти, если создать файл и положить туда открытый ключ, то можно будет заходить без пароля. Обратите внимание, права на файл не должны давать возможность писать в этот файл посторонним пользователям (и группам !!!), иначе ssh его не примет. В ключе последнее поле — user@machine Оно не имеет никакого отношения к авторизации и служит только для удобства определения где чей ключ. Заметим, это поле может быть поменяно (или даже удалено) без нарушения структуры ключа. + +ssh-copy-id user@server +# позволяет скопировать ключ не редактируя файлы вручную. + +# Если у вас ssh на нестандартном порту, то ssh-copy-id требует особого ухищрения при работе: + +ssh-copy-id '-p 443 user@server' # внимание на кавычки ! + +# Первый раз, когда вы заходите на сервер, ssh вас спрашивает, доверяете ли вы ключу. Если отвечаете нет, соединение закрывается. Если да — ключ сохраняется в файл ~/.ssh/known_hosts Узнать, где какой ключ нельзя (ибо несекьюрно). + +# Удалить известный ключ сервера можно командой: +ssh-keygen -R server + +# При этом нужно удалить ещё и ключ IP (они хранятся раздельно): +ssh-keygen -R 127.0.0.1 + +# Ключ сервера хранится в +/etc/ssh/ssh_host_rsa_key + +/etc/ssh/ssh_host_rsa_key.pub + +# Их можно: +# а) скопировать со старого сервера на новый. +# б) сгенерировать с помощью ssh-keygen Пароля при этом задавать не надо (т.е. пустой). Ключ с паролем ssh-сервер использовать не сможет. + +# Заметим, если вы сервера клонируете (например, в виртуалках), то ssh-ключи сервера нужно обязательно перегенерировать. + +# Старые ключи из know_hosts при этом лучше убрать, иначе ssh будет ругаться на duplicate key + +sudo ssh service restart +sudo sshd service restart + +# Чтобы удаленно выполнить команду с локального компьютера, добавьте инструкцию к команде SSH. Например, чтобы удалить файл, введите: + +ssh test.server.com rm ~/Desktop/Dir1/sample4 + +# Введите пароль, и файл на удаленном сервере будет удален без создания новой оболочки. + +sudo systemctl restart ssh + +vim /etc/ssh/sshd_config + +- PasswordAuthentication no +- PermitRootLogin no +- AllowUsers www root + + +sudo service ssh restart + + +# Ключ игнорируется сервером + +# В редких случаях можно столкнуться с проблемой, что вы настроили все корректно, но не можете подключиться к серверу с использованием SSH-ключей. Видя следующий вывод, можно подумать, что сервер не видит ключ: + +Permission denied (publickey). + +# Вероятнее всего, сервер SSH считает, что установлены неподходящие разрешения к некоторым каталогам. Для решения этой проблемы следует изменить права доступа. + +# Настройка удаленного узла + +# Полный доступ к папке .ssh только владельцу, остальным — полный запрет: + +chmod rwx------ ~/.ssh + +# Права на запись и чтение для файла authorized_keys только владельцу, остальным — полный запрет: + +chmod rw------- ~/.ssh/authorized_keys + +# Отмена записи группой и остальными пользователями: + +chmod go-w ~/ + +# Настройка локальной машины + +# Полный доступ к папке .ssh только владельцу, остальным — полный запрет: + +chmod rwx------ ~/.ssh + +# Права на запись и чтение для файла ключа только владельцу, остальным — полный запрет: + +chmod rw------- ~/.ssh/*key* ( 600) + +# После применения этих политик ошибка должна исчезнуть. +``` + +=========================================== +### Как общаться с удаленным сервером через SSH + +`ssh-keygen -t rsa -b 4096 -f host_ca -C host_ca + +Файл `host_ca` является закрытым ключом центра сертификации хоста и должен храниться в защищенном месте. Не выдавайте его никому, не копируйте куда-либо и убедитесь, что как можно меньше людей имеют к нему доступ. В идеале он «живет» на машине, которая не допускает прямого доступа, и все сертификаты выдаются автоматически. + +Рекомендуется использовать два отдельных CA – один для подписания сертификатов хостов, другой для пользователей. Одни и те же процессы не будут добавлять и хосты, и юзеров. А если закрытый ключ скомпрометируют, нужно будет лишь пересоздать сертификаты для хостов или пользователей, а не оба сразу. + +Вот так создается `user_ca`: + +`$ ssh-keygen -t rsa -b 4096 -f user_ca -C user_ca` + +Файл `user_ca` – закрытый ключ пользователя, который должен быть защищен, как и закрытый ключ хоста. + +### Выдача сертификатов хостам + +Создадим новый ключ и подпишем с помощью центра сертификации. + +ssh_host_rsa_key-cert.pub содержит подписанный сертификат хоста. + +```sh +$ ssh-keygen -f ssh_host_rsa_key -N '' -b 4096 -t rsa + +$ ls -l +-rw------- 1 palpatine palpatine 3247 Mar 17 14:49 ssh_host_rsa_key +-rw-r--r-- 1 palpatine palpatine 764 Mar 17 14:49 ssh_host_rsa_key.pub + +$ ssh-keygen -s host_ca -I host.example.com -h -n host.example.com -V +52w ssh_host_rsa_key.pub +Enter passphrase: # the passphrase used for the host CA +Signed host key ssh_host_rsa_key-cert.pub: id "host.example.com" serial 0 for host.example.com valid from 2020-03-16T15:00:00 to 2021-03-15T15:01:37 + +$ ls -l +-rw------- 1 palpatine palpatine 3247 Mar 17 14:49 ssh_host_rsa_key +-rw-r--r-- 1 palpatine palpatine 2369 Mar 17 14:50 ssh_host_rsa_key-cert.pub +-rw-r--r-- 1 palpatine palpatine 764 Mar 17 14:49 ssh_host_rsa_key.pub +``` + +Вот описание используемых флагов: + +- **-s host_ca**: указывает имя файла закрытого CA-ключа, используемого для подписи. +- **-I host.example.com**: буквенно-цифровая строка, идентифицирующая сервер. При необходимости значение можно использовать для отзыва сертификата. + +- **-h**: указывает, что сертификат выдается хосту, а не юзеру. + +- **-n host.example.com**: указывает разделенный запятыми список участников, для проверки подлинности которых будет действителен сертификат. + +- **-V +52w**: срок действия сертификата. В данном случае 52 недели (один год). По умолчанию сертификаты действительны вечно. Указание периода истечения срока действия сертификатов хоста настоятельно рекомендуется для ротации и замены сертификатов. + +## Настройка SSH для использования сертификатов + +Нужно указать серверу, чтобы он использовал новый сертификат. Скопируйте три сгенерированных файла на сервер в каталог `/etc/ssh`, установите разрешения и добавьте в файл `/etc/ssh/sshd_config` следующую строку: + + `HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub` + + +Перезагрузите **sshd** с помощью `systemctl restart sshd`. Теперь сервер настроен на выдачу сертификата. Чтобы локальный ssh-клиент мог автоматически доверять хосту на основе удостоверения сертификата, нужно добавить открытый ключ CA в `known_hosts`. Вы можете сделать это, добавив в начало файла `host_ca.pub` следующее: `@cert-authority *.example.com`, а затем и в `~/.ssh/known_hosts`: + + `@cert-authority *.example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDwiOso0Q4W+KKQ4OrZZ1o1X7g3yWcmAJtySILZSwo1GXBKgurV4jmmBN5RsHetl98QiJq64e8oKX1vGR251afalWu0w/iW9jL0isZrPrmDg/p6Cb6yKnreFEaDFocDhoiIcbUiImIWcp9PJXFOK1Lu8afdeKWJA2f6cC4lnAEq4sA/Phg4xfKMQZUFG5sQ/Gj1StjIXi2RYCQBHFDzzNm0Q5uB4hUsAYNqbnaiTI/pRtuknsgl97xK9P+rQiNfBfPQhsGeyJzT6Tup/KKlxarjkMOlFX2MUMaAj/cDrBSzvSrfOwzkqyzYGHzQhST/lWQZr4OddRszGPO4W5bRQzddUG8iC7M6U4llUxrb/H5QOkVyvnx4Dw76MA97tiZItSGzRPblU4S6HMmCVpZTwva4LLmMEEIk1lW5HcbB6AWAc0dFE0KBuusgJp9MlFkt7mZkSqnim8wdQApal+E3p13d0QZSH3b6eB3cbBcbpNmYqnmBFrNSKkEpQ8OwBnFvjjdYB7AXqQqrcqHUqfwkX8B27chDn2dwyWb3AdPMg1+j3wtVrwVqO9caeeQ1310CNHIFhIRTqnp2ECFGCCy+EDSFNZM+JStQoNO5rMOvZmecbp35XH/UJ5IHOkh9wE5TBYIeFRUYoc2jHNAuP2FM4LbEagGtP8L5gSCTXNRM1EX2gQ== host_ca` + + +Значение `*.example.com` указывает на то, что этому сертификату следует доверять для идентификации любого хоста, к которому вы подключаетесь и который имеет домен `*.example.com`. Это разделенный запятыми список имен хостов для сертификата: **host1**, **host2**, **host3** или **1.2.3.4**, **1.2.3.5** в зависимости от обстоятельств. + +Как только закончите настройку, удалите все старые записи для `host.example.com` в `~/.ssh/known_hosts` и запустите подключение. Правильность создания сертификата проверяется так: + + `$ ssh -vv host.example.com 2>&1 | grep "Server host certificate" debug1: Server host certificate: ssh-rsa-cert-v01@openssh.com SHA256:dWi6L8k3Jvf7NAtyzd9LmFuEkygWR69tZC1NaZJ3iF4, serial 0 ID "host.example.com" CA ssh-rsa SHA256:8gVhYAAW9r2BWBwh7uXsx2yHSCjY5OPo/X3erqQi6jg valid from 2020-03-17T11:49:00 to 2021-03-16T11:50:21 debug2: Server host certificate hostname: host.example.com` + + +На этом этапе вы можете продолжить выдачу сертификатов всех хостов, используя центр сертификации. Польза от этого следующая: вам больше не нужно полагаться на небезопасную модель **Trust On First Use (TOFU)** для новых хостов, и если вы когда-нибудь повторно развернете сервер и, следовательно, измените ключ хоста, новый хост автоматически представит подписанный сертификат без ужасного предупреждения: **WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!** + +## Выдача сертификатов пользователям (для аутентификации пользователей на хостах) + +В этом примере мы создадим новый ключ пользователя и подпишем с помощью центра сертификации. + + `$ ssh-keygen -f user-key -b 4096 -t rsa $ ls -l -rw-r--r--. 1 tatooine tatooine 737 Mar 19 16:33 user-key.pub -rw-------. 1 tatooine tatooine 3369 Mar 19 16:33 user-key $ ssh-keygen -s user_ca -I gus@gravitational.com -n ec2-user,gus -V +1d user-key.pub Enter passphrase: # the passphrase used for the user CA Signed user key user-key-cert.pub: id "gus@gravitational.com" serial 0 for ec2-user,gus valid from 2020-03-19T16:33:00 to 2020-03-20T16:34:54 $ ls -l -rw-------. 1 tatooine tatooine 3369 Mar 19 16:33 user-key -rw-r--r--. 1 tatooine tatooine 2534 Mar 19 16:34 user-key-cert.pub -rw-r--r--. 1 tatooine tatooine 737 Mar 19 16:33 user-key.pub` + + +`user-key-cert.pub` содержит подписанный сертификат пользователя. Для входа в систему понадобится как этот, так и приватный ключ. Вот описание используемых флагов: + +- **-s user_ca**: указывает приватный ключ CA, используемый для подписи. +- **-I tatooine@proglib.io**: буквенно-цифровая строка, которая будет видна в логе SSH. Для точной идентификации пользователя рекомендуем использовать адрес электронной почты или логин. + +- **-n palpatine, tatooine**: указывает разделенный запятыми список участников, для проверки подлинности которых будет действителен сертификат. В примере мы предоставляем сертификат доступа как для `palpatine`, так и для `tatooine`. + +- **-V +1d**: указывает срок действия сертификата, в данном случае +1d означает 1 день. + +Если нужно увидеть параметры подписи сертификата, используйте`ssh-keygen -L`: + + `$ ssh-keygen -L -f user-key-cert.pub user-key-cert.pub: Type: ssh-rsa-cert-v01@openssh.com user certificate Public key: RSA-CERT SHA256:egWNu5cUZaqwm76zoyTtktac2jxKktj30Oi/ydrOqZ8 Signing CA: RSA SHA256:tltbnMalWg+skhm+VlGLd2xHiVPozyuOPl34WypdEO0 (using ssh-rsa) Key ID: "palpatine@proglib.io" Serial: 0 Valid: from 2020-03-19T16:33:00 to 2020-03-20T16:34:54 Principals: tatooine palpatine Critical Options: (none) Extensions: permit-X11-forwarding permit-agent-forwarding permit-port-forwarding permit-pty permit-user-rc` + + +## Настройка SSH для проверки подлинности + +После того как сертификат подписан, нужно сообщить серверу, что можно доверять пользователским сертификатам. Копируем `user_ca.pub` в директорию `/etc/ssh`, поправляем права и добавляем в конфиг `/etc/ssh/sshd_config` строку: + + `TrustedUserCAKeys /etc/ssh/user_ca.pub` + + +Как раньше, перезагружаем **sshd** с помощью `systemctl restart sshd`. Теперь сервер настроен на пропуск всех, кто представляет сертификат, выданный центром сертификации при подключении. Если сертификат хранится в том же каталоге, что и ваш приватый ключ (указанный с флагом `-i`, например `ssh -i /home/tatooine/user-key` `palpatine @proglib.io`), он будет автоматически использоваться при подключении к серверам. + +## Проcмотр логов + +Если вы посмотрите в журнал sshd сервера, запустив `journalctl -u sshd`, то увидите имя используемого для аутентификации сертификата, а также данные центра сертификации: + + `sshd[14543]: Accepted publickey for tatooine from 1.2.3.4 port 53734 ssh2: RSA-CERT ID palpatine@proglib.io (serial 0) CA RSA SHA256:tltbnMalWg+skhm+VlGLd2xHiVPozyuOPl34WypdEO0` + + +Если пользователь попытается войти в систему под логином, у которого нет доступа, в логе вы увидите такую ошибку: + + `sshd[14612]: error: key_cert_check_authority: invalid certificate sshd[14612]: error: Certificate invalid: name is not a listed principal` + + +Если срок действия сертификата истек, вы увидите следующую ошибку: + + `sshd[14240]: error: key_cert_check_authority: invalid certificate sshd[14240]: error: Certificate invalid: expired` + + +В качестве домашнего задания напишите скрипт, который будет использовать центр сертификации для выдачи сертификатов, действительных в течение следующих двух часов. Каждое использование этого скрипта должно журналировать того, кто запросил сертификат и его почту, а потом встраивать этот email в сертификат. Это поможет в мониторинге. + +Взгляните на `man ssh-keygen` для новых идей.
\ No newline at end of file diff --git a/LINUX/SYSTEMCTL.md b/LINUX/SYSTEMCTL.md new file mode 100644 index 0000000..e550eed --- /dev/null +++ b/LINUX/SYSTEMCTL.md @@ -0,0 +1,405 @@ +Почему стоит перейти на ==`systemd`== вместо скриптов: +- Более надёжная демонизация. +- Автоматический перезапуск при падении. +- Логи сохраняются через journalctl. +- Возможность ограничения ресурсов (CPU, память). +- Красивое управление (`start`, stop, restart, `status`). + +#### ▪️ Правильный systemd-юнит для сервиса: +vim /etc/systemd/system/myapp.service: +```sh +[Unit] +Description=MyApp Server +After=network.target + +[Service] +Type=simple +ExecStart=/opt/myapp/server +Restart=always +RestartSec=5 +WorkingDirectory=/opt/myapp/ +StandardOutput=append:/var/log/myapp/server.log # Каталог и файл должны существовать!!! +StandardError=append:/var/log/myapp/server_error.log # Каталог и файл должны существовать!!! +User=appuser +Group=appuser +Environment=ENV=production + +[Install] +WantedBy=multi-user.target +``` +▪️ Что важно здесь: +- Restart=always — перезапуск при сбоях. +- StandardOutput и StandardError перенаправляют логи прямо в файлы. +- User и Group указывают, под кем должен работать процесс. +- WorkingDirectory задаёт рабочую папку для приложения. + +▪️ Как активировать сервис: +```sh +sudo systemctl daemon-reload +sudo systemctl enable myapp +sudo systemctl start myapp +sudo systemctl status myapp +``` +▪️ Теперь управление сервисом становится надёжным и простым. +▪️ Дополнительные вопросы на собеседовании: +▪️ Вывод: +Переход от скриптов к systemd — это шаг к профессиональной эксплуатации Linux-приложений. +Меньше ручных ошибок, лучшее управление процессами, больше надёжности. +```sh +# Проверить, включен ли автозапуск сервиса +systemctl is-enabled your_service.service + +# Посмотреть статус сервиса (включая последние ошибки) +systemctl status your_service.service + +# Включить автозапуск +sudo systemctl enable your_service.service + +# Логи сервиса с момента последнего запуска +journalctl -u your_service.service -b + +# Показать ошибки systemd на загрузке +systemd-analyze blame +``` +🟢 systemctl — управление сервисами и проверка их состояния +🟢 journalctl — просмотр логов systemd +🟢 systemd-analyze помогает понять, что тормозит загрузку системы. + +#======================== +_bot.service_ +```sh +# Создаем НОВУЮ службу или редактируем первоисточник: +sudo systemctl edit --force --full bot + +# или редактируем уже имеющуюся (открывается пустой файл +# для добавления или исправления): +sudo systemctl edit bot + +# Все сервисы находятся в: +cd /etc/systemd/system/ +#============================ +``` + +```shell +[Unit] +Description=bot +After=network.target # только после запуска сети +# After=multi-user.target + +[Service] +Type=simple +ExecStart=python3 /home/vlapa/folder/bot.py +WorkingDirectory=/home/vlapa +Restart=always # on-failure +#---------------------------- +User=root +Group=root +#---------------------------- + +[Install] +WantedBy=multi-user.target # default.target +``` + +```sh +# перезагрузка всех служб: +sudo systemctl daemon-reload + +# включить в автозагрузку: +sudo systemctl enable bot.service + +# перезапуск с перечитыванием конфигов +systemctl restart bot.service + +# перезапуск без перечитывания конфигов +systemctl reload bot.service + +# включить в автозагрузку +systemctl enable bot.service + +# выключить из автозагрузки +systemctl disable bot.service + +# проверка включена или нет в автозагрузку +systemctl is-enabled ssh + +# Все модули: +systemctl list-units --all +systemctl list-unit-files --type=service "ssh*" + +# Оптимизация загрузки: +systemd-analyze blame + +``` + +```sh +# Создание исполняемого скрипта: +#!/usr/bin/env python3 +chmod +x my_script.py + +# Запуск скрипта: +./my_script.py +``` + +=============================== +Все модули можно найти в трёх каталогах: + +`/usr/lib/systemd/system` +— юниты сервисов, установленных с помощью менеджера пакетов. Самый простой пример — веб-серверы: Apache или Nginx. +`/run/systemd/system` +— юниты, которые создаются в процессе работы системы. +`/etc/systemd/system` +— юниты, которые создаёт администратор системы. + +Список всех запущенных модулей можно посмотреть, использовуя команду `systemctl`. В терминале вы увидите таблицу со статусом каждой службы. Разберём, что означает каждый из столбцов: + +1. **UNIT**. Название модуля +2. **LOAD**. Статус загрузки конфигурации, если успешно — loaded. +3. **ACTIVE**. Статус службы. Один из следующих: _active, inactive, running, exited, dead, loaded, not-found, plugged, mounted, waiting, listening._ +4. **SUB**. Детальная информация о юните. +5. **DESCRIPTION**. Краткое описание модуля — за что он отвечает. + +Чтобы увидеть все модули, независимо от того, запущены они или нет, воспользуйтесь командой `systemctl list-units --all`. Вы увидите все модули, которые когда-либо загружал или пытался загрузить диспетчер systemd. Для отображения конкретных записей используйте флаг `--state`. Так, например, для того, чтобы увидеть все неактивные юниты, нужно использовать: + +`sudo systemctl list-units --all --state=inactive` `` + +### Типы юнитов + +Модули делятся на категории по типу. Тип каждого модуля можно узнать из названия файла — он указан через точку. Все типы модулей: + +1. .service — описывает, как управлять службой и приложением +2. .socket — описывает сетевой буфер, который используется для активации сокета +3. .device — описывает устройство как необходимое для управления systemd +4. .mount — определяет точку монтирования в системе +5. .automount — настраивает автоматическую установку точки монтирования +6. .swap — описывает пространство подкачки в системе +7. .target — обеспечивает синхронизацию устройств при загрузке системы +8. .path — определяет путь, который используется для активации +9. .timer — определяет задержку или активацию по плану +10. .snapshot — делает снимок системы для последующего восстановления +11. .slice — ограничивает узлы группы управления +12. .scope — systemd создаёт эти модули автоматически, пользуясь информацией из интерфейса шины + +```sh +[Unit] +Description=OpenSSH server daemon +Documentation=man:sshd(8) man:sshd_config(5) +After=network.target sshd-keygen.target +Wants=sshd-keygen.target + +[Service] +Type=notify Environment +File=-/etc/crypto-policies/back-ends/opensshserver.config EnvironmentFile=-/etc/sysconfig/sshd +ExecStart=/usr/sbin/sshd -D $OPTIONS $CRYPTO_POLICY +ExecReload=/bin/kill -HUP $MAINPID +KillMode=process +Restart=on-failure +RestartSec=42s + +[Install] +WantedBy=multi-user.target +``` + +**Unit** + +Это первая обязательная секция. В ней описаны правила взаимодействия с другими службами и указаны метаданные — описание и документация. Подробнее: + +- Description — описание модуля. +- Documentation — страница руководства man. +- After — после каких демонов и событий нужно запускать модуль. Например, модули веб-серверов запускаются только после сетевых интерфейсов. +- Requires — сервисы, необходимые для работы юнита +- Wants — сервисы, которые желательно запустить перед стартом модуля + +**Service** + +В этой секции необходимо указать, какими командами запускать службу: + +- Type — как запускается демон? По умолчанию — simple, то есть служба просто запускается. Notify это примерно то же самое, но в таком случае процесс сообщит диспетчеру, что он готов к работе. Есть также типы forking (после запуска демона родительский процесс завершается) и one-shot (выполняется один раз). +- PIDFile — ссылка на основной процесс +- WorkingDirectory — текущая рабочая директория для запуска команд +- User — пользователь для запуска сервиса +- Group — группа для запуска сервиса +- ExecStart — команда для запуска сервиса +- ExecStop — команда для остановки сервиса +- TimeoutSec — время, которое служба ожидает остановки или старта +- Restart — настройки перезапуска + +**Install** + +В этой секции нужно указать, на каком уровне загрузки системы нужно запускать сервис. Переменная WantedBy сообщает, как включится устройство, в качестве параметра указываются специальные файлы целей. Они служат для того, чтобы привести систему в разные состояния. + +Список доступных целей вы можете получить, выполнив команду + +`sudo systemctl list-unit-files --type=target` + + +### Редактирование модулей + +Модули нельзя править напрямую. Для этого нужно использовать команду + +`systemctl edit` + +Откроется файл, который можно использовать для переопределения директив службы или добавления новых. При этом настройки из файла переопределения приоритетнее, чем настройки по умолчанию. + +Если нужно отредактировать файл модуля целиком, а не добавлять новые директивы, нужно использовать флаг `--full`: + +`sudo systemctl edit --full apache2.service` `` + +Перед вами откроется редактор, в котором вы можете править настройки юнита. + +По умолчанию файлы переопределения хранятся в + +`/etc/systemd/system/` + +в каталоге который называется так же, как модуль, но с суффиксом `.d`. Например, в случае apache2 это будет директория + +`/etc/systemd/system/apache2.service.d/`. + +Если вы редактируете конфигурацию напрямую с использованием флага `--full`, нужный файл находится в каталоге + +`/etc/systemd/system/` + +и называется так же, как модуль. + +Для того, чтобы удалить переопределения, которые вы добавили, просто удалите каталог с суффиксом `.d`. Если нужно восстановить настройки по умолчанию, удалите весь файл модуля. В случае с apache2 это: + +`sudo rm -r /etc/systemd/system/apache2.service.d` `` +или +`sudo rm /etc/systemd/system/apache2.service` `` + +После этого нужно перезагрузить процесс systemd: + +`sudo systemctl daemon-reload` ` + +=============================== + +## Создание сервиса + +В подсистеме systemd также можно легко создать собственную службу и использовать ее для автозапуска приложений или собственных скриптов. Для этого в каталоге **/usr/lib/systemd/system** создаем юнит (файл) с расширением **.service**. + +Пример простой и частоиспользуемой конфигурации: + +[Unit] +Description=Service Name +After=network.target + +[Service] +User=root +Group=root +Type=simple + +ExecStart=/usr/bin/myapp +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure + +[Install] +WantedBy=multi-user.target + +* как правило, файл разделен на 3 части: + +- **Unit** — позволяет определить метаданные для юнита. +- **Service** — раздел для основной конфигурации юнита. +- **Install** — определение поведения для юнита при его включении или отключении. + +Подробнее можно почитать о структуре и возможных опциях на странице [https://linux-notes.org/pishem-systemd-unit-fajl/](https://linux-notes.org/pishem-systemd-unit-fajl/) + +Более сложный вариант разберем на примере сервиса bind: + +vi /usr/lib/systemd/system/named.service + +Содержимое может быть следующего содержания: + +[Unit] +Description=Berkeley Internet Name Domain (DNS) +Wants=nss-lookup.target +Wants=named-setup-rndc.service +Before=nss-lookup.target +After=network.target +After=named-setup-rndc.service + +[Service] +Type=forking +Environment=NAMEDCONF=/etc/named.conf +EnvironmentFile=-/etc/sysconfig/named +Environment=KRB5_KTNAME=/etc/named.keytab +PIDFile=/run/named/named.pid +ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then /usr/sbin/named-checkconf -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi' +ExecStart=/usr/sbin/named -u named -c ${NAMEDCONF} $OPTIONS +ExecReload=/bin/sh -c '/usr/sbin/rndc reload > /dev/null 2>&1 || /bin/kill -HUP $MAINPID' +ExecStop=/bin/sh -c '/usr/sbin/rndc stop > /dev/null 2>&1 || /bin/kill -TERM $MAINPID' +PrivateTmp=true + +[Install] +WantedBy=multi-user.target + +После внесения изменений и сохранения файла, необходимо перечитать изменения командой: + +systemctl daemon-reload + +Теперь можно разрешить автозапуск: + +systemctl enable named + +### Редактирование сервисов + +Если мы хотим внести изменения в юнит-файл сервиса, который был установлен с последним, необходимо использовать drop-in файл или файл переопределения настроек. В противном случае, после обновления программы наши изменения могут быть удалены. + +И так, мы для примера взяли юнит для bind. Чтобы создать для него drop-in файл, вводим: + +systemctl edit named + +И вносим, например, такие изменения: + +[Service] +Restart=on-failure + +_* будет создан файл **/etc/systemd/system/named.service.d/override.conf**, который будет переопределять настройки основного юнит-файла. В данном примере, мы указываем на необходимость перезапуска сервиса при сбое._ + +Чтобы убедиться в использовании Drop-In файла смотрим статус сервиса: + +systemctl status named + +Мы должны увидеть что-то на подобие: + +Drop-In: /etc/systemd/system/named.service.d + — override.conf + +Также мы можем редаетировать файл юните без drop-in. Для этого добавляем опцию full: + +systemctl edit --full named + +Однако, как было сказано выше, при очередном обновлении приложения, если разработчик пакета предоставляет файл systemd наши изменения могут быть перетерты. + +## Таймеры + +Выше мы рассмотрели возможность автоматического запуска сервисов с помощью systemd. Мы также можем настроить периодический запуск данных юнитов по таймеру. Для этого нам нужно создать юнит с таким же названием, но на конце должен быть суффикс ==.timer==. + +Предположим, мы хотим создать запуск по таймеру named, для которого выше в примере создали юнит сервиса. Тогда создаем файл: + +```sh +vi /usr/lib/systemd/system/named.timer + +[Unit] +Description=Run named every 10 min + +[Timer] +OnBootSec=5min +OnUnitActiveSec=10min + +[Install] +WantedBy=timers.target + +# в данном примере наш таймер будет создан для юнита named; он запустится через 5 минут после старта службы и будет запускать ее каждые 10 минут + +# Перечитаем изменения в systemd: +systemctl daemon-reload + +# Разрешаем наш таймер: +systemctl enable named.timer + +# Запустим его: +systemctl start named.timer + +# Вывести список таймеров можно командой: +systemctl list-timers +``` + diff --git a/LINUX/TAR.md b/LINUX/TAR.md new file mode 100644 index 0000000..7bb98c5 --- /dev/null +++ b/LINUX/TAR.md @@ -0,0 +1,314 @@ +1️⃣ **Распаковка tar** +Для распаковки архива в формате **.tar** в текущую директорию выполните команду: + +`tar xvf archive.tar + +2️⃣ **Распаковка tar.bz2, tar.bzip2, tbz2, tb2, tbz** + +`tar xvjf archive.tar.bz2 + +3️⃣ **Распаковка tar.xz, txz** + +`tar xvJf archive.tar.xz + +4️⃣ **Распаковка tar.gz, tgz** + +`tar xvzf archive.tar.gz + +5️⃣ **Распаковка tar.lzma** + +`tar --lzma -xvf archive.tar.lzma + +🤔 **Пояснение опций** + +x — распаковать архив. +v — Verbose-режим (вывод на экран дополнительной информации во время распаковки). +f — выполнить распаковку архива из файла. +j — вызвать bzip2 для распаковки архива. +z — вызвать gzip +J — вызвать xz + +Чтобы распаковать архив в определенную директорию используется опция -C или --directory, например: + +`tar xvf archive.tar -C /to/directory + +===================================== +  Обновлено: 24.07.2024  Опубликовано: 18.11.2016 +### Создать + +Создание выполняется с ключом **c**. Синтаксис следующий: + +tar -c<опции> <новый архивный файл> <что сжимаем> + +Пример создания: + +tar -czvf archive.tar.gz /home/dmosk + +_* в данном примере будет создан архив **archive.tar.gz** домашней директории пользователя (**/home/dmosk**) +** где **z** — сжать архив в gzip (без этого параметра, tar не сжимает, а создает так называемый тарбол); **c** — ключ на создание архива; **v** — verbose режим, то есть с выводом на экран процесса (очень удобно для наблюдением за ходом работы, но в скриптах можно упустить); **f** — использовать файл (обязательно указываем, так как в большей степени работаем именно с файлами)._ + +### Распаковать + +Распаковка выполняется с ключом **x** с синтаксисом: + +tar -x<опции> <архивный файл> + +Пример распаковки: + +tar -xvf archive.tar.gz + +## Форматы + +Утилита tar позволяет работать с архивами различных форматов. Рассмотрим их кратко. + +### gz + +Сжатие: + +tar -cf archive.tar.gz /files + +Распаковка .gz файла: + +tar -xf archive.tar.gz + +_* при возникновении ошибки **This does not look like a tar archive**, можно воспользоваться командой **gzip -d archive.tar.gz**._ + +### bz2 + +tar -xvjf archive.tar.bz2 + +_* ключ j отвечает за работу с bz2._ + +Если система ругается на bzip2, значит нужно его установить: + +apt-get install bzip2 + +Если видим ошибку **tar: Unrecognized archive format**, воспользуемся следующей командой: + +bzip2 -d archive.tar.bz2 + +### gzip + +tar -xvzf archive.tar.gzip + +_* ключ z отвечает за работу с gzip._ + +### tgz + +TGZ — tar-архив, сжатый с помощью утилиты gzip. По сути, это тот же tar.gz, и работа с ним ведется такими же командами. Рассмотрим примеры работы с данным форматом. + +Для создания архива tgz выполняем: + +tar -czf archive.tgz /files + +Распаковывается, как gzip: + +tar -xzf archive.tgz + +### xz + +Это формат сжатия данных с помощью алгоритма LZMA. Для работы с ним устанавливаем: + +apt install xz-utils + +_* на Debian / Ubuntu._ + +yum install xz + +_* на Rocky Linux / CentOS._ + +Теперь можно пользоваться. + +Создать архив: + +tar -cpJf archive.tar.xz /home/user + +Распаковка: + +tar -xpJf archive.tar.xz + +## Описание ключей tar + +### Команды для действия + +|Ключ|Описание| +|---|---| +|-A|Добавление файлов в архив.| +|-c|Создание нового архивного файла.| +|-d|Показать отличающиеся данные между каталогом-исходником и содержимым архива.| +|--delete|Удалить файлы внутри архива.| +|-r|Добавить файлы в конец архива.| +|-t|Показать содержимое архива.| +|-u|Добавить файлы, которых нет в архиве.| +|-x|Извлечь файлы из архива.| + +_* нельзя использовать несколько вышеперечисленных ключей в одной команде._ + +### Дополнительные опции + +|Ключ|Описание| +|---|---| +|--atime-preserve|Оставить прежнюю метку времени доступа для файла.| +|-b N|Задать размер блока N x 512.| +|-C|Смена каталога. По умолчанию, используется тот, в котором мы находимся.| +|--checkpoint|Показать имена папок при чтении архивного файла.| +|-G|Использование старого формата инкрементального резервирования при отображении или извлечения.| +|-g|Использование нового формата инкрементального резервирования при отображении или извлечения.| +|-h|Не дублировать символьные ссылки. Только файлы, на которые указывают эти симлинки.| +|-i|Игнорировать блоки нулей.| +|-j|Использование bzip2.| +|--ignore-failed-read|Игнорировать не читаемые файлы.| +|-k|При распаковке, существующие файлы не заменяются соответствующими файлами из архива.| +|-L N|Смена магнитной ленты после N*1024 байт.| +|-m|При извлечении игнорировать время модификации объекта.| +|-M|Многотомные архивы.| +|-N DATE|Сохранять только более новые файлы относительно DATE| +|-O|Направление извлекаемого на стандартный вывод.| +|-p|Извлечение защищенной информации.| +|-P|Не отбрасывает начальный слэш (/) из имен.| +|-s|Сортировка файлов при извлечении.| +|--preserve|Аналогично -ps| +|--remove-files|Удалить исходные файлы после добавления в архив.| +|--same-owner|Сохранить владельца при извлечении.| +|--totals|Вывод байт при создании архива.| +|-v|Протоколирование действий — отображение списка объектов, над которыми происходит действие.| +|-V NAME|Создание архива на томе с меткой NAME.| +|--version|Показать версию tar.| +|-w|Требовать подтверждения для каждого действия.| +|-W|Проверка архива после записи.| +|--exclude FILE|Исключить файл FILE.| +|-X FILE|Исключить файлы FILE.| +|-Z|Фильтрует архив с помощью compress.| +|-z|Использование gzip.| + +* актуальный список опций можно получить командой **man tar**. + +## Примеры + +Рассмотрим некоторые сценарии использования tar. + +### Распаковать в определенную папку + +tar -C /home/user -xvf archive.tar.gz + +_* ключ **-C** используется для указания папки, куда необходимо распаковать файлы из архива._ + +### Распаковка без вложенной папки + +Такой способ можно использовать для распаковки в заранее подготовленный каталог. Будет некий эффект переименовывания каталога или аналог «Распаковать здесь»: + +tar -C /home/admin/mytar -xvf admin.tar.gz --strip-components 1 + +_* каталог **/home/admin/mytar** заранее должен быть создан; **--strip-components 1** пропустит одну вложенную папку внутри архива._ + +### Указать конкретный файл или каталог для распаковки + +Если у нас большой архив, но извлечь нужно конкретный файл или каталог, то можно указать его через пробел после указания файла с архивом. Синтаксис: + +tar zxf <путь до архива> <что извлечь> + +Рассмотрим пример. Напомним, что можно посмотреть содержимое архива с помощью опции t: + +tar tf /backup/samba.tar.gz + +Теперь можно распаковать архив: + +tar -zxf /backup/samba.tar.gz mail + +_* предположим, что внутри архива **samba.tar.gz** есть что-то и каталог **mail**. Извлечем мы только последний._ + +### Исключение файлов по маске + +Если необходимо пропустить некоторые файлы, вводим команду с ключом --exclude: + +tar --exclude='sess_*' -czvf archive.tar.gz /wwwsite + +_* в данном примере мы создадим архив **archive.tar.gz**, в котором не будет файлов, начинающихся на **sess_**._ + +Также можно исключить несколько файлов или папок, добавляя несколько опций exclude: + +tar --exclude='/data/recycle' --exclude='*.tmp' -zcf /backup/samba/2021-08-29.tar.gz /data/ + +_* в данном примере мы исключим папку **recycle** и файлы, которые заканчиваются на **.tmp**_ + +### Работа с архивами, разбитыми на части + +Разбить архив на части может понадобиться по разным причинам — нехватка места на носителе, необходимость отправки файлов по почте и так далее. + +Чтобы создать архив, разбитый на части, вводим команду: + +tar -zcvf - /root | split -b 100M - root_home.tar.gz + +_* данная команда создаст архив каталога **/root** и разобьет его на части по **100 Мб**._ + +> Если у нас уже есть архив, который нужно разбить, команда будет такой: +> +> split -b 100M root_home.tar.gz root_home.tar.gz +> +> _* в данном примере архив **root_home.tar.gz** будет разбит на части, состояние из файлов по 100 Мб._ + +В итоге мы получим, примерно, такую картину: + +root_home.tar.gzaa root_home.tar.gzac root_home.tar.gzae +root_home.tar.gzag root_home.tar.gzai root_home.tar.gzab +root_home.tar.gzad root_home.tar.gzaf root_home.tar.gzah + +Чтобы собрать архив и разархивировать его, вводим команду: + +cat root_home.tar.gz* | tar -zxv + +> Если мы хотим только собрать разбитый архив, вводим: +> +> cat root_home.tar.gz* > root_home.tar.gz + +### Создание tar из отдельных файлов с последующим его архивированием + +Мы можем собрать файл tar, добавляя по очереди в него файлы, после чего создать сжатый архив. Предположим, у нас 3 файла: file1, file2, file3. + +Сначала создадим tar-файл с первым файлом внутри: + +tar -cf ./my_archive.tar ./file1 + +Следующими двумя командами мы добавим в архив файлы file2 и file3: + +tar -rf ./my_archive.tar ./file2 + +tar -rf ./my_archive.tar ./file3 + +Сожмем содержимое tar-файла: + +gzip < ./my_archive.tar > ./my_archive.tar.gz + +Файл tar можно удалить: + +rm -f my_archive.tar + +### Рекурсивная распаковка + +Предположим, что у нас есть архив, в котором есть еще архив. И еще и еще много раз. Чтобы не заниматься ручной распаковкой, можно использовать команду с while: + +while [ -f *.tar.gz ]; do filename="$(ls -1 *.tar.gz | head -n 1)"; tar -xvf $filename && rm $filename; done; + +Обратите внимание, что данная команда удалит исходный файл архива, а также будет искать архив по маске, поэтому в каталог, где выполняем команду не стоит копировать другие архивы. + +## ZIP + +Tar не работает с zip-архивами. В системах UNIX для этого используем утилиты zip и unzip. Для начала, ставим нужные пакеты: + +yum install zip unzip + +apt-get install zip unzip + +pkg install zip unzip + +_* соответственно, для RPM based, deb based, BSD based._ + +Создание ZIP: + +zip -r archive.zip /home/dmosk + +_* создает архив каталога **/home/dmosk** в файл **archive.zip**._ + +Распаковка ZIP: + +unzip archive.zip
\ No newline at end of file diff --git a/LINUX/TERMINAL.md b/LINUX/TERMINAL.md new file mode 100644 index 0000000..d71aaa4 --- /dev/null +++ b/LINUX/TERMINAL.md @@ -0,0 +1,207 @@ + +### Смена редактора по умолчанию: +```sh +which vim +export EDITOR=/usr/bin/vim +``` +### Ускорить работу в терминале + +Используй !$ — это магия последнего аргумента предыдущей команды. + +`mkdir new_project +`cd !$ + +📌 Ещё пример: + +`curl https://example.com/file.zip +`wget !$ + +🔁 Работает с vim, cp, mv, tar и др. +💡 Экономит много времени! + + +### Горячие клавиши в терминале + +🌟 Ctrl + A — переход в начало строки +🌟Ctrl + E — переход в конец строки +🌟Ctrl + U — удаление от курсора до начала строки +🌟 Ctrl + K — удаление от курсора до конца строки +🌟Ctrl + W — удаление слова перед курсором +🌟Ctrl + L — очистить экран терминала +🌟Ctrl + C — остановить текущий процесс/команду +🌟 Ctrl + D — выход из системы +🌟 Ctrl + Z — поставить текущий процесс на паузу (в отличии от Ctrl + C, данный процесс можно будет продолжить) +🌟 Ctrl + R — поиск по истории команд +🌟 Стрелка вверх — показать предыдущую команду +🌟 Стрелка вниз — показать следующую команду +🌟 !! — повторить последнюю команду +🌟 !n — повторить n команду +🌟 Tab — автозаполнение команд, файлов и каталогов +🌟 Ctrl + Shift + C — копировать +🌟 Ctrl + Shift + V — вставить +🌟 Ctrl + Shift + N — открыть новое окно терминала +🌟 Ctrl + Shift + T — открыть новую вкладку в терминале +🌟 Ctrl + Tab — переключение между вкладками терминала + +## Управление окном эмулятора терминала + +Указанные ниже сочетания клавиш упростят вашу работу с терминалом GNOME и большинством других приложений эмуляторов терминала. + +- **Ctrl + Shift + Q** — закроет текущее окно терминала. +- **Ctrl + Shift + T** — откроет дополнительную вкладку терминала. +- **Ctrl + Shift + W** — закроет текущую вкладку. +- **Ctrl + Shift + D** — превратит вкладку в отдельное окно. +- **Ctrl + PgUp** — переместит на предыдущую вкладку. +- **Ctrl + PgDown** — переместит на следующую вкладку. + +## Переключение между tty-сессиями в терминале. + +В терминале Linux есть возможность переключаться между разными сессиями терминала, своеобразными «рабочими столами» в контексте командной строки. Для переключения между разными сессиями, например, когда кажется, что терминал завис, нажмите комбинацию: **Ctrl + Alt + f2**. Вы можете переключаться между шестью разными сессиями. Для переключения назад на первую tty-сессию терминала нажмите **Ctrl + Alt + f1**. + +Нажатие комбинации **Ctrl + Alt + f7**, в свою очередь, управляет отображением графической оболочки ОС. + +## Управление вводом: автодополнение команд и исправление опечаток + +### TAB + +Клавиша TAB — самая частая и основная в использовании среди всех горячих клавиш Linux. Данная клавиша поможет вам дополнить команду или путь к файлу или предложит варианты, если их несколько. +Для использования данного функционала начните набирать команду и нажмите TAB после введения нескольких символов. + +- Одно нажатие — дополняет команду. +- Два нажатия — предлагает несколько вариантов, если введенный текст имеет несколько вариантов продолжения. Например, двойное нажатие **Tab** поможет написать длинный путь к нужному файлу или директории. + +### Ctrl + Shift + «-» + +Сочетание клавиш **Ctrl Shift** и знака «минус» поможет вам отменить последний ввод в терминал. Команда очень полезна, если вы выбрали не тот вариант из меню предложенных в автодополнении или сделали опечатку. + +### Ctrl + T + +Это сочетание клавиш меняет местами два соседних символа и может быть очень полезна, если вы опечатались и написали слово неправильно. Например, используя данное сочетание клавиш, можно с легкостью исправить **selectle** на правильный вариант — **selectel**. + +### Alt + T + +Это сочетание клавиш уже посерьезнее, чем предыдущее и меняет местами целые слова. Например, с помощью этого сочетания, можно парой нажатий исправить **I Selectel Love** на правильный вариант — **I Love Selectel**. + +### Alt + Backspace и Alt + D + +Эти два сочетания помогут быстро удалить ненужные части введенных ранее команд. + +- **Alt + Backspace** поможет удалить слово, стоящее перед курсором (слева). +- **Alt + D удаляет** все символы, которые находится справа от курсора в текущей строке. + +## Управление курсором + +В какой-то момент стрелочек на клавиатуре будет недостаточно для быстрого перемещения по тексту, и вам понадобятся новые комбинации. Они позволят быстрее ориентироваться в тексте и набирать команды. + +- **Ctrl+A (Home)** совершит быстрый переход в начало строки. +- **Ctrl+E (End)** переместит курсор в конец строки текста. +- **Alt+B** переместит курсор назад (влево) на одно слово. +- **Ctrl+B** переместит курсор назад (влево) на один символ. +- **Alt+F** сдвинет курсор на слово вправо. +- **Ctrl+F** сдвинет курсор на слово вправо. +- **Ctrl+XX** мгновенно переместит курсор в начало текущей строки, выделив весь текст на своем пути. С помощью этого сочетания клавиш можно выделить строку или исправить что-то в ее начале. Второе нажатие комбинации переместит позицию курсора назад. + +## Работа с буфером обмена + +В терминале также есть возможность пользоваться буфером обмена: копировать в него текст, вырезать и вставлять в строку или в файл, открытый в терминале. + +### Ctrl + W + +Это сочетание клавиш поможет вырезать стоящее перед курсором слово и скопировать его в буфер обмена. + +### Ctrl + U + +Это сочетание вырежет и скопирует в буфер обмена текущую строку. + +### Ctrl + K + +Сочетание клавиш **Ctrl и K** вырежет строку, находящуюся справа от курсора. + +### Shift + Insert + +Данное сочетание клавиш нужно для того, чтобы вставить в терминал текст, находящийся в буфере обмена. + +### Ctrl + Y + +Вставит в поле ввода последний скопированный или вырезанный в буфер обмена текст. + +## Форматирование текста в терминале + +Следующий набор горячих клавиш сильно упростит работу с регистром символов, введенных в терминал. В Linux и UNIX-системах регистр играет важную роль, поэтому данные команды также будут очень полезны. + +### Alt + U + +После того, как вы нажмете это сочетание клавиш, все символы справа от курсора и до конца текущего слова сменят регистр на верхний. + +### Alt + L + +Сочетание **Alt + L** работает наоборот: все символы справа от курсора и до конца текущего слова сменят регистр на нижний. + +### Alt + C + +При активации этого сочетания клавиш символ, находящийся под курсором, изменит регистр с нижнего на верхний, а курсор переместится в конец слова. + +## Работа с историей bash + +Терминал хранит историю выполненных пользователем команд. Если вы хотите ее посмотреть, выполните следующую команду: + + `history` + +Эта команда не очень удобна, когда нужно быстро вспомнить ранее введенную команду и выполнить ее в командной оболочке. В этом нам помогут сочетания клавиш. + +### Ctrl + P + +Данное сочетание клавиш заменяет стрелку «вверх». Используйте его, чтобы переместиться к команде, выполненной ранее. Вы можете использовать данное сочетание неограниченное количество раз, каждый раз перемещаясь к более ранней команде. + +### **Ctrl + N** + +Это сочетание клавиш, в свою очередь, является альтернативой стрелке «вниз» и нужно, чтобы переместиться по истории в обратную сторону. + +### Ctrl + R + +После нажатия этого сочетания клавиш в терминале появится строка поиска, которая поможет найти и выполнить команду из истории команд, ранее запущенных через терминал. Для выполнения предложенной команды, просто нажмите **Enter,** а для редактирования предложенной команды или выхода из поиска — **Esc**. + +- **Ctrl + 0** сразу выполнит найденную команду без изменений. +- **Ctrl + G** завершит работу с инструментом поиска по истории без выполнения найденной команды. + +### Alt + R + +Если вы нашли в истории команду, уже отредактировали и хотите отменить изменения, нажмите это сочетание клавиш. Команда вернется к первоначальному виду. + +## Управление процессами + +В терминале Linux также есть горячие клавиши для легкого управления запущенными в терминале процессами. + +### Ctrl + C + +Данное сочетание клавиш необходимо знать любому пользователю терминала. С его помощью можно завершить процесс, выполняющийся на данный момент в терминале. Это сочетание клавиш мгновенно отправит программе сигнал **SIGINT**, и если программа не завершит работу должным образом, он будет принудительно остановлен. Иными словами, эта комбинация убивает текущий процесс. + +### Ctrl + D + +Сочетание **Ctrl + D** завершает работу терминала, посылая в терминал запрос EOF или «конец файла». После получения данного запроса, работа терминала завершается. Если у вас в терминале открыто активное ssh подключение, то комбинация **Ctrl + D** разъединит вас с удаленным хостом. Это сочетание клавиш является альтернативой команды **exit**. + +### Ctrl + Z + +Сочетание **Ctrl + Z** пошлет текущему процессу запрос **SIGSTP** и приостановит его выполнение до востребования. Запустить приостановленный процесс можно с помощью следующей команды: + +`fg` + +Посмотреть список приостановленных процессов вы можете, выполнив команду: + +`jobs` + +## Сочетания клавиш для управления отображением + +При помощи сочетаний клавиш, приведенных ниже, вы сможете легко управлять количеством информации, находящейся в окне терминала. + +### Ctrl + L + +Это сочетание — альтернатива команде clear. После нажатия Ctrl + L терминал очистится от результатов выполнения предыдущих команд. + +### Ctrl + S + +Сочетание клавиш **Ctrl + S** остановит вывод в терминал. Команда не прекратит выполняться, но вывод данных непосредственно в терминал будет приостановлен. + +### Ctrl + Q + +Нажатие этой горячей клавиши снимет вывод в терминал с паузы, на которую вы его поставили нажатием клавиши **Ctrl + S**.
\ No newline at end of file diff --git a/LINUX/TIMEDATE, CHRONY, NTP.md b/LINUX/TIMEDATE, CHRONY, NTP.md new file mode 100644 index 0000000..606238b --- /dev/null +++ b/LINUX/TIMEDATE, CHRONY, NTP.md @@ -0,0 +1,48 @@ +```sh +# TIMEDATE + +timedatectl status +timedatectl list-timezones +sudo set timedatectl set-timezone Europe/Moscow + +========================================== +# CHRONY + +sudo apt install chrony +sudo systemctl enable chrony +sudo systemctl start chrony + +sudo timedatectl set-timezone Europe-Moscow + +date +Mon Dec 6 07:13:49 UTC 2021 + +========================================== +# NTP + +sudo apt install ntp +sntp --version +sudo vim /etc/ntp.conf +``` + +![[Снимок экрана 2023-10-29 в 14.23.34.png]] +Замените на свои. + +```sh +sudo service ntp restart +sudo service ntp status +sudo ufw allow from any port 123 proto udp + +sudo apt install ntpdate +sudo vim /etc/hosts +sudo ntpdate NTP-server-host +sudo timedatectl set-ntp off +sudo apt install ntp +sudo vim /etc/ntp.conf + +# добавьте: +server NTP-server=host prefer iburst` +sudo service ntp restart` +ntpq -ps` +``` + diff --git a/LINUX/TLDR.md b/LINUX/TLDR.md new file mode 100644 index 0000000..5c05592 --- /dev/null +++ b/LINUX/TLDR.md @@ -0,0 +1,12 @@ +**tldr вместо man: как быстро найти примеры использования команды** + +Вместо того, чтобы блуждать по длинным описаниям команд в man, можем использовать tldr. Эта утилита предоставляет краткие и понятные примеры команд. + +✔️ **Установка** +`sudo apt install tldr # Для Ubuntu/Debian +`sudo pacman -S tldr # Для Arch + +⚙️ **Пример использования** +`tldr tar + +В результате получите наглядные примеры: как распаковать архив, добавить файлы и так далее...
\ No newline at end of file diff --git a/LINUX/TMUX.md b/LINUX/TMUX.md new file mode 100644 index 0000000..6a640e1 --- /dev/null +++ b/LINUX/TMUX.md @@ -0,0 +1,58 @@ +```sh +cd +git clone https://github.com/gpakosz/.tmux.git +ln -s -f .tmux/.tmux.conf +cp .tmux/.tmux.conf.local . +tmux new-session -s name +``` + +**tmux** **команда** **опции** +_**attach-session**_ (attach) - подключиться к уже существующей сессии. В параметре необходимо передать опцию -t и идентификатор сессии; +tmux attach -t 0 +_**detach-session**_ (detach) - отключить всех клиентов (или переданного с помощью опции -t) от сессии, переданной в опции -s; +_**has-session**_ - проверить существует ли сессия, аналогично, надо передать идентификатор сессии; +_**kill-server**_ - остановить все запущенные сессии; +_**kill-session**_ - завершить сессию переданную в параметре -t; +_**list-clients**_ - посмотреть клиентов, подключенных к сессии -t; +_**list-sessions**_ (ls) - вывести список всех запущенных сессий; +_**new-session**_ - создать новую сессию, можно передать имя сессии в опции -s и стартовую директорию в опции -c; +_**rename-session**_ - переименовать сессию, нужно передать идентификатор сессии и новое имя; + +**Ctrl+b c** - создать новое окно; +**Ctrl+b w** - выбрать окно из списка; +**Ctrl+b 0-9** - открыть окно по его номеру; +**Ctrl+b ,** - переименовать текущее окно; +**Ctrl+b %** - разделить текущую панель по горизонтали; +**Ctrl+b "** - разделить текущую панель по вертикали; +**Ctrl+b стрелка** - перейти на панель, находящуюся в стороне, куда указывает стрелка; +**Ctrl+b Ctrl+стрелка** - изменить размер текущей панели; +**Ctrl+b o** - перейти на следующую панель; +**Ctrl+b ;** - переключаться между текущей и предыдущей панелью; +**Ctrl+b x** - закрыть текущую панель; +**Ctrl+b \[** - войти в режим копирования (подробнее ниже); +**Ctrl+b ]** - вставить из внутреннего буфера обмена tmux; +**Ctrl+b d** - отключится от текущей сессии; +**Ctrl+b :** - открыть командную строку. +**Ctrl+b "** - деление окна горизонтально +**Ctrl+b %** - деление окна + + + ```sh +sudo nvim ~/.tmux.conf + +# Установка двух префиксов: от 'Ctrl+b' до 'Alt+b'**_ +set-option -g prefix M-b +set-option -g prefix2 C-b + +# для включения режима мыши (скролл, изменение размера панели, выбор панели и др.) +set -g mouse on + +tmux_conf_copy_to_os_clipboard=true + +tmux source ~/.tmux.conf # -перезапуск + + +tmux new -s name -d # запускаем и отцепляемся от сессии +tmux split-window -v -t name:0.0 # разбиваем вертикально на 2 окна +tmux split-window -h -t name:0.1 # разбиваем горизонтально на 2 окна +``` diff --git a/LINUX/TRANSMISSION.md b/LINUX/TRANSMISSION.md new file mode 100644 index 0000000..9867465 --- /dev/null +++ b/LINUX/TRANSMISSION.md @@ -0,0 +1,30 @@ +`sudo apt install transmission-daemon` + + +**# остановить сервис** + +`sudo systemctl stop transmission-daemon.service` + +**# проверить состояние** + +`sudo systemctl status transmission-daemon.service` + + +_файл инициализирующий старт демона:_ + +`/etc/init.d/transmission-daemon` + + +_Файл конфигурации:_ + +`/etc/default/transmission-daemon` + + +_Файл global settings:_ + +`/etc/transmission-daemon/settings.json` + + +_Файл local settings:_ + +`~/.config/transmission-daemon/settings.json` diff --git a/LINUX/UFW.md b/LINUX/UFW.md new file mode 100644 index 0000000..af92d4e --- /dev/null +++ b/LINUX/UFW.md @@ -0,0 +1,165 @@ +```sh +sudo su + +ufw status + +ufw enable + +ufw app list +ufw allow OpenSSH + +# Чтобы увидеть, что в настоящее время заблокировано или разрешено, вы можете использовать параметр **verbose** при запуске **ufw status**, как показано ниже: + +sudo ufw status verbose + +sudo ufw disable + +# Блокировка обращения IP-адреса +# Чтобы заблокировать все сетевые подключения, исходящие с определенного IP-адреса, выполните следующую команду, заменив выделенный IP-адрес на IP-адрес, который вы хотите заблокировать: + +sudo ufw deny from 91.198.174.190 + +# Заблокировать подсеть +# Если вам нужно заблокировать всю подсеть, вы можете использовать адрес подсети в качестве параметра «from» в команде ufw deny. Это заблокирует все IP-адреса в приведенной в примере подсети 91.198.174.0/24: + +sudo ufw deny from 91.198.174.0/24 + +# Блокирование входящих подключений к сетевому интерфейсу +# Чтобы заблокировать входящие подключения с определенного IP-адреса к определенному сетевому интерфейсу, выполните следующую команду, заменив IP-адрес на IP-адрес, который вы хотите заблокировать: + +sudo ufw deny in on eth0 from 91.198.174.192 + +# Параметр «in» указывает брандмауэру применять правило только для входящих соединений, а параметр «on eth0» указывает, что правило применяется только для интерфейса eth0. +# Это может быть полезно, если у вас есть система с несколькими сетевыми интерфейсами (включая виртуальные), и вам нужно заблокировать внешний доступ к некоторым из этих интерфейсов, но не ко всем. + +# Разрешить обращения IP-адреса +# Чтобы разрешить все сетевые подключения, исходящие с определенного IP-адреса, выполните следующую команду, заменив выделенный IP-адрес на IP-адрес, к которому вы хотите разрешить доступ: + +sudo ufw allow from 91.198.174.192 + +# Если теперь вы запустите «sudo ufw status», вы увидите вывод, похожий на этот, с надписью ALLOW рядом с IP-адресом, который вы только что добавили: + +# Разрешение входящих подключений к сетевому интерфейсу +# Чтобы разрешить входящие подключения с определенного IP-адреса к определенному сетевому интерфейсу, выполните следующую команду, заменив IP-адрес на тот, который вы хотите разрешить: + +sudo ufw allow in on eth0 from 91.198.174.22 + +# Параметр «in» указывает брандмауэру применять правило только для входящих соединений, а параметр «on eth0» указывает, что правило применяется только для интерфейса «eth0». + +# Удалить правило брандмауэра +# Чтобы удалить правило, ранее установленное в UFW, используйте **ufw delete**, затем введите правило (allow / deny) и целевую спецификацию. В следующем примере удаляется правило, ранее установленное для разрешения всех подключений с IP-адреса 91.198.174.192: + +sudo ufw delete allow from 91.198.174.192 + +# Другой способ указать, какое правило вы хотите удалить, – это указать ID правила. Эту информацию можно получить с помощью следующей команды: + +sudo ufw status numbered + +sudo ufw delete 1 + +# Список доступных профилей приложений +# При установке приложения, которые полагаются на сетевые коммуникации, обычно устанавливают профиль брандмауэра, который можно использовать для разрешения подключения с внешних адресов. Это часто то же самое, что запустить «ufw allow from», с тем преимуществом, что это ярлык, который абстрагирует конкретные номера портов, используемые службой, и обеспечивает удобную номенклатуру для ссылающихся служб. + +sudo ufw app list + +# Если вы установили службу, например, веб-сервер или другое сетезависимое программное обеспечение, и профиль не был доступен в брандмауэре, сначала убедитесь, что служба включена. + +# В следующем примере мы включаем профиль OpenSSH, который разрешит все входящие SSH-соединения на стандартном порту SSH. + +sudo ufw allow OpenSSH + +# Отключить профиль для определенного приложения +# Чтобы отключить профиль приложения, который вы ранее настроили в брандмауэре, вам нужно удалить соответствующее правило. + +sudo ufw status + +# Этот вывод указывает на то, что профиль приложения «Nginx Full» в настоящее время включен, разрешая любые соединения с веб-сервером как через HTTP[](https://serverspace.ru/support/glossary/http/), так и через HTTPS. + +# Если вы хотите разрешить только HTTPS-запросы к вашему веб-серверу, вам нужно сначала включить наиболее ограничивающее правило, которым в данном случае будет «Nginx HTTPS», а затем отключить активное правило «Nginx Full»: + +sudo ufw allow "Nginx HTTPS" sudo ufw delete allow "Nginx Full" + +# Помните, что вы можете перечислить все доступные профили приложений с помощью: + +sudo ufw app list + + + +ip addr show +hostname -I + +curl -4 icanhazip.com +``` +===================================================== +### Шпаргалка по ufw для базовой настройки фаервола + +Сетевой безопасностью часто пренебрегают при создании своих проектов на своем сервере. Но даже базовая настройка фаервола уже существенно снижает риски. + +`ufw (Uncomplicated Firewall)` - простой способ настроить фаервол на Linux. Работает как обёртка над iptables, но не требует глубоких знаний сетевого стека. + +Зачем вообще нужен фаервол? + +Когда ты поднимаешь сервер (особенно публичный), он сразу становится видимым всему интернету. И даже если ты не настраиваешь ничего «опасного», какие-то сервисы по умолчанию могут слушать порты. + +ufw помогает ограничить доступ: только те порты, которые ты явно разрешил - будут открыты. Если ты только поднял VPS и хочешь защитить сервер - это именно то, что нужно. Если по какой-то причине у тебя ufw не установлен: + +`sudo apt install ufw + +Перед включением стоит задать основные правила, иначе можно случайно отрезать себя от SSH-доступа (особенно на VPS). Начнем с самого важного: + +`sudo ufw allow ssh + +Теперь можно активировать фаервол: + +`sudo ufw enable + +Просмотр статуса и активных правил: + +`sudo ufw status verbose + +Отключение при необходимости: + +`sudo ufw disable + +Базовые правила, которые стоить задать + +Одно правило по разрешению ssh-доступа мы уже добавили, также будет правильно разрешить веб-траффик (http/https): + + +$ sudo ufw allow 80,443/tcp + + +Полезно запретить весь входящий трафик по умолчанию и разрешить только нужное. Также оставить открытым все на выход: + +`sudo ufw default deny incoming + +`sudo ufw default allow outgoing + +#### Полезные команды: + +**Удалить правило: + +`sudo ufw delete allow 80/tcp + +**Разрешить доступ по конкретному IP: + +sudo ufw allow from 192.168.1.10 + +Это может понадобиться, например, чтобы открыть порты для веб-траффика только Cloudflare или ServicePipe и закрыть для всего остального: + +`ufw default deny incoming +`ufw default allow outgoing + +предположим что 192.168.1.10 - IP нашего ServicePipe + +`sudo ufw allow from 192.168.1.10 to any port 22 + +Сбросить все правила и начать с нуля: + +`sudo ufw reset + +После настройки рекомендую протестировать сервер из другого устройства или использовать онлайн-сервисы вроде portquiz или nmap для сканирования портов. Также полезно мониторить логи: + +`sudo less /var/log/ufw.log + +Если `ufw` работает - ты увидишь записи о заблокированных соединениях, особенно на нестандартные порты. Даже минимальные настройки ufw — это уже огромный шаг вперёд в безопасности. Он не требует глубокого понимания iptables и легко вписывается в ежедневный рабочий процесс. Один раз настроил — и забыл.
\ No newline at end of file diff --git a/LINUX/USER.md b/LINUX/USER.md new file mode 100644 index 0000000..62597a2 --- /dev/null +++ b/LINUX/USER.md @@ -0,0 +1,137 @@ +```sh +# Создаем пользователя и добавляем в SUDO: +sudo adduser vlapa +sudo usermod -aG sudo vlapa + +# Блокируем ROOT: +sudo passwd -l root + +# Переключаемся на VLAPA и проверяем: +su - vlapa +sudo whoami # должно вывести "root" (работает через sudo) +sudo su - # должно запросить ваш пароль, а не root + +# Запрещаем вход по SSH: +sudo vim /etc/ssh/sshd_config + +# Найдите строку PermitRootLogin и установите: + +text + +PermitRootLogin no + +# Затем перезапустите SSH: +sudo systemctl restart sshd + +# ## Что делать, если root уже активирован? +sudo passwd -l root # блокируем пароль +sudo usermod -s /bin/bash root # возвращаем shell (на всякий случай) + +# После этого войти как root будет невозможно. Все административные задачи будут выполняться через `sudo` от вашего пользователя. + +#========================================= +# Заблокировать аккаунт пользователя: +passwd -l имя_пользователя + +# Проверить статус аккаунта: +passwd -S имя_пользователя + +# Разблокировать пользователя: +passwd -u имя_пользователя + +#========================================= +# Создание пользователя с привилегиями Sudo +sudo adduser *имя_пользователя* +sudo adduser *имя_пользователя* sudo +su - *имя_пользователя* + +# ======================================== +# - root - правильное переключение sudo su - +sudo su + +# - НЕ интерактивная (лучше создании пользователя из скрипта), НЕ создаст пользователю домашний каталог и НЕ придумает пароль если не использовать дополнительные опции команды. Зато создаст одноименную группу пользователя и включит этого пользователя в неё. + +useradd # -не задает вопросы, а создает пользователя используя опции +-m # — создать домашний каталог пользователю; +-p <пароль> # — здесь нужно ввести хешированный пароль, что не всегда удобно, поэтому не стоит использовать эту опцию; +-s <путь_к_оболочке> # — указать командную оболочку используемую по умолчанию для пользователя (сейчас мы работает в оболочке bash, путь к ней — /bin/bash); + +# - интерактивная (лучше при ручном создании пользователя) утилита у нас запросит пароль для нового пользователя, создаст одноименную группу для пользователя, также мы можем ввести дополнительную информацию о пользователе: полное имя, номер комнаты, рабочий телефон, домашний телефон. +adduser +—no-create-home # — не создавать домашний каталог пользователю; +—shell <Оболочка> # — задать оболочку для пользователя, по умолчанию задается оболочка bash; + +userdel +-f # — завершить все процессы пользователя и удалить насильно, даже если пользователь сейчас работает в системе; +-r # — удалить домашний каталог пользователя; + +deluser +—remove-home # — удалить домашний каталог пользователя; +—force # — завершить все процессы пользователя и удалить насильно, даже если пользователь сейчас работает в системе; +``` + +#### Как создать Sudo пользователя в Ubuntu 22.04 LTS + +[Подписывайтесь на наш канал в Telegram](https://tx.me/amur_pro) + +В Ubuntu-системе, да и в принципе, в любой другой Linux-системе, обязательно есть один привилегированный пользователь — **root**. + +**sudo** — утилита, которая дает привилегии root для выполнения административных операций. + +Простым языком команда sudo позволяет пользователям Ubuntu выполнять команды от имени суперпользователя root, либо других пользователей. + +## Создание пользователя с привилегиями Sudo + +Сперва, при помощи команды «**adduser**» создаем нового пользователя. + +sudo adduser *имя_пользователя* + +При создании пользователя будет предложено ввести пароль и другие необязательные данные (Имя, Номер комнаты, Телефон и тд), указываем их по своему желанию. + + + +Обращаем внимание, что только суперпользователь с правами root имеет право добавлять новых пользователей в Ubuntu. Следовательно, запускаем команду adduser с sudo. + +## Добавляем пользователя в группу Sudo + +Следующим шагом является **добавление созданного пользователя в группу sudo**, которая выдаст пользователю все привилегии sudo: + +sudo adduser *имя_пользователя* sudo + +При успешном добавлении в группу Sudo получим уведомление в терминале Ubuntu: «**Добавление пользователя *имя* в группу sudo. Готово.**». + + + +Отлично! Пользователь добавлен в группу sudo, теперь проверим, есть ли у него привилегии sudo. + +## Тестирование пользователя Sudo + +Переключаемся на вновь созданного пользователя (пользователь с именем admin в нашем случае) при помощи команды «**su**»: + +su - *имя_пользователя* + +После того, как мы войдем в систему под новым пользователем, мы получим сообщение, как правильно использовать команду «**sudo**» — «**To run a command as administrator (user *root*), use *sudo command***». + + + +Данное уведомление в терминале будет означает, что пользователь находится в группе sudo. + +================================== +```sh +groups +groups plex + +``` + +================================== +▪**Блокировка учетной записи пользователя:** + +`passwd -l user_name + +▪**Проверьте статус акаунта:** + +`passwd -S user_name + +▪**Разблокировать пользователя:** + +`passwd -u user_name
\ No newline at end of file diff --git a/LINUX/VSFTPD.md b/LINUX/VSFTPD.md new file mode 100644 index 0000000..d2e80ed --- /dev/null +++ b/LINUX/VSFTPD.md @@ -0,0 +1,367 @@ +## Обновление системных пакетов + +Перед установкой любого нового ПО рекомендуем обновлять системные пакеты до актуальных версий. Рассмотрим, какие команды для этого следует выполнить. + +1. Обновление списка доступных пакетов и их версий: + + `sudo apt update` + +2. Обновление установленных пакетов до последних версий: + + `sudo apt upgrade -y` + +Теперь система готова к установке FTP-сервера. + +## Выбор FTP-сервера на Ubuntu + +FTP-серверы широко используют для обмена файлами, так как FTP-протокол позволяет одновременно передавать большие объемы данных, например резервные копии. Быстрое копирование файлов на FTP-сервер достигается благодаря передаче данных потоком. + +Существует несколько популярных FTP-серверов для Linux. Рассмотрим некоторые из них. + +**VSFTPD (Very Secure FTP Daemon)** + +Преимущества: высокий уровень безопасности, отличная производительность, поддержка виртуальных пользователей, возможность ограничения доступа к домашним директориям. + +Недостатки: для обеспечения максимальной безопасности нужна детальная настройка. + +**ProFTPD (Professional FTP Daemon)** + +Преимущества: гибкие настройки, поддержка модулей, возможность использования конфигурации, похожей на Apache. + +Недостатки: более длительная настройка по сравнению с VSFTPD, меньшая производительность при высокой нагрузке. + +**Pure-FTPd** + +Преимущества: легкость установки и использования, высокая производительность, поддержка виртуальных пользователей. + +Недостатки: меньшее количество настроек безопасности по сравнению с VSFTPD и ProFTPD. + +### Что выбрали + +Для демонстрации установки и настройки будем использовать VSFTPD по нескольким причинам. + +- Низкий ресурсный overhead. VSFTPD потребляет минимум ресурсов, что делает его идеальным для систем с ограниченными возможностями. +- Высокая скорость передачи данных. Обеспечивает отличную производительность при передаче файлов, что важно для высоконагруженных серверов. +- Встроенная поддержка шифрования для безопасной передачи данных — SSL/TLS. +- Многоплатформенность. Работает на большинстве UNIX-подобных систем. +- Простота интеграции с системами безопасности. Легко интегрируется с PAM и другими системами аутентификации для гибкого управления доступом. + +[ + +## Создайте FTP-сервер на мощностях Selectel + +Более 100 фиксированных конфигураций с готовностью — 2 минуты. + +](https://selectel.ru/services/cloud/servers/ftp/) + +## Установка FTP-сервера на Ubuntu + +### Установка VSFTPD + +VSFTPD прост в установке — он доступен в стандартных репозиториях Ubuntu. Откроем терминал и выполним следующую команду: + + `sudo apt install vsftpd -y` + +### Запуск и настройка автозагрузки VSFTPD + +После установки запустим VSFTPD: + + `sudo systemctl start vsftpd` + +Совет: для настройки автозагрузки VSFTPD при запуске системы выполните команду sudo systemctl enable vsftpd. + +### Проверка состояния VSFTPD + +После запуска службы проверим ее состояние: + + `sudo systemctl status vsftpd` + +В выводе отобразится текущий статус службы VSFTPD. Если она запущена корректно, мы увидим сообщение, что VSFTPD активен и работает (Active: active (running)). + +Пример вывода sudo systemctl status vsftpd: + + `● vsftpd.service - vsftpd FTP server Loaded: loaded (/lib/systemd/system/vsftpd.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2024-06-23 14:35:26 UTC; 2h 15min ago Main PID: 12345 (vsftpd) Tasks: 1 (limit: 4915) Memory: 1.3M CGroup: /system.slice/vsftpd.service └─12345 /usr/sbin/vsftpd /etc/vsftpd.conf` + +### Настройка файрвола для FTP + +Если на сервере включен файрвол, следует открыть порты для FTP, чтобы обеспечить доступ к серверу. + +В примере используем UFW (Uncomplicated Firewall). Команды далее могут отличаться в зависимости от вашего брандмауэра. + + +1. По умолчанию FTP использует порты 20 и 21 (TCP-трафик), а также диапазон 30000-31000 (пассивный режим FTP). Чтобы открыть их, выполним команды: + + `sudo ufw allow 20/tcp sudo ufw allow 21/tcp sudo ufw allow 30000:31000/tcp` + +2. Перезапустим файрвол для применения изменений: + + `sudo ufw reload` + +3. Убедимся, что правила файрвола применены корректно: + + `sudo ufw status` + +Пример вывода sudo ufw status: + + `Status: active To Action From -- ------ ---- 20/tcp ALLOW Anywhere 21/tcp ALLOW Anywhere 30000:31000/tcp ALLOW Anywhere 22/tcp ALLOW Anywhere` + +## Настройка FTP-сервера в Ubuntu + +### Основная настройка + +Конфигурационный файл VSFTPD находится в директории /etc/vsftpd.conf. Чтобы его отредактировать, можно воспользоваться любым текстовым редактором, например nano: + + `sudo nano /etc/vsftpd.conf` + +Для обеспечения безопасности и эффективности работы сервера изменим конфигурационный файл VSFTPD в соответствии с настройками. + +1. Отключим анонимный доступ, заменив значение параметра anonymous_enable на NO: + + `anonymous_enable=NO` + +2. Для разрешения входа на сервер локальным пользователям установим значение параметра local_enable на YES: + + `local_enable=YES` + +3. Разрешим команды записи. Чтобы пользователи могли загружать файлы на сервер, установим значение параметра write_enable на YES: + + `write_enable=YES` + +4. Настроим права доступа для новых файлов, создаваемых пользователями. Их автоматическое назначение осуществляется с помощью параметра local_umask. Значение 022 позволяет владельцу файла записывать, читать и выполнять, а всем остальным — читать и выполнять: + + `local_umask=022` + +Набор прав состоит из трех цифр: + +- первая — права владельца, +- вторая — права группы владельца, +- третья — права остальных пользователей. + +Каждая из них может принимать значения: + +- 0 — нет прав; +- 1 — выполнение; +- 2 — запись; +- 3 — запись и выполнение; +- 4 — чтение; +- 5 — чтение и выполнение; +- 6 — чтение и запись; +- 7 — чтение, запись и выполнение. + +Новые файлы и директории будут создаваться с правами, которые исходят из полного набора (777) за вычетом значений local_umask. Например, маска 022 означает набор прав 755. + +5. Включим отображение приветственных сообщений при входе в директории: + + `dirmessage_enable=YES` + +Для настройки текста сообщений следует перейти в интересующую директорию и создать в ней файл .message. Когда пользователь будет переходить в данную директорию, он будет получать сообщение, текст которого указан в файле. + +6. Для корректной работы файрвола установим использование порта 20 для передачи данных: + + `connect_from_port_20=YES` + +7. Чтобы ограничить пользователей их домашними директориями, установим значение параметра chroot_local_user на YES: + + `chroot_local_user=YES allow_writeable_chroot=YES` + +Параметр allow_writeable_chroot используется, чтобы разрешить запись в домашнюю директорию пользователя при использовании chroot_local_user=YES. + +8. Зададим корневую директорию для каждого пользователя: + + `user_sub_token=$USER local_root=/home/$USER/ftp` + +9. После внесения всех изменений сохраним файл (**Ctrl+O** в nano), закроем редактор (**Ctrl+X** в nano) и перезапустим службу VSFTPD для применения изменений: + + `sudo systemctl restart vsftpd` + +### Тестирование VSFTPD + +1. Проверим корректность применения настроек. Для начала создадим тестового пользователя: + + `sudo adduser testuser sudo passwd testuser` + +2. Создадим необходимые директории и настроим на них права: + + `sudo mkdir -p /home/testuser/ftp/upload sudo chown nobody:nogroup /home/testuser/ftp sudo chmod a-w /home/testuser/ftp sudo chown -R testuser:testuser /home/testuser/ftp/upload` + +3. Добавим пользователя в список пользователей VSFTPD: + + `echo "testuser" | sudo tee -a /etc/vsftpd.userlist` + +4. Подключимся к серверу с учетными данными тестового пользователя и убедимся в возможности загружать файлы в директорию /upload. Можно использовать любой FTP-клиент, в рамках инструкции будем пользоваться FileZilla. + +### Настройка часового пояса + +Чтобы сервер использовал локальное время, откроем конфигурационный файл VSFTPD и установим параметр use_localtime на YES: + + `use_localtime=YES` + +### Настройка логов + +Для настройки логирования перейдем в конфигурационный файл VSFTPD и включим логирование всех FTP-транзакций: + + `xferlog_enable=YES xferlog_std_format=YES` + +Параметр xferlog_std_format позволяет использовать стандартный формат логов. + +Также следует указать корректный путь к файлу логов (обычно /var/log/vsftpd.log): + + `xferlog_file=/var/log/vsftpd.log` + +### Настройка PAM-сервиса + +PAM (Pluggable Authentication Modules) — это система аутентификации, используемая в UNIX-подобных ОС, включая Linux. PAM позволяет администраторам настраивать политику аутентификации для различных приложений и служб. Для этого используется набор модулей, которые могут быть включены и настроены по мере необходимости. + +Для использования PAM необходимо предварительно включить его в файле /etc/vsftpd.conf. + +1. Откроем файл /etc/vsftpd.conf: + + `sudo nano /etc/vsftpd.conf` + +2. Отредактируем строку pam_service_name: + + `pam_service_name=vsftpd` + +Для дальнейшей настройки сервиса PAM в VSFTPD следует внести изменения в файл /etc/pam.d/vsftpd. Внутри него — инструкции, которые PAM использует для управления аутентификацией пользователей при подключении к FTP-серверу. + +3. Откроем файл конфигурации PAM для VSFTPD: + + `sudo nano /etc/pam.d/vsftpd` + +4. Настроим параметры аутентификации в соответствии со списком: + + `auth required pam_listfile.so item=user sense=deny file=/etc/vsftpd.ftpusers onerr=succeed auth required pam_shells.so auth include common-auth account include common-account session include common-session` + +- auth required pam_listfile.so item=user sense=deny file=/etc/vsftpd.ftpusers onerr=succeed — отказывает в доступе пользователям из файла /etc/vsftpd.ftpusers. +- auth required pam_shells.so — проверяет, что оболочка пользователя разрешена. +- auth include common-auth — включает общие правила аутентификации. +- account include common-account — включает общие правила управления учетными записями. +- session include common-session — включает общие правила управления сеансами. + +5. Создадим файл списка пользователей: + + `sudo nano /etc/vsftpd.ftpusers` + +Добавим имена пользователей, которым следует запретить доступ (по одному на строку). + +6. Перезапустим службу VSFTPD: + + `sudo systemctl restart vsftpd` + +## Создание пользователей + +### Создание + +Для создания нового пользователя на сервере выполним команду: + + `sudo adduser ftpuser` + +Далее следует ввести пароль для нового пользователя и несколько дополнительных данных (имя, номер телефона и прочее). Можно оставить эти поля пустыми, нажав **Enter**. + +Для назначения или изменения пароля пользователя нужно выполнить команду sudo passwd ftpuser, ввести новый пароль и подтвердить его. + +### Настройка домашних папок + +1. Создадим домашнюю папку пользователя: + + `sudo mkdir -p /home/ftpuser/ftp` + +2. Домашний каталог пользователя должен быть защищен от записи, чтобы соблюсти требования безопасности VSFTPD: + + `sudo chown nobody:nogroup /home/ftpuser/ftp sudo chmod a-w /home/ftpuser/ftp` + +3. Далее создадим папку, в которую пользователь сможет загружать файлы, и настроим права доступа: + + `sudo mkdir -p /home/ftpuser/ftp/upload sudo chown -R ftpuser:ftpuser /home/ftpuser/ftp/upload sudo chmod -R 0755 /home/ftpuser/ftp/upload` + +### Настройка анонимного или только авторизованного доступа + +VSFTPD поддерживает доступ как анонимных, так и авторизованных пользователей. Для настройки политики следует внести изменения в конфигурационный файл. + +#### Анонимный доступ + +Если нам нужно разрешить анонимный доступ к FTP-серверу, следует указать для параметра anonymous_enable значение YES в конфигурационном файле /etc/vsftpd.conf: + + `anonymous_enable=YES` + +#### Авторизованный доступ + +Чтобы разрешить доступ к FTP-серверу авторизованным пользователям, укажем для параметра local_enable значение YES: + + `local_enable=YES` + +### Разрешение / запрет доступа по SSH + +Все настройки доступа к серверу по SSH находятся в конфигурационном файле /etc/ssh/sshd_config. В нем описываются разрешающие и запрещающие правила. + +Для разрешения или запрета доступа к серверу по SSH группе пользователей следует указать в конфигурационном файле одну из следующих строк соответственно: + + `AllowGroups ftpusers` + + `DenyGroups ftpusers` + +Чтобы разрешить или запретить доступ для отдельных пользователей, следует использовать следующие параметры: + + `AllowUsers ftpuser` + + `DenyUsers ftpuser` + +Группа ftpusers и пользователь ftpuser указаны в качестве примера. + +Для применения новых правил перезапустим службу SSH: + + `sudo systemctl restart ssh` + +Порядок правил, указанных в конфигурационном файле SSH, имеет важное значение в определении политики доступа пользователя. Например, первым указано правило, запрещающее пользователям группы ftpusers доступ по SSH, а затем указано правило, разрешающее доступ пользователю ftpuser1 (входящему в группу ftpusers). + +В таком случае у пользователя ftpuser1 будет доступ к серверу по SSH. При установке этих правил в обратном порядке, пользователь ftpuser1 не получит доступ по SSH. + +## Настройка SSL/TLS + +Обеспечение безопасности FTP-соединений может обеспечить [SSL/TLS-протокол](https://selectel.ru/blog/what-is-ssl/). + +1. Создадим директорию, в которой будут храниться SSL-сертификаты и ключи: + + `sudo mkdir /etc/ssl/private` + +2. Создадим самоподписанный сертификат: + + `sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/ssl/private/vsftpd.key -out /etc/ssl/private/vsftpd.pem` + +Ключ будет действителен в течение 10 лет. + +В процессе генерации будет предложено ввести информацию о сертификате: страну, город, организацию и т. д. Можно оставить некоторые поля пустыми, нажав **Enter**. + +3. Для использования созданного сертификата откроем конфигурационный файл VSFTPD: + + `sudo nano /etc/vsftpd.conf` + +4. Внесем в него изменения в соответствии со следующим списком: + + `rsa_cert_file=/etc/ssl/private/vsftpd.pem rsa_private_key_file=/etc/ssl/private/vsftpd.key ssl_enable=YES allow_anon_ssl=NO force_local_data_ssl=YES force_local_logins_ssl=YES ssl_tlsv1=YES ssl_sslv2=NO ssl_sslv3=NO require_ssl_reuse=NO ssl_ciphers=HIGH` + +- rsa_cert_file и rsa_private_key_file — указывают путь к сертификату и приватному ключу. +- ssl_enable=YES — включает SSL. +- allow_anon_ssl=NO — запрещает анонимный доступ через SSL. +- force_local_data_ssl=YES и force_local_logins_ssl=YES — требуют использование SSL для всех данных и логинов локальных пользователей. +- ssl_tlsv1=YES, ssl_sslv2=NO, ssl_sslv3=NO — включают TLSv1 и отключают устаревшие SSLv2 и SSLv3. +- require_ssl_reuse=NO — отключает требование повторного использования SSL-сессий. +- ssl_ciphers=HIGH — устанавливает использование только сильных шифров. + +5. После внесения изменений сохраним файл и перезапустим службу VSFTPD: + + `sudo systemctl restart vsftpd` + +При установке SSL/TLS-сертификатов подключение через FTP-клиенты осуществляется стандартным путем. + +## Проверка подключения к серверу через FTP-клиент + +Теперь FTP-сервер настроен и готов принимать безопасные соединения от клиентов. + +Популярные программы, позволяющие подключаться к FTP-серверам, — FileZilla, WinSCP, FAR Manager, Total Commander, Midnight Commander и Cyberduck. Далее будем использовать наиболее известный кроссплатформенный FTP-клиент — FileZilla. + +Для его установки в Ubuntu выполним команду: + + `sudo apt-get install filezilla` + +Для подключения к FTP-серверу следует заполнить поля **Хост** (IP-адрес сервера), **Имя пользователя**, **Пароль** и нажать на кнопку **Быстрое соединение**. После подключения к FTP-серверу в правой части окна появится список директорий, доступных для данного пользователя.
\ No newline at end of file diff --git a/LINUX/WIREGUARD.md b/LINUX/WIREGUARD.md new file mode 100644 index 0000000..14a8597 --- /dev/null +++ b/LINUX/WIREGUARD.md @@ -0,0 +1,170 @@ +***На сервере: + +```bash +sudo su +apt update && apt upgrade -y +apt install -y wireguard + +cd /etc/wireguard + +wg genkey | tee privatekey | wg pubkey | tee publickey + +chmod 600 privatekey +cat privatekey +``` + +**Редактируем/создаем: vim wg0.conf** +```sh +# <interface> - заменить на свой !!! +[Interface] +PrivateKey = <server_privatekey> +Address = 10.0.0.1/24 +ListenPort = 51830 +PostUp = iptables -A FORWARD -i %i(wg0) -j ACCEPT; iptables -t nat -A POSTROUTING -o <interface> -j MASQUERADE +PostDown = iptables -D FORWARD -i %i(wg0) -j ACCEPT; iptables -t nat -D POSTROUTING -o <interface> -j MASQUERADE + +[Peer] +# user_1 Publickey +Publickey = <user1_publickey> +AllowedIPs = 10.0.0.2/32 + +[Peer] +# user_2 Publickey** +Publickey = <user2_publickey> +AllowedIPs = 10.0.0.3/32 + +[Peer] +# user_3 Publickey +Publickey = <user3_publickey> +AllowedIPs = 10.0.0.4/32 +``` + +```sh +echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf +sysctl -p + +net.ipv4.ip_forward=1 + +systemctl enable wg-quick@wg0.service + +>Created symlink /etc/systemd/system/multi-user.target.wants/wg-quick@wg0.service → /lib/systemd/system/wg-quick.service + +systemctl start wg-quick@wg0.service +systemctl status wg-quick@wg0.service + +systemctl restart wg-quick@wg0.service +systemctl status wg-quick@wg0.service + +systemctl daemon-reload + +sudo wg show + +#################################### +# ОБЯЗАТЕЛЬНО ПРОВЕРЯЙТЕ ФАЕРВОЛ !!! +sudo ufw status +sudo ufw allowed http +sudo ufw allowed https +#################################### +``` +===================================== +***Генерация ключей клиента: + +`wg genkey | tee /etc/wireguard/_key/win_home_priv | wg pubkey | tee /etc/wireguard/win_home_pub` + + +***На компьютере-клиенте: +```sh +sudo apt install wireguard resolvconf -y +vim Documents/new_wg.conf +``` + +***Конфигурация клиента: + vim wg0.conf +```bash +[Interface] +PrivateKey = <CLIENT-PRIVATE-KEY на сервере> +Address = 10.10.10.2/32 +DNS = 8.8.8.8,77.88.8.8 + +[Peer] +PublicKey = <SERVER-PUBKEY> +Endpoint = <SERVER-IP>:51830 +AllowedIPs = 0.0.0.0/0 # (весь трафик через WireGuard) >>> 10.10.10.0/24 +PersistentKeepalive = 20 +``` + +```sh +systemctl enable wg-quick@wg0 +systemctl start wg-quick@wg0.service +systemctl restart wg-quick@wg0.service +systemctl status wg-quick@wg0.service + +systemctl daemon-reload + +curl ifconfig.me + +sudo wg show + +``` +===================================== +***GUI для Wireguard: +``` +wget https://github.com/UnnoTed/wireguird/releases/download/v1.0.0/wireguird_amd64.deb +sudo dpkg -i ./wireguird_amd64.deb + + +nm-connection-editor +nmcli + +wg-quick up wg0-client +wg-quick down wg0-client +``` +===================================== +### Настройка VPN с WireGuard и LDAP за пару шагов + +Представь, как удобно: ты настраиваешь VPN, а доступ автоматически контролируется через LDAP. Никакого хаоса с ключами, всё централизовано. Вот как это сделать, чтобы всё работало чётко. + +### **1. Настраиваем WireGuard** + +Сначала давай поднимем сам VPN: +1. Устанавливаешь WireGuard: + `sudo apt install wireguard + +2. Генерируешь ключи: + `wg genkey | tee privatekey | wg pubkey > publickey + +3. Делаешь конфиг /etc/wireguard/wg0.conf: + ```sh +[Interface] + PrivateKey = <твой_приватный_ключ> + Address = 10.0.0.1/24 + ListenPort = 51820 + + [Peer] + PublicKey = <публичный_ключ_клиента> + AllowedIPs = 10.0.0.2/32 + ``` + +4. Включаешь VPN: + `sudo wg-quick up wg0 + +Теперь у тебя рабочий VPN, но это только начало. + +--- +### **2. Подключаем LDAP** + +Теперь сделаем так, чтобы только проверенные пользователи могли подключаться: + +1. Установи LDAP-клиент: + `sudo apt install ldap-utils + +2. Проверяешь связь с сервером: + `ldapsearch -x -H ldap://<ip-сервера> -D "cn=admin,dc=example,dc=com" -W + +3. Подключаешь проверку пользователей через LDAP. Например, перед выдачей ключей скрипт проверяет, есть ли юзер в базе: + `ldapsearch -x -b "dc=example,dc=com" "uid=<имя_пользователя>" + +4. Для автоматизации можно настроить генерацию конфигов и ключей через API или свои скрипты. +### **Почему это круто?** + +Тебе больше не нужно вручную раздавать доступы или следить за кучей ключей. Всё централизовано: добавил юзера в LDAP — он тут же получил доступ к VPN. Удобно, чисто, профессионально.
\ No newline at end of file diff --git a/LINUX/WORDPRESS.md b/LINUX/WORDPRESS.md new file mode 100644 index 0000000..68cfc81 --- /dev/null +++ b/LINUX/WORDPRESS.md @@ -0,0 +1,471 @@ +## Установка WordPress: техническая часть + +### Проверим пользователя + +Убедимся, что мы находимся в нужной папке (директории) и не под пользователем root: + +``` +serveruser@ИМЯСЕРВЕРА:/root$ +``` + +Если у Вас как на примере выше (вместо serveruser может быть другой пользователь, главное, чтобы не root), то все хорошо. А если у Вас так: + +``` +root@ИМЯСЕРВЕРА:~# +``` + +То выполите инструкции по созданию нового пользователя из этой статьи: [Установка Nginx, MySQL 8, PHP8.1-FPM на Ubuntu 22.04 LTS с нуля](https://dnevnik.dev/2022/07/11/ustanovka-nginx-mysql-8-php8-1-fpm-na-ubuntu-22-04-lts-s-nulya/). + +### Скачаем WordPress + +Перейдем в нужный каталог, введем: + +``` +cd /var/www/ +``` + +Затем, нужно создать каталог для нашего будущего сайта (у меня НАЗВАНИЕ_САЙТА это dnevnik): + +``` +sudo mkdir НАЗВАНИЕ_САЙТА +sudo chown -R www-data:www-data НАЗВАНИЕ_САЙТА +sudo find /var/www/НАЗВАНИЕ_САЙТА/ -type d -exec chmod 750 {} \; +sudo find /var/www/НАЗВАНИЕ_САЙТА/ -type f -exec chmod 640 {} \; +``` + +Если нужно проверить, свободно ли выбранное Вами название сайта, то сделать это можно здесь: [https://reg.ru](https://www.reg.ru/domain/new/?rlink=reflink-10793423). + +Переместимся в созданный каталог, и скачаем WordPress: + +``` +cd НАЗВАНИЕ_САЙТА +sudo -u www-data wget https://wordpress.org/latest.tar.gz +``` + +Распакуем архив: + +``` +sudo -u www-data tar -xzvf latest.tar.gz +``` + +И удалим его: + +``` +sudo -u www-data rm latest.tar.gz +``` + +Затем переместим содержимое папки wordpress в родительскую для ее директорию: + +``` +sudo -u www-data mv wordpress/* . +``` + +И удалим папку wordpress: + +``` +sudo -u www-data rm -R wordpress +``` + +### Создадим базу данных в MySQL + +Войдем в командную строку MySQL: + +``` +sudo mysql +``` + +Создадим новую базу данных: + +``` +CREATE DATABASE НАЗВАНИЕ_САЙТА; +``` + +Создадим нового пользователя: + +``` +CREATE USER 'НАЗВАНИЕ_САЙТА'@'localhost' IDENTIFIED BY 'aef@234R345t2iJhfvar'; +``` + +«aef@234R345t2iJhfvar» — это пароль пользователя НАЗВАНИЕ_САЙТА. + +Дадим пользователю НАЗВАНИЕ_САЙТА права на базу данных НАЗВАНИЕ_САЙТА: + +``` +GRANT ALL ON НАЗВАНИЕ_САЙТА.* TO 'НАЗВАНИЕ_САЙТА'@'localhost'; +``` + +Выйдем из командной строки MySQL: + +``` +exit +``` + +Теперь давайте войдем в командную строку MySQL под новым MySQL-пользователем: + +``` +mysql -u НАЗВАНИЕ_САЙТА -p +``` + +Вас попросят ввести пароль, введите его. Если Вы не меняли его в примере выше, то он будет такой: + +``` +aef@234R345t2iJhfvar +``` + +Вот мы и вошли под новым пользователем. Посмотрим, какие базы данных ему видны, введем: + +``` +SHOW DATABASES; +``` + +Результат будет такой: + +``` ++---------------------------+ +| Database | ++---------------------------+ +| information_schema | +| НАЗВАНИЕ_САЙТА | ++---------------------------+ +2 rows in set (0.06 sec) +``` + +Видим нашу базу НАЗВАНИЕ_САЙТА, отлично, выйдем из командной строки MySQL: + +``` +exit +``` + +Давайте двигаться дальше, настроим Nginx. + +### Настроим Nginx + +Тут нужно сказать, что я буду иметь в виду под «НАЗВАНИЕ_САЙТА», «НАЗВАНИЕ_САЙТА.ДОМЕН» и «www.НАЗВАНИЕ_САЙТА.ДОМЕН». + +На примере моего сайта это будет так: + +НАЗВАНИЕ_САЙТА = dnevnik +НАЗВАНИЕ_САЙТА.ДОМЕН = dnevnik.dev +www.НАЗВАНИЕ_САЙТА.ДОМЕН = www.dnevnik.dev + +Если Вам нужен домен, советую приобретать его здесь: [https://reg.ru](https://www.reg.ru/domain/new/?rlink=reflink-10793423). К примеру, домен этого сайта (dnevnik.dev) зарегистрирован именно там. + +Создадим новый конфигурационный файл для Nginx, для этого введем: + +``` +sudo vim /etc/nginx/sites-available/НАЗВАНИЕ_САЙТА +``` + +Команда выше создаст новый пустой файл. Вставим в него содержимое: + +``` +server { + listen 80; + server_name НАЗВАНИЕ_САЙТА.ДОМЕН www.НАЗВАНИЕ_САЙТА.ДОМЕН; + root /var/www/НАЗВАНИЕ_САЙТА; + + index index.html index.htm index.php; + + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; + } + + location ~ /\.ht { + deny all; + } +} +``` + +И для сохранения файла, нажмите «:wq», а затем Enter. Файл сохранен. + +Активируем конфигурацию этой командой: + +``` +sudo ln -s /etc/nginx/sites-available/НАЗВАНИЕ_САЙТА /etc/nginx/sites-enabled/ +``` + +Перезапустим Nginx, чтобы изменения вступили в силу: + +``` +sudo systemctl reload nginx +``` +======================================== +Сначала вы проверяете, что порт 80 уже работает, + +``` +sudo netstat -plunt | grep 80 + +tcp6 0 0 :::80 :::* LISTEN 1573456/apache2 +``` + +Если он запущен, то отключите apache: + +``` +sudo service apache2 stop +``` + +Теперь запустите Nginx: + +``` +sudo service nginx start +``` +======================================== + +Теперь нужно настроить WordPress. + +### Настроим WordPress + +Переместимся в директорию сайта: + +``` +cd /var/www/НАЗВАНИЕ_САЙТА +``` + +Переименуем файл wp-config-sample.php: + +``` +sudo -u www-data mv wp-config-sample.php wp-config.php +``` + +И откроем его для редактирования: + +``` +sudo -u www-data vim wp-config.php +``` + +Увидим что-то вроде этого: + +``` +<?php +/** + * The base configuration for WordPress + * + * The wp-config.php creation script uses this file during the installation. + * You don't have to use the web site, you can copy this file to "wp-config.php" + * and fill in the values. + * + * This file contains the following configurations: + * + * * Database settings + * * Secret keys + * * Database table prefix + * * ABSPATH + * + * @link https://wordpress.org/support/article/editing-wp-config-php/ + * + * @package WordPress + */ + +// ** Database settings - You can get this info from your web host ** // +/** The name of the database for WordPress */ +define( 'DB_NAME', 'database_name_here' ); + +/** Database username */ +define( 'DB_USER', 'username_here' ); + +/** Database password */ +define( 'DB_PASSWORD', 'password_here' ); + +/** Database hostname */ +define( 'DB_HOST', 'localhost' ); + +/** Database charset to use in creating database tables. */ +define( 'DB_CHARSET', 'utf8' ); + +/** The database collate type. Don't change this if in doubt. */ +define( 'DB_COLLATE', '' ); + +/**#@+ + * Authentication unique keys and salts. + * + * Change these to different unique phrases! You can generate these using + * the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}. + * + * You can change these at any point in time to invalidate all existing cookies. + * This will force all users to have to log in again. + * + * @since 2.6.0 + */ +define( 'AUTH_KEY', 'put your unique phrase here' ); +define( 'SECURE_AUTH_KEY', 'put your unique phrase here' ); +define( 'LOGGED_IN_KEY', 'put your unique phrase here' ); +define( 'NONCE_KEY', 'put your unique phrase here' ); +define( 'AUTH_SALT', 'put your unique phrase here' ); +define( 'SECURE_AUTH_SALT', 'put your unique phrase here' ); +define( 'LOGGED_IN_SALT', 'put your unique phrase here' ); +define( 'NONCE_SALT', 'put your unique phrase here' ); + +/**#@-*/ + +/** + * WordPress database table prefix. + * + * You can have multiple installations in one database if you give each + * a unique prefix. Only numbers, letters, and underscores please! + */ +$table_prefix = 'wp_'; + +/** + * For developers: WordPress debugging mode. + * + * Change this to true to enable the display of notices during development. + * It is strongly recommended that plugin and theme developers use WP_DEBUG + * in their development environments. + * + * For information on other constants that can be used for debugging, + * visit the documentation. + * + * @link https://wordpress.org/support/article/debugging-in-wordpress/ + */ +define( 'WP_DEBUG', false ); + +/* Add any custom values between this line and the "stop editing" line. */ + + + +/* That's all, stop editing! Happy publishing. */ + +/** Absolute path to the WordPress directory. */ +if ( ! defined( 'ABSPATH' ) ) { + define( 'ABSPATH', __DIR__ . '/' ); +} + +/** Sets up WordPress vars and included files. */ +require_once ABSPATH . 'wp-settings.php'; +``` + +Тут нам нужно заменить следующее: + +- **database_name_here** на **НАЗВАНИЕ_САЙТА** +- **username_here** на **НАЗВАНИЕ_САЙТА** +- **password_here** на **aef@234R345t2iJhfvar** (или на тот пароль, который Вы придумали) +- **put your unique phrase here** на набор случайных букв и символов + +Сохраним файл. Нажмем «:wq», затем Enter. + +Всё, мы готовы к запуску установки WordPress! + +## Установка WordPress: запуск скрипта установки + +Чтобы начать скрипт установки WordPress, нужно перейти по адресу: + +``` +http://НАЗВАНИЕ_САЙТА.ДОМЕН +``` + +И увидим это: + + + +Выберем язык, и увидим подобное: + + + +Введите данные, и нажмите «Установить WordPress». После успешной установки увидите такой экран: + + + +Далее нажмите «Войти», и введите только что придуманные имя пользователя и пароль: + + + +Ещё раз нажмите «Войти», и, добро пожаловать в WordPress! + + + +## Заключение + +Вот и завершена установка WordPress на Ubuntu 22.04 LTS! Если у Вас остались какие-то вопросы, пожелания, или возникли ошибки в процессе установки — задавайте их в сообщения сообщества [https://vk.com/webdevdiary](https://vk.com/webdevdiary), буду рад помочь! + +Пока! + +## А вот и нет! + +Адрес сайта сейчас **http**://НАЗВАНИЕ_САЙТА.ДОМЕН, а должен быть **https**://НАЗВАНИЕ_САЙТА.ДОМЕН. + +Давайте исправлять! + +### Установка Certbot + +Введем команду: + +``` +sudo snap install core; sudo snap refresh core +``` + +А затем: + +``` +sudo snap install --classic certbot +``` + +И, наконец: + +``` +sudo ln -s /snap/bin/certbot /usr/bin/certbot +``` + +### Запуск Certbot + +Введем: + +``` +sudo certbot +``` + +Выведется это: + +``` +Saving debug log to /var/log/letsencrypt/letsencrypt.log +Enter email address (used for urgent renewal and security notices) + (Enter 'c' to cancel): +``` + +Нужно ввести свой e-mail — вводим, и жмем «Enter». Далее везде отвечаем «Y», и выведется такое: + +``` +Account registered. + +Which names would you like to activate HTTPS for? +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +1: НАЗВАНИЕ_САЙТА.ДОМЕН +2: www.НАЗВАНИЕ_САЙТА.ДОМЕН +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Select the appropriate numbers separated by commas and/or spaces, or leave input +blank to select all options shown (Enter 'c' to cancel): +``` + +Далее можете просто нажать «Enter», и сертификаты установятся для обоих адресов: + +``` +Requesting a certificate for НАЗВАНИЕ_САЙТА.ДОМЕН and www.НАЗВАНИЕ_САЙТА.ДОМЕН + +Successfully received certificate. +Certificate is saved at: /etc/letsencrypt/live/НАЗВАНИЕ_САЙТА.ДОМЕН/fullchain.pem +Key is saved at: /etc/letsencrypt/live/НАЗВАНИЕ_САЙТА.ДОМЕН/privkey.pem +This certificate expires on 2022-10-14. +These files will be updated when the certificate renews. +Certbot has set up a scheduled task to automatically renew this certificate in the background. + +Deploying certificate +Successfully deployed certificate for НАЗВАНИЕ_САЙТА.ДОМЕН to /etc/nginx/sites-enabled/НАЗВАНИЕ_САЙТА +Successfully deployed certificate for www.НАЗВАНИЕ_САЙТА.ДОМЕН to /etc/nginx/sites-enabled/НАЗВАНИЕ_САЙТА +Congratulations! You have successfully enabled HTTPS on https://НАЗВАНИЕ_САЙТА.ДОМЕН and https://www.НАЗВАНИЕ_САЙТА.ДОМЕН + +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +If you like Certbot, please consider supporting our work by: + * Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate + * Donating to EFF: https://eff.org/donate-le +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +``` + +После этого, когда Вы зайдете на свой сайт **http**://НАЗВАНИЕ_САЙТА.ДОМЕН, Вы будете автоматически перенаправлены на **https**://НАЗВАНИЕ_САЙТА.ДОМЕН. + +Если Вам нужен сертификат с более высоким статусом, советую приобретать его здесь: [https://reg.ru](https://www.reg.ru/domain/new/?rlink=reflink-10793423). + +Теперь точно все!
\ No newline at end of file diff --git a/LINUX/XARGS, TEE и WATCH.md b/LINUX/XARGS, TEE и WATCH.md new file mode 100644 index 0000000..231b92d --- /dev/null +++ b/LINUX/XARGS, TEE и WATCH.md @@ -0,0 +1,24 @@ +1️⃣ xargs: передача аргументов для гибкости +Часто команды выводят списки, и вам нужно обработать их. xargs позволяет передать эти списки в другие команды. + +Например, найдем и удалим все файлы логов (.log) в текущей директории: +`find . -name "*.log" | xargs rm + +2️⃣ tee: записываем и видим одновременно +Классная штука, если нужно и результат команды глянуть, и сохранить его на потом. Например: + +`ls -la | tee files.txt + +Выводим список файлов и одновременно сохраняем этот список в файл. + +3️⃣ watch: мониторинг в реальном времени +Наблюдение за изменениями системы? Легко с watch. Он обновляет выполнение команды через заданные интервалы. + +Например, чтобы отслеживать использование диска каждую секунду, выполним: + +`watch -n 1 df -h + +Полезно для мониторинга системных метрик, таких как загрузка процессора, состояние сети или места на диске. + +================= + diff --git a/LINUX/YAML.md b/LINUX/YAML.md new file mode 100644 index 0000000..8290d0b --- /dev/null +++ b/LINUX/YAML.md @@ -0,0 +1,84 @@ +**Некоторые правила написания YAML-файлов**: + +- Файл должен начинаться с 3 дефисов — «---», а заканчиваться 3 точками — «…» +- Первый блок (словарь или список) задаёт рабочее количество отступов, которое будет использовано во всём документе. +- После знака «-», обозначающего элемент списка, ставится пробел. +- Все именованные наборы данных и строки должны иметь уникальные названия. +- В качестве отступов используются пробелы, табуляции не допускаются. +- Строки можно не брать в кавычки, за исключением случая, когда прописываются конвейеры команд в Linux. +- Комментарии начинаются с символа # и отделяются от других токенов пробелами. + +**Основная часть данных в YAML представлена в форме пар «ключ-значение»**, где ключ представляет имя, а значение — соответствующие данные. + + +## Синтаксис YAML + +В синтаксисе **YAML** _значительным является интервал_. Каждый **отступ с двумя пробелами** представляет новый уровень: + +```sh +level1: + level2: + Level3: +``` +Каждый новый уровень - _это объект_. В этом примере объект **level1** содержит объект **level2**, который содержит объект **level3**. + +### Поддержка мультидокументов + +Вы можете объединить несколько YAML-документов в один YAML-файл для облегчения организации файлов и парсинга данных. + +Документы разделяются тремя дефисами (—): + +``` +--- +player: playerOne +action: attack (miss) +--- +player: playerTwo +action: attack (hit) +--- +``` + +### Поддержка комментариев + +YAML позволяет добавлять комментарии после символа #, как в Python: + +``` +key: #Это однострочный комментарий + - value line 5 + #Это + #многострочный комментарий + - value line 13 +``` + +### Легко читаемый синтаксис + +В синтаксисе YAML-файлов используется система отступов, как в Python. Необходимо использовать пробелы, а не табуляцию, чтобы избежать путаницы. + +Это избавляет от лишних символов, которые есть в JSON и XML (кавычки, скобки, фигурные скобки). + +В итоге читаемость файла значительно повышается. + +### YAML + +``` +#YAML + Imaro: + author: Charles R. Saunders + language: English + publication-year: 1981 + pages: 224 +``` + +### Явная и неявная типизация + +YAML предлагает как автоопределение типов, так и возможность явно указать тип данных. Чтобы использовать конкретный тип, нужно написать `!![тип]` перед значением. + +``` +# Это значение преобразуется в int: +is-an-int: !!int 14.10 +# Превращает любое значение в строку: +is-a-str: !!str 67.43 +# Значение должно быть boolean: +is-a-bool: !!bool yes +``` + diff --git a/LINUX/__FLENOV.md b/LINUX/__FLENOV.md new file mode 100644 index 0000000..9940655 --- /dev/null +++ b/LINUX/__FLENOV.md @@ -0,0 +1,1582 @@ +#========================================== +**/ - основной раздел +/bin - основные исполняемые файлы системы +/boot - файлы необходимые для загрузки системы +/dev - представления для подключенных устройств +/etc - конфигурационные системные файлы +/home - пользовательские папки и файлы (лучше выносить на другой физический диск!) +/lib - библиотеки ядра ОС +/opt - дополнительные програмные пакеты +/proc - раздел для монтирования виртуальной файловой системы +/sbin - исполняемые файлы ROOTa +/tmp - временные файлы +/usr - системные файлы +/var - журналы, буферные или заблокированные файлы (лучше выносить на другой физический диск!) +Swap - раздел подкачки + +Если /home и /var вынесены на отдельные диски, то для корневого каталога хватит 3Gb, но можно и больше. + +#================================== + +`sudo apt update + +Собрать зависимости: +`sudo apt build-dep имя_программы + +Если вы не хотите реально устанавливать пакеты, а только посмотреть, что будет +устанавливаться, го можно использовать ключ -s (simulate, симулировать установку): +`sudo apt-get -s install me + +Для проверки зависимостей нужно выполнить команду: +`sudo apt-get check + +Если нужно исправить зависимости: +`sudo apt-get -f install + +Если нужно исправить зависимости: +`sudo apt-get -f install + +Для удаления программы или пакета используем: +`sudo apt remove имя_программы + +Если какая-то программа была удалена, но остались зависимости, то можно авто +матически удалить все лишнее: +`sudo apt-get autoremove + +Для установки обновлений уже установленных программ выполняем следующую +команду: +`sudo apt-get upgrade + +Чтобы обновить саму ОС, выполняем команду: +`sudo apt-get dist-upgrade + +Следующую тройку команд желательно выполнять регулярно для установки об +новлений: +`sudo apt-get update +`sudo apt-get upgrade +`sudo apt-get dist-upgrade + +Все эти команды позволят вам содержать вашу копию релиза Ubuntu и установлен +ных программ в обновленном состоянии, но на новый релиз автоматически вы так +не перейдете. +Если вышел новый релиз, а не просто обновление существующего, то +на него можно перейти командой: +`sudo do-release-upgrade + +Чтобы узнать, какие вообще в системе уже установлены пакеты, можно выполнить +следующую команду +`sudo apt list —installed + +*which* - определить где находится программа: +`which ls` + +#========================================== +### GRUB2 + +В Linux сейчас самым популярным загрузчиком является GRUB2. Конфигурация +этой программы чаще всего содержится в файле +`/boot/grub/grub.cfg` +но можно встретить дистрибутивы, где этот путь выглядит так: +`/boot/grub2/grub.cfg. +В файле загрузчика записаны настройки, определяющие, нужно ли показывать меню выбора варианта загрузки, и каким должно быть это меню, а также указываются сами варианты +загрузки и предписывается, какую версию ядра следует загрузить (мы уже говори +ли, что в Linux может быть более одного ядра). +После этого может формироваться `initrd` — виртуальный диск в оперативной памя +ти (ramdisk) для временной файловой системы, используемой ядром Linux при на +чальной загрузке. Дело в том, что если ядро скомпилировано в минимальном вари +анте и все его возможности подключаются в дальнейшем с помощью модулей, то +в этот момент у ядра не будет возможности работать со специфичными жесткими +дисками, и оно не сможет загружать дальнейшие данные. Поэтому сначала грузит +ся диск /boot/initrd.xxxx, который загружает критически важные модули, и, если я не +ошибаюсь, именно он и загружает красивую картинку, которую показывает ОС во +время старта системы. +Теперь у ядра есть все необходимое для загрузки самой системы. Появляется кра +сивая картинка, которая прячет выводимый в консоль лог дальнейшей загрузки, +и управление передается программе /sbin/init. +Программа init загружает файл /etc/inittab — конфигурационный файл, в котором +прописан процесс инициализации ОС Linux, и выполняет все предусмотренные +в нем операции. + +#========================================== +### Работа с модулями ядра + +Система должна загружаться только с теми модулями, которые применяются. Все +остальные следует подгружать по мере необходимости и отключать, когда они не +используются. +модули ядра — это немного другое. Они выполняются на более низком уровне, который в основном позволяет работать с устройствами, управлять памятью и т. п. При модульной +компиляции вы можете включать и выключать функции уровня ядра с помощью +следующих команд: +`lsmod` — эта команда выведет список загруженных модулей. +`modinfo` — очень трудно разобраться, какие модули нужны в системе, а какие +нет. А ведь необходимо загружать только то, что действительно используется, +иначе увеличивается время старта системы, понапрасну расходуются ресурсы +компьютера и т. д. Так как же в этом разобраться? Необходимо знать каждый +модуль «в лицо» и понимать, для чего он нужен. +Получить информацию о модуле и помогает команда modinfo. В качестве пара +метра нужно передать ей имя интересующего модуля, и на экран будет выведена +информация о нем. Например, следующая команда запрашивает у системы ин +формацию о модуле ext3: +`modinfo ext3 + +В ответ на это мы увидим примерно следующее: +filename: +/lib/modules/2.4.18-5asp/kernel/fs/ext3/ext3.о +description: "Second Extended Filesystem with journaling extensions" +author: +"Remy Card, Stephen Tweedie, Andrew Morton, Andreas Dilger, +Theodore Ts’o and others" +license: +"GPL" +parm: +do_sync_supers int, description "Write superblocks +synchronously" + +Таким образом, нам становятся известными имя и расположение файла, его опи +сание, автор, лицензия и т. д. Количество отображаемой информации сильно +зависит от модуля, и, если честно, в некоторых случаях она настолько скудна, +что предназначение модуля остается непонятным; +`modprobe` — эта команда в основном используется системой для загрузки уста +новленных модулей, но можно это делать и самостоятельно. В качестве един +ственного параметра нужно передать команде имя модуля, который надо за +грузить. +Например, следующая команда загружает модуль `iptabie_nat` +`modprobe iptable_nat +`rmmod` — эта команда выгружает модуль, имя которого указано в качестве пара +метра. Если вы воспользовались модулем для выполнения определенных дейст +вий, то не забудьте по окончании работы его выгрузить. + +#========================================== +### Переменная $РАТН + +`echo $РАТН +В результате вы должны увидеть список папок, разделенных двоеточиями: +`/usr/local/bin:/bin:/usr/bin +Соответственно, когда мы отдаем в командной строке команду is, то система нахо +дит ее исполняемый файл в каталоге `/bin` и запускает его. +При запуске программ из командной строки Linux нужно обязательно указывать **./** +перед именем программы или скрипта. Например, допустим, что в текущем катало +ге есть исполняемый файл publish — тогда для его запуска нужно в консоли выпол +нить следующую команду: +./publish +В Windows и в Linux точка указывает на текущий каталог, а, значит, приведенная +команда просит запустить publish именно из текущего каталога. Впрочем, в Windows при запуске программ из текущего каталога можно ничего не указывать, по +тому что эта ОС сначала ищет файлы в текущей папке, а потом уже в системных. +Но из-за этого у Windows были проблемы с безопасностью. +ОС Linux ведет себя немного иначе, и из текущего каталога по умолчанию ничего +не запускает, а ищет в папках, указанных в переменной $PATH +Linux можно заставить «смотреть» и в текущую папку — просто для этого точку +нужно добавить в переменную $PATH: +`PATH = $РАТН:. +Не уверен, что в реальности делать что-то подобное — хорошая идея. Но я привел +этот пример, для того чтобы показать, как добавлять папку к переменной $PATH. +Если у вас есть программа `/usr/local/myprogram/publish` и вы не хотите при каждом +запуске писать этот полный путь, то можно добавить путь /usr/local/myprogram/ +в переменную $PATH: +`РАТН=$РАТН:/usr/local/myprogram/ +Смысл этой команды в том, что мы присваиваем переменной новое значение, кото +рое состоит из уже существующего, плюс двоеточие и новая папка. Если просто +написать: +PATH=/usr/local/myprogram/ +то текущее значение $PATH будет потеряно, и теперь $PATH станет равна лишь +/usr/local/myprogram/. + +#========================================== +### Права на файлы и папки + +Права можно задавать и последовательностью нулей и единиц. Если в определен +ном месте стоит 1 (указан один из символов г, w или х), то операция разрешена, а +если 0 (указан дефис) — действие запрещено. Давайте попробуем записать права +rwxr-xr— в виде нулей и единиц. Запишите вместо букв единицы, а вместо дефи +сов — нули. Должно получиться 111101100. Разобьем эту комбинацию на три час +ти: 111, 101 и 100. Теперь каждую тройку переведем в восьмеричную систему по +следующей формуле: + +Цифра1 ★ 4 + Цифра2 ★ 2 + ЦифраЗ + +У нас получатся три цифры: 7, 5 и 4, которые можно рассматривать как десятичное +число 754. Запомните его, оно нам пригодится при назначении прав на файлы и ка +талоги. Чтобы вам в дальнейшем было проще регламентировать доступ, предлагаю +все возможные варианты значений для отдельного разряда числа: +□ О — запрещено все; +□ 1 — разрешено выполнение; +П 2 — разрешена запись; +□ 3 — разрешены запись и выполнение; +□ 4 — разрешено чтение; +□ 5 — разрешены чтение и выполнение; +□ 6 — разрешены чтение и запись; +□ 7 — разрешено все. + +#### chmod +chmod параметры права файл +Компонент параметры может включать комбинацию следующих значений: +□ и — изменить права владельца; +□ g — изменить права группы; +□ о — изменить права остальных пользователей; +□ а — изменить все права (то же самое, что передать значение: ugo). + +**Внимание! +Если вы решили использовать символьные ссылки, то всегда помните особенность +формирования прав доступа на файлы. Можете даже выбить на мониторе надпись: +«Ссылки на файлы создаются с полными правами!!!» + +#========================================== +### Редактирование пользователя + +Для редактирования параметров учетной записи можно напрямую корректировать +файл /etc/passwd, но я советую использовать команду `usermod`. У нее такие же клю +чи, что и у `useradd`, но она не создает пользователя, а изменяет параметры уже су +ществующего. +С помощью `usermod` вы можете добавлять уже имеющегося пользователя в ранее +созданную группу. Давайте проделаем такую процедуру с учетной записью robert и +определим ее в группу root. Это позволит пользователю robert выполнять некото +рые административные функции: + +`usermod -G root robert + +Здесь мы выполняем команду с ключом -G. Этот ключ позволяет указать, членом +каких групп должен быть пользователь. Можно указать несколько групп, разделен +ных запятыми. В нашем случае мы добавляем пользователя только в одну группу +root. Для получения более подробной информации о команде `usermod` выполните +команду: + +`man usermod + +### Удаление пользователя + +Для удаления пользователя служит команда `userdel`. В качестве параметра ей пере +дается только имя учетной записи, которую надо удалить, и можно распрощаться +с ней навсегда. Например: + +`userdel Denver + +Если пользователь в этот момент находится в системе, будет выдано сообщение об +ошибке. +Необходимо учитывать, что удаление пользователя не уничтожает его домашний +каталог — вы должны сделать это вручную. Если же указать в команде ключ -г, то +вместе с пользователем будут удалены и его файлы в домашнем каталоге: + +`userdei -г Denver + +**Совет +Никогда не указывайте этот ключ. Выполняйте операцию только вручную, просмотрев +содержимое каталога и убедившись, что там нет нужных файлов. + +#========================================== +### Порты / протоколы + +HTTP 80 +HTTPS 443 +SMTP 25 +SMTPS 465 +LDAP 329 +LDAPS 636 +TELNET 23 +TELNETS 992 +SHELL 514 +SSH 22 +FTP 21 +FTPS 990 +FTP-DATA 20 +FTPS-DATA 989 +IMAP 143 +IMAPS 993 +POP3 110 +POP3S 995 +IRC 194 +IRCS 994 + +#========================================== +### Шифрование файлов + +В качестве примера зашифруем список паролей `/etc/passwd` в файл `/home/passwd` по +алгоритму DES. Для этого выполним команду: + +`/usr/bin/openssl des -in /etc/passwd -out /home/passwd + +В ответ на эту директиву программа попросит вас указать пароль и затем повторить +его ввод, чтобы исключить возможные ошибки. +Выполните команду: `cat /home/passwd` и убедитесь в том, что содержимое этого +файла нечитаемо. + +Для расшифровки файла выполните команду: + +`/usr/bin/openssl des -d -in /home/passwd -out /etc/passwd + +Таким нехитрым способом мы можем безопасно хранить копию файла с паролями. + +#========================================== +### Основные параметры конфигурации сервера SSH + +```sh +#Port 22 +#Protocol 2,1 +#ListenAddress 0.0.0.0 +#ListenAddress : : +# HostKey for protocol version 1 +# Ключ для первой версии протокола +#HostKey /etc/ssh/ssh_host_key +# HostKeys for protocol version 2 +# Ключи для второй версии протокола +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_dsa_key +# Lifetime and size of ephemeral version 1 server key +# Время жизни и размер регенерируемого серверного ключа версии 1 +#KeyRegenerationInterval 3600 +#ServerKeyBits 768 +# Logging +# Ведение логов +#obsoletes QuietMode and FascistLogging +#SyslogFacility AUTH +SyslogFacility AUTHPRIV +#LogLevel INFO +``` +Рассмотрим основные параметры, которые вам могут пригодиться: +□ Port — порт, на котором ожидаются подключения. По умолчанию — 22. Неко +торые администраторы любят изменять значение по умолчанию, перенося сер +вер на другой порт. В какой-то степени это оправданно. Например, если у вас +нет веб-сервера, то можно поместить SSH на порт 80. Хакеры будут думать, что +это веб-сервер, и не станут его ломать; +□ Protocol — поддерживаемые протоколы. Обратите внимание, что первым идет +число 2, а затем 1. Это значит, что сервер будет сначала пытаться подключиться +по второй версии протокола, и только затем по первой. Я рекомендую убрать +знак комментария с этой строки и удалить число 1, чтобы использовалась только +последняя версия. Уже давно пора обновить клиентское программное обеспече +ние и перейти на более безопасные технологии. Зацикливание на старых про +граммах приносит только убытки; +□ ListenAddress — адрес для прослушивания подключения. У вашего сервера мо +жет быть несколько сетевых карт. По умолчанию идет прослушивание всех ин +терфейсов. Вы должны указать только те, с которых будете подключаться по +SSH. Например, очень часто одна сетевая карта смотрит во внутреннюю сеть, а +другая — в Интернет. Если вы будете подключаться по SSH-протоколу лишь из +внутренней сети, то следует прослушивать только этот адрес (задается в формате +адрес:порт). Разрешается включать несколько таких записей, чтобы указать тре +буемые интерфейсы; +□ HostKey — путь к файлам, содержащим ключи шифрования. Нужно прописывать +только закрытые ключи, которые использует сервер для расшифровки пакетов; +□ KeyRegenerationintervai — интервал времени, через который сервер обновляет +ключи. В версии 1 во время сессии ключи могут регенерироваться. Это позволяет сделать невозможным раскрытие пакетов за счет смены ключей, которые по +умолчанию меняются каждые 3600 секунд. Если установить 0, то регенерации +не будет. Так как мы отказались от первой версии протокола (см. параметр +Protocol), то этот атрибут не влияет на работу; +□ serverKeyBits — длина серверного ключа. По умолчанию установлено 7 68, мини +мальное значение — 512; +□ SysiogFaciiity — тип сообщений, которые будут сохраняться в журнале; +□ LogLevei — уровень событий, которые будут попадать в журнал. Возможные +уровни соответствуют системным, которые мы будем рассматривать в разд. 12.5; +□ LoginGraceTime— интервал времени, в течение которого пользователь должен +ввести правильный пароль, иначе соединение будет разорвано; +□ РеrmitRootLogin— флаг, определяющий, разрешено (yes) или запрещено (по) +входить в систему по протоколу SSH под пользователем root. Мы уже говорили, +что root — это бог в системе, и его возможности нужно использовать аккуратно. +Если в систему нельзя входить с такими правами, то и по SSH тем более. Срочно +меняйте этот параметр на по; +□ strictModes — флаг, регламентирующий необходимость проверки состояния +файлов и их владельцев, пользовательских файлов и домашнего каталога до ввода +пароля. Желательно установить yes, потому что многие начинающие пользовате +ли делают все свои файлы доступными для записи; +□ RSAAuthentication— разрешена ли аутентификация по алгоритму RSA. Пара +метр действует для первой версии протокола; +□ PubkeyAuthentication— дозволена ли аутентификация по публичному ключу. +Параметр действует для второй версии протокола; +□ AuthorizedKeysFile — файл с публичным ключом, который может использовать- +ся для аутентификации; +□ RhostsAuthentication — флаг, разрешающий аутентификацию по файлам $НОМЕ/ +.rhosts и /etc/hosts.equiv. По умолчанию стоит по, и без особой надобности менять +этот параметр не стоит, потому что это небезопасно; +□ ignoreRhosts — флаг, запрещающий читать файлы -/.rhosts и -/.shosts. Без необ +ходимости значение лучше не изменять, потому что это может повлиять на +безопасность; +□ AuthorizedKeysFile— файл для хранения списка авторизованных ключей. Если +пользователь входит в систему с имеющимся в этом файле ключом, то его пус +тят автоматически без ввода дополнительных паролей; +□ RhostsRSAAuthentication— флаг, регламентирующий использование при аутен +тификации ключа хоста из каталога /etc/ssh/ssh_known_hosts. Параметр применя +ется в первой версии SSH; +□ ignoreUserKnownHosts — параметр, указывающий на необходимость игнориро +вания пользовательских списков доверенных компьютеров. Если он равен по, то необходимо доверять компьютерам из списка ~/.ssh/known_hosts при аутентифика +ции RhostsRSAAuthentication. Не верьте никому, поэтому параметр лучше всего +изменить на yes; +□ PasswordAuthentication — требование пароля. Если значение равно yes, то будет +требоваться пароль. При использовании авторизации через ключи параметр +можно отключить; +□ РеrmitEmptyPasswords — разрешение пустых паролей. По умолчанию установле +но по, что запрещает использование пустых паролей, и изменять это значение не +стоит; +□ KerberosAuthentication— использование проверки подлинности по протоколу +Kerberos. В последнее время именно эта аутентификация набирает большую по +пулярность благодаря своей безопасности; +□ KerberosOrLocaiPasswd— если пароль Kerberos не был принят, то включается +проверка локального файла паролей из файла /etc/shadow; +□ KerberosTicketcieanup— удаление билета Kerberos из кэша при выходе из сис +темы; +□ Banner— позволяет указать файл, в котором находится текст приветствия, ото +бражаемого пользователям. + +#### Параметры доступа к серверу sshd +Кроме директив, можно использовать следующие: +□ AllowGroups — позволить вход в систему только пользователям указанных групп +(указываются через пробел в одной строке); +□ Allowusers — разрешить вход в систему пользователям, имена которых указаны +через пробел; +□ DenyGroups— запретить вход в систему пользователям указанных через пробел +групп; +□ DenyUsers — запретить вход в систему пользователям, имена которых указаны +через пробел. Этот параметр бывает удобен, когда дано разрешение на вход +группе, но нужно отказать в подключении к SSH-серверу одному из ее пользо +вателей. +Я рекомендую вам явно прописать группы или имена пользователей, которые мо +гут входить в систему по SSH. + +#### Конфигурирование клиента SSН + +Настройки SSH-клиента содержат еще меньше параметров. В файле `/etc/ssh/ssh_config` находятся глобальные настройки для всех пользователей в системе. Но вы +можете для любого из них переопределить произвольный параметр в файле +`ssh_config` из каталога пользователя. + +#### Вход по ключу + +```sh +ssh-keygen +# -t - тип ключа (rsa, dsa, rsa1) +# -f - файл в котором будет сохранен закрытый ключ +# -b - длина ключа (не менее 512. по умолч = 1024) + +ssh-keygen -t rsa -f ~/.ssh/mykey + +cd /home/vlapa +mkdir .ssh +``` +Получив ключи, вы должны отправить файл `~/.ssh/mykey.pub` на удаленный ком +пьютер, чтобы SSH-сервер мог использовать его для аутентификации. + +Администратор сервера должен добавить содержимое публичного ключа в файл +`ssh/authorized_keys`. Для этого можно выполнить на сервере следующую команду: +`cat myrsakey.pub » .ssh/authorized_keys` +Теперь можно подключаться к серверу, используя публичный ключ для подтвер +ждения личности. Но перед этим убедитесь, что в конфигурационном файле серве +ра включены следующие директивы: + +RSAAuthentication yes +PubkeyAuthentication yes + +Для подключения к серверу выполните команду: + +`ssh -i ~/.ssh/myrsakey` + +С помощью параметра -i мы указываем файл публичного ключа. Если этого не +сделать, то будет использоваться id_rsa— файл по умолчанию, его имя задает +директива identityFile в конфигурационном файле SSH-клиента. +Теперь сервер будет запрашивать у вас не пароль, а слово (фразу), которое вы ука +зали при генерации публичного ключа: +`Enter passphrase for key` +Если в конфигурационном файле SSH-сервера изменить параметр PasswordAuthen- +tication на по, то пароль проверяться не будет, а связь станет устанавливаться +только на основании ключей. Для обеспечения безопасной связи этого достаточно. + +#========================================== +### Анализ безопасности дистрибутива + +```sh +sudo apt-get install lynis +sudo lynis audit system +# -c - выполнить все проверки (check all) +``` + +#========================================== +### SAMBA + +Основным конфигурационным файлом для Samba является `smb.conf`, который мож +но найти в каталоге `/etc/samba/` (в некоторых дистрибутивах это может быть каталог +`/etc`). Кроме него, в этом каталоге находится файл `Imhosts`, с помощью которого про +исходит сопоставление IP-адресов и имен компьютеров, — аналогично файлам +`/etc/hosts` в Linux и `[Диск:]\Windows\System32\dnvers\etc\lmhosts.sam` в Windows. +Дополнительно в этом же каталоге можно создать следующие файлы (некоторые из +них могут существовать): + +□ smbusers — список пользователей, которым разрешено подключаться к серверу +Samba; + +□ smbpasswd — пароли пользователей из файла smbusers. + +Как видите, у Samba — свои конфигурационные файлы для хранения списка +пользователей. Если вы создаете их вручную, то убедитесь, что права на чтение и +запись установлены правильно. Файл должен быть доступен только администра +тору, т. е. владельцем может быть лишь root и никто иной. +Конфигурационный файл `smb.conf` содержит не так много директив +#### Основные настройки +Конфигурационный файл разбит на секции. Самой первой идет секция +[global], +в которой описываются глобальные настройки сервера. В ней можно увидеть сле +дующие директивы: +□ workgroup = имя — имя группы, в которую входит сервер. Когда в Windows вы +входите в сетевое окружение, то можете увидеть все доступные ресурсы, разби +тые на категории. В каждой группе могут быть свои компьютеры или серверы; +□ netbios name = имя — имя, которое пользователи будут видеть в сетевом окру +жении для этого сервера, оно не должно совпадать с именем рабочей группы; +□ server string = описание — Свободный ТеКСТ, КОТОрЫЙ МОЖНО будет увидеть В +поле Description (Комментарий) свойств сервера или окне сетевого окружения +в режиме Details (Таблица). В этом поле вы можете поместить комментарий, +описывающий содержимое сервера, — например: Файловый архив Сергея; +□ hosts allow = адреса — указание (через пробел) IP-адресов или сетей, которым +разрешен доступ к Samba-серверу. Например, чтобы открыть доступ всем компьютерам сети 192.168.1.x и одному компьютеру с адресом 192.168.2.2 из другой +сети, надо написать следующую строку: +hosts allow = 192.168.1. 192.168.2.2 +□ printcap name = файл — файл описания принтеров, подключенных к системе. По +умолчанию это `/etc/printcap` +□ load printers = yes — установка режима (yes) автоматического включения +принтеров в список открытых ресурсов. Если в этом нет надобности, то укажи +те по; +□ printing = система— тип системы печати. Здесь можно использовать одно из +Следующих Значений: bsd, sysv, pip, lprng, aix, hpux ИЛИ qnx. +#### Безопасность +□ guest account = имя — определяет учетную запись, с правами которой пользо +ватель сможет входить в систему. Если ваш сервер не содержит секретной ин +формации и используется для открытого обмена файлами, то можно завести гос +тевую учетную запись, иначе такой вход небезопасен; +□ log file = файл — задает название файла-журнала. Например, можно написать: +/var/iog/samba/%m. log. Обратите внимание, что в имени присутствует символ +процента, за которым следует буква т. Эта комбинация во время работы будет +заменена именем пользователя, активность которого сохраняется. Так, напри +мер, для пользователя robert будет создан журнал /var/log/samba/robert.log; +□ max log size = n— определяет максимальный размер журнала в килобайтах. +Укажите значение о, если ограничения не должно быть; +□ sysiog only — предписывает использовать для журналирования только syslog; +□ security = уровень — определяет степень доступа. Параметр уровень может +принимать одно из следующих значений: +• user — доступ на уровне пользователя; +• share — аутентификация на основе имени и пароля; +• server— проверка пароля производится сторонним smb-сервером. Имя сер +вера, на котором хранятся пароли, задается с помощью директивы: +password server = ИмяСервера +что позволяет держать пароли на другом smb-сервере, который и будет осу +ществлять проверку пароля; +• domain — включает сервер в домен Windows NT, а пароль для доступа указы +вается в файле, определенном с помощью директивы: +smb passwd file = ИмяФайла +□ encrypt passwords = yes — позволяет шифровать пароли, передаваемые по сети. +Если вы работаете в режиме с шифрованными паролями, то при авторизации +будет использоваться файл /etc/samba/smbpasswd (это можно изменить с помощью +директивы: smb passwd file). Такой файл необходим из-за различий в шифрова +нии Windows и Linux; +***Совет +Не применяйте открытые пароли без особой надобности. Не забывайте о существова +нии программ-снифферов, которые анализируют сетевой трафик и позволяют найти +пароли, передаваемые по сети. Если они не зашифрованы, то злоумышленник сможет +проникнуть в вашу систему.*** +□ smb passwd file = файл— указывает на расположение файла с паролями. По +умолчанию он находится в том же каталоге, где расположены конфигурацион +ные файлы Samba; +□ ssi с а certFile = файл— задает файл сертификата, необходимый для работы +протокола SSL, гарантирующего безопасность передачи данных; +□ unix password sync = yes — разрешает пользователям Windows менять пароль +Samba, одновременно обновляя системные пароли в Linux. Если в этом нет необходимости, установите значение параметра по. Для работы этой директивы +нужно указать программы, которые будут изменять пароли (параметр: passwd +program) и сообщения, появляющиеся перед пользователем (параметр: passwd +chat). +Приведу пример использования: +```sh +unix password sync = Yes +passwd program = /usr/bin/passwd %u +passwd chat = *New*password* %n\n *Retype*new*password* %n\n +*passwd:*al^authentication*tokens*updated*successfully* +``` +Помимо ЭТОГО, необходимо использовать директивы: encrypt passwords И smb +passwd file; +□ username map = файл — указание на файл, где хранится список пользователей +сервера Samba для клиентов Windows 9х (о нем подробнее мы поговорим в +разд. 6.3)\ +□ usershare max shares — максимальное количество пользовательских каталогов, +которые могут быть открыты для общего доступа; +□ usershare allow guests = yes — еСЛИ ЭТОТ параметр ВКЛЮЧен, ТО ГОСТИ системы +смогут получить доступ к открытым ресурсам. По умолчанию этот параметр от +ключен, чтобы доступ к открытым ресурсам получали только авторизованные +пользователи. +#### Сеть +В этом разделе мы изучим директивы настройки сетевого протокола: +□ include = файл— позволяет подключить дополнительный конфигурационный +файл, например, с другого компьютера или содержащий настройки для конкрет +ного подключающегося компьютера. В последнем случае имя файла обычно +задается в формате: путь.%т. Здесь путь— это начало полного имени файла, а +%т — NetBIOS-имя компьютера. Например: /etc/samba/smb.conf.robert; +□ socket options = TCP_NODELAY SO_RCVBUF=8192 SO_SNDBUF=8192— Задает Парамет +ры протокола и размер входного и выходного буферов. Здесь: +• t c p_n o d e l a y — позволяет быстрее (без задержек) передавать данные; +• s o_r c v bu f — размер принимающего буфера; +• s o_s n d b u f — размер передающего буфера; +□ interfaces = интерфейсы— если у вас установлены две сетевые карты, которые +направлены на разные сети, то с помощью этой директивы можно позволить +работать с Samba пользователям из какой-то конкретной или из обеих сетей; +□ bind interfaces only— позволяет привязаться только к именованным интер +фейсам или сетям, указанным в параметре interfaces. Если ваша сеть не защи +щена сетевым экраном, то настоятельно рекомендуется использовать этот параметр, чтобы доступ к серверу Samba имели только компьютеры из вашей сети, а +не любой удаленный пользователь из Интернета. +#### Замена сервера Windows +Если посмотреть на параметры, описанные в этом разделе, можно увидеть, что +Samba способна полностью заменить Windows-сервер, и рабочие станции на базе +Windows не заметят неудобств: +□ local master = yes — позволяет сделать Samba-сервер основным браузером сети; +□ domain master = yes — дает возможность сделать Samba-сервер главным брау +зером домена. Не устанавливайте значение yes, если в вашей сети уже есть кон +троллер домена Windows NT; +□ logon script = файл— если директива domain logons включена, то в этом па +раметре можно указать файл bat-сценария, который будет выполняться на ком +пьютере клиента при входе в систему. Сценарий может, например, задаваться +в виде %m.bat (заменяется именем компьютера) или %u.bat (подменяется на имя +пользователя); +□ logon path = путь — определяет место хранения пользовательских профилей. +При использовании этой директивы необходимо убрать комментарии с секции +[Profiles], которую можно найти в файле конфигурации по умолчанию. +#### Поддержка WINS и DNS +WINS (Windows Internet Naming Service, служба имен Интернета для Windows) — это +сервис для сопоставления имен компьютеров и IP-адресов. База данных WINS чем-то +напоминает DNS (Domain Name Service), только в ней хранятся NetBIOS-имена ком +пьютеров, a DNS обслуживает доменные имена. +Для настройки работы через WINS служат следующие директивы: +□ wins support ~ yes — разрешить использование WINS-сервера; +□ wins server = w.x.y.z — IP-адрес WINS-сервера; +□ d n s Proxy = yes— если эта опция разрешена, то нераспознанные NetBIOS- +имена можно попытаться определить через DNS-сервер; +□ name resolve order — последовательность, в которой будет производиться по +иск адреса хоста по его имени. В конфигурационном файле можно найти сле +дующий пример: +name resolve order = lmhosts host wins beast +Если использовать это значение, то при поиске адреса система сначала будет ис +кать имя хоста в файле lmhosts, затем в hosts, потом будет задействован файл wins +и последним — beast (broadcast, или широковещательный запрос). +#### Отображение файлов +В Linux и Windows применяются разные правила именования файлов. Например, +в Linux названия чувствительны к регистру, а в Windows — нет. Это значит, что +файлы DATA.TXT и data.txt в Windows будут восприняты как один и тот же файл, +чего не скажешь о Linux. +Для решения этой проблемы есть несколько команд: +□ case sensitive = no — не принимать во внимание различие в регистре; +□ default case = lower — отображать все файлы в нижнем регистре; +□ preserve case = по И short preserve case = no — запретить сохранение имен +файлов с учетом регистра. +Если у вас в сети работают пользователи Windows-систем, то рекомендуется оста +вить указанные значения. Для однородной Linux-сети можно разрешить сохранять +регистр. +#### Описание объектов +После определения основных параметров Samba-сервера можно описывать объек +ты, к которым может получать доступ пользователь. Это делается в отдельных сек +циях, которые идут после секции [Global], рассмотренной в разд. 6.1. +#### Пора домой +Конечно же, любой пользователь захочет работать со своим каталогом. Для этого +он должен иметь учетную запись в Linux, с которой и будет связан его каталог. Об +ращение к такому каталогу будет выглядеть так: \\сервер\имя, где сервер — это имя +сервера или его IP-адрес, а имя — это имя пользователя, домашний каталог которо +го необходимо увидеть. +Чтобы разрешить пользователям работать с собственными каталогами, нужно опи +сать раздел [homes]. Рассмотрим пример такой секции: +[homes] +comment = Home Directories +browseable = no +writable = yes +valid users = %S ; %S - строка с перечислением пользователей +create mode = 0750 +directory mode = 0775 +В этой секции могут присутствовать следующие директивы: +□ comment — текстовый комментарий, который не влияет на работу; +□ browseable = по— режим отображения ресурса в списке просмотра. Если ука +зать yes, то в сетевом окружении будут видны папки пользователей; +□ writable = yes — предикат записи в домашний каталог. При значении по созда +ние и изменение файлов станет невозможным; +□ create mode = 0750 — права доступа для создаваемых файлов. В этом случае +владелец файла имеет полные права, пользователи группы могут читать и выпол +нять его, а остальные — бесправные. Иногда следует понизить значение параметра +до 740, чтобы пользователи группы могли только читать; +□ directory mode = 0775 — права доступа для создаваемых каталогов. В этом слу +чае у пользователей группы тоже слишком высокие права, и я бы понизил их +до 755, чтобы запретить им создавать файлы в новом каталоге. Остальные мо +гут только просматривать каталог, но и это может оказаться лишним, и для боль +шей безопасности лучшим решением будет значение 750; +□ valid users = пользователи — список пользователей, которым разрешен доступ +к домашним каталогам. Значения разделяются пробелом. По умолчанию доступ +имеют все, но в реальных условиях это необходимо только некоторым. Поэтому я +рекомендую в явном виде прописать имена тех пользователей, которые нуждаются +в работе со своим домашним каталогом. +#### Доменный вход +Если вы настроили сервер Linux так, чтобы пользователи Windows могли входить +в систему через smb, используя его как сервер домена, то необходимо убрать ком +ментарии С секции [netlogon]: +; [netlogon] +; comment = Network Logon Service +; path = /usr/local/samba/lib/netlogon +; guest ok - yes +; writable = no +В этой секции также присутствуют комментарий и директива writable. Пользова +тели не должны иметь право записи в этот каталог, потому что здесь хранятся сце +нарии, которые выполняются при входе в систему. Файлы из этого каталога должен +изменять только администратор. +Помимо этого, есть еще две команды: +□ path = путь — полный путь к каталогу netlogon; +□ guest ok = yes — разрешение гостевого входа. +Также нужно убрать комментарии в секции [Profiles], которая выглядит следую +щим образом: +; [Profiles] +; path = /usr/local/samba/profiles +; browseable = no +; guest ok = yes +В этом каталоге хранятся профили пользователей, и он не должен быть виден в +сетевом окружении Windows, поэтому директива browseable = по запрещает отображение. +#### Распечатка +Чтобы пользователям стали доступны принтеры, подключенные к Linux-c^ +нужно настроить следующую секцию: +[printers] +comment = All Printers +path = /var/spool/samba +browseable = no +guest ok = no +writable = no +printable = yes +Обратите внимание, что по умолчанию секция открыта, и зарегистрированные +пользователи уже имеют доступ к принтерам. Если вы хотите, чтобы +смогли использовать принтер, то добавьте строку public = yes. Однако я не ремендую это делать, потому что такая добавка предоставит пользователям возможность для подшучивания. Например, в моей сети был случай, когда +ник рассылал на все принтеры с общим доступом разные картинки. Вроде ' +но, а работу тормозит, и бесполезно расходуются картриджи. +#### Общий доступ +Чаще всего на сервере необходим каталог, через который любой пользователь с +жет обмениваться файлами с другими участниками сети. Для настройки такого +талога служит секция [tmpj: +; [tmp] +; comment = Temporary file space +; path = /tmp +; read only = no +; public = yes +По умолчанию секция закрыта, и для разрешения доступа к ней необходимо убрать +комментарии. Обратите внимание на путь к открытому каталогу. Это /tmp — ката +лог для хранения временных файлов пользователей. С помощью директив read +only (значение по) и public (значение yes) мы указываем серверу Samba, что ката +лог открыт, и в него могут записывать и из него читать файлы все пользователи. +Несмотря на удобства, которые предоставляет этот каталог, я рекомендую исполь +зовать его только в закрытых сетях. Если у вас есть выход в Интернет, то с помо +щью сетевого экрана необходимо ограничить доступ к Samba. Так как в каталог +разрешено записывать любым пользователям, злоумышленник может записать +в него свои файлы, которые помогут ему получить на сервере права администратора. +#### Личные каталоги +Все рассмотренные ранее разделы (секции) конфигурационного файла имеют усто +явшиеся имена и предназначены для решения определенных задач. Но в некоторых +случаях вы можете изменить имя раздела, и на работе сервера это не скажется. +Однако я не рекомендую это делать, потому что в одной версии Samba все будет +работать хорошо, а при обновлении программ сервера ситуация может измениться, +и потом трудно будет найти ошибку, из-за которой Samba станет работать неверно. +Тем не менее вы можете создавать собственные разделы, в которых будете описы +вать некоторые права. Например, вам понадобится организовать общий каталог, +в котором файлы будут доступны всем пользователям, но записывать туда смогут +только пользователи определенной группы. Допустим, что в этом каталоге должны +храниться какие-то картинки. Для решения такой задачи можно создать раздел +[shareimages] следующим образом: +[share images] +comment = Share Images +path = /home/samba/images +public = yes +writable = yes +write list = @staff +printable = no +Для этого раздела задаются следующие директивы: +□ path = /home/samba/images — каталог, который МЫ ХОТИМ Открыть; +□ public = yes — признак доступности; +□ writable = yes — запись в каталог разрешена; +□ write list = Gstaff — определяет группу staff, которая может записывать +файлы. Для всех остальных каталог открыт только для чтения. +В таких объявлениях каталогов можно использовать любые директивы, которые мы +рассматривали в этой главе. +#### Управление пользователями +Для начала разберемся с именами пользователей. По умолчанию для доступа к сер +веру Samba применяются имена из системного файла /etc/passwd. Но вы можете за +вести отдельные записи Samba-сервера, которые будут соответствовать реальным, +однако их можно будет использовать только для подключения к Samba, а не к сис +теме. +Имена Samba описываются в файле /etc/samba/smbusers, расположение и название +которого могут быть изменены директивой username map файла smb.conf. Содержимое файла /etc/samba/smbusers может быть, например, таким: +```sh +# Unix_name = SMB_namel SMB_name2 +root = administrator admin +nobody = guest pcguest smbguest +``` +У списка имен несколько предназначений. Во-первых, с его помощью вы можете +отразить имена, привычные для пользователей DOS и Windows, на учетные записи +Linux. Например, в Windows максимальные права принадлежат пользователю +Administrator, а в Linux это root. И во второй строке приведенного примера уста +навливается соответствие имен administrator и admin пользователю root. +Во-вторых, использование этого файла позволяет аккумулировать несколько имен +на одной учетной записи. Например, у вас есть группа пользователей, которым +должны быть назначены одинаковые права. Для этого заводим в Linux только одну +учетную запись nobody (под ней будут работать пользователи), а в Samba прописы +ваем ДЛЯ нее неСКОЛЬКИХ пользователей: guest, pcguest И smbguest— под этими +именами они будут входить в систему. +Несмотря на то что мы отразили имена пользователей administrator и admin, и это +разные учетные записи, для них будет использоваться один пароль — назначенный +пользователю root. +Информация о пользователях, которым разрешен доступ, хранится в файле +/etc/samba/smbpasswd. Его расположение и имя могут быть изменены с помощью директивы smb passwd file файла smb.conf. \ +Рассмотрим пример содержимого файла: +fienov:0:813D6593C11F1173ED98178CA975D79:[UX ]:LCT-41FA818F +robert:500:813D6593C11F1173ED98178CA975D79:[UX ]:LCT-41FA818F +Сразу видно, что файл чем-то похож на /etc/passwd. Он также разделен двоеточиями +на несколько колонок. Наиболее интересны из них первые три: имя пользователя, +его UID в системе Linux и пароль. +Но добавлять пользователей вручную не очень удобно, потому что требуется за +шифровать и прописать пароль, что не так уж и просто. Чтобы облегчить задачу, +в пакет Samba включена утилита smbpasswd, которая принимает следующие пара +метры: +□ -а — добавить пользователя в систему. Учетная запись должна уже существо +вать в файле /etc/passwd. Например, давайте пропишем Роберта, с которым мы +уже не раз работали: +smbpasswd -a robert +В ответ на это программа попросит вас дважды ввести пароль. Указанная вами +комбинация никак не влияет на системный пароль и используется только для +доступа к Samba. Таким образом, пароли могут отличаться друг от друга. Я даже +рекомендую сделать их различными. ОС Windows умеет запоминать пароли и +хранить в своей системе, и версии Windows 9х делают это небезопасным спосо +бом. Если злоумышленник сможет украсть пароль на Samba, то он проникнет и +в систему; +□ -х — удалить пользователя. Чтобы исключить Роберта из системы, выполните +команду: smbpasswd -х robert; +□ -d — деактивировать пользователя. Если необходимо временно отключить дос +туп для пользователя, не удаляя его из системы, выполните команду smbpasswd +-d robert. Давайте посмотрим на строку, соответствующую Роберту, после вы +полнения этой команды: +robert:500:813D6593C11F1173ED98178CA975D79:[DUX ]:LCT-41FA818F +Обратите внимание, что в четвертой колонке в квадратных скобках появилась +буква D. Она как раз и указывает на то, что запись деактивирована. Так вы легко +можете определить, какие записи активны, а какие нет; +□ -е — активирЪвать пользователя. С помощью этой команды можно подключить +деактивированного ранее пользователя: smbpasswd -е robert. +Если запустить smbpasswd без параметров, то вы сможете поменять свой пароль, +и это можно будет сделать с текущими правами. Выполнение всех остальных +команд требует прав администратора, поэтому их придется выполнять с использо +ванием sudo. +Напоминаю, что файл /etc/samba/smbpasswd используется, если пароли передаются +по сети в зашифрованном виде. В этом случае, чтобы предоставить доступ к Samba +всем пользователям системы, необходимо для каждого выполнить команду: +smdpasswd. +#### Использование Samba +Сервис Samba в основном создавался для пользователей Windows, но и поклонники +Linux тоже оценили все преимущества этой технологии, тем более что ОС Linux +управляет совместным доступом к файлам по сети не хуже Windows, а в чем-то да +же лучше. Для работы с Samba из ОС Linux служит команда: smbciient. +Чтобы подключиться к серверу, необходимо указать как минимум два ключа: -L +(адрес сервера) и -и (имя пользователя). В ответ на это программа запросит ввести +пароль. Если вы не используете шифрование, то необходимо ввести системный па +роль, в противном случае воспользуйтесь тем, который вы указали при переносе +пользователя в файл /etc/samba/smbpasswd (при запуске команды smbpasswd). +Итак, выполните следующую команду для тестирования сервера: +smbciient -L localhost -U root +После ввода пароля пользователя root вы должны увидеть все открытые ресурсы +сервера. +Вы должны учитывать, что в этом списке находятся не все каталоги. Если, например, у домашних каталогов из раздела [homes] файла конфигурации директива +browseable установлена в значение по (см. разд. 6.2.7), таких каталогов видно не +будет. Это вполне логично, потому что злоумышленнику нельзя давать возмож +ность лицезреть имена каталогов, особенно если они соответствуют именам поль +зователей или содержат конфиденциальные данные. Никогда не изменяйте этот +параметр, чтобы хакер не знал, что он должен сломать. +Для подключения к открытому ресурсу сервера нужно подать команду: smbciient, +передав ей имя ресурса, которое задается в формате UNC (Universal Naming +Convention, универсальное именование объектов) с применением следующего синтаксиса: +\\ИмяСервера\ресурс +Например, вы хотите подключить домашний каталог пользователя flenov. Его адре +сом будет: +\\192.168.1. l\flenov +Но здесь надо сделать одно замечание: в Linux обратная косая черта (\) является +служебным символом, поэтому каждый такой знак должен заменяться двумя сим +волами \\ (т. е. каждый знак \ экранируется еще одним знаком \). Поскольку в на +чале UNC-имени идут два знака \\, то они заменяются на четыре обратных косых +черты, и приведенный здесь адрес должен быть введен так: +WW192.168.1. lWflenov. +Итак, для подключения к ресурсу выполняем команду: +smbclient \\\\192.168.1. lWflenov +Если вы обращаетесь к ресурсу, который требует авторизации, необходимо указать +имя пользователя, обладающего правами: +smbclient \W\192.168.1. lWflenov -U flenov +При удачном подключении к открытому ресурсу вы увидите такое приглашение: +Smb: \> +Теперь можно выполнять различные операции над файлами. Чтобы узнать, какие +команды доступны, введите директиву help или знак вопроса (?). Команды, кото +рые вы увидите, очень похожи на команды FTP (более подробно с FTP мы позна +комимся в главе 10, см. также приложение /). Для отключения от ресурса необхо +димо выполнить команду exit. + +#========================================== +### Настройка sendmail + +Основной конфигурационный файл, который вам понадобится, — `/etc/sendmail.cf` или +`/etc/mail/sendmail cf`. Если файла не существует, то, скорей всего, sendmail не установ +лен, что случается в установке дистрибутива по умолчанию. Устанавливаем: +`sudo apt-get install sendmail +После установки сразу же должна начаться автоматическая настройка. Если и по +сле этого файла конфигурации не оказалось в системе, то выполните эту команду: +sudo sendmaiIconfig +Программа начальной настройки запросит пути к конфигурационным файлам* Со +гласитесь со значениями по умолчанию, но обратите внимание, какие пути у вас +спрашивают. В последнем релизе дистрибутива Ubuntu версия sendmail предлагает +использовать путь /etc/mail/ для всех файлов. +Сервер sendmail имеет плохую репутацию в связи со сложностью его настройки. +Действительно, стоит лишь взглянуть на размер файла sendmail.cf, и становится жут +ко — это 32 килобайта (более 1000 строк). А если заглянуть внутрь файла, то станет +еще страшнее от непонятных ключей и директив. +В файле конфигурации sendmail все параметры сгруппированы по разделам. Раз +биение оформляется в виде следующих строк: +```sh +################## +# Local info # +################## +``` +Столь «красивое» оформление означает, что далее следует раздел Local info. По +добных секций несколько — в частности, обычно присутствуют такие: +□ Local info — локальная информация, основные сведения о сервере и домене; +□ options — настройки работы программы sendmail; +□ Message precedences — приоритеты сообщений; +□ Trusted users — доверенные пользователи; +□ Format of headers — форматы Заголовков. +Рассмотреть все настройки в этой книге невозможно — в моей системе этот конфи +гурационный файл занимает 50 Кбайт. Если расписывать каждый параметр, понадо +бится отдельное издание. Наша цель— эффективность и безопасность, поэтому +рассмотрим только те настройки, которые касаются этих вопросов, и испытаем +sendmail в действии. +Для упрощения конфигурирования sendmail в последних версиях этого почтового +сервиса используется новый файл — sendmail.mс (МС означает Minimal Configuration или +минимальная конфигурация), который можно найти в каталоге /etc/mail. +```sh +divert(-1) +dnl This is the sendmail macro config file. If you make changes to this +dnl file, you need the sendmail-cf rpm installed and then have to +dnl generate a new /etc/sendmail.cf by running the following command: +dnl Это файл макроконфигурации sendmail. Если вы делаете изменения в этом +dnl файле, вам нужна инсталляция rpm sendmail-cf, с помощью которой можно +dnl сгенерировать новый /etc/sendmail.cf, запустив команду: +dnl +dnl +m4 /etc/mail/sendmail.me > /etc/sendmail.cf +dnl +include('/usr/share/sendmail-cf/m4/cf .m4’) +VERSIONID('linux setup for ASPLinux’)dnl +OSTYPE('linux') +dnl Uncomment and edit the following line if your mail needs to be +dnl sent out through an external mail server: +dnl Раскомментируйте следующую строку, если вам нужно посылать почту +dnl через внешний почтовый сервер: +dnl define('SMART_HOST','smtp.your.provider') +define('confDEF_USER_ID',''8:12'')dnl +undefine('UUCP_RELAY')dnl +undefine('BITNET_RELAY')dnl +``` +Файл sendmail.me имеет более простой формат по сравнению со старым sendmail.cf, +благодаря чему уменьшается вероятность допустить ошибку при его конфигуриро +вании. После внесения изменений его необходимо скомпилировать (перевести файл +sendmail.mс в формат sendmail.cf) специальной командой, в результате чего получает +ся файл /etc/sendmail.cf. +Мы в основном будем рассматривать параметры, которые нужно устанавливать +в файле sendmail.cf, а если описывается параметр файла sendmail.mс, я буду явно указывать на это. +Иногда при неправильных настройках загрузка Linux может зависать при старте +сервиса sendmail. Это происходит из-за того, что ваш почтовый сервер не может +определить имя компьютера. Откройте файл /etc/hosts. В нем чаще всего будет в наличии только одна строка: +127.0.0.1 localhost.localdomain localhost + +Подробно с этим файлом мы познакомимся в главе 77, когда станем рассматривать +DNS. Сейчас нам надо знать, что эта строка описывает IP-адрес 127.0.0.1, который +соответствует имени localhost. В любой системе такие адрес и имя указывают на +вашу локальную машину. Когда в сетевых программах вы указываете localhost, то +это имя преобразуется в IP-адрес 127.0.0.1. +Настройка DNS может быть очень важна для правильной отправки почты. Допустим, что у меня есть домен www.flenov.ru. Этот домен может загружать веб-сайт, а +может отправлять почту, если послать что-либо на почтовый ящик info@flenov.ru +(в реальности такой ящик не существует). В обоих 'случаях домен один и тот же, но +веб-сервер и почтовый сервер могут находиться на разных серверах с разными IP. +Настройка DNS позволяет указать, какой IP-адрес у веб-сервера и какой адрес +у почтового. +Для почтового сервера нужно будет в настройки DNS добавить следующие строки: +IN MX 10 mx.flenov.ru. +mx.flenov.ru. IN АААА 1р_адрес_в_формате^6 +mx.flenov.ru. IN А 1р_адрес_в_формате^4 +Домен Fienov.ru поменяйте здесь на свой, и точка после него не случайна. +Ну и домен этот должен быть прописан в host-файле: +127.0.0.1 flenov.ru +Пока мы с DNS еще не знакомы, поэтому в sendmail можно для тестов использовать +имя компьютера, которое вы указали во время установки ОС Linux. Выполните +команду hostname, чтобы это имя узнать. В моем случае это FienovM. Но на деле все +сетевые обращения происходят не по имени, а по IP-адресу, и если sendmail не может определить адрес по имени FienovM, происходит зависание. Чтобы исправить +эту ситуацию, добавьте в файл /etc/hosts строку вида: +192.168.77.1 FlenovM +FlenovM +Не забудьте только заменить приведенный здесь параметр FienovM на имя вашего +компьютера и, разумеется, указать свой IP-адрес. После этого программу sendmail +можно помещать в автозапуск, и она будет работать великолепно даже с настрой +ками по умолчанию. +Для каждого пользователя системы автоматически создается почтовый ящик, и ес +ли сервис sendmail запущен, то им уже можно пользоваться. Все почтовые ящики +хранятся в каталоге /var/spool/mail/. Их имена соответствуют именам пользователей. +Так, для учетной записи root ящик будет расположен в файле /var/spool/mail/root. +Для работы с почтой нам нужен почтовый клиент, который будет отправлять пись +ма серверу и принимать от него новые сообщения. Таких программ существует +множество, и в некоторых дистрибутивах Linux предлагается аж семь различных +клиентов. Какой выберете вы, зависит только от личных предпочтений. +В общем-то можно обойтись и без клиента, а соединение с сервером осуществлять +напрямую через сервис Telnet, благо команды SMTP достаточно просты и ими легко пользоваться. В этом случае для отправки почты нужно подключиться к порту 25 (порт SMTP), а для получения — к 110 (порт POP3). +#### Полезные команды +Давайте рассмотрим некоторые команды, которые помогут вам в администрирова +нии sendmail-сервера: +□ hoststat— показать состояние хостов, которые недавно работали с локальным +почтовым сервером. Команда является эквивалентом sendmail -bh, которая по +умолчанию неактивна; +□ mailq — отобразить краткую информацию о сообщениях в очереди, ожидающих +обработки. Пример результата выполнения команды: + +/var/spool/mqueue (1 request) +--- Q-ID----—Size—------Q-Time----- ---- Sender/Recipient----- +jOIAnST11838 6 Tue Jan 18 13:49 <flenov@flenovm.ru> +(host map: lookup (flenovm.ru): deferred) +<root@flenovm.ru> + +Из первой строки видно, что в очереди находится одно сообщение. Вторая строка включает дату посылки и адрес отправителя: fienov@fienovm.ru. В последней +строке отображается получатель сообщения: root@f lenovm. ru; +□ mailstats — отобразить статистику сообщений и количества байтов; +□ sendmail — запустить сервер sendmail. Используя эту команду с различными +ключами, можно увидеть достаточно много полезной информации. За более +подробной справкой следует обратиться к документации: man sendmail. +#### Только отправка почты +Очень часто почтовые сервисы используют только для отправки почты. Например, +на веб-серверах sendmail может стоять лишь для того, чтобы можно было отослать +письмо прямо из сценариев на Perl или РНР. Если ваш сервер не должен принимать +писем, то необходимо запретить режим приема. Для этого откройте файл /etc/ +sysconfig/sendmail и измените его содержимое: +DEAMON=yes +QUEUE=”qlh” +Вторая строка задает параметры, которые будут передаваться программе sendmail +при запуске. Чтобы возобновить прием почты, измените значение qih на ьа. +Если у вас нет файла /etc/sysconfig/sendmail (он используется не во всех дистрибути +вах), то придется редактировать сценарий /etc/rc.d/init.d/sendmail. Найдите в этом фай +ле параметры, которые передаются программе, и измените их на qih прямо в тексте +сценария. + +Для защиты сервера от DoS-атак со стороны хакера нам помогут следующие пара +метры сервиса sendmail: +□ MaxDeamonChiidren— ограничение количества одновременно запущенных про +цессов. С помощью этого параметра мы можем защитить ресурсы сервера (про +цессор) от излишней перегрузки. По умолчанию установлено значение 12, но +для мощного компьютера его можно повысить, чтобы эффективнее использо +вать процессор, а для слабого — уменьшить; +□ connectionRateThrottie— максимальное количество открываемых соединений +в секунду. По умолчанию этот параметр равен 3, и повышать его без особой на +добности не стоит, разве что вы уверены в производительности сервера. + +#========================================== +### Локальный файл hosts + +Мы уже знаем, что изначально для сопоставления имен и адресов использовался +файл /etc/hosts. Это текстовый файл с записями типа: +127.0.0.1 localhost.localdomain localhost +192.168.77.1 FlenovM +Каждая строка — это соответствие IP-адреса его имени. По умолчанию в файле +будет всего две строки, как в приведенном примере. Первая строка — это «пет +ля». Напоминаю, что во всех компьютерах имя localhost и IP-адрес 127.0.0.1 ука +зывают на текущую машину. Это значит, что, если нужно выполнить ping, адре +совав запросы на локальный компьютер, можно написать: +ping 127.0.0.1 +Во второй записи устанавливается соответствие между IP-адресом, заданным для +вашего сетевого интерфейса, и символьным именем. Здесь моей сетевой карте при +своен адрес 192.168.77.1, И ему соответствует ИМЯ FlenovM. Это значит, что при вы +полнении команды ping можно указывать или IP-адрес, или имя компьютера. +Следующие две команды идентичны: +ping 192.168.77.1 +ping FlenovM +При выполнении второй команды сначала происходит обращение к файлу /etc/hosts, +который вернёт программе адрес 192.168.77.1 и уже на него направится эхо-запрос. +А что будет использоваться для поиска адреса первым: файл /etc/hosts или DNS-база +данных? Это зависит от настроек ОС. Посмотрим на файл /etc/host.conf. В нем нахо +дится строка: +order hosts,bind +Директива order как раз и задает порядок просмотра. В нашем случае на первом +месте находится файл /etc/hosts, и только после этого будет запущена команда bind +для выполнения запроса к DNS-серверу. Что это нам дает? А то, что можно увели +чить скорость доступа к основным серверам. Допустим, что вы каждый день посе +щаете сайт www.redhat.com. При этом каждый раз происходит запрос к DNS- +серверу, что может приводить к задержке в пару секунд перед началом загрузки +страницы. Чтобы ускорить этот процесс, можно вручную прописать в файле +/etc/hosts следующую запись: +209.132.183.105 +www.redhat.com +Вн и м ан и е! +Адрес 209.132.183.105 действительно соответствует сайту www.redhat.com на мо +мент подготовки этого издания книги, но может измениться. +Еслй сайт по каким-либо причинам перестал загружаться, то необходимо удалить +Соответствующую ЗаПИСЬ ИЗ файла hosts И С ПОМОЩЬЮ КОМаНДЫ ping redhat.com +проверить связь с сервером, а заодно и уточнить его адрес. В ответ на эту директи +ву на экране обязательно отображается реальный IP-адрес, с которым происходит +обмен эхо-сообщениями. Благо IP-адреса у большинства сайтов изменяются редко, +так что, один раз добавив такую запись в локальный файл /etc/hosts, можно сэконо +мить достаточно много времени и нервов в случае проблем с DNS-сервером, пото +му что запросов к нему отправляться не будет. +#### Внешние DNS-серверы +Если в локальном файле /etc/hosts не найдено записи о нужном имени, то компьютер +должен запросить эту информацию у DNS-сервера. Для этого нужно знать IP-адрес +этого самого сервера. Как система его узнает? Из файла /etc/resolv.conf, который +должен выглядеть примерно следующим образом: +search' FlenovM +domain domain.name +nameserver 10.1.1.1 +nameserver 10.1.1.2 + +В первой строке находится команда search с параметром (сервер поиска имени хос +та). В вашем файле, скорее всего, есть эта запись, и в качестве сервера вписано имя +вашего компьютера. Здесь может быть перечислено несколько серверов, разделен +ных пробелами или символами табуляции. Например: +search FienovM MyServer +Поиск на локальном сервере происходит довольно быстро, а вот на удаленных — +может отнять достаточно много времени. +Вторая строка содержит команду domain с параметром. Пользователи иногда любят +задавать имя компьютера без указания домена — например, redhat вместо +redhat. com. Вы должны задать полное имя узла. Чаще всего этот параметр настраи +вается в локальных сетях со специфичным именем домена. +Оставшиеся две строки содержат команду nameserv с параметром. Это внешний +DNS-сервер, которому будут направляться запросы. В системе их может быть не +сколько (в настоящее время для большинства дистрибутивов не более трех). Они +будут опрашиваться в порядке указания в файле, пока искомый адрес не будет +найден. +В большинстве случаев достаточно и одного сервера, потому что все они работают +рекурсивно. Но я рекомендую указывать два. Бывают случаи, когда один DNS- +сервер выходит из строя, тогда второй спасает положение и вступает в работу. +#### Настройка DNS-сервиса +В настоящее время наиболее распространенным сервисом DNS для Linux является +bind. Для этого сервиса существует программа bindconf, которая имеет графический +интерфейс и проста в использовании. Зайдите в графическую оболочку и в консоли +выполните команду: +bindconf & +Знак & говорит о том, что, запустив программу, консоль не должна дожидаться ее +завершения. Это очень удобно при запуске графических утилит, чтобы они не оста +навливали работу консоли. Учтите, что если закрыть окно консоли, то все про +граммы, запущенные с ключом &, тоже закроются. +На рис. 11.2 изображено запущенное приложение для настройки DNS-сервиса. +В центре показано окно, которое открывается по нажатию кнопки Добавить. Как +видите, достаточно только выбрать тип зоны и ввести имя домена — и все готово. +Несмотря на наличие простой графической программы, мы рассмотрим конфигура +ционные файлы, которые могут использоваться сервисом DNS. Их прямое редакти +рование позволит сделать более тонкую настройку, и вы лучше будете понимать +процесс работы DNS. +Настройка DNS-сервера начинается с файла /etc/named.conf. Пример его содержимо +го приведен в листинге 11.1. +```sh +options { +directory "/var/named/"; +}; +zone ”." { +type hint; +file "named.ca" ; +}; +zone "sitename.com" { +type mastery +file "sitename.zone"; +}; +zone "10.12.190.in-addr.arpa" { +type mastery +file "10.12.190.in-addr.arpa.zone"; +}; +``` +В этом примере файл разбит на четыре раздела, каждый из которых имеет следую +щий формат: +тип имя { +Параметр1; +Параметр2; +}; +Давайте рассмотрим назначение разделов. Первым идет options: +options { +directory "/vaг/named/"; +}; +В фигурных скобках только один параметр— directory, который указывает на +домашний каталог DNS-сервера. Все его файлы будут располагаться там. +Остальные разделы имеют тип zone и через пробел в кавычках содержат имя зоны. +В каждом из них по два параметра: type (определяет тип зоны) и file (файл, в ко +тором содержится описание). +Самая первая зона в нашем примере— zone ".". Что это за зона в виде точки? +Вспомните структуру DNS, которую мы рассматривали в начале главы. В базе дан +ных DNS так обозначается корень иерархии. Получается, что раздел описывает +корневую зону. Тип зоны hint, т. е. здесь хранятся лишь ссылки на DNS-cepfeepw. +Поскольку это корневая зона, то и ссылки будут на корневые серверы. +В параметре file указывается имя файла, содержащего все ссылки на корневые +серверы. В системе этого файла может и не быть, потому что информация в нем +подвержена изменениям, и лучше всего получить его последнюю версию с сервера +internic.net. Для этого выполните команду: +dig 0rs.internic.net . ns > named.са +А можно скачать этот файл непосредственно с FTP-сервера internic.net по адресу +ftp://ftp.rs.internic.net/domain/named.root и поместить в каталог /var/named. +Перейдем к следующему разделу zone "sitename.com". Здесь описывается зона +sitename.com. Тип записи master, значит, ваш DNS-сервер будет главным, а все +остальные станут только сверяться с ним и кэшировать информацию. Сведения об +этой зоне будут храниться в файле sitename.zone рабочего каталога. В нашем случае +это: /var/named. +Следующая зона описывает обратное преобразование IP-адресов 190.12.10.* в име +на. Тип записи — снова master, и в последней строке указан файл, где будет находиться описание зоны. + +#========================================== +### Журналирование + +В Linux работают одновременно сразу несколько журналов, и по содержащейся +в них информации вы сможете вычислить хакера и увидеть, когда он проник в сис +тему, а также узнать еще много интересного. Так как журналирование — один из +инструментов обеспечения безопасности, мы рассмотрим журналы достаточно +подробно. Это позволит вам лучше контролировать свои владения. +#### Основные команды +Информация о текущих пользователях системы сохраняется в файле /var/run/utmp. +Если попытаться посмотреть его содержимое, например, командой cat, то ничего +хорошего нашему взору не откроется. Этот журнал хранит данные не в текстовом, +а бинарном виде, и получение информации из него возможно только с помощью +специализированных программ (команд). +who +Команда who позволяет узнать, кто сейчас зарегистрирован в системе и сколько вре +мени он в ней находится. Информация, как уже было отмечено, извлекается из +файла /var/run/utmp. Введите эту команду, и на экране появится список примерно сле +дующего вида: +robert , ttyl +Dec 8 10:15 +root +tty2 +Dec 8 11:07 +Из этого списка становится ясно, что пользователь robert работает за первым тер +миналом (ttyl) и вошел в систему 8 декабря в 10 часов 15 минут, а через 52 минуты +со второго терминала вошел пользователь root. +Большинство хакеров при входе в систему выполняют эту команду, чтобы выяс +нить, есть ли сейчас в системе администратор. Если пользователь root присутству +ет, то начинающие хакеры часто уходят, т. к. опасаются, что их знаний не хватит, +чтобы остаться незамеченными. При этом, вероятно, потом они вернутся. +А это еще одна причина, по которой администратор не должен входить в систему +под учетной записью root. Лучше всего работать как простой пользователь, а когда +не хватает прав, то переключаться на привилегированного. На такой случай я создал +учетную запись, для которой установил UID равным нулю. Она позволяет получить +доступ ко всей системе, и при этом имеет имя, отличное от root, и не вызывает по +дозрений, когда я под ним работаю. Так что в моей системе никогда нельзя увидеть +пользователя root. +users +Команда users также позволяет вытащить из журнала /var/run/utmp список всех поль +зователей, которые сейчас зарегистрированы в системе. +В этом журнале информация хранится временно — только в период присутствия +пользователя. Когда он выходит из системы, соответствующая запись удаляется. +После этого выяснить, кто и когда работал, удастся только из журнала /var/log/wtmp. +Это также бинарный файл, и его содержимое можно увидеть с помощью специали +зированных программ. +last +Команда last позволяет выяснить, когда и сколько времени определенный пользо +ватель находился в системе. В качестве параметра ей передается интересующее +имя. Например, следующая директива отображает время входа и продолжитель +ность нахождения в системе пользователя robert: +last Robert +Выполнив команду, вы увидите на экране примерно следующий текст: +robert +ttyl +Thu Dec 2 12:17 - 12:50 (00:33) +По этой записи можно понять, что robert находился за терминалом (ttyl), зашел +в систему 2 декабря на 33 минуты (с 12:17 до 12:50). Если пользователь работал не +локально, а через сеть, то будет отображена информация о хосте, с которого он +входил в систему. +Если выполнить эту команду для себя, то может вывалиться такой список, что чи +тать его будет невозможно, потому что вы довольно часто работаете в системе. +Чтобы ограничить выводимые данные, можно указать ключ -п и количество ото +бражаемых строк. Например, следующая команда выдаст информацию о последних +пяти входах: +last -п 5 robert +history +Команда history выводит историю выполненных в консоли команд. Список может +быть достаточно большим. Вы можете ограничить список, указав после имени +команды число. Следующий пример покажет только последние 10 выполненных +в консоли команд: +history 10 +lastlog +Если выполнить команду lastlog, то она выведет на экран перечень всех пользова +телей с датами их последнего подключения к системе. Пример результата ее вы +полнения можно увидеть в листинге 12.1. +Список состоит из четырех колонок: +“1 имя пользователя из файла/etc/passwd; +□ порт или терминал, на который происходило подключение; +□ адрес компьютера, если вход был по сети; +□ время входа. +С помощью lastlog удобно контролировать системные записи. У них дата послед +него входа должна быть **Never logged in**, потому что под ними нельзя войти +в систему (в качестве командных оболочек установлены /bin/false, /dev/null, /sbin/nologin +и др.). Если вы заметили, что кто-либо вошел в систему через одну из этих учетных +записей, то это значит, что хакер использует ее, изменив настройки. +Простая замена командной оболочки в файле /etc/passwd может открыть хакеру +«потайную дверь», и администратор не заметит этой трансформации. Но после +выполнения команды lastlog все неявное становится явным. +Обращайте внимание на тип подключения и адрес. Если что-то вызывает подозре +ние, то можно выявить атаку на этапе ее созревания. +Isof +С помощью команды lsof можно определить, какие файлы и какими пользователя +ми открыты в текущий момент. Результат ее выполнения приведен в листинге 12.2. +Это далеко не полный результат. Даже если вы сейчас один работаете с системой, +количество открытых файлов может исчисляться парой десятков, и число их замет +но растет, если в системе несколько пользователей, — ведь один файл может от +крываться несколько раз каждым из них. Это касается в основном системных кон +фигурационных файлов. +#### Пользовательские журналы +Все команды, выполненные пользователем, сохраняются в файле .bash_history (если +используется интерпретатор команд /bin/bash), который находится в пользователь +ском домашнем каталоге. Когда вы определили, под какой учетной записью в сис +теме находился взломщик, его действия можно проследить по этому журналу. +Вы сможете узнать, какие команды или программы выполнялись, и эта информация +подскажет, как злоумышленник проник в систему и что изменил. Если хакер добавил +пользователя или модифицировал какой-либо важный системный файл, то вы это +увидите, сможете вернуть все в исходное состояние и тем самым быстро закрыть +«двери» в системе, которые открыл хакер. + +#========================================== +### Резервирование в Linux + +Самый простой вариант создания резервной копии — использование команды ср +(копирование файлов). Только при копировании необходимо обязательно сохранять +права доступа к файлу. Вот как может выглядеть команда, сохраняющая каталог +/home на примонтированный к системе диск /mnt/resdisk: +ср -a /home /mnt/resdisk +Здесь я использовал ключ -а, который равносилен указанию сразу трех ключей +(-dpR): +□ -d — не следовать по символьным ссылкам. Мы копируем каталог в таком виде, +как он есть; +□ -р — сохранять права доступа к файлу; +□ -R — копировать каталоги рекурсивно, чтобы на архивный диск попали все фай +лы и подкаталоги. +Получается, что команда, показанная ранее, идентична следующей: +ср -dpR /home /mnt/resdisk +С помощью этой директивы мы создаем полную копию каталога. А как можно со +хранить изменения? Для этого необходимо произвести копирование на тот же диск, +только с указанием ключа -и: +ср -аи /home /mnt/resdisk +При этом будут скопированы все файлы из каталога /home, дата изменения которых +позже, чем у документов из каталога /mnt/resdisk. +#### Утилита tar +Копировать по одному файлу не очень удобно. Лучше, когда все находится под од +ной крышей. В Linux есть утилита tar, которая собирает все файлы в один. Этот +процесс называют архивированием, но вы должны учитывать, что tar не сжимает +файлы. Если вы объединяете файлы общим размером в 2 Мбайт, то архив будет +иметь размер чуть больше (размер всех файлов плюс заголовок tar). +Преимущество сборки целого каталога в один файл состоит в том, что им проще +потом управлять и сжимать специализированными программами. +Для архивирования как минимум нужно выполнить команду: +tar cf архив.tar каталог +Здесь у нас два параметра: +П с — указывает на необходимость создания архива; +□ f — назначает архивный файл или устройство. По умолчанию используется уст +ройство /dev/rmtO. +Итак, для архивирования каталога /home выполняем следующую команду: +tar cf backup.tar /home +При использовании параметров cf в архиве сохраняется и путь к файлам. Если рас +паковать созданный нами архив, то в результате в текущем каталоге будет создан +каталог home, а в нем уже будут располагаться все пользовательские каталоги. На +пример, если запустить команду разархивирования в каталоге /home, то каталоги +пользователей окажутся в /home/home, а если находиться в /etc, то пользователи уви +дят свои каталоги в /etc/home. +Чтобы добиться правильного результата, нужно перейти в корень диска и выпол +нять команду там: +cd / +tar xf /home/backup.tar +Здесь мы из корневого каталога разархивируем резервную копию, которая находит +ся в файле /home/backup.tar. +Помимо этого, для архивных операций могут пригодиться следующие ключи ути +литы tar: +□ v— вывести на экран информацию об архивируемом (или распаковываемом) +в данный момент файле; +□ z — найти и обработать при распаковке GZIP-архивы; +□ р — разархивировать всю информацию о безопасности; +□ d — найти различия между архивом и файлами системы; +О t — просмотреть содержимое архива; +□ и — обновить файлы в архиве, которые были изменены; +□ n date — добавить в архив только те файлы, которые изменены позже указанной +даты. Параметр date должен быть заменен необходимой датой; +□ р — не удалять первый символ /. В этом случае, в каком каталоге вы ни запусти +те разархивирование, файлы попадут на свое родное место. +С помощью утилиты tar можно архивировать сразу несколько каталогов. Следую +щая команда помещает в архив каталоги /home и /etc: +tar cf backup.tar /home /etc +Чтобы просмотреть содержимое архива, можно выполнить команду: +tar tvf backup.tar +В ответ на это на экране будут показаны все файлы и каталоги архива, их права +доступа и владельцы. +Посмотрите на последнюю колонку, где показано расположение файла. Обратите +внимание, что путь не начинается с символа /, указывающего на корень диска. По +этому такой архив нужно распаковывать в корне, иначе он будет создаваться +в текущем каталоге. +С помощью tar очень удобно и легко создавать резервные копии, которые содержат +только изменения. Для этого мы просто задаем ключ -д и указываем какой-то файл, +который будет использоваться для отслеживания того, какие файлы уже резервиро +вались и какая версия уже находится в архиве. +Давайте рассмотрим все это на примере: +tar -cf backupO.tar -g home-flenov.snar /home/flenov +Здесь: +□ c — указывает на необходимость создания архива; +□ f — указывает на то, что мы хотим задать определенное имя файла и это имя +идет сразу после ключа: backupO.tar; +□ g — указывает на то, что нужно копировать только изменения. После этого клю +ча надо указать файл, где будут храниться изменения. В качестве его имени я +использую полный путь к нему, просто вместо слэшей указываю символ тире. +Я заметил, что очень часто таким файлам дают расширение snar. Я точно не +знаю, но мне кажется, что это объединение двух слов: sn, которое указывает +на слово snapshot (снимок), и аг, которое указывает на архив tar. Возможно, я тут +не прав, но в любом случае расширение может быть и другим; +□ ну и в конце идет каталог, резервную копию которого вы хотите создать. +Запустите команду: tar создаст полную резервную копию, если файл flenov.snar не +существует. +Теперь попробуйте изменить пару файлов или создать новый и снова выполнить +команду: +tar -cf backupl.tar -g home-flenov.snar /home/flenov +Я увеличил число в имени файла архива с 0 до 1. В этот файл уже попадут только +изменения с момента последней резервной копии. +#### Утилита gzip +В ОС Linux есть достаточно много различных утилит для упаковки данных. Наибо +лее популярной из них является gzip. Преимущество архивирования перед простой +упаковкой данных заключается в том, что результирующая копия занимает меньше +места, а значит, носитель для резервирования понадобится меньшего объема. +Чаще всего копируются документы, размер которых в заархивированном виде мо +жет уменьшаться на 90%. Текстовые данные сжимаются намного лучше, чем про +граммы. +Недостаток архивирования — возрастает нагрузка на процессор, и может потребо +ваться больше времени на создание полной копии. Однако за счет того, что архив +занимает намного меньше места, его копирование на сетевые ресурсы или запись +на съемные носители (ZIP, JAZ, CD-R/RW, DVD-R/RW и др.) производится быст +рее. В итоге может выйти, что затраты времени на копирование и процессорного +времени на архивирование окажутся идентичны затратам на копирование без архи +вирования или даже меньше. +Прежде чем сжимать какой-либо файл, рекомендуется подготовить TAR-архив. По +том достаточно выполнить команду упаковки: +gzip -уровень файл.tar +В качестве ключа -уровень нужно указать степень компрессии. Максимальный уро +вень равен 9. После этого указывается имя TAR-архива. Давайте сожмем архивный +файл, который мы создали из каталога /home, применяя наибольшую компрессию. +Выполните следующую команду: +gzip -9 backup.tar +Теперь просмотрите содержимое каталога (команда is). Обратите внимание, что +файла backup.tar больше нет. Вместо него появился файл backup.tar.gz, размер кото +рого значительно уменьшился. +Чтобы разархивировать такой файл, можно пользоваться все той же командой tar, +только необходимо указать ключи xf z: +cd / +tar xfz /home/backup.tar. gz +Эта команда сначала разархивирует GZ-файл и тут же распакует TAR-архив. +Если необходимо из GZ-файла снова получить TAR-архив (без его распаковки), то +можно выполнить команду: +gzip -d /home/backup.tar.gz +После этого вы снова увидите файл backup.tar, а файл backup.tar.gz исчезнет. +Теперь вы готовы написать свой сценарий, который будет собирать каталоги для +архивирования в TAR-файл, а затем сжимать его, чтобы уменьшить размер. Но +можно не использовать две команды, а обойтись одной. Вот как это делается: +tar cvf - /home | gzip -9с > backup.tar.gz +Здесь мы собираем в TAR-архив каталог /home и тут же сжимаем его утилитой gzip. +Помимо gzip для архивирования иногда используется утилита compress, но ее воз +можности по сжатию ниже, и к тому же вокруг нее возникали скандалы и разбира +тельства по поводу лицензии. Большинство администраторов уже перешли на ис +пользование gzip, и я вам рекомендую с самого начала привыкать к этой программе. +#### Утилита dump +Все предыдущие команды, которые мы рассматривали в этой главе, не являются +специализированными командами резервирования. Это просто команды копирова +ния и архивирования файлов. А вот утилита dump предназначена именно для соз +дания резервной копии файловой системы Ext (поддерживаются версии, начиная со +второй). +Для выполнения резервной копии нужно как минимум указать: +□ -п — уровень резервной копии, где п может изменяться от о до 9. При значе +нии о создается полная резервная копия. Уровни выше о означают формирова +ние резервной копии изменений, произошедших с момента последней полной +резервной копии или создания копии с меньшим уровнем; +□ -и— требование при удачном завершении резервирования обновить файл +/etc/dumpdates, в котором хранятся даты создания резервных копий; +□ -f файл— имя файла или устройство, на которое нужно производить резервное +копирование. +Итак, простейшая команда создания полной резервной копии выглядит следующим +образом: +dump -Ou -f /home/backup.bak +Для сохранения изменений нужно изменить уровень, указав значение больше нуле +вого, например: +dump — lu -f /home/backup.bak +Для восстановления файлов из архива служит команда restore. Но прежде чем ее +запускать, вы должны убедиться, что находитесь в каталоге, который принадлежит +восстанавливаемой файловой системе. +В команде restore достаточно только указать ключ -f и файл, который нужно вос +становить. Если применить ключ -i, то вы попадаете в интерактивный режим, +в котором можно задать файлы для восстановления. Интерактивный режим похож +на режим командной строки, только вместо реальной файловой системы в нем вы +путешествуете по архиву. При этом можно выполнять следующие директивы: +□ help — вывести краткую помощь по доступным командам; +□ is — отобразить содержимое текущего каталога; +□ pwd — показать текущий каталог; +□ add каталог — добавить в список для восстановления указанный в качестве ар +гумента каталог; +□ cd — сменить текущий каталог; +□ delete каталог — удалить из списка восстановления каталог, указанный в каче +стве параметра; +□ extract — восстановить все файлы из созданного списка; +□ quit — ВЫХОД. + +#========================================== +### CHAGE + +Как заставить пользователя менять пароли через определенные промежутки време +ни? Нет, не нужно ходить и нудить ему над ухом — есть способ проще и намного +эффективнее. В Linux имеется утилита chage, которая запускается следующим об +разом: +chage параметры пользователь +В качестве параметра можно указывать следующие ключи: +□ -mN— минимальное число дней (n ) д о смены пароля. Указав это значение чуть +меньше, чем максимальный период (см. следующий параметр), вы защитите +систему от нежелательной смены паролей. Это значит, что если хакер захватит +учетную запись, он не сможет изменить пароль. Конечно же, злоумышленник +тоже может выполнить команду chage, но только если у него есть права админи +стратора. Три-четыре дня разницы между минимальным и максимальным значе +нием необходимы для того, чтобы пользователь смог сменить пароль, пока он не +устарел. Устанавливать разницу меньше трех дней нежелательно, потому что +существуют выходные, и если срок действия попадет на воскресенье, пользо +ватель не успеет сменить пароль. По умолчанию используется значение -1, что +соответствует отсутствию проверки; +□ -м N — максимальный диапазон в днях (n ), в течение которого действует пароль. +После этого пароль считается недействительным, и пользователь не сможет вой +ти в систему. По умолчанию установлено 99999, что соответствует бесконечно +сти, а значит, пароль никогда не устареет; +□ -d N— дата последнего изменения пароля. Параметр N указывает количество +дней начиная с 1 января 1970 года. Если установить юоо, то получится 27 сен +тября 1972 года. Чтобы не высчитывать дату в днях, можно указать ее явным +образом в формате ГГГГ-ММ-ДЦ; +□ -Е дата — дата окончания действия пароля; +□ -I N— период в днях, после которого неиспользуемая учетная запись блокиру +ется. Рекомендую указать не менее 3 и не более 4 дней, чтобы приостановить +действие записи на время отпуска или болезни работника; +□ -w n — количество дней до окончания срока действия пароля, когда пользовате +лю будет выводиться предупредительное сообщение. Нежелательно указывать +менее 3 дней, чтобы не попасть на выходные; +□ -1 пользователь — с этим параметром команда может вызываться любыми поль +зователями и позволяет им узнать информацию о времени жизни их пароля. +Чтобы получить сведения о пароле root, выполните директиву: +chage -1 root. +Результат выполнения команды имеет следующий вид: +Password Expires: Never +Password Inactive:Never +Minimum: +-1 +Maximum: +99999 +Warning: +Inactive: +-1 +-1 +Last Change: +Feb 04, 2004 +Account Expires: +Never +Здесь отображаются следующие значения: +• Minimum — минимальный срок действия пароля; +• Maximum— максимальный период для пользования паролем; +• warning— количество дней, за которые будет выдаваться предупреждение +о завершении срока действия пароля; +• inactive— максимальное количество дней, в течение которых учетная за +пись может не активироваться; +• Last change — последняя дата изменения; +• Password Expires — дата окончания действия пароля; +• Password inactive — дата, когда пароль стал неактивным; +• Account Expires — дата окончания действия учетной записи. +Чтобы задать максимальное количество дней жизни пароля в 60 дней, выполните +команду: +chage -М 60 robert +Слишком частая смена паролей приводит к тому, что пользователи просто не успе +вают запомнить их. Из-за этого сложные комбинации начинают писать на бумаге, +чтобы случайно не забыть, или просто меняют пароль на старый. Ваша задача — +контролировать замену, и в то же время не стоит заставлять пользователя делать +это слишком часто. Период в 2-3 месяца (или 60-90 дней) считается вполне прием +лемым. +А как проверить, что выбран достаточно сложный пароль и при этом не указан снова +старый? В этом нам поможет РАМ-модуль pam_cracklib.so, который выполняет +основные проверки и позволяет сделать их сложнее. Например, нельзя будет устано +вить старый пароль или воспользоваться большей его частью. + +#========================================== diff --git a/LINUX/iso fs _ ISO9660.md b/LINUX/iso fs _ ISO9660.md new file mode 100644 index 0000000..0ca6fa5 --- /dev/null +++ b/LINUX/iso fs _ ISO9660.md @@ -0,0 +1,46 @@ +Так, а чем по своей сути вообще является iso файл? Грубо говоря, это несжатый архив с точной копией данных блочного устройства **(hdd, ssd, cd)** в формате файловой системы **ISO9660**: + +`file linuxmint-22.1-xfce-64bit.iso + +linuxmint-22.1-xfce-64bit.iso: ISO 9660 CD-ROM filesystem data (DOS/MBR boot sector) ... (bootable) + +В зависимости от содержимого образ может быть загрузочным и не загрузочным. Давайте из приведенного выше описания iso-шника выведем возможные способы работы с ним: +1) он представляет собой архив - его можно **распаковать**; +2) он содержит файловую систему ISO9660 - его можно **монтировать**; + +Начнем с "**распакоуки**". Для этого можем воспользоваться утилитой **7z**: + +`sudo apt install p7zip-full +$ 7z x linux*.iso -o./extract + +После завершения процесса мы вольны вносить в файловую структуру свои коррективы. После правок нужно будет заново собрать установочный iso образ. Для этого есть утилитка **mkisofs**, которая корректно собирает **ISO9660** архивы из файлов и каталогов. + +**Второй способ** - монтировать образ и скопировать его содержимое. + +В привычном для нас понимании, монтирование относится к блочным устройствам **(usb, ssd, hdd)**, которые содержат определенную файловую систему **(exFAT, ext, zfs)**. Такой механизм позволяет нам получить доступ к содержимому носителей. + +Но вот в чем дело, **iso** файл не является **блочным устройством** и его нельзя просто так примонтировать... Ничего, есть решение - механизм **loop device**. + +**Loop device** — это виртуальное блочное устройство, которое позволяет использовать обычный ресурс с файловой системой, как физический девайс. Через него можно смонтировать образ и получить доступ к его файловой системе. + +Посмотреть то, какое устройство с каким ресурсом соотносится, можно через команду **losetup**: + +`sudo losetup -a + +/dev/loop13: [64512]:2672215 (/path/to/output.iso) + +Для того, чтобы создать и монтировать "**loop device**", используем **mount**: + +$ sudo mount -o loop test.iso ./mnt_point + +Теперь, т.к. файловая система **ISO9660** является "**read-only**", нам нужно скопировать все содержимое образа в отдельный каталог: + +`mkdir new_mnt_point + +`cp -rT mnt_point new_mnt_point + +Далее "по уму" редактируем образ и пересобираем его с помощью **mkisofs**: + +`sudo mkisofs -o ./new.iso -quiet -R -b boot/grub/bios.img -no-emul-boot -boot-load-size 4 -boot-info-table ./new_mnt_point + +Если все сделано правильно, на выходе мы получим "**bootable**" образ.
\ No newline at end of file diff --git a/LINUX/tr.md b/LINUX/tr.md new file mode 100644 index 0000000..34a584a --- /dev/null +++ b/LINUX/tr.md @@ -0,0 +1,84 @@ +**Прокачиваемся в tr** + +Более глубокий взгляд на tr +`cat file | tr [:lower:] [:upper: ] + +Сегодня у нас на разборе одна из культовых утилит "**tr**". Используется она для форматирования текста: удаления и замены символов. + +Чаще всего можно встретить в паре со сторонними командами, которые выводят в **stdout** текстовый поток: **cat, head, echo** и т.д. + +Команда имеет следующий вид: + +$ tr флаг SET1 [SET2] + +Сетами представлены наборы символов, с которыми **tr** будет работать. Второй набор не всегда используется. + +Команда разбирает текст посимвольно и выполняет операцию для каждого элемента отдельно. + +Флаги команды tr: +1) **-d (--delete)**: удаляет элементы из первого набора: +$ echo "Hello 123 world" | tr -d ' a-zA-Z' +123 +1) **-c (--complement)**: обработать каждый символ, который не входит в набор "**SET1**": +$ echo "Hello 123 world" | tr -cd 'a-zA-Z\n' +Helloworld + +В примере мы удалили все символы, которые не входят в перечисление '**a-zA-Z\n**'; + +Таким же макаром можно все буквы удалить и оставить только цифры: + +$ cat customer.csv + +USA,1234567890 + +Canada,0987654321 + +$ cat customer.csv | tr -cd '0-9\n' + +1234567890 + +0987654321 + +1) **-s (--squeeze-repeats)**: cжимает повторяющиеся символы из первого набора до одного: + +$ echo "Hello World" | tr -s ' ' + +Hello World + +1) **-t (--truncate-set1)**: обрезает первый набор до длины второго: + +$ echo "abcdef" | tr -t 'abcdef' '123' + +123def + +Если длина первого набора больше и мы не указываем флаг, каждый последующий его символ, который (**по номеру > конец "SET2"**), будет заменен: + +$ echo "abcdef" | tr 'abcdef' '123' + +123333 + +При своем дефолтном поведении, программа заменяет символы из "**SET1**" на элементы "**SET2**": + +$ echo "Hello World" | tr 'a-z' 'A-Z' + +HELLO WORLD + +Команда умеет парсить шаблоны: **[:upper:], [:lower:]** и т.д. Детальнее можно ознакомиться в мануале: + +$ echo "hello world" | tr [:lower:] [:upper:] + +HELLO WORLD + +Данные на вход можно получить и через файлик. Для этого мы используем перенаправление: + +$ cat infile + +www.baeldung.com + +$ tr 'a-z' 'A-Z' < infile + +WWW.BAELDUNG.COM + +Измененные данные нужно куда-то записать? Правильно, перенаправляем вывод: + +$ tr 'a-z' 'A-Z' < infile > outfile
\ No newline at end of file diff --git a/LINUX/Автоматическое обнаружение подозрительных процессов в Linux.md b/LINUX/Автоматическое обнаружение подозрительных процессов в Linux.md new file mode 100644 index 0000000..4e359d0 --- /dev/null +++ b/LINUX/Автоматическое обнаружение подозрительных процессов в Linux.md @@ -0,0 +1,27 @@ +Этот скрипт помогает находить потенциально вредоносные или несанкционированные процессы, которые маскируются под системные, используют необычные порты или расположены в нетипичных путях. +```sh +#!/bin/bash + +echo "Поиск подозрительных процессов..." + +# Процессы, запущенные из /tmp, /dev, /run — подозрительно +echo "Процессы из временных директорий:" +ps axeo pid,comm,args | grep -E '/(tmp|dev|run)/' | grep -v grep + +# Процессы без имени (заглушки или rootkit-обфускация) +echo -e "\n❓ Процессы без имени:" +ps -eo pid,comm | awk '$2 == "[]" || $2 == ""' + +# Подозрительные net-соединения (в обход ssh, http, https) +echo -e "\n🌐 Нетипичные входящие соединения:" +sudo lsof -i -nP | grep LISTEN | grep -Ev ':(22|80|443)' | grep -v "COMMAND" +``` + +🟢 Ищет процессы в нестандартных директориях (/tmp, /dev) +🟢 Показывает безымянные процессы — возможные признаки rootkit +🟢 Выводит порты, на которых слушают нетипичные демоны + +➡️ **Можно добавить в crontab для ежедневного обновления**: + +`0 * * * * /path/to/proc_check.sh >> /var/log/proc_scan.log + diff --git a/LINUX/Автоматическое отключение зависших процессов в Linux.md b/LINUX/Автоматическое отключение зависших процессов в Linux.md new file mode 100644 index 0000000..bb7ee74 --- /dev/null +++ b/LINUX/Автоматическое отключение зависших процессов в Linux.md @@ -0,0 +1,20 @@ +Этот скрипт проверяет системную нагрузку, выявляет зависшие процессы (с высоким временем работы) и автоматически завершает их, если они мешают работе системы. +```sh +#!/bin/bash + +# Пороговое время работы процесса (в секундах) +THRESHOLD=600 + +# Поиск и завершение зависших процессов +ps -eo pid,etimes,comm --no-headers | while read pid etime command; do + if [ "$etime" -gt "$THRESHOLD" ]; then + echo "Процесс $command (PID $pid) завис! Завершаем..." + kill -9 "$pid" + fi +done +``` + +🟢 Скрипт автоматически ищет процессы, которые работают дольше указанного времени, и принудительно завершает их. +🟢 Полезен для предотвращения зависания системы из-за долгих или неответственных процессов. + +Можно расширить функционал и добавить отправку уведомлений в Telegram, чтобы контролировать, какие процессы были завершены.
\ No newline at end of file diff --git a/LINUX/Безопасность Linux - простые шаги.md b/LINUX/Безопасность Linux - простые шаги.md new file mode 100644 index 0000000..d6da61c --- /dev/null +++ b/LINUX/Безопасность Linux - простые шаги.md @@ -0,0 +1,29 @@ +Linux сам по себе безопасная система, но небольшие улучшения могут значительно повысить её защиту. Эти команды и практики помогут вам укрепить систему. + +**Обновить пакеты и установить критические исправления** +`sudo apt update && sudo apt upgrade + +**Включить автоматическую установку обновлений безопасности** +`sudo apt install unattended-upgrades +`sudo dpkg-reconfigure unattended-upgrades + +**Установить и настроить брандмауэр UFW** +`sudo apt install ufw +`sudo ufw enable +`sudo ufw default deny incoming +`sudo ufw default allow outgoing +`sudo ufw allow ssh + +**Проверить активные сетевые соединения** +`sudo lsof -i -P -n | grep LISTEN + +**Проверить наличие rootkit'ов** +`sudo apt install rkhunter +`sudo rkhunter --check + +🟢 unattended-upgrades автоматически ставит обновления +🟢 ufw защищает от несанкционированного доступа +🟢 rkhunter помогает находить потенциальные угрозы + +🗣 Регулярная проверка и настройка безопасности особенно важна для серверов и публичных систем. + diff --git a/LINUX/Быстро посмотреть версию и название дистрибутива linux.md b/LINUX/Быстро посмотреть версию и название дистрибутива linux.md new file mode 100644 index 0000000..7a44420 --- /dev/null +++ b/LINUX/Быстро посмотреть версию и название дистрибутива linux.md @@ -0,0 +1,4 @@ +а так же на базе чего он построен. + +==`cat /etc/*release* + diff --git a/LINUX/Исправить USB, смонтированную как read-only в Linux.md b/LINUX/Исправить USB, смонтированную как read-only в Linux.md new file mode 100644 index 0000000..b74f25d --- /dev/null +++ b/LINUX/Исправить USB, смонтированную как read-only в Linux.md @@ -0,0 +1,39 @@ +Иногда Linux монтирует флешку или диск в режиме только для чтения (`read-only`), что блокирует любые операции записи. Вот пошаговое решение: + +🔹 Шаг 1: Проверка и исправление файловой системы + +`sudo umount /dev/sdX1 +`sudo fsck -f /dev/sdX1 + +Замените sdX1 на имя вашего устройства (узнать можно через `lsblk`). + +🔹 Шаг 2: Примонтировать с правами на запись + +`sudo mount -o remount,rw /dev/sdX1 + +🔹 Шаг 3: Физическая защита от записи + +Некоторые USB-накопители имеют переключатель блокировки записи. Убедитесь, что он отключён. + +🔹 Шаг 4: Аппаратная блокировка из-за износа + +Если dmesg показывает: + +Write-protect is on + +— это может означать, что флешка изношена и контроллер принудительно заблокировал запись. Здесь помочь может только замена. + +🔹 Шаг 5: Очистка и форматирование (если данные не важны) + +`sudo wipefs -a /dev/sdX +`sudo mkfs.vfat /dev/sdX1 + +📌 Быстрая таблица + +| 🚩 Проблема | 💡 Решение | +|-------------------------------|----------------------------------------| +| Ошибки в файловой системе | `fsck` и перемонтирование | +| Случайный `read-only` | `mount -o remount,rw` | +| Защита от записи физическая | Проверить переключатель | +| Аппаратная блокировка | Устройство под замену | +| Данные не важны | `wipefs` + `mkfs` | diff --git a/LINUX/Как разбить файл на части.md b/LINUX/Как разбить файл на части.md new file mode 100644 index 0000000..88b812e --- /dev/null +++ b/LINUX/Как разбить файл на части.md @@ -0,0 +1,71 @@ +В некоторых случаях один файл нужно разбить на несколько частей, например, для загрузки либо копирования в случае слишком большого размера. Проще всего это сделать с помощью утилиты ==split. + +Команда используется для разбивки файлов по размеру, количеству строк и на определенное количество составных частей. +### Разбить по размеру + +Для этого используем опцию "-b", определяющую максимальный размер элемента. За основу возьмем файлик в 721 байт. Его нужно разбить на части по 100 байт. Для удобства зададим префикс имени "file-part_": + + +`$ ls -l +`721 Apr 20 13:11 file_orig + + +`$ split -b 100 ./file_orig ./file-part_ + + +Помимо оригинала появятся еще 8 файлов: + + +`$ ls -l +`721 Apr 20 13:11 file_orig +`100 Apr 20 13:12 file-part_aa +`... +`21 Apr 20 13:12 file-part_ah + + +Разбить по количеству строк + +Может быть нужно разбить один текстовый документ на несколько, с количеством строк "<= n". В этом вопросе полезной окажется опция "-l". Команда поделит большой log-файл на части по 10 тысяч строк в каждом: + + +`$ split -l 1000 ~/Logs/log ~/Logs/log-part_ + +### Разбить на определённое количество файлов + +Еще одна интересная задача, в решении которой поможет опция "-n". Размер будет, по возможности, поделен поровну. Достаточно прописать итоговое количество файлов и запустить команду: + + +`$ split -n 3 ./file_orig ./file-part_ + + +`$ ls -l +`240 Apr 20 13:31 file-part_aa +`240 Apr 20 13:31 file-part_ab +`241 Apr 20 13:31 file-part_ac +`721 Apr 20 13:11 file_orig + + +Настройка имени частей файла + +Как мы уже поняли, для split префикс определяет название части файла, после чего идет дефолтный суффикс из двух латинских букв. + +С помощью опций можно изменить его длину "-a", переключиться на числа "-d" или hex-символы "-x". В последних двух сценариях получится выбрать начальную точку отчета "--numeric-suffixes" для чисел и "--hex-suffixes" для hex-символов. + +Возьмем задачу – разделить текстовый файл на 3 части равного размера, чтобы каждый из них имел префикс "file-part_" и числовой суффикс из одного символа, начиная с 1: + + +$ split -a 1 --numeric-suffixes=1 -n 3 ./file_orig ./file-part_ + + +$ ls -l +240 Apr 20 13:36 file-part_1 +240 Apr 20 13:36 file-part_2 +241 Apr 20 13:36 file-part_3 + + +Объединение частей файла + +Следующий шаг – объединение нескольких частей в единый файл. Для этих целей отлично подойдет утилита cat. Сначала нужно задать имена частей, потом итоговый файл: + + +`cat ~/Archive/split-archive.part_* > ~/Archives/cat-archive.tar.gz diff --git a/LINUX/Как удалять ссылки.md b/LINUX/Как удалять ссылки.md new file mode 100644 index 0000000..404e9a9 --- /dev/null +++ b/LINUX/Как удалять ссылки.md @@ -0,0 +1,28 @@ +В общем-то, задача довольно примитивная, но и пост получится не гигантский. Посмотрим на несколько команд, которые помогут более эффективно расправляться с ссылками. + +Обычно мы как проверяем ссылку - выполняем "ls -l" и смотрим на тип: + +`$ ls -l +`lrwxrwxrwx ... sftp-server -> openssh/sftp-server + + +Если будем работать через find, руками тип смотреть не придется. Для фильтрации по символьным ссылкам у нас есть опция "-type l": + + +`$ find /usr/lib -type l +`/usr/lib/gvfs/gvfsd-afp +`/usr/lib/libmultipath.so + + +Если мы переживаем, что удалим что-то лишнее, можем проверить путь к файлу, на который указывает ссылка. Для этого нужно для вывода команды выполнить "-exec ls -l {} \": + + +`$ find /usr/lib -type l -exec ls -l {} \; +`/usr/lib/gvfs/gvfsd-afp -> ../../libexec/gvfsd-afp +`/usr/lib/libmultipath.so -> libmultipath.so.0 + + +Напоследок разберемся с поиском и удалением битых ссылок, которые никуда не ведут. Для такой цели подойдет параметр "-xtype l": + + +`$ find ~/broken_links/ -xtype l
\ No newline at end of file diff --git a/LINUX/Логи в Linux.md b/LINUX/Логи в Linux.md new file mode 100644 index 0000000..6357dc5 --- /dev/null +++ b/LINUX/Логи в Linux.md @@ -0,0 +1,226 @@ +# Как разбирать: journalctl, grep, awk и sed + +Каждый админ хотя бы раз сталкивался с ситуацией, когда сервер внезапно начинает тупить: подвисают процессы, появляются странные задержки, что‑то перестаёт работать. И первая остановка в таком случае — это журналы событий. В Linux системные логи хранятся в **systemd‑journald**, а его главный инструмент для работы — `journalctl`. Но просто читать логи — это скучно. Разберем, **как фильтровать, искать ошибки, анализировать данные и автоматизировать разбор логов** с помощью `grep`, `awk`, `sed` и других утилит. + +#### Работа с journalctl + +Самый простой способ заглянуть в системные журналы — просто вызвать `journalctl`: + +``` +journalctl +``` + +Но если сервер работает долго, логов там будет много. Выведем их без постраничного режима: + +``` +journalctl --no-pager +``` + +Если команда зависает, скорее всего, журнал слишком большой. Ограничим его по времени, например, **выведем логи за последний час**: + +``` +journalctl --since "1 hour ago" +``` + +Теперь можно быстро отследить недавние события. + +#### Как понять, что произошло после перезагрузки? + +Если сервер неожиданно перезагрузился, полезно посмотреть, что записалось в логи **после старта**: + +``` +journalctl -b +``` + +Если система несколько раз перезагружалась, можно посмотреть события после конкретного запуска, например, **минус первый перезапуск**: + +``` +journalctl -b -1 +``` + +Это поможет понять, что произошло перед падением системы. + +#### Фильтрация по сервису + +Чтобы разобраться в проблемах конкретного сервиса, например, `nginx`, используем флаг `-u`: + +``` +journalctl -u nginx.service --no-pager +``` + +Пример вывода: + +``` +Feb 02 10:15:00 myserver nginx[1234]: Starting nginx: [OK] +Feb 02 10:16:00 myserver nginx[1234]: 502 Bad Gateway +``` + +Можно дополнительно **сортировать логи от новых к старым**: + +``` +journalctl -u nginx.service --reverse +``` + +#### grep для поиска ошибок + +Журналы бывают огромными, но нам нужны только ошибки. Отфильтруем их с помощью `grep`: + +``` +journalctl -u nginx.service | grep "error" +``` + +Пример вывода: + +``` +Feb 02 11:20:00 myserver nginx[5678]: error: failed to bind socket +Feb 02 11:22:00 myserver nginx[5678]: error: no permission to access /var/www/html +``` + +Чтобы **не зависеть от регистра**, используем `-i`: + +``` +journalctl -u nginx.service | grep -i "error" +``` + +Теперь `ERROR`, `Error` и `error` будут найдены. + +Иногда важно видеть не только саму ошибку, но и контекст вокруг. Например, **две строки до и после найденного слова**: + +``` +journalctl -u nginx.service | grep -A 2 -B 2 "error" +``` + +Так можно увидеть, что происходило **до и после ошибки**. + +#### Выбираем нужные данные с awk + +Журналы содержат много лишней информации. Например, оставим **только дату, время и сообщение**: + +``` +journalctl -u nginx.service | awk '{print $1, $2, $3, $6}' +``` + +Вывод: + +``` +Feb 02 12:00:00 Starting +Feb 02 12:05:00 error: +Feb 02 12:10:00 Listening +``` + +Если сообщения длинные, можно **обрезать текст**: + +``` +journalctl -u nginx.service | awk '{print substr($0, index($0,$6))}' +``` + +Это оставит только само сообщение без метаданных. + +#### Очистка логов с sed + +Некоторые логи содержат ненужные теги или мусорные символы. Например, уберём всё, что находится в квадратных скобках `[INFO]`: + +``` +journalctl -u nginx.service | sed 's/\[.*\]//g' +``` + +Исходный лог: + +``` +Feb 02 13:30:00 myserver nginx[1234]: [INFO] Server started +``` + +После обработки: + +``` +Feb 02 13:30:00 myserver nginx[1234]: Server started +``` + +Также можно **заменить текст**, например, заменить `error` на `!!!ERROR!!!` для выделения: + +``` +journalctl -u nginx.service | sed 's/error/!!!ERROR!!!/g' +``` + +#### Автоматический сбор ошибок + +Допустим, мы нужно **каждый день автоматически собирать ошибки Nginx** и записывать их в файл. Используем **crontab** и скрипт: + +``` +journalctl -u nginx.service --since "1 day ago" | grep -i "error" | awk '{print $1, $2, $3, $6, $7, $8}' > /var/log/nginx_errors.log +``` + +Этот код: + +1. Берёт логи Nginx за последние 24 часа. +2. Ищет строки с `"error"`. +3. Оставляет только дату, время и сообщение. +4. Записывает результат в `/var/log/nginx_errors.log`. +#### Вывод логов в реальном времени + +А если хочется **смотреть логи в реальном времени**, то спользуйте флаг `-f`: + +``` +journalctl -u nginx.service -f= +``` + +Это как `tail -f`, но для journalctl — вы сразу увидите, если сервис упадёт. + +===================================== +## Расположение логов по умолчанию + +Большинство файлов логов Linux находятся в папке **/var/log/** вы можете список файлов логов для вашей системы с помощью команды ls: + + `ls -l /var/log/` + +Ниже мы рассмотрим 20 различных файлов логов Linux, размещенных в каталоге **/var/log/**. Некоторые из этих логов встречаются только в определенных дистрибутивах. + +- **/var/log/messages** - содержит глобальные системные логи Linux, в том числе те, которые регистрируются при запуске системы. В этот лог записываются несколько типов сообщений: это почта, cron, различные сервисы, ядро, аутентификация и другие. +- **/var/log/dmesg** - содержит сообщения, полученные от ядра. Регистрирует много сообщений еще на этапе загрузки, в них отображается информация об аппаратных устройствах, которые инициализируются в процессе загрузки. Можно сказать это еще один лог системы Linux. Количество сообщений в логе ограничено, и когда файл будет переполнен, с каждым новым сообщением старые будут перезаписаны. Вы также можете посмотреть сообщения из этого лога с помощью команды dmseg. +- **/var/log/auth.log** - содержит информацию об авторизации пользователей в системе, включая пользовательские логины и механизмы аутентификации, которые были использованы. +- **/var/log/boot.log** - Содержит информацию, которая регистрируется при загрузке системы. +- **/var/log/daemon.log** - Включает сообщения от различных фоновых демонов +- **/var/log/kern.log** - Тоже содержит сообщения от ядра, полезны при устранении ошибок пользовательских модулей, встроенных в ядро. +- **/var/log/lastlog** - Отображает информацию о последней сессии всех пользователей. Это нетекстовый файл, для его просмотра необходимо использовать команду lastlog. +- **/var/log/maillog /var/log/mail.log** - журналы сервера электронной почты, запущенного в системе. +- **/var/log/user.log** - Информация из всех журналов на уровне пользователей. +- **/var/log/Xorg.x.log** - Лог сообщений Х сервера. +- **/var/log/alternatives.log** - Информация о работе программы update-alternatives. Это символические ссылки на команды или библиотеки по умолчанию. +- **/var/log/btmp** - лог файл Linux содержит информацию о неудачных попытках входа. Для просмотра файла удобно использовать команду last -f /var/log/btmp +- **/var/log/cups** - Все сообщения, связанные с печатью и принтерами. +- **/var/log/anaconda.log** - все сообщения, зарегистрированные при установке сохраняются в этом файле +- **/var/log/yum.log** - регистрирует всю информацию об установке пакетов с помощью Yum. +- **/var/log/cron** - Всякий раз когда демон Cron запускает выполнения программы, он записывает отчет и сообщения самой программы в этом файле. +- **/var/log/secure** - содержит информацию, относящуюся к аутентификации и авторизации. Например, SSHd регистрирует здесь все, в том числе неудачные попытки входа в систему. +- **/var/log/wtmp или /var/log/utmp -** системные логи Linux**,** содержат журнал входов пользователей в систему. С помощью команды wtmp вы можете узнать кто и когда вошел в систему. +- **/var/log/faillog** - лог системы linux, содержит неудачные попытки входа в систему. Используйте команду faillog, чтобы отобразить содержимое этого файла. +- **/var/log/mysqld.log** - файлы логов Linux от сервера баз данных MySQL. +- **/var/log/httpd/ или /var/log/apache2** - лог файлы linux11 веб-сервера Apache. Логи доступа находятся в файле access_log, а ошибок в error_log +- **/var/log/lighttpd/** - логи linux веб-сервера lighttpd +- **/var/log/conman/** - файлы логов клиента ConMan, +- **/var/log/mail/** - в этом каталоге содержатся дополнительные логи почтового сервера +- **/var/log/prelink/** - Программа Prelink связывает библиотеки и исполняемые файлы, чтобы ускорить процесс их загрузки. /var/log/prelink/prelink.log содержит информацию о .so файлах, которые были изменены программой. +- **/var/log/audit/**- Содержит информацию, созданную демоном аудита auditd. +- **/var/log/setroubleshoot/ -** SE Linux использует демон setroubleshootd (SE Trouble Shoot Daemon) для уведомления о проблемах с безопасностью. В этом журнале находятся сообщения этой программы. +- **/var/log/samba/** - содержит информацию и журналы файлового сервера Samba, который используется для подключения к общим папкам Windows. +- **/var/log/sa/** - Содержит .cap файлы, собранные пакетом Sysstat. +- **/var/log/sssd/** - Используется системным демоном безопасности, который управляет удаленным доступом к каталогам и механизмами аутентификации. + +## Просмотр логов в Linux + +Смотрим лог **/var/log/dmesg**, с возможностью прокрутки: + `less /var/log/dmesg` + +Просмотр логов Linux, в реальном времени: + `tail -f /var/log/dmesg` + +Открываем лог файл dmesg: + `cat /var/log/dmesg` + +Первые строки dmesg: + `head /var/log/dmesg` + +Выводим только ошибки из /var/log/messages: + `grep -i error /var/log/dmesg` + +===================================== diff --git a/LINUX/Оптимизация загрузки Linux - ускоряем старт системы.md b/LINUX/Оптимизация загрузки Linux - ускоряем старт системы.md new file mode 100644 index 0000000..65ead09 --- /dev/null +++ b/LINUX/Оптимизация загрузки Linux - ускоряем старт системы.md @@ -0,0 +1,26 @@ +Со временем Linux-система может загружаться медленнее из-за лишних сервисов. Эти команды помогут проанализировать и ускорить процесс загрузки. + +Посмотреть общее время загрузки системы +`systemd-analyze + +Показать список сервисов и их время загрузки +`systemd-analyze blame + +Проверить, какие службы запускаются при старте +`systemctl list-unit-files --state=enabled + +Отключить ненужный сервис +`sudo systemctl disable имя_сервиса + +Установить задержку старта для тяжёлого сервиса +`sudo systemctl edit имя_сервиса + +В открывшемся файле добавить: +`[Service] +`ExecStartPre=/bin/sleep 30 + +🟢 systemd-analyze показывает время загрузки системы +🟢 systemctl позволяет отключать или откладывать запуск лишних служб +🟢 Оптимизация автозагрузки ускоряет старт системы + +🗣 Регулярная проверка и настройка автозагрузки особенно важна для серверов и рабочих станций с ограниченными ресурсами. diff --git a/LINUX/Очистка и удаление старых ядер.md b/LINUX/Очистка и удаление старых ядер.md new file mode 100644 index 0000000..a9745c8 --- /dev/null +++ b/LINUX/Очистка и удаление старых ядер.md @@ -0,0 +1,78 @@ +```sh +ls /boot + +sudo uname -a + +sudo apt list --installed | egrep "linux-image | linux-headers" + +dpkg --list | egrep "linux-image | linux-headers" + +# Здесь **rc** - означает, что пакет был установлен, но уже удален, а **ii** - что пакет сейчас установлен. На основе полученного списка вы можете удалить любую версию, например, 4.15.0-91: + +sudo apt purge linux-image-4.15.0-66-generic linux-headers-4.15.0-91-generic + +# Удалить все старые ядра: +sudo apt --purge autoremove +sudo apt autoremove +sudo update-grub + + +``` + +========================================== +#### Как очистить сервер от мусора + +Со временем сервер обрастает мусором: логи, временные файлы, старые кэши. Разберем команды по удалению всего этого добра. Бонусом в конце будет готовый файл для запуска скрипта с логированием, который будет каждый день удалять все ненужное 🙂 + +Команда для удаления .log-файлов старше 7 дней: + +`find /var/log -type f -name "*.log" -mtime +7 -delete + +find - команда поиска файлов +/var/log - папка, где хранятся логи +"-type f" - ищем только файлы +-name "*.log" - по шаблону *.log +"-mtime +7" - старше 7 дней +-delete - сразу удалять + +Очистить systemd-журналы: + +`journalctl --vacuum-time=7d + +Эта команда удаляет внутренние журналы системы, которым больше 7 дней. Такие журналы хранят события: старты, ошибки, перезагрузки, службы — всё, что происходило в системе. Обычно занимают много места, особенно если сервер работает давно. + +Очистить временные файлы: + +`rm -rf /tmp/* /var/tmp/* + +Удаляет всё из временных директорий. Будь осторожен, если на сервере кто-то работает прямо сейчас. + +Очистить кэш и мусор после установки пакетов: + +`apt clean && apt autoremove -y + +"apt clean" - очищает кэш установленных .deb файлов +"apt autoremove" - удаляет больше не нужные зависимости +"-y" - выполнять без лишних вопросов + +Ниже прикладываю готовый файл. Его нужно будет сделать исполняемым и можно добавить в crontab с запуском в каждое воскресенье в 0:00. Все результаты будут сохраняться в файл "/var/log/server-cleanup.log": + +```sh +#!/bin/bash + +LOG_FILE="/var/log/server-cleanup.log" +echo "[$(date)] Очистка начата" >> "$LOG_FILE" + +find /var/log -type f -name "*.log" -mtime +7 -delete >> "$LOG_FILE" 2>&1 +journalctl --vacuum-time=7d >> "$LOG_FILE" 2>&1 +rm -rf /tmp/* /var/tmp/* >> "$LOG_FILE" 2>&1 +apt clean && apt autoremove -y >> "$LOG_FILE" 2>&1 + +echo "[$(date)] Очистка завершена" >> "$LOG_FILE" + +sudo chmod +x /usr/local/bin/clean-server.sh + +sudo crontab -e + +0 0 * * 0 /usr/local/bin/clean-server.sh +``` diff --git a/LINUX/Сборка программ из исходного кода в Linux.md b/LINUX/Сборка программ из исходного кода в Linux.md new file mode 100644 index 0000000..9b11cd1 --- /dev/null +++ b/LINUX/Сборка программ из исходного кода в Linux.md @@ -0,0 +1,42 @@ + +Иногда, случаются ситуации когда нам нужен какой-то софт, но для него нет пакета, или же версия пакета сильно отстает. Тут может пригодиться умение установки из исходников. + +Для большинства приложений процесс установки из исходного кода состоит из шагов, которые мы разберём на примере популярного инструмента для мониторинга процессов — **htop**. + + 1️⃣ **Скачиваем исходный код** +Для этого найдите архив с исходниками на офциальном сайте или репозитории GitHub. Для нашего примера, скачаем архив исходников htop: + +`wget https://github.com/htop-dev/htop/archive/refs/tags/3.2.2.tar.gz + +2️⃣ **Распаковываем архив и переходим в каталог** +`tar -xvzf 3.2.2.tar.gz +`cd htop-3.2.2 + +3️⃣ **Устанавливаем зависимости** +Большинство программ требуют компилятора и библиотек. Убедитесь, что у вас установлен набор инструментов для сборки: +`sudo apt update +`sudo apt install build-essential libncursesw5-dev + +**4️⃣ Конфигурируем сборку** +Подготовьте сборку для вашей системы. Это этап проверки зависимостей и настройки параметров: +`./autogen.sh +`./configure + +Если программа не поддерживает autogen.sh, обычно достаточно только ./configure. Используется для проверки окружения и создания Makefile, который нужен для компиляции. + +5️⃣ **Компилируем программу** +`make + +Процесс может занять некоторое время в зависимости от мощности системы. + +**6️⃣ Устанавливаем программу** +Завершаем установку в системный каталог: +`sudo make install + +7️⃣ **Проверяем установку** +Для проверки, что мы установили все корректно, проверим версию: +`htop --version + +➡️ Если вам нужно выполнить установку в пользовательский каталог, то можем указать префикс конфигурации: +`./configure --prefix=$HOME/local + diff --git a/LINUX/Скрытые процессы и rootkits, как найти невидимку в Linux.md b/LINUX/Скрытые процессы и rootkits, как найти невидимку в Linux.md new file mode 100644 index 0000000..7202ac0 --- /dev/null +++ b/LINUX/Скрытые процессы и rootkits, как найти невидимку в Linux.md @@ -0,0 +1,40 @@ +Иногда ps, top и даже htop не показывают зловредный процесс, хотя ты точно знаешь — он где-то сидит. + +Вот как найти спрятанное 👇 + +🔍 **1. chkrootkit — проверка на известные rootkit'ы** +```sh +sudo apt install chkrootkit +sudo chkrootkit +``` +📌 Ищет признаки заражения, подмену системных утилит, скрытые процессы, ловушки ядра. + +🛡 **2. rkhunter — глубокий аудит системы** +```sh +sudo apt install rkhunter +sudo rkhunter --check +``` +📌 Проверяет бинарники, конфиги, права, подозрительные изменения. + +🫥 **3. unhide — находим скрытые процессы** +```sh +sudo apt install unhide +sudo unhide proc +sudo unhide sys +``` +📌 Сравнивает процессы в /proc, syscalls и ps. Помогает, если ps не показывает, но процесс явно есть. + + 🧩**4. auditctl — аудит системных вызовов** + ```sh + sudo apt install auditd +sudo auditctl -a exit,always -F arch=b64 -S execve +``` +📌 Ловит каждый execve, даже если процесс мгновенно скрывается. Идеально для отлова аномалий. + + +🧠 **Что делать, если ps и top молчат, а диск, сеть и CPU загружены?** + +✅ Смотри /proc, ищи "дырки" в PID +✅ Используй lsof, netstat, ss — они могут показать процессы, которых нет в ps +✅ Сравни /bin/ps с версией из пакета (debsums, rpm -V) +✅ Запусти live-дистрибутив и просканируй систему снаружи
\ No newline at end of file diff --git a/LINUX/Удаление программ.md b/LINUX/Удаление программ.md new file mode 100644 index 0000000..9977ef4 --- /dev/null +++ b/LINUX/Удаление программ.md @@ -0,0 +1,26 @@ +```sh +sudo apt remove name_progr +sudo apt purge name_progr +apt list --installed + + +snap list +sudo snap remove name_progr + + +sudo apt autoremove +sudo apt autoclean + + +# Удаление программ без пакетного менеджера + +cd /папка/с/исходниками +sudo make uninstall + +# Первая команда переходит в папку с исходниками, вторая - удаляет программу. Если скрипта не сохранилось, то вы можете скачать его из интернета. Иначе программу не удалить. Еще один вариант установки программ - с помощью скрипта из официального сайта. Такие программы удалить намного проще. Они устанавливаются, как правило, в одну папку, для них существует специальный каталог в файловой системе Linux. Здесь они создают новую папку, где расположены все их файлы, включая исполняемые и файлы настроек. Например, чтобы удалить браузер Google Chrome, установленный, таким образом, нужно набрать: + + sudo rm -Rf /opt/google-chrome/ + +# Возможно, после удаления программы, вам стоит проверить папку /etc, так как там обязательно должны были остаться файлы настроек пути к библиотекам этой программы, исполняемым файлам и файлы сервисов. + +```
\ No newline at end of file diff --git a/LINUX/Файловая система Linux.md b/LINUX/Файловая система Linux.md new file mode 100644 index 0000000..177e091 --- /dev/null +++ b/LINUX/Файловая система Linux.md @@ -0,0 +1,176 @@ +#========================================== + +Помимо единственного корневого раздела root, присутствуют подкаталоги, к которым привязаны соответствующие каталоги. Типовая структура первых двух уровней выглядит так: + +├── bin -> usr/bin +├── boot +│ ├── grub +│ └── lost+found +├── dev +│ ├── block +│ └── … +├── etc +│ ├── … +│ ├── update-manager +│ ├── update-motd.d +│ └── xdg +├── home +├── lib -> usr/lib +├── lib32 -> usr/lib32 +├── lib64 -> usr/lib64 +├── libx32 -> usr/libx32 +├── lost+found +├── media +├── mnt +├── opt +├── proc +│ ├── … +│ └── tty +├── root +├── run +│ └── … +├── sbin -> usr/sbin +├── srv +├── sys +├── tmp +│ └── … +├── usr +└── var + +Каждый файл в ФС Линукса имеет конкретный индекс Inode. У одного файла в такой структуре может быть несколько имен, т.е. путей. В структуре системы файлы могут различаться, но на жестком диске им соответствует один файл. Такое свойство называют перекрестной иерархичностью — ветви дерева пересекаются. + +Inode — индексный дескриптор, который используется в UNIX-системах. В нем хранятся все мета-данные о файле: владелец, время последнего обращения, размер, другая информация. + +Каталог можно переместить в другую ФС, после этого его дескриптор будет создан заново. Только после этого исходник будет удален. В пределах одной системы изменится только путь файла. Каталоги и файлы существуют до момента, пока хранятся данные об их имени или пути к ним. При удалении этой информации блоки освобождаются под другие файлы. + +В числе других особенностей Linux — ссылки двух типов: + +- жесткая ссылка (то есть hard link) — путь файла, заданный командой ls -li; +- символьная ссылка (symbolic link) — файл стандарта UNIX с текстовой строкой, задающей путь к оригинальному файлу. + +Существует также суперблок, где хранятся все параметры ФС — общее количество блоков и Inode, наличие свободных блоков и данные о них. Суперблок должен сохранять целостность — это напрямую влияет на стабильность и работу всей системы. Для безопасности в Линукс создается несколько копий для восстановления информации в случае ее потери. + +## Ключевые директории и их назначение + +В Linux директория — фундаментальный элемент системы. Это каталог (папка), где содержатся другие файлы, расположенные согласно иерархии. Отправной точкой, а точнее, базой выступает корневой каталог «/», на котором выстраивается вся система. + +В некоторых источниках этот каталог сравнивают с диском С на винде, но это не совсем корректное сравнение — в Линуксе нет буквенных дисков. В Windows дополнительный раздел расположен на диске D, в Линукс это будет отдельная папка с тем же корнем. Иными словами, путь к любому файлу проходит через корень. + +Пример. Файл /home/user/alien имеет структуру root -> home -> user -> documents, и никакую другую. + +Рассмотрим наиболее востребованные директории и их функции. + +### Пользовательские файлы /bin и /sbin + +В этой директории расположены бинарные исполняемые файлы, которые требуются для загрузки системы и ее работы. Именно здесь вы найдете команды ls, cp, grep, mv, ping и прочие утилиты, доступные каждому юзеру. В /usr/bin хранятся и такие приложения, как браузер Chrome, Firefox и другие. + +В каталоге /sbin, который имеет схожую конфигурацию, содержатся важные исполняемые файлы для системного администрирования. Это iptables, reboot, fdisk и т.д.. + +### Пользовательские данные /home (домашняя папка) + +В этом каталоге хранится домашняя папка с личными файлами и настройками. Например, если ваше имя юзера — denis, у вас будет домашняя папка /home/denis. Сюда записываются, например, скачанные файлы. Если пользователей несколько, они будут записывать файлы в свои папки. Для изменения других системных файлов потребуются права root. + +### Конфигурационные файлы /etc + +Это место хранения файлов конфигурации — сетевых настроек, приложений, файлов инициализации различных служб (серверов печати, хостов и т.д). Папка крайне важна для работы всей системы. При необходимости общие файлы можно редактировать вручную в текстовом редакторе. + +### Файлы устройств /dev и системные файлы /proc + +В директории /dev хранятся интерфейсы системных и периферийных устройств — дисков, принтеров, динамиков и прочих. В Линуксе устройства представлены в виде файлов. Стоит понимать, что это не совсем файлы в том виде, к которому мы привыкли. Псевдоустройства из этого каталога — это, например, генераторы случайных чисел /dev/random. + +Файлы ядра и процессов содержатся в каталоге /proc. В нем, как и в /dev, нет стандартных файлов, а есть специфические области данных о системах и процессах. Здесь есть информация о процессоре /proc/cpuinfo; сведения о памяти, используемой системой — /proc/meminfo; время — /proc/uptime. Тут же содержится информация о системных ресурсах. + +### Переменные (изменяемые) данные /var + +В этой директории лежат данные, которые в процессе работы системы постоянно меняются. Сюда относятся логи, кэши, системные журналы, почтовые очереди и прочие файлы, создаваемые программами. + +Это записываемая директория, которая в стандартном режиме доступна только для чтения. Логи содержатся в /var/log, базы данных в /var/lib, файлы для перезагрузки в /var/tmp. + +### Статические файлы для загрузки /boot + +Это важные файлы для загрузки ОС, в том числе ядра Линукс. Запуск системы без этой папки проблематичен. При этом файлы конфигурации загрузчика находятся в другой папке — /etc, о которой мы писали выше. + +### Общие файлы библиотеки /lib + +В директории /lib находятся файлы, которые обеспечивают работу системных программ и ядра. Эти данные приложения и бинарные файлы /bin и /sbin используют для выполнения заданных функций. + +### Съемный носитель /media + +В этом директории монтируются съемные носители. К примеру, когда вы используете компакт-диск или флешку, автоматически создается каталог. Юзеры получают полный доступ к содержимому носителей. + +### Временные файлы /tmp + +В директории хранятся временные файлы, которые создаются приложениями в процессе работы. При перезагрузке содержимое этого каталога, как правило, очищается. Можно удалить файлы без перезагрузки с помощью соответствующих утилит. + +### Восстановленные данные /lost+found + +В любой ФС Linux есть такая директория. Если в системе случится сбой, при следующей загрузке произойдет автоматическая проверка, и любые обнаруженные поврежденные файлы отправятся в каталог /lost+found, чтобы пользователь при необходимости смог восстановить данные. + +### Файлы состояния приложений /run + +Относительно новая директория в Линукс. Здесь хранятся необходимые приложениям временные файлы — сокеты, идентификаторы процессов и другие. В отличие от каталога /tmp данные в /run нельзя удалить. + +## Типы файлов в Linux + +Линукс подчиняется концепции «Всё есть файл», то есть работа с системой — это по сути взаимодействие с файлами. Применяются разные файлы, используемые как для стандартных данных, так и для устройств. + +Плюс этой концепции в том, что система не тратит ресурсы на реализацию отдельных API для каждого приложения, так как все стандартные программы и процессы реализуются через работу с файлами. + +Рассмотрим типы файлов, которые используются в Linux: + +- Обычные (regular). Это стандартные файлы, используемые чаще других — данные, текст, исходный код, медиа и все остальное; +- Файлы устройств. Сюда входят символьные и блочные файлы (char и block devices), представляющие внешние устройства — принтеры, жесткий диск и другие; +- Именованные каналы. Файлы named pipes требуются для взаимодействия между процессами и передачи данных; +- Ссылки. Используется два типа ссылок. Символические (так называемые симлинки) — работают как ярлыки для указания других каталогов и файлов; жесткие — отвечают за альтернативные способы доступа к физическим данным на диске и выступают дубликатами файлов, но без их фактического дублирования; +- Каталоги. Папки со ссылками на файлы и другие папки. Отвечают за организацию данных и их распределение по категориям, упрощают доступ к различным разделам; +- Сокеты. Файлы, предназначенные для обмена данными при выполнении рабочих задач. Используются как внутри системы, так и при взаимодействии устройств друг с другом. По сути, это своего рода почтовые службы, с помощью которых программы пересылают информацию друг другу; +- Двери. Еще один механизм, ответственный за взаимодействие между процессами. + +В Линукс у каждого типа файла своя задача в системе управления. Такая схема работы обеспечивает мощность, придает ей гибкость и устойчивость. Поскольку внешние устройства представлены блочными файлами, с ними можно работать с помощью стандартных методов. + +## Управление файловой системой + +Через консоль управления (терминал) в Linux доступен целый арсенал для работы с директориями и файлами. + +Основные команды управления: + +- `Is`. Просмотр содержимого папки и навигация. При использовании с различными опциями возможности расширяются, например, команда ls -l создает подробный список; +- `cd`. Путь к папке — меняет текущую директорию на указанную пользователем; +- `pwd`. Обозначает текущие местоположение юзера в структуре каталогов; +- `mkdir`. Используется для создания новой папки с присвоением ей имени; +- `cp`. Копирует файлы из исходной папки в целевую; +- `mv`. Перемещает или переименовывает каталоги и файлы; +- `rm`. Удаляет указанные файлы; +- `cat`. Выводит содержимое указанного файла в окно панели управления; +- `less`. Используется для постраничного пролистывания файла; +- `touch`. Создает новый пустой файл (с новым именем); +- `ln -s`. Генерирует символьную ссылку, указывающую на выбранный каталог или файл; +- `which`. Демонстрирует пошаговый путь к исполняемым файлам; +- `nano`. Используется для текстовой редактуры файла; +- `locate`. Применяется для поиска файлов по заданным шаблонам. + +Указанные выше команды формируют основу для взаимодействия с ФС. При необходимости используется более расширенный список. + +## Монтирование и файловые системы Linux + +Монтирование — подключение файловых систем, жестких дисков и внешних носителей, получение доступа к ним, ручное и автоматическое отключение. При загрузке ядро устройства на Linux автоматически монтирует разделы, считывая данные из конфигурации /etc/fstab. + +При подключении устройств каталог ассоциируется с ними с помощью драйвера, которому передается сгенерированная ссылка. В случае успешного взаимодействия ядро вносит данные в таблицу монтирования. + +Монтирование системы реализуется с помощью команд mount и umount, подготавливающих устройства и точки монтирования. Они реализуют автоматическое подключение ФС. К виртуальной машине подключается диск /dev/sdc, на котором создаются разделы для файловой системы. После создания точки монтирования процесс загрузки ФС запускается. + +Какие файловые системы на Linux наиболее востребованные: + +- Ext. Первая ФС, разработанная специально для Линукс. Система значительно превзошла предыдущие решения по производительности и функционалу; +- Ext2. Улучшенная версия первой ФС. Повышает надежность, улучшает управление и позволяет работать с большим количеством данных и файлов; +- Ext3. Третье поколение ФС для Линукс. Поддерживает журналирование, что делает работу более стабильной и минимизирует риски потери данных после сбоев системы; +- Ext4. Новейшая версия с повышенной эффективностью и масштабируемостью. Технически более совершенный вариант, упрощающий работу с большим объемом данных; +- JFS. Разработка компании IBM. Эффективно использует ресурсы, отличается высокой производительностью, рассчитана на сильные нагрузки; +- XFS. Высокопроизводительная ФС, разработанная компанией SGI. Подходит для работы с серверами и системами хранения данных; +- ReiserFS. Разработка программиста Ганса Райзера. Главная особенность — возможность при необходимости увеличивать размер блока, тем самым избегая фрагментации; +- Btrfs. Продукт компании Oracle. Обеспечивает гибкость при управлении данными, предоставляет опции оперативной проверки и восстановления файлов, интегрирует разные устройства в единую ФС; +- Swap. Позволяет использовать дисковое пространство как виртуальную память, если физические возможности RAM исчерпаны. Технически этот инструмент нельзя назвать файловой системой, но он обеспечивает широкие возможности для управления данными в Линукс. + +ОС Linux уникальна в плане адаптации к разным сценариям использования, поскольку поддерживает работу многих файловых систем. Функциональное разнообразие обогащает платформу, позволяя тестировать и внедрять в практику множество полезных инноваций. + +> При этом у пользователя всегда есть свобода выбора — например, между стабильностью линейки Ext и гибкими возможностями файловых систем XFS, JFS. Безграничные возможности выбора отражают дух сообщества Linux, где независимые разработчики постоянно предлагают новые решения, обеспечивая постоянное развитие экосистемы.
\ No newline at end of file diff --git a/Linux ставим на MacBook.md b/Linux ставим на MacBook.md new file mode 100644 index 0000000..e1684ca --- /dev/null +++ b/Linux ставим на MacBook.md @@ -0,0 +1,98 @@ +## Шаг 1. Подготовка установочной USB-флешки + +Одним из самых важных этапов при установке Linux является подготовка установочной флешки. Несмотря на наличие специализированных утилит, позволяющих создать загрузочный USB-накопитель из графической оболочки, наиболее верным является монтирование образа посредством командной строки и приложения Терминал в OS X. + +Первое, что необходимо сделать – загрузить установочный образ Linux с официального сайта. В качестве примера рассмотрим установку дружественной и популярной версии **Linux Ubuntu**. + +1. Откройте [страницу загрузки Ubuntu](http://www.ubuntu.com/download/desktop) и выберите **64-битную версию** ОС (32-битная для Mac попросту не подойдет). Нажмите _Download_ и на следующей страницу выберите пункт _Not now, take me to the download_. + + + +Дистрибутив Ubuntu распространяется абсолютно бесплатно и, таким образом, вы отказываетесь от финансовой поддержки разработчиков, но если вы все же хотите выразить слова благодарности – перетяните соответствующие «долларовые» ползунки на требуемое значение и оплатите результирующую сумму с помощью платежной системы _PayPal_. + +Дождитесь окончания загрузки дистрибутива. + +2. Подключите USB-флешку. Для монтирования загрузочного образа подойдет накопитель, размер которого будет **не менее 2 ГБ**. Все данные, которые в настоящий момент находятся на флешке, будут **безвозвратно стерты**. + +3. Откройте утилиту **Терминал**. Наша задача – конвертировать скачанный образ ISO в общепринятый в среде Mac IMG/DMG. Для этого нам потребуется запустить выполнение несложной команды. + +**`hdiutil convert -format UDRW -o ~/path/to/target.img ~/path/to/ubuntu.iso`** + +По умолчанию все загрузки в среде Mac попадают в папку Загрузки. Для того, чтобы указать путь к скачанному образу наберите в Терминале первую часть команды: **hdutil convert -format UDRW -o**. Затем перетяните скачанный файл с образом (он имеет расширение ISO) на окно терминала. Автоматически вставится правильный адрес к исходному файлу. Имя файла **ubuntu-14.04.2-desktop-amd64.iso** можно изменить на любое, скажем, _LinuxInstall_, но с **ОБЯЗАТЕЛЬНЫМ** изменением расширения _ISO_ на **IMG**. Поставьте пробел и вновь перетяните скачанный образ в окно терминала. На этот раз никаких изменений вносить не нужно. Так выглядит введенная команда: + + + +**Будьте внимательны**: сначала следует указать имя и место размещения директории с получаемым в процессе конвертации образом, а после – с файлом исходником (скачанным дистрибутивом). После ввода команды нажимаем ENTER и ждем пока окончится процесс конвертации. + + + +Если после выполнения команды вы увидели строку **created** с адресом файла в формате **DMG** – все прошло успешно и можно переходить к следующему этапу. + +4. Разворачиваем установочный образ на флешку. После выполнения вышеуказанной команды в папке _Загрузки_ появился новый файл **LinuxInstall.img.dmg**. Наша задача – развернуть его на флешку. Убедитесь, что USB-накопитель подключен к Mac и в Терминале введите следующую команду: + +**`diskutil list`** + +Результат выполнения команды: + + + +Перед нами открылась картина всех подключенных логических дисков системы. Наша USB-флешка числится под идентификатором **disk1** (размер 2 ГБ). Отключим ее с помощью следующей команды: + +**`diskutil unmountDisk /dev/disk1`** + +**Disk1** – частный случай и в команде прописывайте имя, под которым флешка значится в вашей системе. После извлечение накопителя приступим к записи конвертированного образа: + +**`sudo dd if=/path/to/downloaded.img of=/dev/rdisk1 bs=1m`** + +Как и в пункте 3 для более быстрого указания директории введите фрагмент команды **sudo dd if=** после чего перетяните новый образ (с расширением DMG) на окно терминала. Закончите ввод команды. Помните, что **rdisk1** может иметь другой порядковый номер и имя, и зависит от конкретно вашей системы. Нажмите ENTER и введите пароль администратора. + +В течение последующих нескольких минут в окне терминала не будет происходить никаких процессов. **Не спешите закрывать утилиту** и беспокоиться о подвисании процесса. Разворачивание образа происходит «_в тихом режиме_». Дождитесь окончания процесса. Сигналом об окончании разворачивания образа станет сообщение: + + + +Нажмите **Извелчь**, но саму **флешку из порта не доставайте**. Загрузочный накопитель готов. + +## Шаг 2. Раздел диска. + +Для установки операционной системы Linux по-соседству с OS X потребуется создать дополнительный логический раздел диска. Сделать это можно с помощью **Дисковой утилиты**, являющейся штатным приложением OS X. + +Запустите Дисковую утилиту. Подсветите ваш основной винчестер (именно винчестер, не раздел) и откройте вкладку **Раздел диска**. + + + +Нажите на знак **«+»** и установите желаемый размер раздела (для нормальной работы Linux достаточно 10 ГБ; рекомендуем объем – 20 ГБ). Нажмите **Применить** и дождитесь окончания процесса. + + + +**Примечание:** Если по какой-либо причине схема разделения недоступна (знак «+/-» серым цветом и не реагирует на нажатия), добавить новый раздел можно из **загрузочного меню Mac**. Перезагрузите компьютер и сразу после того, как экран загорится белым зажмите клавиши **Command + R**. Удерживайте до появления системного меню Mac, а затем откройте Дисковую утилиту. Процесс разделения полностью аналогичен с описанным выше. + +После разделения диска **ОБЯЗАТЕЛЬНО** запомните точный размер отделенной логической области в гигабайтах. Перезагружаем компьютер. + +## Шаг 3. Установка Linux Ubuntu + +Сразу после того, как началась перезагрузка, зажмите **клавишу ALT** и дождитесь появления меню с выбором логических дисков, с которых следует произвести установку. Выберите **съемный накопитель EFI** (первый) и нажмите Enter. + + + +В появившемся установочном меню операционной системы Ubuntu выберите: Установить Ububntu. Начнется подготовительный процесс установки. После выбора требуемого языка и подключения к сети Wi-Fi установщик спросит как именно мы планируем устанавливать систему. + +**Внимание! Будьте осторожны!**Если вы хотите сохранить работоспособность OS X и Linux выбирайте пункт **Другой Вариант** (выбор пункта _Стереть диск и установить Ubuntu приведет_ к **полному форматированию всей структуры диска и удалению ранее созданных разделов**, включая раздел с системой OS X). + +В открывшемся меню найдите созданный под Linux раздел диска (поможет знание его размера). + + + +Дважды щелкните по требуемому разделу и установите следующие параметры: + +– файловая система Ext2; +– точка монтирования: /; + + + +Нажмите **ОК** и **Установить сейчас**. Сообщение об отсутствии файла-подкачки проигнорируйте, нажав Продолжить. Начнется установка операционной системы Linux. Возможно однократная перезагрузка Mac. После завершения установки пользователю будет предложено указать имя учетной записи и придумать пароль. Сам процесс инсталяции занимает от 5 до 15 минут. + + + +Установка завершена. При включении Mac по умолчанию теперь будет загружаться Linux Ubuntu. Изменить приоритетную систему можно в OS X, перейдя в **Настройки – Загрузочный том** и _подсветив раздел_, загрузка которого должна осуществляться в первую очередь. + + (**29** голосов, общий рейтинг: **4.62** из 5)
\ No newline at end of file diff --git a/MacOS/Homebrew.md b/MacOS/Homebrew.md new file mode 100644 index 0000000..17d98e2 --- /dev/null +++ b/MacOS/Homebrew.md @@ -0,0 +1 @@ +`/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` diff --git a/MacOS/Postgresql.md b/MacOS/Postgresql.md new file mode 100644 index 0000000..57b6bda --- /dev/null +++ b/MacOS/Postgresql.md @@ -0,0 +1,113 @@ +`brew install postgresql` + +_В автозагрузку:_ +`brew services start postgresql` + +_Запуск вручную:_ +`pg_ctl -D /usr/local/var/postgresql start` + +_Перезагрузка:_ +`brew services restart postgresql` + +_Проверка версии:_ +`brew info postgresql` + +_Инициализация по умолчанию:_ +`initdb --locale=C -E UTF-8 /opt/homebrew/var/postgresql@14` + +_Экспортировать переменную:_ +`export PATH=/opt/homebrew/bin:$PATH` + +_Если автозагрузка не сработает, директория здесь:_ +`~/Library/LaunchAgents` + +_Создаем симлинк:_ +`ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents` + +_Добавляем в автозагрузку:_ +`launchctl load ~/Library?LaunchAgents/homebrew.mxcl.postgresql.plist` + +_Подключение к БД на MacOS (отличается от Linux). Проверяем пользователей:_ +`psql -l` + +![[Pasted image 20240312221215.png]] + +_Для подключения указываем Owner из таблицы выше:_ +`sudo psql -U vlapa -d postgres` + +_Создаем пользователя с паролем (сначала подключаемся как описано выше):_ +`CREATE USER ploshadka WITH PASSWORD '123456';` + +_Назначаем ему кодировку:_ +`ALTER ROLE ploshadka SET client_encoding TO 'utf8';` + +_Создаем БД для русской локали, чтобы была возможность поиска:_ +`CREATE DATABASE dbname TEMPLATE=template0 ENCODING 'UTF-8' LC_COLLATE 'ru_RU.UTF-8' LC_CTYPE 'ru_RU.UTF-8';` + +_Назначаем этому пользователю новую БД:_ +`GRANT ALL PRIVILEGES ON DATABASE dbname TO ploshadka;` + +_проверим по таблице все-ли верно:_ +`\list` + +_Как подключиться (на localhost достаточно создать БД и подключиться к ней указав только имя):_ +`dbname='stocks'` +_Остальное подхватится по умолчанию._ + +### Узнаем размер БД: + +_Заходим на сервер в оболочку Postgres:_ +`sudo psql -U vlapa -d postgres` + +_Проверяем размер:_ +`SELECT pg_size_pretty(pg_database_size('vlapa_db'));` + +### Узнаем размеры таблиц: + +_Находясь в оболочке Postgres входим в нужную БД:_ +`\c vlapa_db` + +_Вводим команду отсортировать таблицы по большему размеру:_ +```bash +SELECT nspname || '.' || relname AS "relation", + pg_size_pretty(pg_total_relation_size(C.oid)) AS "total_size" + FROM pg_class C + LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) + WHERE nspname NOT IN ('pg_catalog', 'information_schema') + AND C.relkind <> 'i' + AND nspname !~ '^pg_toast' + ORDER BY pg_total_relation_size(C.oid) DESC + LIMIT 20; +``` + +################################################## + +_PostgreSQL - SQL команды:_ + +Как добавить колонку в правильное место: +```bash +alter table tablename rename to oldtable; +create table tablename (column defs go here); +insert into tablename (col1, col2, col3) select col1, col2, col3 from oldtable; +``` + +_Как добавить колонку в конец:_ +```sh +ALTER TABLE portfolio ADD COLUMN price_diff_open_current FLOAT NULL; +``` + +_Переименовать таблицу:_ +```sh +alter table portfolio rename column lots to quantity; +``` + +_Обновить всю колонку одним значением:_ +```sh +UPDATE operations SET user_id='1' +``` + +_Переименовать базу данных:_ +```sh +alter database "stocks" rename to "captures"; +``` + diff --git a/MikroTik проброс портов.md b/MikroTik проброс портов.md new file mode 100644 index 0000000..571a917 --- /dev/null +++ b/MikroTik проброс портов.md @@ -0,0 +1,150 @@ +Находя в сети различные инструкции по работе с Mikrotik RouterOS, я обратил внимание, что большинство из них мало информативны — в лучшем случае они представлены в виде скриншотов Winbox со скудными комментариями, в худшем — просто груда строк для командной строки, из которых что-либо понять вообще нет никакой возможности. + +Поэтому я решил написать небольшой набор инструкций, в которых буду объяснять, не только ЧТО нужно выполнить, но и ЗАЧЕМ и ПОЧЕМУ. + +Итак, начнём с самого простого и, в тоже время, нужного — проброс порта из внутренней сети «наружу». + +Любую сеть можно схематично представить вот в таком виде: + +![[7c6829decd5925af5d5e0dcc27128438.png]] + +Посмотрим немного подробнее, на примере роутера Mikrotik RB951-2n. У него 5 портов, плюс модуль WiFi. + +Подключаемся через web-интерфейс или Winbox'ом к роутеру(по умолчанию логин admin, без пароля), заходим в список интерфейсов, это второй пункт в меню, **Interfaces**. + +![[1fd1edd152259cb558abab7409faa120.png]] + +Тут мы видим все интерфейсы, доступные нашему роутеру(интерфейс — это, грубо говоря, канал связи роутера с другим компьютером/компьютерами) + +![[15d72c288938426db50c249effd75b07.png]] + +Первым идёт **bridge-local** — «локальный мост». В данном случае он служит для установки моста между локальной сетью и беспроводными подключениями по WiFi (В Микротике для соединения вместе нескольких интерфейсов можно или объединить их в свитч, или соединить мостом(бриджем). У каждого из этих двух вариантов есть свои сильные и слабые стороны. Но это тема для отдельной статьи). + +Далее, **ether1-gateway**. Ether1 — это первый RJ45 разъём на лицевой панели роутера. По умолчанию считается, что в него воткнут кабель с интернетом, приходящий от провайдера. Поэтому он называется Gateway — «Ворота» (В принципе, это не обязательно должен быть первый порт, при желании можно назначить gateway'ем любой другой порт, и даже несколько портов одновременно, при наличии подключений от нескольких провайдеров). + +Затем идут 4 порта локальной сети. Они объединены в свитч, начиная со второго RJ45 разъёма, он называется **ether2-master-local**, остальные 3 — неполиткорректно называются **slave-local**. Тут логика простая: в параметрах портов 3, 4 и 5 прописано, что главным у них является порт №2 — соответственно, все четыре порта автоматически объединяются в виртуальный свитч. А этот виртуальный свитч внутренней сети объединен с беспроводной сетью посредством бриджа из первой строки. + +Между внешним интернетом и внутренней сетью находится NAT — Network Address Translation, дословно «Трансляция сетевых адресов». Механизм, позволяющий компьютерам, находящимся в локальной сети, иметь внутренние адреса своей сети, и при этом нормально общаться со всеми другими компьютерами и серверами в интернете (при этом для внешнего сервера все компьютеры внутри сети представляются одним адресом — внешним адресом роутера). Вкратце, это работает таким образом: при обращении из внутренней сети куда-нибудь в интернет, роутер подменяет внутренний адрес компьютера своим адресом, принимает ответ от сервера в интернете, и переадресовывает на локальный адрес компьютера, сделавшего исходный запрос. Это называется Masquerade — «Маскарад»: данные от машины в локальной сети как-бы «надевают маску» и для внешнего сервера это выглядит так, будто они исходят от самого роутера. Точно так же, при подключении из внешней сети, нам нужно где-то задать правило, какой именно компьютер в локальной сети должен отвечать на попытки подключиться к какому-то порту на роутере. Поясню на примере: + +![[eafc9c46561ec70fb5744ea1d85eb1c0.png]] + +Мы хотим, чтобы при подключении к нашей сети в лице роутера на порты 110 и 51413 — подключение переадресовывалось на компьютер PC1, а при подключении к порту 3189 — на машину PC2. + +Для этого и нужен NAT. + +В Микротике управление NAT'ом находится в разделе **IP**->**Firewall**->**NAT**: + +![[b74e1c10b7c49ab7cc235e746af07128.png]] +![[ae81365154ba5166845a7a9171d2248b.png]] + +Тут мы видим, что одно правило уже есть. Оно автоматически создаётся конфигурацией по-умолчанию — это тот самый «Маскарад», он необходим для того, чтобы компьютеры во внутренней сети могли нормально обращаться к серверам в интернете. + +Нам же нужно ровно наоборот — добавить правило для подключения извне к одной из машин в локальной сети. Например, для эффективной работы торрент-клиента мы хотим сделать доступным порт 51413 на машине с именем nas. + +Нажимаем синий плюсик + +![[370b55d2c9ca15ef5153581f2af2b9b0.png]] + +В появившемся окне нового правила нам нужно всего несколько опций + +![[32de918111f7fed3e8183b4b9148286b.png]] + +Первое — это + +**Chain** (Цепочка). Тут может быть всего два варианта — **srcnat** и **dstnat**. Цепочка — это, грубо говоря, направление потока данных. + +**Srcnat** — из внутренней сети во внешнюю, + +**dstnat** — соответственно, из внешней во внутреннюю. + +Как легко догадаться, в нашем случае требуется выбрать **dstnat**. + +Далее идут + +**Src. Address** (исходный адрес) и + +**Dst. Address** (адрес назначения). Для подключения извне исходный адрес будет адресом одного из миллиардов компьютеров в интернете, а адрес назначения — всегда внешний адрес роутера. Эти пункты ставить смысла нет. + +Потом пункт + +**Protocol** (протокол). Здесь значение надо выбрать обязательно, иначе мы не сможем указать номер порта. Для торрентов выбираем тут **tcp**. + +**Src. Port** (исходящий порт) — это тот порт, из которого удалённая машина инициирует соединение с нашим роутером. Нам это абсолютно без разницы, тут ничего не ставим. + +**Dst. Port** (порт назначения) — а это как раз тот порт, на который мы хотим принимать соединение. В моём торрент-клиенте это 51413. + +Затем идёт забавный пункт + +**Any. Port** (любой порт) — так и есть, это объединение двух предыдущих пунктов, тут можно указать значение, которое будет работать и как исходный порт, так и как порт назначения. В целом, нам это не требуется. + +Теперь очень важный момент: + +**In. interface** (входящий интерфейс) — это, грубо говоря, тот интерфейс, на котором «слушается» указанный порт. Если не указан это параметр, то этот порт перестанет так же быть доступен из внутренней сети, даже несмотря на то, что цепочка у нас **dstnat**. Поэтому, выбираем тут интерфейс, через который мы подключены к интернету, в нашем случае — **ether1-gateway**. + +**Out. interface** (исходящий интерфейс) — интерфейс, к которому подключена та машина, на которую мы делаем переадресацию. Тут что-либо ставить не имеет смысла. + +Далее идут узкоспециализированные параметры, я сейчас не буду на них останавливаться. + +В итоге, получается вот такая картина: + +![[9132596e2802a7ecd3a47842f6475f60.png]] + +Вкладки **Advanced** (Продвинутый) и **Extra** (Дополнительный) содержат различные параметры тонкой настройки, нам они без надобности, идём сразу в **Action** (Действие) + +![[dbb44ce3043db021cfbb195941e52a5a.png]] + +Тут из списка **Action** нужно выбрать конкретное действие, которое следует выполнить с подключением на указанный ранее порт. Выбрать есть из чего: + +**accept** — Просто принимает пакет; + +**add-dst-to-address-list** — Добавляет адрес назначения в указанный список адресов; + +**add-src-to-address-list** — Аналогично предыдущему, но для исходного адреса; + +**dst-nat** — Переадресовывает данные, пришедшие из внешней сети, во внутреннюю; + +**jump** — Позволяет применить для данных правила из другой цепочки. Например, для цепочки **srcnat** — применить правила цепочки **dstnat**; + +**log** — Просто добавляет информацию о пакете в лог роутера; + +**masquerade** — Тот самый «Маскарад»: подмена внутреннего адреса машины из локальной сети на адрес роутера; + +**netmap** — Отображение одного адреса на другой. Фактически, развитие dst-nat; + +**passthrough** — Пропуск текущего пункта правил и переход к следующему. Используется в основном для статистики; + +**redirect** — Перенаправляет данные на другой порт в пределах роутера; + +**return** — Возвращает управление обратно, если в эту цепочку выполнялся прыжок правилом jump; + +**same** — применяется в очень редких случаях, когда нужно применять одни и те-же правила для группы адресов; + +**src-nat** — Обратная dst-nat операция: перенаправление данных из внутренней сети во внешнюю. + +Для наших целей подходит **dst-nat** и **netmap**. Последнее является более новым и улучшенным вариантом первого, логично использовать его: + +![[2e66d41ed2450e40ffd3fa20ba1036d7.png]] + +В поле **To Addresses** нужно указать адрес машины, на которую мы хотим переадресовать порт, в поле To Ports — соответственно, сам порт. + +Чтобы не заморачиваться, вместо адреса пишу имя комьютера, и указываю порт: + +![[264fc3d5aab6c1a767f7659c207e1618.png]] + +И нажимаю кнопку **Apply**, роутер сам находит адрес машины: + +![[73ce6d03c95dd19ac4263224cc1acac4.png]] + +Напоследок имеет смысл нажать **Comment** и указать комментарий для правила, чтобы в дальнейшем не приходилось вспоминать, что это за правило и зачем + +![[4a2b1a7f1e1bee9a26d7885b1d5e0f8c.png]] + +Всё, нажимаем **OК** в окошке ввода комментария и **ОК** в окне ввода правила: + +![[6cda81091bcc9f99ad224bd5c5426f8e.png]] + +Правило создано, всё работает. + +Обратите внимание — дополнительно «открывать» порт не требуется! + +Инструкция актуальна для версий прошивки 5 и 6. До версии 7 вряд-ли что-либо изменится.
\ No newline at end of file diff --git a/NuPhy Air75.md b/NuPhy Air75.md new file mode 100644 index 0000000..e03f9ec --- /dev/null +++ b/NuPhy Air75.md @@ -0,0 +1,122 @@ + Ресурсы из видео: + +1. [https://typerun.top/#rus_basic](https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbU5WZVdmMnVnVnh4bXp0Vm9DTGpNRF9tdjI5d3xBQ3Jtc0tsY2NWM3RtTXE1dnlKR0J0b0lzd2swTFV0S3NyTWlCWkt2eUZ5S1BWTjcya0UyMXpUZjAwZzViNjgxM2FJdXFlZ05PLWV0QVFMdjZvXzZIZEdVNmhsYl9iaHZVUzJrejNjeTFoTUdLNmdTN2wzekpMTQ&q=https%3A%2F%2Ftyperun.top%2F%23rus_basic&v=TGVoOBmvTJs) - тренировка зон пальцев +2. [https://www.keybr.com/](https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbXFxYVd0ZlVDb3M4YWFuSkxBaWtLaUhJN2k3UXxBQ3Jtc0ttUlVFdVY3ekl5Zk5iWEpoN1lKTGhKMVZJTXlBR2NnanA4alg4cHNiNUQxU29abnZsSXlYaVdsQXYzMDVRQW9rU2ZONDZ6SEtBRERTRnBIeXloQm5PaHFhVkR0SGxYak5wRzh5YTBueUY2VFZFa2VhOA&q=https%3A%2F%2Fwww.keybr.com%2F&v=TGVoOBmvTJs) - отработка букв и сочетаний +3. [https://monkeytype.com/](https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbW9QVGg5TGtucXhBVURUX3NkN28wQjNfZExGZ3xBQ3Jtc0ttUzU3blVFNUFHaWVDUnV3WW9yaHJTbDBtenJpVmlkVFNKQUdjOWRQQ1g0cE5xWkpqTndhTlpOYXlyOFZNd3JqRENaQmlfLThkeDFvTzQzazBNVnNBM2pvUU96YkZXeTJNc3dhZmJvWWJTbEtYYkw0RQ&q=https%3A%2F%2Fmonkeytype.com%2F&v=TGVoOBmvTJs) - соревнования и практика + +![[Снимок экрана 2024-03-25 в 22.49.58.png]] + + +# Инструкция NuPhy Air75 + +[Главная](https://wiki.geekboards.ru/) / [Инструкции](https://wiki.geekboards.ru/category/instructions/) / Инструкция NuPhy Air75 + + + +## Боковые LED индикаторы + + + +## Переключатели на задней стороне + + + + + +Поменять режим ОС. + + + +**Выбрать режим подключения.** + +_В проводном режиме важно использовать data-кабель для подключения клавиатуры._ + +--- + +## Беспроводное подключение + + + +Используйте короткое нажатие для переключения между устройствами; +Нажмите **FN + 1…3** и удерживайте **3 секунды**, чтобы войти в режим сопряжения. +_*Подключите приемник 2,4 ГГц к компьютеру, прежде чем нажать **FN + 4**._ + +--- + +## Настройки подсветки + + + +**FN + <** = Переключение эффекта подсветки. + +**FN + >** = Переключение цвета подсветки. + +**FN + ˄ ˅** = Яркость +/-. + +--- + +## Настройка подсветки боковых индикаторов + + + +**FN + ? + <** = Переключение эффекта подсветки боковых индикаторов. + +**FN + ? + >** = Переключение цвета подсветки боковых индикаторов. + +**FN + ? + ˄ ˅** = Яркость боковых индикаторов +/-. + +--- + +## Индикатор заряда батареи + + + +Зажмите **FN + |**, чтобы показать цвет текущего уровня заряда батареи. _см. «[Боковые LED индикаторы](https://wiki.geekboards.ru/instructions/instruction-nuphy-air75/#1)» в начале инструкции._ + +**FN + ]** = Всегда показывать цвет текущего уровня заряда батареи. + +**FN + |** = Одиночное нажатие выключает постоянную индикацию заряда. + +--- + +## Другие комбинация клавиш + + + +**✀** **(Mac)** = Скриншот области. + +**FN + ✀ (Mac)** = Полноэкранный скриншот. + +**✀ (Win)** = Полноэкранный скриншот. + +**ᗢ** = Активация виртуального помощника (Siri / Cortana)*. + +_*MacOS (Siri): Системные настройки → Siri → Выберете опцию «Press Fn(Function) Space»._ + +_*Windows (Cortana): Пуск → Параметры → Cortana и установите горячие клавиши для вызова на Win+C._ + +--- + +## Настройка спящего режима + + + +**FN + TAB + S** = Выключить спящий режим. + +**FN + TAB + W** = Включить спящий режим. + +--- + +## Сброс к заводским настройкам + + + +**FN + TAB + R** = Сбросить клавиатуру до заводских настроек. + +--- + +## Переназначить F-ряд на мультимедиа + + + +**FN + TAB + F**. После этого F-ряд будет выполнять функции мультимедиа клавиш. Повторное нажатие комбинации вернет ряду исходное значение.
\ No newline at end of file diff --git a/OBSIDIAN.md b/OBSIDIAN.md new file mode 100644 index 0000000..03a1fc6 --- /dev/null +++ b/OBSIDIAN.md @@ -0,0 +1,56 @@ + +# New +## New +### New +**Text** +*Text* +_Text_ +~~Text~~ +==Text== +Суперпользователь: `sudo su` +%% Text %% + +$Text$ + +![[Снимок экрана 2023-10-06 в 10.40.44.png | 400]] + +- первый +- второй +- третий + +[Ссылка на Яндекс](www://ya.ru) + +>"ну шо там" +>задница? + +> пипец! +> kkkkkk + +>lkjhgfd + +>[!info]- # Заголовок: +>Здесь какой-то текст +>и тут тоже - текст +>супер! + +>[!faq]- # Заголовок: +>Здесь какой-то текст + +>[!note] # Заголовок: +>Здесь какой-то текст + +>[!example]- Заголовочек +># Заголовок: +>Здесь какой-то текст + +>[!note]- Заметка о птичках +> # Заголовок: +>Здесь какой-то текст + +```rust +fn main() { + println("Hello World !") +} +``` + +![[Снимок экрана 2023-08-06 в 22.19.19.png | 600]] @@ -0,0 +1,11 @@ +### Порты от 0 до 1023 + +По сути, это самые узнаваемые порты, потому что **зарезервированы для операционной системы рассматриваемого компьютера** и, кроме того, они являются наиболее важными протоколами, гарантирующими оптимальную работу. Таким образом, яркий пример этого можно найти в **порт 80 для HTTP**. + +### Порты с 1024 по 49151 + +Во второй обозначенной категории мы находим зарегистрированные порты, состоящие из тех, которые **используются исключительно приложениями и играми, установленными на машине**. Таким образом, они обычно автоматически настраиваются в процессе установки. + +### Порты с 49152 по 65535 + +Эти интерфейсы называются **«Динамические или частные порты»** и они относятся к тем, которые соответствуют всем тем приложениям, которые должны подключаться к серверу для правильной работы. Среди наиболее ярких примеров в этом отношении мы находим **клиенты скачивания P2P**.
\ No newline at end of file diff --git a/PYTHON/-args, --kwargs.md b/PYTHON/-args, --kwargs.md new file mode 100644 index 0000000..22029c2 --- /dev/null +++ b/PYTHON/-args, --kwargs.md @@ -0,0 +1,43 @@ +**args**, когда неуверены, сколько аргументов будет передано функции, или если хотим передать сохраненный список или кортеж аргументов функции. + +**kwargs** используется, когда мы не знаем, сколько аргументов ключевых слов будет передано, а также он может быть использован для передачи значений словаря в качестве аргументов ключевых слов. Идентификаторы args и kwargs необязательны, вы можете изменить их на другие, типа *другой **пример, но лучше использовать имя по умолчанию. + +```python +# Пример *args +def sample( *args) : +print(args) +sample( 'time', 1, True) + +# Пример **kwargs +def sample(**kwargs) : +print(kwargs) +sample(a = 'time', b = 1) +``` + +==================================== +В Python можно распаковывать коллекции в аргументы или структуры — это мощно. + +```python +#_***args и kwargs — собирают аргументы:_ + +def f(*args, **kwargs): + print(args, kwargs) + +f(1, 2, x=10) # (1, 2), {'x': 10} + +# 👍 _* и ** — распаковывают:_ + +nums = [1, 2, 3] +print(*nums) # 1 2 3 + + +params = {"sep": " | "} +print(1, 2, 3, **params) # 1 | 2 | 3 + +#_Распаковка в переменные:_ + +a, *middle, b = [1, 2, 3, 4, 5] +print(middle) # [2, 3, 4] +``` + +**Где применять: передача параметров, адаптация API, читаемый код** diff --git a/PYTHON/12 однострочных конструкций.md b/PYTHON/12 однострочных конструкций.md new file mode 100644 index 0000000..1e5d8be --- /dev/null +++ b/PYTHON/12 однострочных конструкций.md @@ -0,0 +1,65 @@ +`pip instal bpython` + +```python +x, y, z = map(int, input().strip().split()) +print(f"{x=}, {y=}, {z=}") + +x, y, z = map(int, (x, y, z)) + +############### +from functools import reduce + +volume = reduce(lambda x, y: x * y, map(int, input().strip().split())) +print(f"(volume= {volume})) + +############### +names = ['Христофор', 'Адемар', 'Тэя', 'Стефания', 'Архип'] +names_starts_a = [name for name in names if name.startswith('A')] +print(names_starts_a) + +############### +names = ['Христофор', 'Адемар', 'Тэя', 'Стефания', 'Архип'] +names_starts_a = filter(lambda name: name.startswith('A'), names) +print(list(names_starts_a)) + +############### +numbers = [1, 2, 3] + +# Копирование массива: +another_numbers = numbers[:] + +# Перевернуть массив: +numbers[::-1] + +# Проверка на вхождение: +name = "Василий" +if name in ('Алексей', 'Пётр', 'Христофор'): + print(name) + +# Проверка все-ли True: +if all((a, b, c, d, e)): print("All True") + +# Проверка если хотя бы один True: +if any((a, b, c, d, e)): print("Any True") +############### +# Тернарный оператор: +admin_email = "admin@site.ru" if config.IS_PRODACTION else "mymail@mail.com" + +# Выбор метода в зависимости от <group>: +class User: + def __init__(self, group: str): + self.group = group + +user = User(group='admin') + +group_to_process_method = { + 'admin': process_admin_request, + 'manager': process_manager_request, + 'client': process_client_request, + 'anon': process_anon_request, + +} + + +``` + diff --git a/PYTHON/8Gb файл открыть на ОЗУ 2Gb.md b/PYTHON/8Gb файл открыть на ОЗУ 2Gb.md new file mode 100644 index 0000000..6ee4267 --- /dev/null +++ b/PYTHON/8Gb файл открыть на ОЗУ 2Gb.md @@ -0,0 +1,9 @@ +#### Как прочитать файл объемом 8 ГБ на Python с помощью компьютера с 2 ГБ ОЗУ? + +Это решение работает для любых больших файлов. + +Когда вы открываете файл, всё, что вам нужно сделать, это использовать объект файла в качестве итератора: при циклическом просмотре этого объекта, вы будете извлекать по одной строке за раз, а предыдущие строки будут удалены из памяти. + +Таким образом, файл никогда не будет полностью загружен в память, и ваша обработка будет выполняться на ходу. + +![[Pasted image 20250214130256.png]]
\ No newline at end of file diff --git a/PYTHON/AsyncIO.md b/PYTHON/AsyncIO.md new file mode 100644 index 0000000..2c8e365 --- /dev/null +++ b/PYTHON/AsyncIO.md @@ -0,0 +1,155 @@ +### Под капотом asyncio: принципы работы и ключевые концепции ### + +**Библиотека asyncio предоставляет полный набор инструментов для организации параллельного выполнения кода в Python с использованием концепции асинхронности. Но как на самом деле работает asyncio? Давайте разберемся в ключевых принципах и понятиях. + +Библиотека asyncio предоставляет полный набор инструментов для организации параллельного выполнения кода в Python с использованием концепции асинхронности. Но как на самом деле работает asyncio? Давайте разберемся в ключевых принципах и понятиях. + +Этот материал взят из нашей субботней [email-рассылки](https://proglib.io/w/edaee37b), посвященной Python. Подпишитесь, чтобы быть в числе первых, кто получит дайджест. + +**Asyncio** – библиотека, которая предоставляет инфраструктуру для написания параллельного кода с использованием концепции асинхронного программирования. Она позволяет эффективно обрабатывать многочисленные задачи ввода-вывода (например, сетевые операции или чтение/запись из файлов) без необходимости создавать множество потоков или процессов. Ключевые концепции asyncio: + +**Событийный цикл (Event Loop)** + +- Это ядро **asyncio**, которое отвечает за планирование и выполнение задач (корутин). +- Реализован на языке **C** для максимальной эффективности. +- Работает по принципу **однопоточной многозадачности** (single-threaded concurrency). +- Постоянно опрашивает очередь событий и выполняет соответствующие задачи. + +**Корутины (Coroutines)** + +- Это специальные функции, выполнение которых можно приостанавливать на определенных точках с помощью ключевого слова `await`. +- Используются для написания асинхронного, событийно-ориентированного кода. +- Могут ждать асинхронные события (ввод-вывод, сигналы или другие корутины). +- Определяются с помощью `async def`. + +**Задачи (Tasks)** + +- Представляют собой обертки над корутинами для их планирования и выполнения. +- Создаются с помощью `asyncio.create_task(coroutine)` или `asyncio.ensure_future(coroutine)`. +- Задачи можно отменять, объединять с другими задачами и отслеживать на предмет исключений. +- Задачи добавляются в событийный цикл и выполняются в порядке их готовности. + +**Будущие объекты (Futures)** + +- Представляют результат асинхронной операции, который может быть доступен позже. +- Задачи наследуются от класса `Future` и добавляют собственные методы управления. +- Могут быть использованы для координации между разными частями программы. + +**Транспорты и протоколы** + +- **Транспорты** (sockets) отвечают за **передачу** данных по сети. +- **Протоколы** определяют, как **интерпретировать** эти данные (HTTP, WebSocket и др.). +- asyncio предоставляет высокоуровневые абстракции для работы с транспортами и протоколами. + +**Другие инструменты** + +- Пулы для управления ограниченными ресурсами (потоки, подпроцессы). +- Синхронизаторы для координации между корутинами (**Lock****,** **Event**, **Condition)**. +- Очереди для безопасной передачи данных между корутинами. +- Сигналы для обработки внешних событий (UNIX-сигналы). + +**Вот как работает asyncio:** + +- Создается **событийный цикл**, который будет управлять выполнением задач. +- Определяются корутины, которые выполняют некоторую работу и могут приостанавливаться на операциях ввода-вывода с помощью ключевого слова `await`. +- Корутины оборачиваются в задачи с помощью функции `asyncio.create_task()`. +- Задачи планируются для выполнения в событийном цикле с помощью функции `asyncio.run()` или добавляются в цикл вручную. +- Событийный цикл начинает выполнять задачи по очереди. Когда задача достигает операции ввода-вывода (например, `await asyncio.sleep()`), она приостанавливается, и событийный цикл переключается на другую задачу. +- Когда операция ввода-вывода завершается, задача возобновляется и продолжает выполнение до следующей операции ввода-вывода или завершения. + +Статья по теме + +[Упрощаем разработку: асинхронные функции Python](https://proglib.io/p/uproshchaem-razrabotku-asinhronnye-funkcii-python-2019-10-06) + +Так asyncio позволяет эффективно использовать **один** поток для выполнения **множества** задач с операциями ввода-вывода, избегая блокировки и простоев из-за ожидания завершения этих операций. + +Важно отметить, что asyncio оптимален **именно для I/O-bound задач** (сеть, файловые операции и т. д.), и **не подходит для CPU-bound операций** (вычислительно-интенсивных задач), поскольку в этом случае он не сможет переключаться на другие задачи во время блокировки. Для такого рода задач лучше использовать многопоточность или многопроцессорность. Подробнее о принципах работы asyncio – в [этой статье](https://jacobpadilla.com/articles/recreating-asyncio) и [в этом видео](https://www.youtube.com/watch?v=RIVcqT2OGPA)[](http://savefrom.net/?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DRIVcqT2OGPA&utm_source=userjs-safari&utm_medium=extensions&utm_campaign=link_modifier "Получи прямую ссылку"). + +======================================= +🚀 **asyncio** – это библиотека для асинхронного программирования, позволяющая писать код, выполняющий несколько задач параллельно (без потоков и процессов). + +✅ async def – объявление асинхронной функции (корутина). +✅ await – приостанавливает выполнение до завершения асинхронной операции. +✅ asyncio.run() – запускает главную асинхронную функцию. +✅ asyncio.create_task() – запускает корутину в фоновом режиме. +✅ asyncio.sleep() – имитирует задержку без блокировки других задач. + +```python +import asyncio + +async def say_hello(): + await asyncio.sleep(1) # Имитация задержки + print("Привет, мир!") + +async def main(): + task1 = asyncio.create_task(say_hello()) + task2 = asyncio.create_task(say_hello()) + await task1 + await task2 + +asyncio.run(main()) # Запуск асинхронной программы +``` +**Выведет с задержкой 1 секунду:** + +Привет, мир! +Привет, мир! + +Обе задачи выполняются **параллельно**, а не последовательно! + +📌 Когда использовать asyncio? + +✅ Запросы в интернет (aiohttp). +✅ Работа с базами данных (aiomysql, asyncpg). +✅ Асинхронное чтение файлов/сокетов. +✅ Многозадачность без потоков и процессов. + +#====================================== +**Вот как работает asyncio — без магии:** + +📌 1. **Event Loop — это «планировщик», который гоняет корутины. Он сам не создаёт потоки. Он ждёт, когда одна задача «освободит» CPU, и сразу берётся за другую.** +```python +import asyncio + +async def main(): + print("Start") + await asyncio.sleep(1) + print("End") + +asyncio.run(main()) +``` +Здесь await asyncio.sleep(1) — не блокирует поток. Это ***yield*,** который говорит: "я пока жду, можешь заняться чем-то ещё". + +📌 2. **Ты не должен использовать time.sleep() в async-коде. Это блокирует весь event loop. Заменяй на await asyncio.sleep().** + +📌 3. **Любая синхронная тяжёлая операция — угроза. Например, чтение файла или SQL-запрос через обычный драйвер.** + +✔️ Как решить: + +👍 Используй aiofiles для файлов. +👍 Используй async-драйверы (asyncpg, aiomysql, httpx и т.д.) +👍 Если нет async-аналога — выноси в ThreadPoolExecutor. +```python +from concurrent.futures import ThreadPoolExecutor +import asyncio + +def blocking(): + ... + +async def main(): + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, blocking) +``` + +📌 4.**Не забывай про семафоры, если запускаешь 1000 задач одновременно. async не значит всё сразу.** +```python +sem = asyncio.Semaphore(100) + +async def safe_task(): + async with sem: + await do_something() +``` + +🧠 asyncio — это не про параллельность. Это про эффективное переключение задач, которые ждут чего-то: ответа сервера, данных, паузы. + +🗣 **Применяется в: Web-серверах (FastAPI, aiohttp), парсинге, бэкенде с I/O, высоконагруженных API.** + diff --git a/PYTHON/BEGIN + PIP.md b/PYTHON/BEGIN + PIP.md new file mode 100644 index 0000000..d67c8c0 --- /dev/null +++ b/PYTHON/BEGIN + PIP.md @@ -0,0 +1,226 @@ + +![[Python_шпаргалка.png]] + +```bash +which python3 +which pip + +sudo apt install -y python3-pip +pip install numpy + +sudo apt install -y python3-virtualenv +which virtualenv + +# в папке проекта для ДЕактивации среды: +deactivate + +#========================================= +cd code +mkdir pythontests +cd pythontests + +python3 -m venv .venv-name +. ./.venv-name/bin/activate + +nvim pyrightconfig.json + +{ + "venv": "env", + "venvPath": "." +} + +# в папке проекта для ДЕактивации среды: +deactivate + +#========================================== +# Список пакетов с зависимостями: +pip freeze + +# Создаем файл с зависимостями: +pip freeze > requirements.txt + +# Устанавливаем все зависимости в другом месте: +pip install -r requirements.txt + +# список установленных пакетов: +pip list + +# удалить пакет: +pip uninstall <packet> -y + +``` + +```sh +# Активация в Windows: +python -m venv venv +.\venv\Scripts\activate + +# Создаем файл с зависимостями: +pip freeze > requirements.txt + +# создаем файл requirements.txt (со всеми необходимыми зависимостями) и запускаем команду: +pip install -r .\requirements.txt + +``` +```sh +git clone user@address:~/packet.git +cd packet + +# Создаем сам скрипт: +vim install.sh + +# Вносим в него: +apt update +apt install -y gcc python3-dev python3-venv +python3 -m venv env +source env-allocation/bin/activate +pip install wheel +pip install -r requirements.txt +export KEY_SECRET='' + +# возможно нужно дать разрешение на запуск! +sudo sh ./install.sh + +sudo python3 ./runner.py + +``` +========================= +Вот как поместить Python-программу в systemd для автозапуска: + +## 1. Создайте unit-файл для systemd + +Создайте файл в `/etc/systemd/system/` (для системных сервисов) или `~/.config/systemd/user/` (для пользовательских сервисов): + +bash + +sudo nano /etc/systemd/system/myapp.service + +## 2. Пример конфигурации unit-файла + +ini + +[Unit] +Description=My Python Application +After=network.target + +[Service] +Type=simple +# Если используете виртуальное окружение: +ExecStart=/path/to/venv/bin/python /path/to/your/app.py +# Или без виртуального окружения: +# ExecStart=/usr/bin/python3 /path/to/your/app.py + +# Рабочая директория +WorkingDirectory=/path/to/your/app/directory + +# Пользователь для запуска (опционально) +User=username +Group=groupname + +# Перезапуск при ошибке +Restart=always +RestartSec=5 + +# Переменные окружения +Environment="PYTHONPATH=/path/to/your/app" +Environment="DATABASE_URL=mysql://user:pass@localhost/db" + +[Install] +WantedBy=multi-user.target + +## 3. Для приложений с зависимостями (виртуальное окружение) + +ini + +[Service] +Type=simple +ExecStart=/home/user/myapp/venv/bin/python /home/user/myapp/main.py +WorkingDirectory=/home/user/myapp +Environment="PATH=/home/user/myapp/venv/bin" + +## 4. Основные команды для управления + +bash + +# Загрузить изменения конфигурации +sudo systemctl daemon-reload + +# Запустить сервис +sudo systemctl start myapp + +# Остановить сервис +sudo systemctl stop myapp + +# Включить автозапуск при загрузке +sudo systemctl enable myapp + +# Проверить статус +sudo systemctl status myapp + +# Просмотреть логи +sudo journalctl -u myapp -f + +## 5. Полезные опции для Python-приложений + +- **`Restart`** - политика перезапуска: + + - `no` - не перезапускать + + - `on-success` - только при успешном завершении + + - `on-failure` - при ошибке (рекомендуется) + + - `always` - всегда перезапускать + +- **`StandardOutput`/`StandardError`** - куда перенаправлять вывод: + + ini + + StandardOutput=journal + StandardError=journal + # Или в файл: + StandardOutput=file:/var/log/myapp.log + ## 6. Пример для Flask/Django приложения + +ini + +[Unit] +Description=My Flask Web Application +After=network.target mysql.service + +[Service] +Type=simple +User=www-data +Group=www-data +WorkingDirectory=/var/www/myapp +ExecStart=/var/www/myapp/venv/bin/gunicorn --workers 3 --bind unix:/tmp/myapp.sock wsgi:app +Environment="FLASK_ENV=production" +Restart=always + +[Install] +WantedBy=multi-user.target + +## 7. Проверка и отладка + +bash + +# Проверить синтаксис конфигурации +sudo systemd-analyze verify /etc/systemd/system/myapp.service + +# Тестовый запуск без systemd +sudo -u username /path/to/python /path/to/app.py + +# Подробные логи +sudo journalctl -u myapp -n 100 -f + +## Советы: + +1. Убедитесь, что Python-скрипт имеет права на выполнение + +2. Используйте абсолютные пути во всех настройках + +3. Для разработки можно запускать как user-сервис без sudo + +4. Для приложений с GUI может понадобиться `Environment="DISPLAY=:0"` + +===================== diff --git a/PYTHON/COM-порт.md b/PYTHON/COM-порт.md new file mode 100644 index 0000000..b1706c3 --- /dev/null +++ b/PYTHON/COM-порт.md @@ -0,0 +1,41 @@ +```python +import serial + +from serial.tools import list_ports +ports = list(list_ports.comports()) +i = 1 +for p in ports: + print(i, ' - ', p.device) + i += 1 + +port = 8 # int(input('Введите номер COM-порта: ')) +baudrate = 115200 # int(input('Введите скорость COM-порта: ')) + +while port > len(ports): + print("Такого порта - НЕТ !") + port = int(input('Введите номер COM-порта: ')) + +print(ports[port - 1].device) + +try: + ser = serial.Serial(ports[port - 1].device, baudrate=baudrate) + print("Serial connection established.") + + while True: + line = ser.readline().decode().strip() + + if line: + print(line) + +except serial.SerialException as se: + print("Serial port error:", str(se)) + +except KeyboardInterrupt: + pass + +finally: + if ser.is_open: + ser.close() + print("Serial connection closed.") +``` + diff --git a/PYTHON/DAEMON на Python.md b/PYTHON/DAEMON на Python.md new file mode 100644 index 0000000..94416cc --- /dev/null +++ b/PYTHON/DAEMON на Python.md @@ -0,0 +1,126 @@ + +Демон — это процесс, который работает в фоновом режиме, обычно без прямого взаимодействия с пользователем. Демоны часто запускаются системой при старте и продолжают работать независимо от пользовательских сеансов. + +### Разница между демоном и обычным процессом + +Отличие демона от обычного процесса состоит в том, что демон отделён от терминала, не имеет своего интерфейса ввода-вывода и продолжает работать в фоновом режиме, даже когда пользователь завершает свою сессию. + +### Области применения демонов + +- Проверка состояния серверов и служб. +- Автоматическая фоновая обработка данных. +- Выполнение периодических задач, таких как обновление баз данных или отправка отчетов. + +## Основные библиотеки для создания демонов + +Для создания демонов на языке Python существует несколько популярных библиотек: + +- `daemon`: Простая и удобная библиотека для создания демонов. +- `systemd`: Более продвинутый инструмент для управления демонами, интегрированный с системой и использующий все её возможности. +- `multiprocessing`: Позволяет создавать многопроцессорные приложения с функциональностью демонов. + +### Преимущества и недостатки каждой библиотеки + +- **daemon**: +- Преимущества: Простота использования. +- Недостатки: Ограниченные возможности конфигурации и интеграции с системой. + +- **systemd**: +- Преимущества: Глубокая интеграция с системой, мощные возможности конфигурации. +- Недостатки: Требует знания специфики конфигурации systemd. + +- **multiprocessing**: +- Преимущества: Поддержка многопроцессорных задач. +- Недостатки: Сложность в настройке и управлении демонами. + +## Подготовка окружения Python + +Перед тем как начать писать код демона, необходимо установить необходимые библиотеки и настроить окружение Python. + +### Установка необходимых библиотек + +```plain +pip install daemon +``` + +## Написание базового демона + +Создадим базовую структуру демона, уделяя внимание функции `run_as_daemon()`. + +### Создание базовой структуры демон-программы + +```python +import daemon +import time + +def run() -> None: + """Основная функция работы демона.""" + while True: + print('Демон работает...') + time.sleep(5) + +if __name__ == '__main__': + with daemon.DaemonContext(): + run() +``` + +## Логирование и какие библиотеки использовать + +Логирование позволяет отслеживать события и ошибки, что облегчает диагностику и поддержку демона. + +### Примеры настройки логирования + +```python +import logging + +logging.basicConfig(level=logging.INFO, filename='daemon.log') + +def run() -> None: + """Основная функция работы демона с логированием.""" + while True: + logging.info('Демон работает...') + time.sleep(5) +``` + +## Обработка сигналов + +Сигналы позволяют управлять работой демона, например, корректно завершить его работу. + +### Объяснение, что такое сигналы и как их обработка помогает управлять демоном + +Сигналы — это механизмы межпроцессного взаимодействия, позволяющие одному процессу посылать уведомления другому. + +### Примеры обработки сигналов, таких как SIGTERM и SIGINT + +```python +import signal + +def handle_signal(signum: int, frame) -> None: + """Обработка сигнала завершения.""" + logging.info('Демон завершает работу...') + exit(0) + +signal.signal(signal.SIGTERM, handle_signal) +signal.signal(signal.SIGINT, handle_signal) +``` + +### Создание systemd юнита для автоматического запуска демона +```sh +[Unit] +Description=Мой Демон + +[Service] +ExecStart=/path/to/your/daemon + +[Install] +WantedBy=multi-user.target +``` + +### Как управлять демоном с помощью системных команд +```sh +sudo systemctl start my-daemon.service +sudo systemctl stop my-daemon.service +sudo systemctl enable my-daemon.service +sudo systemctl disable my-daemon.service +``` + diff --git a/PYTHON/DEFAULTDICT.md b/PYTHON/DEFAULTDICT.md new file mode 100644 index 0000000..4efc9f9 --- /dev/null +++ b/PYTHON/DEFAULTDICT.md @@ -0,0 +1,33 @@ +**Использование коллекций** defaultdict + +Часто при работе со словарями в Python приходится проверять наличие ключа перед его использованием. + +Вместо этого можно использовать defaultdict из модуля collections. + +`from collections import defaultdict + +# Создаем словарь с дефолтным значением 0 + +`word_count = defaultdict(int) + +`words = ["python", "code", "python", "script", "code", "python"] + +`for word in words: + + word_count[word] += 1 # Автоматически создаются новые ключи + +`print(word_count) + +# Вывод: defaultdict(<class 'int'>, {'python': 3, 'code': 2, 'script': 1}) + +**📌 Почему это удобно?** + +🟢 Не нужно вручную проверять наличие ключа + +🟢 Автоматически создаются значения по умолчанию + +🟢 Полезно для счетчиков, группировки и работы со структурами данных + + + +➡️ Также можно использовать `defaultdict(list)` или `defaultdict(set)` для удобного хранения данных.
\ No newline at end of file diff --git a/PYTHON/DJANGO.md b/PYTHON/DJANGO.md new file mode 100644 index 0000000..07d5ac8 --- /dev/null +++ b/PYTHON/DJANGO.md @@ -0,0 +1,36 @@ +```bash +python3 -V + +python3 -m venv venv + +. venv/bin/activate +# deactivate - выключить вирт окружение + +pip install --upgrade pip +pip install django + +django-admin startproject coolsite +cd coolsite +python3 manage.py runserver +# CTRL_C + +python3 manage.py startapp women +``` + +```python +# coolsite/settings.py +'women.apps.WomenConfig' + +# women/views.py +from django.http import HttpResponse + +def index(Request): #HttpRequest + return HttpResponse("Страница приложения women.") + +# coolsite/urls.py +from coolsite.women.views import index + +path('women/', index), + + +```
\ No newline at end of file diff --git a/PYTHON/ENUMERATE, ENUM.md b/PYTHON/ENUMERATE, ENUM.md new file mode 100644 index 0000000..ee2ed64 --- /dev/null +++ b/PYTHON/ENUMERATE, ENUM.md @@ -0,0 +1,11 @@ +Итерация по любому содержимому в Python проста – обычный цикл for. Однако очень часто требуются одновременно и индекс элемента, и сам элемент. Программисты используют len() и range() для перебора списка по индексу, но есть более простой способ. + + +![[Pasted image 20250501031753.png]] + +![[Pasted image 20250217114433.png]] + +## **ENUM + +![[telegram-cloud-photo-size-2-5221956955604644168-y.jpg]] + diff --git a/PYTHON/ENUMERATE, ZIP.md b/PYTHON/ENUMERATE, ZIP.md new file mode 100644 index 0000000..139597f --- /dev/null +++ b/PYTHON/ENUMERATE, ZIP.md @@ -0,0 +1,2 @@ + + diff --git a/PYTHON/FOR, WHILE.md b/PYTHON/FOR, WHILE.md new file mode 100644 index 0000000..5bf423d --- /dev/null +++ b/PYTHON/FOR, WHILE.md @@ -0,0 +1,227 @@ +```python +# Фильтрация элементов по типу: +def filter_list(list_to_filter, value_type): + filtered_list = [] + for element in list_to_filter: + if type(element) == value_type: + filtered_list.append(element) + + # Not recomended! Because bool is subclass of int ! + # if isinstance(element, value_type): + # filtered_list.append(element) + + return filtered_list + + +print(filter_list([35, True, 'abc', 10], int)) +print(filter_list([35, True, 'abc', 10], str)) +print(filter_list([35, True, 'abc', 10], bool)) + +########################################################### +# Сокращенный FOR: +all_nums = [-3, 1, 0, 10, -20, 5] + +absolute_nums = [abs(num) for num in all_nums] + +print(absolute_nums) +print(all_nums) + +#----------------- +all_nums = [-3, 1, 0, 10, -20, 5] + +positive_nums = [num for num in all_nums if num > 0] + +# positive_nums = [] + +# for num in all_nums: +# if num > 0: +# positive_nums.append(num) + +print(positive_nums) +print(all_nums) + +#----------------- +my_set = {1, 10, 15} + +new_set = {val * val for val in my_set} + +print(new_set) +print(my_set) + +#----------------- +my_scores = { + 'a': 10, + 'b': 7, + 'm': 14 +} + +scores = {k: v * 10 for k, v in my_scores.items()} + +# scores = {} + +# for key, value in my_scores.items(): +# scores[key] = value * 10 + +print(scores) +print(my_scores) + +#----------------- +my_scores = [10, 7, 14] + +scores = {index: elem * 2 for index, elem in enumerate(my_scores)} + +print(scores) + +#----------------- +# my_motorbike = { + # 'brand': 'bmw', + # 'country': 'germany', + # 'owner': 'vlapa' +# } + +# bike = {k: v.upper() for k, v in my_motorbike.items()} + + +my_motorbike = ['BMW', 'Germany', 'Vlapa'] + +bike = [el for el in my_motorbike if len(el) > 3] + + +print(bike) + +print(my_motorbike) + +########################################################### + +while True: + try: + num_one = float(input("Please enter number one: ")) + num_two = float(input("Please enter number two: ")) + except ValueError as e: + print(e) + print("You must enter numbers!") + continue + + print(num_one / num_two) + + answer = input("Do you want to continue? (yes/no): ") + if answer == 'no': + break +``` + +1️⃣ **Цикл** for + +Используется для перебора элементов в итерируемых объектах (списки, строки, множества, кортежи, словари, генераторы и т. д.). + +**Пример 1: Перебор списка** + +fruits = ["яблоко", "банан", "вишня"] +for fruit in fruits: + print(fruit) + +**Пример 2:** for **с** range() + +for i in range(5): # От 0 до 4 + print(i) + +**Пример 3: Перебор словаря** + +person = {"имя": "Алиса", "возраст": 25} +for key, value in person.items(): + print(f"{key}: {value}") + +2️⃣ **Цикл** while + +Выполняется **до тех пор, пока условие истинно.** + +**Пример:** + +x = 5 +while x > 0: + print(f"x = {x}") + x -= 1 # Уменьшаем x + +🗣️ Используйте for когда заранее известно количество итераций, и while когда не известно. + +🖥 **Циклы. Управление потоком** + +1️⃣ break – прерывает выполнение цикла. +2️⃣ continue – пропускает текущую итерацию и переходит к следующей. +3️⃣ pass – заглушка, ничего не делает (используется, когда код еще не написан). + +**Примеры:** +# break – выход из цикла при достижении 5 + +for i in range(10): + if i == 5: + break + print(i) # Выведет 0,1,2,3,4 + +# continue – пропускает 5 + +for i in range(7): + if i == 5: + continue + print(i) # Выведет 0,1,2,3,4,6 + + +# pass – заглушка + +for i in range(3): + pass # Код пока не написан + + +🖥 **Использование** else **в циклах** + +else в цикле выполняется, если цикл завершился естественным образом (без break и return). + +**Пример 1:** for **...** else + +for i in range(3): + print(i) +else: + print("Цикл завершился без break") +0 +1 +2 +# Цикл завершился без break + +**Пример 2:** while **...** else + +x = 3 +while x > 0: + print(x) + x -= 1 +else: + print("Цикл while завершен") +3 +2 +1 + +# Цикл while завершен + +**Пример 3: проверка вхождения в список** + +def contains(num, numbers): + for n in numbers: + if n == num: + return True + else: + return False +print(contains(3, [1,2,3,4,5])) # True +print(contains(3, [1,2,4,5])) # False + +**Пример 4: определение простого числа** + +def is_prime_number(num): + for i in range(2, int(num ** 0.5) + 1): + if num % i == 0: + print(f"{num} не простое число") + break + else: + print(f"{num} — простое число") + +is_prime_number(17) # 17 — простое число +is_prime_number(16) # 16 не простое число + +🗣️ Если нет break и return, else всегда сработает, и его можно просто вынести за пределы цикла.
\ No newline at end of file diff --git a/PYTHON/HTTP.md b/PYTHON/HTTP.md new file mode 100644 index 0000000..9e83b93 --- /dev/null +++ b/PYTHON/HTTP.md @@ -0,0 +1,18 @@ +```shell +# Быстрый запуск простого HTTP сервера для передачи ФАЙЛОВ ! +# по умолчанию порт = 8000 + +# его нужно открыть в firewall: +sudo ufw allow 8000 +# sudo firewall-cmd --add-port=8000/tcp + +# переходим в нужный каталог: +cd </my/catalog> + +# запускаем: +python3 -m http.server 8000 +# python3 -m http.server --bind 127.0.0.1 8000 + +# открываем в браузере http://89.110.92.137:8000 +# и качаем нужные ФАЙЛЫ ! +``` diff --git a/PYTHON/LIST COMPREHENSION.md b/PYTHON/LIST COMPREHENSION.md new file mode 100644 index 0000000..0f9f0bd --- /dev/null +++ b/PYTHON/LIST COMPREHENSION.md @@ -0,0 +1,6 @@ +![[telegram-cloud-photo-size-2-5220111584251211568-y.jpg]] +![[telegram-cloud-photo-size-2-5220111584251211577-y.jpg]] +![[telegram-cloud-photo-size-2-5220111584251211571-y.jpg]] +![[telegram-cloud-photo-size-2-5220111584251211570-y.jpg]] +![[telegram-cloud-photo-size-2-5220111584251211574-y.jpg]] +![[telegram-cloud-photo-size-2-5220111584251211572-y.jpg]]![[telegram-cloud-photo-size-2-5220111584251211573-y.jpg]] diff --git a/PYTHON/MAP.md b/PYTHON/MAP.md new file mode 100644 index 0000000..f1b79ff --- /dev/null +++ b/PYTHON/MAP.md @@ -0,0 +1,15 @@ +```python +# Функция map() применяется к каждому элементу последовательности и возвращает итератор. + +numbers = [1, 2, 3, 4] +squared = map(lambda x: x**2, numbers) +print(list(squared)) # [1, 4, 9, 16] + +# Можно передавать несколько списков: + +a = [1, 2, 3] +b = [4, 5, 6] +print(list(map(lambda x, y: x + y, a, b))) # [5, 7, 9] + +# Полезно для применения функций к списку без for +``` diff --git a/PYTHON/MATH.md b/PYTHON/MATH.md new file mode 100644 index 0000000..6912e2c --- /dev/null +++ b/PYTHON/MATH.md @@ -0,0 +1 @@ +![[telegram-cloud-photo-size-2-5230966980947866123-y.jpg]] diff --git a/PYTHON/Matprotlib.md b/PYTHON/Matprotlib.md new file mode 100644 index 0000000..ed42f2d --- /dev/null +++ b/PYTHON/Matprotlib.md @@ -0,0 +1,29 @@ +📊 **Как нарисовать график в 3 строки кода** + +С matplotlib можно строить графики всего за пару секунд! + +**✔️ Установка:** + +```sh +pip install matplotlib +``` + +**➡️ Простой график:** +```python +import matplotlib.pyplot as plt + +plt.plot([1, 2, 3, 4], [10, 20, 25, 30]) +plt.show() +``` + +⚙️ matplotlib поддерживает линии, гистограммы, диаграммы и многое другое. +🗣 **Если хочешь визуализировать данные —** matplotlib **твой инструмент**! + + +![[Pasted image 20250621003213.png]] +![[Pasted image 20250621003225.png]] +Базовый пример построения графика с помощью Python + +Для начала требуется установить библиотеку matplotlib — pip install matplotlib. Сама программа для создания примера простого графика представлена на картинке. + +В функцию plot передаются два массива — координаты точек кривой. Первый массив — координаты по x, второй массив — y. Функция show выдает картинку с графиком.
\ No newline at end of file diff --git a/PYTHON/NAMEDTUPLE.md b/PYTHON/NAMEDTUPLE.md new file mode 100644 index 0000000..0c5614f --- /dev/null +++ b/PYTHON/NAMEDTUPLE.md @@ -0,0 +1,18 @@ +```python +# Обычно для хранения данных используют словари или классы. namedtuple — это легковесная альтернатива, сочетающая удобство атрибутов класса с простотой кортежей. + +# Создание namedtuple: + +from collections import namedtuple + +Point = namedtuple("Point", ["x", "y"]) +p = Point(3, 4) + +print(p.x, p.y) # 3 4 + +# Работает как обычный кортеж, но с доступом по именам: + +print(p[0], p[1]) # 3 4 + +# Полезно, когда нужен неизменяемый контейнер с понятными атрибутами! +``` diff --git a/PYTHON/OPEN - работа с файлами.md b/PYTHON/OPEN - работа с файлами.md new file mode 100644 index 0000000..e8d3d51 --- /dev/null +++ b/PYTHON/OPEN - работа с файлами.md @@ -0,0 +1,39 @@ +_Ключи команды:_ +r - открывает файл на чтение (по умолчанию) +w - запись, если нет - будет создан, если есть - будет перезаписан. +x - запись, если есть - ошибка +a - добавляет данные, если файла нет - создает +t - открывает файл как текстовый (по умолчанию) +\+ - и чтение и запись +```python +file = open(file='hello.txt', mode='r') +print(file.name) # имя файла с которым сейчас работаем +print(file.mode) # режим в котором работаем с файлом +file.close() # устаревшее + +with open(file='hello.txt) as file: + print(file.read(8)) + print(file.readline()) + print(file.readlines()) + + for line in file.readlines(): + print(linestrip()) + + for line in file: + print(line.strip()) + +password_list = ['qwerty, '12345', 'xxxxx'] + +with open(file='secret_file.txt', mode='w) as file: + for passwd in password_list: + file.write(f'{passwd}\n) + + file.writelines([line+'\n' for line in password_list]) + +# Сохранение изображений и др.: +import requessts + +response = requests.get('https://<ссылка>') + file.write(response.content) +``` + diff --git a/PYTHON/OS.md b/PYTHON/OS.md new file mode 100644 index 0000000..1a95ef9 --- /dev/null +++ b/PYTHON/OS.md @@ -0,0 +1,18 @@ +**Узнать текущую директорию + +`os.getcwd() +`C:/Users/...` + + +**Удаляем файл из папки на компьютере** + +Здесь сначала мы импортируем модуль `os`. Затем указываем полный путь к удаляемому файлу в переменной `file_path`. С помощью функции `os.path.exists()` проверяем, существует ли файл по указанному пути. + +Если файл существует, мы удаляем его с помощью функции `os.remove()`. В противном случае выводим информацию, что такой файл не найден. + +![[telegram-cloud-photo-size-2-5235693785730632253-y.jpg]] + + +**Выключение компьютера** +![[telegram-cloud-photo-size-2-5425027665104058215-x.jpg]] + diff --git a/PYTHON/PATH.md b/PYTHON/PATH.md new file mode 100644 index 0000000..6eaa7c7 --- /dev/null +++ b/PYTHON/PATH.md @@ -0,0 +1,111 @@ +```python +import pathlib + +p = pathlib.Path(__file__) +print(p) +# получим путь к файлу, в котором сейчас находимся: + +/home/name/package/example.py + +``` + +``` +from pathlib as Path +``` +- `Path.cwd()`: Возвращает путь объекта текущей рабочей директории; +- `Path.home()`: Возвращает путь объекта домашней директории; +- `Path.stat()`: Возвращает информацию о пути; +- `Path.chmod()`: Меняет режим и уровень доступа файла; +- `Path.glob(pattern)`: Получение всех файлов которые соответствую паттерну, например `*.jpg` (все картинки) или `*.mp3` (все песни); +- `Path.mkdir()`: создает новую папку по данному пути; +- `Path.open()`: Открывает файл, созданный в пути; +- `Path.rename()`: Переименовывает файл или директорию указанной цели; +- `Path.rmdir()`: Удаляет пустую директорию; +- `Path.unlink()`: Удаляет файл или символическую ссылку. + + +## Генерация кроссплатформенных путей в Pathlib + +Пути используют разные соглашения в разных операционных системах. Windows использует обратный слеш `\` между названиями папок, тогда как все другие популярные операционные системы используют прямой слеш `/`. + +Если вы хотите, чтобы ваш код работал, независимо от базовой ОС, вам нужно будет обрабатывать различные соглашения, характерные для базовой платформы. Модуль Pathlib упрощает работу с путями к файлам. В Pathlib можно просто передать путь или название файла объекту `Path()`, используя слеш, независимо от ОС. Pathlib занимается всем остальным. + +Python + +| | | +|---|---| +|1|pathlib.Path.home() / 'python' / 'samples' / 'test_me.py'| + +Объект `Path()` конвертирует `/` в слеш соответствующий операционной системе. `pathlib.Path` может представлять путь Windows или Posix. Кроме того, Pathlib решает многие кросс-функциональные баги, легко обрабатывая пути. + +## Получение информации о пути в Pathlib + +Во время работы с путями зачастую требуется найти родительскую директорию файла/папки или получить символические ссылки. У класса Path есть несколько удобных для этого методов, различные части пути доступны как свойства, что включают следующее: + +- `drive`: [строка](https://python-scripts.com/string "Создание строки в питоне"), что представляет название жесткого диска. К примеру, `PureWindowsPath('c:/Program Files/CSV').drive` вернет `"C:"`; +- `parts`: возвращает [кортеж](https://python-scripts.com/lists-tuples-dictionaries#tuple "Создать кортеж в питоне"), что дает доступ к компонентам пути; +- `name`: компонент пути без директории; +- `parent`: последовательность обеспечивает доступ к логическим предкам пути; +- `stem`: финальный компонент пути без суффикса; +- `suffix`: разрешение файла финального компонента; +- `anchor`: часть пути перед директорией. `/` используется для создания дочерних путей и имитации поведения `os.path.join`; +- `joinpath`: совмещает путь с предоставленными аргументами; +- `match(pattern)`: возвращает `True/False`, основываясь на совпадении пути с предоставленным шаблоном поиска. + +Например, у нас есть данный путь `"/home/projects/pyscripts/python/sample.md"`: + +- `path`: — возвращает _PosixPath(‘/home/projects/pyscripts/python/sample.md’);_ +- `path.parts`: — возвращает _(‘/’, ‘home’, ‘projects’, ‘pyscripts’, ‘python’);_ +- `path.name`: — возвращает _‘sample.md’;_ +- `path.stem`: — возвращает _‘sample’;_ +- `path.suffix`: — возвращает _‘.md’;_ +- `path.parent`: — возвращает _PosixPath(‘/home/projects/pyscripts/python’);_ +- `path.parent.parent`: — возвращает _PosixPath(‘/home/projects/pyscripts’);_ +- `path.match('*.md')`: возвращает _True;_ +- `PurePosixPath('/python').joinpath('edited_version')`: возвращает _(‘home/projects/pyscripts/python/edited_version._ + + +## Альтернатива для модуля glob + +Помимо модулей `os`, `os.path` в Python также доступен модуль `glob`, что предоставляет путь связанных утилит. Функция `glob.glob` модуля `glob` используется для **нахождения файлов**, соответствующих шаблону. + +Python + +| | | +|---|---| +|1<br><br>2<br><br>3<br><br>4|from glob import glob<br><br>top_xlsx_files = glob('*.xlsx') # Все файлы с расширением .xlsx<br><br>all_xlsx_files = glob('**/*.xlsx', recursive=True)| + +Pathlib предоставляет свою реализацию `glob`: + +Python + +| | | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| 1<br><br>2<br><br>3<br><br>4 | from pathlib import Path<br><br>top_xlsx_files = Path.cwd().glob('*.xlsx')<br><br>all_xlsx_files = Path.cwd().rglob('*.xlsx') | + +Функциональность **glob** доступна с объектами `Path`. Следовательно, модуль Pathlib упрощают сложные задачи. + +## Чтение и запись файлов с использованием Pathlib + +Следующие методы используются для выполнения основных операций, таких как **чтение и запись файлов**: + +- `read_text`: Файл открывается в **текстовом режиме** для чтения содержимого файла и его закрытия после чтения; +- `read_bytes`: Используется для открытия файла в **бинарном режиме**, возвращения содержимого в бинарном форме и последующего закрытия файла; +- `write_text`: Используется для открытия файла, записи туда текста и последующего закрытия; +- `write_bytes`: Используется для записи бинарных данных в файл и закрытия файла по завершении процесса. + +Давайте испытаем модуль Pathlib используя популярные файловые операции. Следующий пример используется для **чтения содержимого файла**: + +Python + +| | | +|---|---| +|1<br><br>2|path = pathlib.Path.cwd() / 'Pathlib.md'<br><br>path.read_text()| + +Метод `read_text()` для объекта `Path` используется для чтения содержимого файла. В примере ниже данные записываются в файл, в текстовом режиме: + +Python + +| | | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| 1<br><br>2<br><br>3<br><br>4 | from pathlib import Path<br><br>p = Path('sample_text_file')<br><br>p.write_text('Образец данных для записи в файл') |
\ No newline at end of file diff --git a/PYTHON/PAUSE в PYTHON.md b/PYTHON/PAUSE в PYTHON.md new file mode 100644 index 0000000..9a611f0 --- /dev/null +++ b/PYTHON/PAUSE в PYTHON.md @@ -0,0 +1,13 @@ +Иногда нужно приостановить выполнение программы: + +➡️ Использование time.sleep(): + +```python +import time + +print("Начало") +time.sleep(2) # Пауза в 2 секунды +print("Конец") +``` + + diff --git a/PYTHON/PEP8.md b/PYTHON/PEP8.md new file mode 100644 index 0000000..7ce650b --- /dev/null +++ b/PYTHON/PEP8.md @@ -0,0 +1,37 @@ +***Отступы*** - 4 пробела +Макс длина строки = 79 символов (можно разбить " \\ " или операторы конкатенации) + +***Пробелы*** - операторы и операнды, скобки НЕ отделять! + +***Именование*** - верблюд и змея + +***Комментарии*** - # и \`\`\` ... \`\`\` + +***Импорты*** - в алфавитном порядке, лучше без " * " +группы разделяйте пустой строкой + +***Пробелы*** вокруг операторов + +***Название функций и методов*** - глаголы с резделением " _ " + +***Название переменных*** - подробно ! + +***Расположение функций и классов*** - разделяйте пустыми строками, функции должны быть определены перед их использованием. + +***Тройные кавычки*** - документация кода + +--------------------------- +_Полезные инструменты:_ + +***Линтеры***: + +- *pylint +- *flake8 +- *PyCharm + + +***Автоматические форматировщики:*** + +- *Black +- *autopep8 +- *YAPF diff --git a/PYTHON/PROGRAMS.md b/PYTHON/PROGRAMS.md new file mode 100644 index 0000000..b4d3d53 --- /dev/null +++ b/PYTHON/PROGRAMS.md @@ -0,0 +1,160 @@ +```sh +mkdir weather +cd weather + +true > weather +chmod +x weather +ls + +nvim weather +#--------------- +#!/usr/bin/env python3.11 +print("Hello World !") +#--------------- +./weather +-> Hello World ! + +# создать ссылку чтобы запускалось везде: +sudo ln -s $(pwd)/weather /usr/local/bin/ +# удалить: +sudo rm /usr/local/bin/weather + +################# +true > gps_coordinates.py +true > weather_api_service.py +true > printer.py + +ls -l + +gps_coordinates.py +printer.py +weather +weather_api_service.py + +mv printer.py weather_formatter.py + +nvim weather +``` + +```python +#!/usr/bin/env python3.11 + +from gps_coordinates import get_gps_coordinates +from weather_api_service import get_weather +from weather_formatter import format_weather + +def main(): + coordinates = get_gps_coordinates() + weather = get_weather(coordinates) + print(format_weather(weather)) + +if __name__ == "__main__": + main() +``` +`nvim gps_coordinates.py` +```python +def get_gps_coordinates(): + """ Returns current coordinates using MackBook GPS """ + pass + +########################## +# Структура программы со слоями: + +from abc import get_abc + +def main(): + abc = get_abc() + print("Что то там") + +if __name__ == "__main__": + main() + +########################## +# Просто кортеж: + +def abc() -> tuple[float, int]: + """Return""" + return (0.0, 890) + +# Просто список: + +def abc() -> dict[str, int]: + """Return""" + return {'latitude': 10.0, 'longitide': 890} + +#################################################### +# Именованный кортеж: + +from typing import NamedTuple + +class Coordinates(NamedTuple): + latitude: float + longitude: float + +def get_coordinates() -> tuple[float, float]: + """Return""" + return Coordinates(latitude=10, longitude=20) + +coordinates = get_coordinates() + print(coordinates.latitude) + print(coordinates.longitude) + +lat, long = get_coordinates() +print(lat, long) + +########################## +# Именованный словарь: + +from typing import TupedDict + +class Coordinates(TupedDict): + latitude: float + longitude: float + +def get_coordinates() -> Coordinates: + """Return""" + return Coordinates(**{latitude=10, longitude=20}) + +########################## +# Список: + +from typing import Literal + +def get_coordinates() \ + -> dict[Literal['latitude'] | Literal['longitude], float]: + """Return""" + return {'latitude': 10.0, 'longitude': 20.0} + + +########################## +# DataClass лучше именованного кортежа и можно менять в программе: +from dataclasses import dataclass + +@dataclass +class Coordinates: + latitude: float + longitude: float + +def get_coordinates() -> Coordinates: + """Return""" + return Coordinates(**{latitude=10, longitude=20}) + +########################## +``` + +```python +# Выход/завершение программы: +import sys + +sys.exit([status]) +# Необязательный аргумент `status` представляет собой статус выхода. Это целочисленное значение, которое указывает на причину завершения программы. Принято считать, что статус 0 означает успешное выполнение, а любой ненулевой статус указывает на ошибку или ненормальное завершение. + +# Если аргумент `status` не указан, используется значение по умолчанию 0. + +################# +quit() + +################# +exit() + +```
\ No newline at end of file diff --git a/PYTHON/PROGRAMS_py.md b/PYTHON/PROGRAMS_py.md new file mode 100644 index 0000000..01de73d --- /dev/null +++ b/PYTHON/PROGRAMS_py.md @@ -0,0 +1,150 @@ +=============================================== +### TinyDB ### +это легковесная, чисто Python база данных без сервера, которая сохраняет данные в виде JSON-файлов. +Подходит для небольших проектов, скриптов и приложений, где не требуется полноценная SQL-база данных. +Подходит для разработчиков, которым нужно быстро внедрить базу данных без сложной настройки и установки. +```python +from tinydb import TinyDB, Query + +# Создание базы данных в файле db.json +db = TinyDB('db.json') + +# Добавление данных +db.insert({'name': 'Alice', 'age': 24}) +db.insert({'name': 'Bob', 'age': 30}) + +# Поиск данных +User = Query() +results = db.search(User.age > 25) +print(results) + +# Обновление данных +db.update({'age': 26}, User.name == 'Alice') + +# Удаление данных +db.remove(User.name == 'Bob') + +# Установка +pip install tinydb +``` +=============================================== +### Библиотека Python: Arrow ### + +**Arrow** — библиотека для работы с датами и временем, которая упрощает создание, обработку и форматирование временных меток. + +Подходит для разработчиков, которые хотят избежать сложности работы с модулями `datetime` и `timezones`, предоставляя более интуитивные и мощные инструменты для управления временем. +```python +import arrow + +# Текущее время +now = arrow.now() +print(now) + +# Создание объекта даты и времени +date = arrow.get('2023-08-13T12:30:00') +print(date) + +# Форматирование даты и времени +formatted_date = date.format('DD MMMM YYYY HH:mm') +print(formatted_date) + +# Работа с часовыми поясами +utc_time = arrow.utcnow() +local_time = utc_time.to('US/Pacific') +print(local_time) + +# Установка +pip install arrow +``` +=============================================== +### REMBG ### +```python +# удаление фона изображения: +pip install rembg + +from rembg inport remove +from PIL import Image + +input_path = 'name.jpg' +output_path = 'out.png' + +input = Image.open(input_path) +output = remove(input) +output,save(output_path) + +# фото в ч/б: +from PIL import Image + +img = Image.open('girl.png') +baw = img.convert('L') # L - оттенки серого +baw.save('baw_girl.png') +``` +=============================================== +### PANDAS ### +Обработка CSV-файлов с помощью pandas позволяет эффективно управлять, анализировать и преобразовывать данные из таблиц, что полезно для работы с большими объемами данных. +```python +pip install pandas + +import pandas as pd + +# Чтение CSV-файла +df = pd.read_csv('data.csv') + +# Вывод первых 5 строк таблицы +print(df.head()) + +# Фильтрация данных: выбор строк, где значение в столбце 'age' больше 30 +filtered_df = df[df['age'] > 30] + +# Запись отфильтрованных данных в новый CSV-файл +filtered_df.to_csv('filtered_data.csv', index=False) +``` +Этот скрипт читает CSV-файл, отображает первые 5 строк таблицы, фильтрует строки по значению столбца и сохраняет отфильтрованные данные в новый файл. pandas упрощает обработку табличных данных и позволяет работать с большими массивами данных в удобной форме. + +=============================================== +```python +# Объединение нескольких CSV-файлов: +import pandas as pd + +# Чтение нескольких CSV-файлов и их объединение в один DataFrame +files = ['data1.csv', 'data2.csv', 'data3.csv'] +df_list = [pd.read_csv(file) for file in files] +combined_df = pd.concat(df_list) + +# Сохранение объединенных данных в новый CSV-файл +combined_df.to_csv('combined_data.csv', index=False) +``` +В этом примере несколько CSV-файлов объединяются в одну таблицу, что упрощает работу с данными из разных источников. pandas позволяет легко манипулировать такими данными и проводить различные операции, такие как фильтрация, группировка и сортировка. + +=============================================== +### % заряда батареи ### + +```python +# заряд батареи: +import psutil + +battery psutil.sensors_battery() +persent = int(battery.persent) +print(f'Заряд батареи: {persent}%) +``` +=============================================== +### Скриншот ### +```python +import pyautogui + +pyautogui.screenshot( + 'path_to_file.jpg', + region=(0, 0, 300, 400) +) +# функционал - большой ! +``` +=============================================== +### MAX элемент СПИСКА +который входит самое большое кол-во раз: +```python +my_list = [5, 9, 0, 5, 3, 6, 3, 5, 0, 4, 3] + +mpv = max(set(my_list), key=my_list.count) +print(mpv) +``` +===============================================
\ No newline at end of file diff --git a/PYTHON/PROGRESS BAR.md b/PYTHON/PROGRESS BAR.md new file mode 100644 index 0000000..d9d054d --- /dev/null +++ b/PYTHON/PROGRESS BAR.md @@ -0,0 +1,138 @@ +# + + +Зачем нужны индикаторы прогресса? + +Индикаторы прогресса (progress bar) — визуальное отображение процесса работы. Они избавляют нас от необходимости беспокоиться о том, не завис ли скрипт, дают интуитивное представление о скорости его выполнения и подсказывают, сколько времени осталось до завершения. + +Человек ранее не использовавший индикаторы прогресса может предположить, что их внедрение может сильно усложнить код. К счастью, это не так. Небольшие примеры ниже покажут, как быстро и просто начать отслеживать прогресс в консоли или в интерфейсе быстро набирающей популярность графической библиотеки [PySimpleGUI](https://github.com/PySimpleGUI/PySimpleGUI). + +# Используем Progress + +Первым у нас идёт модуль [Progress](https://github.com/verigak/progress/)_._ + +Всё, что от вас потребуется, это указать количество ожидаемых итераций, тип индикатора и вызывать функцию при каждой итерации: + +```python +import time +from progress.bar import IncrementalBar + +mylist = [1,2,3,4,5,6,7,8] + +bar = IncrementalBar('Countdown', max = len(mylist)) + +for item in mylist: + bar.next() + time.sleep(1) + +bar.finish() +``` +Результат работы: + + +Есть индикаторы на любой вкус: + + +# Используем tqdm + +> Быстрый и расширяемый индикатор прогресса для Python и CLI + +Всего один вызов функции понадобится для получения результата аналогичного предыдущему: + +```python +import time +from tqdm import tqdm + +mylist = [1,2,3,4,5,6,7,8] + +for i in tqdm(mylist): + time.sleep(1) +``` +Получаем: + + +Само собой, в комплекте идёт куча настроек и опций. + +# Используем alive-progress + +Ещё один [вариант](https://github.com/rsalmei/alive-progress) синтаксиса, побольше дефолтных анимаций, чем в предыдущих примерах: + +```python +from alive_progress import alive_bar +import time + +mylist = [1,2,3,4,5,6,7,8] + +with alive_bar(len(mylist)) as bar: + for i in mylist: + bar() + time.sleep(1) +``` +Результат: + + + +# GUI индикатор прогресса для скрипта + +Иногда возникает необходимость предоставить конечному пользователю графический индикатор. + + + +Сколько кода нужно, чтобы достигнуть такого результата? Немного: +```python +import PySimpleGUI as sg +import time + +mylist = [1,2,3,4,5,6,7,8] + +for i, item in enumerate(mylist): + sg.one_line_progress_meter('This is my progress meter!', i+1, len(mylist), '-key-') + time.sleep(1) +``` + +# Индикатор в приложении PySimpleGUI + +Рассмотрим реализацию индикатора в [PySimpleGUI](https://github.com/PySimpleGUI/PySimpleGUI). + + + +Вот как это сделать: + +```python +import PySimpleGUI as sg +import timemylist = [1,2,3,4,5,6,7,8] + +progressbar = [ + [sg.ProgressBar(len(mylist), orientation='h', size=(51, 10), key='progressbar')]] +outputwin = [ + [sg.Output(size=(78,20))] +] + +layout = [ + [sg.Frame('Progress',layout= progressbar)], + [sg.Frame('Output', layout = outputwin)], + [sg.Submit('Start'),sg.Cancel()] +] + +window = sg.Window('Custom Progress Meter', layout) +progress_bar = window['progressbar'] + +while True: + event, values = window.read(timeout=10) + if event == 'Cancel' or event is None: + break + elif event == 'Start': + for i,item in enumerate(mylist): + print(item) + time.sleep(1) + progress_bar.UpdateBar(i + 1) + +window.close() +``` +# Заключение + +Как видите, нет ничего сложного в добавлении информации о прогрессе выполнения: кода немного, а отзывчивость повышается очень сильно. Используйте индикаторы, чтобы больше никогда не гадать, завис ли процесс или нет! + +========================================== + +![[telegram-cloud-photo-size-2-5337133182321027005-y.jpg]] diff --git a/PYTHON/PYINSTALLER.md b/PYTHON/PYINSTALLER.md new file mode 100644 index 0000000..8ba9c87 --- /dev/null +++ b/PYTHON/PYINSTALLER.md @@ -0,0 +1,164 @@ +`pip install pyinstaller` + +`pyinstaller main.py` +- cоздастся папка `dist` с исполняемым файлом + +`pyinstaller --onefile main.py` +- одним файлом + +`pyinstaller --noconsole --onefile main.py` +- будет создан исполняемый файл, работающий в фоновом режиме + +================================ + +# Превращаем код на Python в исполняемый EXE-файл + +Отредактировано 20 дек 2023 + +Как скомпилировать Python-код в файл EXE, чтобы защитить свой код или наглядно показать работу вашей программы. + +Обычно код на Питон запускают двумя способами: через терминал или через текстовый редактор / IDE. + +При этом стоит отметить, что файлик Python — это всего лишь скрипт, который сохранён в расширении .py. Да, это очевидно, но в этом и кроется проблема: иногда нужно скрыть исходный код, а поделиться программкой (в виде альфа-версии, например) очень хочется. + +Одно из решений, которое закрывают боль с исходным кодом — это конвертация файла Python в исполняемый exe-файлик. + +В этой статье мы научимся создавать «экзешники» при помощи двух библиотек: Pyinstaller and auto-py-to-exe. + +Перед тем, как продолжить чтение статьи, скачайте этот репозиторий. Он здорово поможет вам понять процесс, если вы будете повторять все шаги вместе с нами. + +## Почему возникает потребность конвертировать Python-код в EXE + +1. Код могут украсть. Об этом мы уже говорили выше. Куда проще достать исходный код непосредственно из py-файла, чем из скомпилированного приложение. Py-файл достаточно просто открыть! +2. Хочется поделиться кодом с другими юзерами. Дело не только в том, что ваш код могут украсть. Зачастую код содержит зависимости, которыми должен владеть другой юзер перед запуском вашего Python-кода. Но что, если он вообще ничего не смыслит в программировании? Не заставишь ведь его сперва выучить программирование, чтобы оценить ваше новое суперприложение. Проще создать исполняемый файл. +3. Исполняемый файл можно запускать автоматически, по расписанию. Этот пункт актуален, если вы решили написать утилиту для вашего ПК, и эта утилита должна запускаться либо в определенное время, либо вместе с запуском ОС. + +## Конвертация кода Python в файл .exe + +Для начала создадим скрипт на Python. В качестве примера мы используем открытый код бронирования жилья AitBnb в Нью-Йорке. Если у вас уже есть готовый код, можете сразу использовать его. + +```python +import pandas as pd +from pandas.api.types import CategoricalDtype + +def perform_validation(filename:str): + """ + A function to validate inputs for NYC Airbnb Open data. + """ + path_to_data = "../.." + data = pd.read_csv(f"{path_to_data}{filename}") + + # Convert neighbourhood_group to type category + neighbourhood_group_to_cat = CategoricalDtype( + categories=["Manhattan", "Brooklyn", "Queens", "Bronx", "Staten Island"], + ordered=False + ) + + data["neighbourhood_group"] = data["neighbourhood_group"].astype(neighbourhood_group_to_cat) + + # Convert room_type to type category + room_type_to_cat = CategoricalDtype( + categories=["Entire home/apt", "Private room", "Shared room"], + ordered=False + ) + + data["room_type"] = data["room_type"].astype(room_type_to_cat) + + # Convert last_review to datetime + data["last_review"] = pd.to_datetime(data["last_review"]) + + # Minimum nights a person can stay is one night + assert data["minimum_nights"].min() >= 1 + + # Minimum number of reviews is 0 + assert data["number_of_reviews"].min() >= 0 + + # Minimum number of reviews per month + assert data["reviews_per_month"].min() >= 0.00 + + # Minimum amount of listings per host + assert data["calculated_host_listings_count"].min() >= 1 + + # Number of days when listing is available for books + # Could be 0 if tennant has long term booking + assert data["availability_365"].min() >= 0 + + # Save validated data + data.to_csv("validated_ab_nyc_2019.csv", index=False) + +if __name__ == "__main__": + # User inputs filename + filename = input("Enter filename: ") + + # Ensure it's a string + if not filename.isalpha(): + filename = str(filename) + + # Automated validation + perform_validation(filename) +``` + +Вы не забыли скачать библиотеки по ссылке по введении? Сделайте это сейчас. + +](https://tproger.ru/articles/secure-app-development) + +Вот структура файлов на GitHub: + +``` +# Directory structure +| AB_NYC_2019.csv +| ++---auto_py_to_exe +| | requirements.txt +| | validation.py ++---pyinstaller + | requirements.txt + | validation.py +``` + +Каждая из директорий должна обладать своей виртуальной средой. Убедитесь, что среды развёрнуты и в директории pyinstaller, и в auto-py-to-exe. + +Сохраните ваш код и скопируйте его в обе директории. + +## Что делать в Pyinstaller + +Ещё раз убедитесь, что виртуальная среда развёрнута. Да, мы настаиваем. Проверьте, установлены ли все зависимости между библиотеками и кодом. + +Всё в порядке? Запускайте команду: + +``` +pyinstaller --onefile validation.py +``` + +Процесс должен запуститься, и в конце консоль сообщит: «Completed successfully». Значит, всё прошло успешно. ? + + + +В папке с библиотекой и кодом должны появиться две новые директории под названиями build и dist. В папке dist вы найдёте заветный exe-файл. Запустите его и проверьте работу приложения. + +## Что делать в Auto-py-to-exe + +Также скиньте файлик с кодом в папку с библиотекой и проверьте зависимости между кодом и библиотекой. + +Запустите команду: + +``` +auto-py-to-exe +``` + +Команда запустит графический интерфейс. + + + +Запускаем компилятор Auto-py-to-exe // DataCamp + +1. В строчке «Script Location» нажмите «Browse» и выберите py-скрипт. +2. В строчке «Onefile» выберите, скомпилируется код в единый файл или в папку. В папке будет содержаться exe-файл и все зависимости кода. +3. В строчке «Console Window» выберите настройки отображения exe-приложения, когда оно будет запущено. Если выбрать «Console Based», запустится и программа, и консоль, в которой будут показаны процессы. Опция «Window Based» скрывает консоль. +4. Нажмите кнопку «Convert .py to .exe». Готово! + +## Запускаем exe-файл + +В обоих случаях, после компиляции и запуска exe-файла, тот запустит консоль и попросит вас ввести имя файла. Придумайте его и укажите расширение, в котором всё должно работать. К примеру, `Tproger-is-the-best-it-media-in-the-world.csv`. Тогда консоль закроется, а в папке появится заветный файлик с расширением .csv. + +И всё — вы великолепны!
\ No newline at end of file diff --git a/PYTHON/PYTHON_tuturial.md b/PYTHON/PYTHON_tuturial.md new file mode 100644 index 0000000..f223a47 --- /dev/null +++ b/PYTHON/PYTHON_tuturial.md @@ -0,0 +1,2910 @@ + +https://habr.com/ru/articles/728568/ +# Ядро планеты Python. Интерактивный учебник + +Туториал + +Добрый день! Меня зовут Михаил Емельянов, недавно я опубликовал на «Хабре» небольшую [статью](https://habr.com/ru/articles/697760/) с примерным путеводителем начинающего Python-разработчика. Пользуясь этим материалом как своего рода оглавлением книги, я написал первые четыре главы мини-учебника «Ядро планеты Python», где постарался коротко, но достаточно ёмко раскрыть специфику, удобство, красоту и силу этого прекрасного языка. + + + +Оригинал учебника лежит на [GitHub](https://github.com/amaargiru/pycore), вы вольны сколько угодно дополнять и переделывать его. Самое главное — учебник написан на Jupiter Notebook, а это значит, что вы можете интерактивно редактировать код, мгновенно добавляя новые сущности или проясняя непонятные моменты. + + + + + + + +Погружаясь в Python, не забывайте про прекрасную официальную документацию [docs.python.org](https://docs.python.org/). Изучив её, хотя бы по диагонали, и постепенно углубляясь в нужные разделы, вы сможете убедиться, что многие «хаки», «открытия» и прочие неочевидные вещи уже давно разжеваны, описаны и имеют подробные примеры применения. + + + +Также я бы рекомендовал для изучения базового синтаксиса Python на полную катушку использовать [leetcode.com](https://leetcode.com/problemset/all/?difficulty=EASY&page=1&status=NOT_STARTED). Если отфильтровать задачи по уровню «Easy», а потом добавить дополнительную сортировку по столбцу «Acceptance», то перед вами предстанет не волчий оскал соревновательной платформы, а ванильный букварь с плавно нарастающим уровнем задачек. + + + +## 1. Структуры данных + + + + + + + +Как известно, программирование = структуры данных + алгоритмы (у Никлауса Вирта даже книжка такая есть). Начнем с данных, а потом плавненько перейдем к методам их обработки. + + + +### Список (list) + + + +Список — самая универсальная и популярная структура данных в Python. Если вы пока точно не определились, какая структура понадобится в вашем проекте, просто возьмите список, с него достаточно просто мигрировать на что-нибудь более специализированное. +Список представляет собой упорядоченную изменяемую коллекцию объектов произвольных типов. Внутреннее строение списка — массив (точнее, vector) указателей, т. е. список является динамическим массивом. + + + +``` +a = [] # Создаем пустой список +a: list[int] = [10, 20] +b: list[int] = [30, 40] +a.append(50) # Добавляем значение в конец спискаb.insert(2, 60) # Вставляем значение по определенному индексу +print(a, b) + +a += bprint(f"Add: {a}") +a.reverse()b = list(reversed(a)) # reversed() возвращает итератор, а не списокprint(f"Reverse: {a}, {b}") + +b = sorted(a) # Возвращает новый отсортированный список +a.sort() # Модифицирует исходный список и не возвращает ничего +print(f"Sort: {a}, {b}") + + +s: str = "A whole string"list_of_chars: list = list(s)print(list_of_chars)list_of_words: list = s.split()print(list_of_words)i: int = list_of_chars.index("w") # Возвращает индекс первого вхождения искомого элемента или вызывает исключение ValueErrorprint(i)list_of_chars.remove("w") # Удаляет первое вхождение искомого элемента или вызывает исключение ValueErrore = list_of_chars.pop(9) # Удаляет и возвращает значение, расположенное по индексу. pop() (без аргумента) удалит и вернет последний элемент спискаprint(list_of_chars, e)a.clear() # Очистка списка +``` + +``` +[10, 20, 50] [30, 40, 60] +Add: [10, 20, 50, 30, 40, 60] +Reverse: [60, 40, 30, 50, 20, 10], [10, 20, 50, 30, 40, 60] +Sort: [10, 20, 30, 40, 50, 60], [10, 20, 30, 40, 50, 60] +['A', ' ', 'w', 'h', 'o', 'l', 'e', ' ', 's', 't', 'r', 'i', 'n', 'g'] +['A', 'whole', 'string'] +2 +['A', ' ', 'h', 'o', 'l', 'e', ' ', 's', 't', 'i', 'n', 'g'] r +``` + +### Кортеж (tuple) + +Кортеж — тоже список, только неизменяемый (immutable) и хэшируемый (hashable). Кортеж, содержащий те же данные, что и список, занимает меньше места: + +``` +a = [2, 3, "Boson", "Higgs", 1.56e-22]b = (2, 3, "Boson", "Higgs", 1.56e-22)print(f"List: {a.__sizeof__()} bytes")print(f"Tuple: {b.__sizeof__()} bytes") +``` + +``` +List: 104 bytes +Tuple: 64 bytes +``` + +### Именованный кортеж (named tuple) + +В соответствии с названием, имеет именованные поля. Удобно! + +``` +from collections import namedtuplerectangle = namedtuple('rectangle', 'length width')r = rectangle(length = 1, width = 2)print(r)print(r.length)print(r.width)print(r._fields) +``` + +``` +rectangle(length=1, width=2) +1 +2 +('length', 'width') +``` + +### Словарь (dict) + +Словарь — вторая по частоте использования структура данных в Python. dict — реализация хеш-таблицы, поэтому в качестве ключа нельзя брать нехешируемый объект, например, список (тут-то нам и может пригодиться кортеж). Ключом словаря может быть любой неизменяемый объект: число, строка, datetime и даже функция. Такие объекты имеют метод **__hash__()**, который однозначно сопоставляет объект с некоторым числом. По этому числу словарь ищет значение для ключа. + +Списки, словари и множества (которые мы рассмотрим чуть ниже) изменяемы и не имеют метода хеширования, при попытке подставить их в словарь возникнет ошибка. + +``` +d = {} # Создаем пустой словарьd: dict[str, str] = {"Italy": "Pizza", "US": "Hot-Dog", "China": "Dim Sum"} # Непосредственное создание словаряk = ["Italy", "US", "China"]v = ["Pizza", "Hot-Dog", "Dim Sum"]d = dict(zip(k, v)) # Создание словаря из двух коллекций при помощи zipk = d.keys() # Коллекция ключей. Отражает изменения в основном словареv = d.values() # Коллекция значений. Тоже отражает изменения в основном словареk_v = d.items() # Кортежи ключ-значение, которые тоже отражают изменения в основном словареprint(d)print(k)print(v)print(k_v)print(f"Mapping: {k.mapping['Italy']}")d.update({"China": "Dumplings"}) # Добавление значение. При совпадении ключа старое значение будет перезаписаноprint(f"Replace item: {d}")c = d["China"] # Читаем значениеprint(f"Read item: {c}")try: v = d.pop("Spain") # Удаляет значение или вызывает исключение KeyErrorexcept KeyError: print("Dictionary key doesn't exist")# Примеры dict comprehension (более подробно comprehension будет рассмотрено ниже)b = {k: v for k, v in d.items() if "a" in k} # Вернет новый словарь, отфильтрованный по значению ключаprint(b)c = {k: v for k, v in d.items() if len(v) >= 7} # Вернет новый словарь, отфильтрованный по длине значенийprint(c)d.clear() # Очистка словаря +``` + + + +``` +{'Italy': 'Pizza', 'US': 'Hot-Dog', 'China': 'Dim Sum'} +dict_keys(['Italy', 'US', 'China']) +dict_values(['Pizza', 'Hot-Dog', 'Dim Sum']) +dict_items([('Italy', 'Pizza'), ('US', 'Hot-Dog'), ('China', 'Dim Sum')]) +Mapping: Pizza +Replace item: {'Italy': 'Pizza', 'US': 'Hot-Dog', 'China': 'Dumplings'} +Read item: Dumplings +Dictionary key doesn't exist +{'Italy': 'Pizza', 'China': 'Dumplings'} +{'US': 'Hot-Dog', 'China': 'Dumplings'} +``` + +### Решение проблемы вычисления хеша при работе со словарем + +Любая хеш-таблица, в том числе и питоновский словарь, должна уметь решать проблему вычисления хеша. Для этого используются техники **open addressing** или **chaining**. Python [использует](https://stackoverflow.com/questions/9010222/why-can-a-python-dict-have-multiple-keys-with-the-same-hash) open addressing. + +Новый словарь инициализируется с 8 пустыми слотами. + +Интерпретатор сначала пытается добавить новую запись по адресу, зависящему от хеша ключа. + +``` +addr = hash(key) & mask, +``` + +где + +``` +mask = PyDictMINSIZE - 1 +``` + +Если этот адрес занят, то интерпретатор проверяет (при помощи ==) хеш и ключ. Если оба совпадают, то, значит, запись уже существует. Тогда начинается зондирование свободных слотов, которое идет в псевдослучайном порядке (порядок зависит от значения ключа). Новая запись будет добавлена по первому свободному адресу. + +Чтение из словаря происходит аналогично, интерпретатор начинает поиск с позиции addr и идет по тому же псевдослучайному пути, пока не прочитает нужную запись. + +### Defaultdict + +Если попытаться прочитать из обычного словаря значение ключа, которого там нет, то будет выброшено исключение KeyError (исключения будут рассмотрены ниже). Defaultdict позволяет не писать обработчик исключений, а просто воспринимает чтение несуществующего ключа как команду записать в этот ключ и вернуть значение по умолчанию; например, defaultdict(int) вернет 0. + +``` +from collections import defaultdictdd = defaultdict(int)print(dd[10]) # Печать int, будет выведен ноль, значение по умолчаниюdd = {} # "Обычный" пустой словарь# print(dd[10]) # вызовет исключение KeyError +``` + +``` +0 +``` + + +### Счетчик (counter) + +Счетчик подсчитывает передаваемые ему объекты. Иногда очень удобно просто бухнуть в счетчик какой-нибудь список и сразу получить структуру данных с подсчитанными элементами. + +``` +from collections import Countershirts_colors = ["red", "white", "blue", "white", "white", "black", "black"]c = Counter(shirts_colors)print(c)c["blue"] += 1print(f"After shopping: {c}") +``` + +``` +Counter({'white': 3, 'black': 2, 'red': 1, 'blue': 1}) +After shopping: Counter({'white': 3, 'blue': 2, 'black': 2, 'red': 1}) +``` + +Объяснение работы Counter() при помощи defaultdict(): + +``` +from collections import defaultdictshirts_colors = ["red", "white", "blue", "white", "white", "black", "black"]d = defaultdict(int)for shirt in shirts_colors: d[shirt] += 1print(d) +``` + + + +``` +defaultdict(<class 'int'>, {'red': 1, 'white': 3, 'blue': 1, 'black': 2}) +``` + + + +### Множество (set) + + + +Третья по распространенности питоновская структура данных. Когда-то, когда Python был молод, множества представляли собой несколько редуцированные словари, но со временем их судьбы (и реализации) стали расходиться. Однако, множество всё-таки является хеш-таблицей с соответствующим быстродействием на разных типах операций. + + + +``` +big_cities: set["str"] = {"New-York", "Los Angeles", "Ottawa"}american_cities: set["str"] = {"Chicago", "New-York", "Los Angeles"}big_cities |= {"Sydney"} # Добавить значение (или add())american_cities |= {"Salt Lake City", "Seattle"} # Сложить множества (или update())print(big_cities, american_cities)union_cities: set["str"] = big_cities | american_cities # Или union()intersected_cities: set["str"] = big_cities & american_cities # Или intersection()dif_cities: set["str"] = big_cities - american_cities # Или difference()symdif_cities: set["str"] = big_cities ^ american_cities # Или symmetric_difference()issub: bool = big_cities <= union_cities # Или issubset()issuper: bool = american_cities >= dif_cities # Или issuperset()print(union_cities)print(intersected_cities)print(dif_cities)print(symdif_cities)print(issub, issuper)big_cities.add("London")big_cities.remove("Ottawa") # Удаляет значение, если оно имеется или выбрасывает KeyErrorbig_cities.discard("Los Angeles") # Удаляет значение без выбрасывания KeyErrorbig_cities.pop() # Возвращает и удаляет случайное значение (порядок в set неопределен) или выбрасывает KeyErrorbig_cities.clear() # Очищает множество +``` + + + +``` +{'New-York', 'Los Angeles', 'Sydney', 'Ottawa'} {'New-York', 'Seattle', 'Chicago', 'Los Angeles', 'Salt Lake City'} +{'Ottawa', 'Salt Lake City', 'Chicago', 'New-York', 'Seattle', 'Sydney', 'Los Angeles'} +{'New-York', 'Los Angeles'} +{'Ottawa', 'Sydney'} +{'Seattle', 'Ottawa', 'Chicago', 'Salt Lake City', 'Sydney'} +True False +``` + + + +### Иммутабельное множество (frozen set) + + + +Frozen set — то же множество, только иммутабельное и хешируемое. Напоминает разницу между списком и кортежем, не правда ли? + + + +``` +a = frozenset({"New-York", "Los Angeles", "Ottawa"}) +``` + + + +### Массив (array, bytes, bytearray) + + + +Я перешел на Python с языков, более приближенных к «железу» (C, C#, даже на ассемблере когда-то писал за деньги :) и сначала немного удивлялся, что обычный массив, в котором всё так удобно лежит на своих местах, используется относительно редко. Массив в Python не является структурой данных, выбираемой по умолчанию и используется только в случаях, когда решающую роль начинают играть размер структуры и скорость её обработки. Но, с другой стороны, если вы смотрите в сторону NumPy и Pandas (немного затронуты ниже), то массивы — ваше всё. + + + +Массив хранит переменные определенного типа, поэтому, в отличие от списка, не требует создания нового объекта для каждой новой переменной и выигрывает у списка в размерах и скорости доступа. Можно сказать, что это тонкая обёртка над Си-массивами. + + + +Следует различать array («просто» массив), bytes (иммутабельный массив, содержащий только байты, наследие str из Python 2) и bytearray (мутабельный байтовый массив). + + + +``` +from array import arraya1 = array("l", [1, 2, 3, -4])a2 = array("b", b"1234567890")b = bytes(a2)print(a1)print(a2[0])print(b)print(a1.index(-4)) # Возвращает индекс элементы или выбрасывает ValueError +``` + + + +``` +array('l', [1, 2, 3, -4]) +49 +b'1234567890' +3 +``` + + + +``` +# Созданиеb1 = bytes([1, 2, 3, 4]) # Целые числа должны быть в диапазоне от 0 to 255b2 = "The String".encode('utf-8')b3 = (-1024).to_bytes(4, byteorder='big', signed=True) # byteorder = "big"/"little"/"sys.byteorder", signed = False/Trueb4 = bytes.fromhex('FEADCA') # Для большей читаемости hex-значения могут быть разделены пробеламиb5 = bytes(range(10,30,2))print(b1, b2, b3, b4, b5)# Преобразованиеc: list = list(b"\xfc\x00\x00\x00\x00\x01")s: str = b'The String'.decode("utf-8")b: int = int.from_bytes(b"\xfc\x00", byteorder='big', signed=False) # byteorder = "big"/"little"/"sys.byteorder", signed = False/Trues2: str = b"\xfc\x00\x00\x00\x00\x01".hex(" ")print(c, s, b, s2)with open("1.bin", "wb") as file: # Байтовая запись в файл file.write(b1)with open("1.bin", "rb") as file: # Чтение из файла b6 = file.read()print(b6) +``` + + + +``` +b'\x01\x02\x03\x04' b'The String' b'\xff\xff\xfc\x00' b'\xfe\xad\xca' b'\n\x0c\x0e\x10\x12\x14\x16\x18\x1a\x1c' +[252, 0, 0, 0, 0, 1] The String 64512 fc 00 00 00 00 01 +b'\x01\x02\x03\x04' +``` + + + +### Односвязный список + + + +[Односвязный список](https://en.wikipedia.org/wiki/Linked_list#Singly_linked_list) представляет набор связанных узлов, каждый из которых хранит собственные данные и ссылку на следующий узел. В практике применим редко, но его любят использовать интервьюеры на собеседованиях, чтобы кандидат мог блеснуть своими алгоритмическими знаниями. В Python встроенной реализации не имеет, можно или использовать deque (в основе которого лежит двусвязный список), или написать свою реализацию. + + + +### Двусвязный список (Deque) + + + +Ссылки в каждом узле [двусвязного списка](https://en.wikipedia.org/wiki/Linked_list#Doubly_linked_list) указывают на предыдущий и на последующий узел в списке. Можно или использовать deque, или написать свою реализацию. + + + +``` +from collections import dequed = deque([1, 2, 3, 4], maxlen=1000)d.append(5) # Add element to the right side of the dequed.appendleft(0) # Add element to the left side of the deque by appending elements from iterabled.extend([6, 7]) # Extend the right side of the dequed.extendleft([-1, -2]) # Extend the left side of the dequeprint(d)a = d.pop() # Remove and return an element from the right side of the deque. Can raise an IndexErrorb = d.popleft() # Remove and return an element from the left side of the deque. Can raise an IndexErrorprint(a, b)print(d) +``` + + + +``` +deque([-2, -1, 0, 1, 2, 3, 4, 5, 6, 7], maxlen=1000) +7 -2 +deque([-1, 0, 1, 2, 3, 4, 5, 6], maxlen=1000) +``` + + + +### Queue + + + +Queue реализует FIFO со множественными поставщиками данных и множественными потребителями. Может быть особенно полезен при многопоточности, позволяя корректно обмениваться информацией между потоками. Также существуют LifoQueue для реализации LIFO и PriorityQueue для реализации очереди с приоритетом. + + + +``` +from queue import Queueq = Queue(maxsize=1000)q.put("eat", block=True, timeout=10)q.put("sleep") # По умолчанию block=True, timeout=Noneq.put("code")q.put_nowait("repeat") # Эквивалент put("repeat", block=False). Если свободный слот не будет предоставлен немедленно, будет вызвано исключение queue.Fullprint(q.queue)a = q.get(block=True, timeout=10) # Удалить и возвратить элемент из FIFOb = q.get() # По умолчанию block=True, timeout=Nonec = q.get_nowait() # Эквивалент get(False)print(a, b, c, q.queue) +``` + + + +``` +deque(['eat', 'sleep', 'code', 'repeat']) +eat sleep code deque(['repeat']) +``` + + + +### Бинарное дерево (binary tree) + + + +Иерархическая структура данных, в которой каждый узел имеет не более двух потомков. Встроенной реализации не имеет, нужно писать свою. Как правило, используются деревья с дополнительными свойствами, рассмотренные ниже. + + + +### Куча (heap) + + + +Бинарное дерево, удовлетворяющее свойство кучи: если B является узлом-потомком узла A, то ключ(A) ≥ ключ(B). Куча является максимально эффективной реализацией абстрактного типа данных, который называется очередью с приоритетом и поддерживающего две обязательные операции — добавить элемент и извлечь минимум (или максимум, в зависимости от реализации). + + + +В Python min-куча (наименьшее значение всегда лежит в корне) реализована на базе списка при помощи встроенного модуля heapq. Если вам нужна max-куча, с максимальным значением в корне, можете воспользоваться советами со [Stackoverflow](https://stackoverflow.com/questions/2501457/what-do-i-use-for-a-max-heap-implementation-in-python). + + + +``` +import heapqh = [211, 1, 43, 79, 12, 5, -10, 0]heapq.heapify(h) # Превращаем список в кучуprint(h)heapq.heappush(h, 2) # Добавляем элементprint(h)m = heapq.heappop(h) # Извлекаем минимальный элементprint(h, m) +``` + + + +``` +[-10, 0, 5, 1, 12, 211, 43, 79] +[-10, 0, 5, 1, 12, 211, 43, 79, 2] +[0, 1, 5, 2, 12, 211, 43, 79] -10 +``` + + + +Пробежимся коротенько по остальным структурам данных, которые в Python не имеют встроенной реализации, но, тем не менее, могут весьма пригодиться в реальном проекте. + + + +### Би-дерево (B-tree) + + + +Сбалансированное дерево, оптимизированное для доступа к относительно медленным элементам памяти (например, дисковым структурам или индексам баз данных); как ветви, так и листья представляют собой списки (для того, чтобы можно было считать такой список в один проход для дальнейшего быстрого разбора в ОЗУ). Нужно писать свою реализацию. Либо — воспользоваться встроенной в Python поддержкой базы данных sqlite3, эта БД как раз реализована на би-дереве. + + + +### Красно-черное дерево + + + +Самобалансирующееся двоичное дерево поиска, позволяющее быстро выполнять основные операции: добавление, удаление и поиск узла. Сбалансированность достигается за счёт введения дополнительного признака узла дерева — «цвета». Этот атрибут может принимать одно из двух возможных значений — «чёрный» или «красный». Листовые узлы КЧ деревьев не содержат данных, поэтому не требуют выделения памяти — достаточно просто записать в узле-предке нулевой указатель на потомка. Нужно писать свою реализацию. +Возможно, вы читали о том, что при собеседовании в FAANG претендентов «заставляют крутить красно-черное дерево на доске». Это «кружение» и есть балансировка, после операции вставки или удаления элемента дерево нужно отбалансировать, с примерным объемом кода вы можете ознакомиться [здесь](https://blog.boot.dev/python/red-black-tree-python/) или [здесь](https://codereview.stackexchange.com/questions/244971/red-black-tree-implementation-in-python). + + + +### АВЛ-дерево + + + +В АВЛ-деревьях операции вставки и удаления работают медленнее, чем в красно-черных деревьях (при том же количестве листьев красно-чёрное дерево может быть выше АВЛ-дерева, но не более чем в 1,388 раза). Поиск же в АВЛ-дереве выполняется быстрее (максимальная разница в скорости поиска составляет 39 %). Нужно писать свою реализацию. + + + +### Префиксное дерево + + + +[Префиксное дерево](https://en.wikipedia.org/wiki/Trie) (или trie) — структура данных, позволяющая хранить ассоциативный массив, ключами которого являются строки. Нужно писать свою реализацию. + + + +### Таблица выбора структуры данных + + + +В квадратных скобках показан худший случай. + + + +|Структура|Реализация|Применение|Индексация|Поиск|Вставка|Удаление|Память| +|---|---|---|---|---|---|---|---| +|Динамический массив|list||1|n|n|n|n| +|Хэш таблица|dict, set|||1 <br>[n]|1 <br>[n]|1 <br>[n]|n| +|Массив|array, bytes, bytearray|Для хранения однотипных данных|1|n|n|n|n| +|Односвязный список|— (~deque)||n|n|1|1|n| +|Двусвязный список|deque|FIFO, LIFO|n|n|1|1|n| +|Бинарное дерево|-||logn <br>[n]|logn <br>[n]|logn <br>[n]|logn <br>[n]|n| +|Куча|heapq|Очередь с приоритетом||1 <br>(find min)|logn|logn <br>(del min)|n| +|B-tree (Би-дерево)|~sqlite|Для памяти с медленным доступом|logn|logn|logn|logn|n| +|КЧ дерево|-||logn|logn|logn|logn|n| +|АВЛ дерево|-||logn|logn|logn|logn|n| +|Префиксное дерево|-|T9, <br>алгоритм [Ахо–Корасик](https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm), <br>алгоритм [LZW](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch)||key|key|key|| + + + +### Перечисление (Enum, IntEnum) + + + +Удобные конструкции для определения заранее известных перечислений. + + + +``` +from enum import Enum, autoimport randomclass Currency(Enum): euro = 1 us_dollar = 2 yuan = auto()local_currency = Currency.us_dollarprint(local_currency)local_currency = Currency["us_dollar"] # Может вызвать исключение KeyErrorprint(local_currency)local_currency = Currency(2) # Может вызвать исключение ValueErrorprint(local_currency)print(local_currency.name)print(local_currency.value)list_of_members = list(Currency)member_names = [e.name for e in Currency]member_values = [e.value for e in Currency]random_member = random.choice(list(Currency))print(list_of_members, "\n", member_names, "\n", member_values, "\n", random_member) +``` + + + +``` +Currency.us_dollar +Currency.us_dollar +Currency.us_dollar +us_dollar +2 +[<Currency.euro: 1>, <Currency.us_dollar: 2>, <Currency.yuan: 3>] + ['euro', 'us_dollar', 'yuan'] + [1, 2, 3] + Currency.euro +``` + + + +### Целочисленный диапазон (range) + + + +range() возвращает иммутабельную последовательность чисел, которая часто используется как задатчик диапазона для цикла for. + + + +``` +r1: range = range(11) # Возвращает последовательность чисел от 0 до 10r2: range = range(5, 21) # Возвращает последовательность чисел от 5 до 20r3: range = range(20, 9, -2) # Возвращает последовательность чисел от 20 до 10 с шагом 2print("To exclusive: ", end="")for i in r1: print(f"{i} ", end="")print("\nFrom inclusive to exclusive: ", end="")for i in r2: print(f"{i} ", end="")print("\nFrom inclusive to exclusive with step: ", end="")for i in r3: print(f"{i} ", end="")print(f"\nFrom = {r3.start}")print(f"To = {r3.stop}") +``` + + + +``` +To exclusive: 0 1 2 3 4 5 6 7 8 9 10 +From inclusive to exclusive: 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 +From inclusive to exclusive with step: 20 18 16 14 12 10 +From = 20 +To = 9 +``` + + + +### Классы данных (dataclass) + + + +Декоратор, автоматически создающий методы init(), repr() и eq(). Нужен для создания классов, главной задачей которых является хранение данных. Аннотации типов обязательны. Существует более продвинутая альтернатива под названием [attrs](https://pypi.org/project/attrs/). + + + +``` +from dataclasses import dataclassfrom decimal import *from datetime import datetime@dataclassclass Transaction: value: Decimal issuer: str = "Default Bank" dt: datetime = datetime.now()t1 = Transaction(value=1000_000, issuer="Deutsche Bank", dt = datetime(2022, 1, 1, 12))t2 = Transaction(1000)print(t1)print(t2) +``` + + + +``` +Transaction(value=1000000, issuer='Deutsche Bank', dt=datetime.datetime(2022, 1, 1, 12, 0)) +Transaction(value=1000, issuer='Default Bank', dt=datetime.datetime(2022, 9, 6, 17, 50, 36, 162897)) +``` + + + +Dataclass может быть сделан иммутабельным с директивой _frozen=True_. + + + +``` +from dataclasses import dataclass@dataclass(frozen=True)class User: name: str account: int +``` + + + +### Бинарная запаковка (struct) + + + +Запаковка (и распаковка, разумеется) данных в байтовые последовательности с предопределенными размерами каждого элемента данных, их порядка в структуре, а также порядка байт для многобайтовых типов данных. Позволяет превращать Python-овский int в, например, short int или long int ([подробности про систему типов языка Си](https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%82%D0%B8%D0%BF%D0%BE%D0%B2_%D0%A1%D0%B8)). +При работе со структурами вам нужно будет знать, что такое little-endian и big-endian, а также не забывать, что размер типа данных в Си бывает разным. + + + +``` +from struct import pack, unpack, iter_unpackb = pack(">hhll", 1, 2, 3, 4)print(b)t = unpack(">hhll", b)print(t)i = pack("ii", 1, 2) * 5print(i)print(list(iter_unpack('ii', i))) +``` + + + +``` +b'\x00\x01\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04' +(1, 2, 3, 4) +b'\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00' +[(1, 2), (1, 2), (1, 2), (1, 2), (1, 2)] +``` + + + +## Строка (string) + + + +Строки в Python 3 — иммутабельные последовательности, использующие кодировку Unicode. + + + +``` +se: str = "" # Пустая строкаsi: str = str(12345) # Создает строку из числаsj: str = " ".join(["Follow", "the", "white", "rabbit"]) # Собирает строку из кусочков, используя указанный сепараторprint(f"Joined string: {sj}")is_contains: bool = "rabbit" in sj # Проверка наличия подстрокиis_startswith = sj.startswith("Foll")is_endswith = sj.endswith("bbit")print(f"is_contains = {is_contains}, is_startswith = {is_startswith}, is_endswith = {is_endswith}")sr: str = sj.replace("rabbit", "sheep") # Замена подстроки. Можно указать количество замен: sr: str = sj.replace("rabbit", "sheep", times)print(f"After replace: {sr}")i1 = sr.find("rabbit") # Возвращает стартовый индекс первого вхождения или -1. Есть еще rfind(), начинающий искать с конца строкиi2 = sr.index("sheep") # Возвращает стартовый индекс первого вхождения или выкидывает ValueError. Есть еще rindex(), начинающий искать с конца строкиprint(f"Start index of 'rabbit' is {i1}, start index of 'sheep' is {i2}")d = str.maketrans({"a" : "x", "b" : "y", "c" : "z"})st = "abc".translate(d)print(f"Translate string: {st}")sr = sj[::-1] # Реверс через slice с отрицательным шагомprint(f"Reverse string: {sr}") +``` + + + +``` +Joined string: Follow the white rabbit +is_contains = True, is_startswith = True, is_endswith = True +After replace: Follow the white sheep +Start index of 'rabbit' is -1, start index of 'sheep' is 17 +Translate string: xyz +Reverse string: tibbar etihw eht wolloF +``` + + + +### Datetime + + + +Для работы с датами и временем в _datetime_ есть типы _date_, _time_, _datetime_ и _timedelta_. Все они хешируемы и иммутабельны. + + + +### Конструкторы + + + +``` +from datetime import date, time, datetime, timedeltad: date = date(year=1964, month=9, day=2)t: time = time(hour=12, minute=30, second=0, microsecond=0, tzinfo=None, fold=0)dt: datetime = datetime(year=1964, month=9, day=2, hour=10, minute=30, second=0)td: timedelta = timedelta(weeks=1, days=1, hours=12, minutes=13, seconds=14)print (f"{d}\n {t}\n {dt}\n {td}") +``` + + + +``` +1964-09-02 + 12:30:00 + 1964-09-02 10:30:00 + 8 days, 12:13:14 +``` + + + +### Now + + + +Получение текущей даты или даты/времени. + + + +``` +from datetime import date, datetimeimport pytzimport timed: date = date.today()dt1: datetime = datetime.today()dt2: datetime = datetime.utcnow()dt3: datetime = datetime.now(pytz.timezone('US/Pacific'))t1 = time.time() # Эпоха Unixt2 = time.ctime()print (f"{d}\n {dt1}\n {dt2}\n {dt3}\n {t1}\n {t2}") +``` + + + +``` +2022-09-27 + 2022-09-27 09:47:02.430474 + 2022-09-27 04:47:02.430474 + 2022-09-26 21:47:02.430474-07:00 + 1664254022.4304743 + Tue Sep 27 09:47:02 2022 +``` + + + +### Timezone + + + +Часовые пояса. + + + +``` +from datetime import date, time, datetime, timedelta, tzinfofrom dateutil.tz import UTC, tzlocal, gettz, datetime_exists, resolve_imaginarytz1: tzinfo = UTC # Часовой пояс UTCtz2: tzinfo = tzlocal() # Местный часовой поясtz3: tzinfo = gettz() # Местный часовой поясtz4: tzinfo = gettz("America/Chicago") # Или, например, "Asia/Kolkata". Полный список: en.wikipedia.org/wiki/List_of_tz_database_time_zoneslocal_dt = datetime.today()utc_dt = local_dt.astimezone(UTC) # Конвертация местного часового пояса в часовой пояс UTCprint (f"{tz1}\n {tz2}\n {tz3}\n {tz4}\n {local_dt}\n {utc_dt}") +``` + + + +``` +tzutc() + tzlocal() + tzlocal() + tzfile('US/Central') + 2022-09-27 09:19:35.399362 + 2022-09-27 04:19:35.399362+00:00 +``` + + + +## 2. Обработка данных + + + + + + + +### Срез (slice) + + + +Самый простой метод обработки данных, просто возвращает ту часть данных, местоположение которой (индексы) удовлетворяет определенным условиям. + + + +``` +a:str = "Pack my box with five dozen liquor jugs"start, stop = 8, 21b:str = a[start:stop] # Значения от start до stop-1c:str = a[start:] # Значения от start до конца структурыd:str = a[:stop] # Значения от начала до stop-1e:str = a[:] # Полная копия структурыprint(b, "\n", c, "\n", d, "\n", e, "\n") +``` + + + +``` +box with five + box with five dozen liquor jugs + Pack my box with five + Pack my box with five dozen liquor jugs +``` + + + +Значения start и stop могут быть отрицательными, это будет означать, что отсчет ведется от конца структуры. Можно также использовать значение step, чтобы на выход среза попали не все подряд данные из входной структуры. + + + +``` +a:str = "Step on no pets"b:str = a[-4:] # «Хвостик»c:str = a[::-1] # Реверс входной строкиd:str = a[4::-1] # Первые четыре значения, реверсированыe:str = a[::2] # Каждый второй символprint(b, "\n", c, "\n", d, "\n", e, "\n") +``` + + + +``` +pets + step on no petS + petS + Se nn es +``` + + + +### Сортировка (sort, sorted) + + + +В сортировке всё самое интересное спрятано под капотом (мы ненадолго вернемся к этой теме чуть ниже, в разделе «Алгоритмы»), пока рассмотрим только Python-специфичный синтаксис. +Надо различать методы sort() и sorted(), первый сортирует данные [in-place](https://en.wikipedia.org/wiki/In-place_algorithm), второй порождает новую структуру. + + + +``` +a: list = [5, 2, 3, 1, 4]b: list = sorted(a)print(a, b)a.sort()print(a) +``` + + + +``` +[5, 2, 3, 1, 4] [1, 2, 3, 4, 5] +[1, 2, 3, 4, 5] +``` + + + +И sort(), и sorted() имеют параметр key для указания функции, которая будет вызываться на каждом элементе. Если вам больше по нраву сортировка при помощи функции, принимающей два аргумента (или вы привыкли к cmp в Python 2), присмотритесь к functools.cmp_to_key(). + + + +``` +# Регистрозависимое сравнение строкdinos: str = "Dinosaurs were Big and small"a = sorted(dinos.split())print(a)# Регистронезависимое сравнение строкdinos: str = "Dinosaurs were Big and small"b = sorted(dinos.split(), key=str.lower)print(b) +``` + + + +``` +['Big', 'Dinosaurs', 'and', 'small', 'were'] +['and', 'Big', 'Dinosaurs', 'small', 'were'] +``` + + + +Сложносочиненные структуры данных можно сортировать по key=lambda el: el[1] или даже, например по key=lambda el: (el[1], el[0]). + + + +### Comprehension + + + +Comprehension, которое переводится то как включение в список, то как абстракция списков ([Википедия](https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%D0%BE%D0%B5_%D0%B2%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5)), то вообще никак не переводится — способ компактного описания операций обработки списков (а применительно к Python — еще и словарей, и множеств). + + + +Проще говоря, если вам нужно получить из списка другой список, включающий только те значения, которые удовлетворяют какому-то определенному условию, или вычисляемые из первого списка по каким-то определенным правилам, то comprehension — претендент на решение этой задачи № 1. + + + +``` +# Примеры Comprehensiona = [i+1 for i in range(10)] # listb = {i for i in range(10) if i > 5} # setc = (2*i+5 for i in range(10)) # iterd = {i: i**2 for i in range(10)} # dictprint(a,"\n", b, "\n", list(c), "\n", d) +``` + + + +``` +[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + {8, 9, 6, 7} + [5, 7, 9, 11, 13, 15, 17, 19, 21, 23] + {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} +``` + + + +Тут главное не перегнуть палку. Если запись comprehension становится слишком сложной и нечитаемой, возможно, стоит развернуть логику в «нормальный» цикл или в другой более удобочитаемый алгоритм. Comprehension соблазняет записывать «однострочниками» достаточно сложные выражения, но не забывайте, что программист примерно 90 % времени читает код, и только 10 % пишет, так что если выражение будет плохочитаемым, вы усложните жизнь и себе, и свои коллегам. + + + +Есть более-менее [удачные](https://leetcode.com/problems/flipping-an-image/discuss/2378360/python-1-liner-988-speed-97-mem) «однострочники», есть быстрые, но [плохочитаемые](https://leetcode.com/problems/reverse-string-ii/discuss/2281269/python-fast-beats-984-and-short-almost-1-line-solution-with-python-38-features-pep572), написанные из спортивного интереса (это ссылки на решенные мной задачки на leetcode), желательно использовать comprehension в меру; лучше написать понятный развернутый алгоритм, чем непонятный, но обложенный пояснениями (если нет особых требований к производительности, само собой). + + + +Еще немного про list comprehension: + + + +``` +# new_list = [expression for member in iterable (if conditional)]fruits: list = ["Lemon", "Apple", "Banana", "Kiwi", "Watermelon", "Pear"]e_fruits = [fruit for fruit in fruits if "e" in fruit]# ☝ условиеprint(e_fruits)upper_fruits = [fruit.upper() for fruit in fruits]# ☝ выражениеprint(upper_fruits)# Пример разбиения списка на фрагменты одинаковой длиныchunk_len = 2chunk_fruits = [fruits[i:i + chunk_len] for i in range(0, len(fruits), chunk_len)]print(chunk_fruits) +``` + + + +``` +['Lemon', 'Apple', 'Watermelon', 'Pear'] +['LEMON', 'APPLE', 'BANANA', 'KIWI', 'WATERMELON', 'PEAR'] +[['Lemon', 'Apple'], ['Banana', 'Kiwi'], ['Watermelon', 'Pear']] +``` + + + +Dict comprehension, включение в словарь: + + + +``` +# new_dict = {expression for member in iterable (if conditional)}d: dict = {"Italy": "Pizza", "US": "Hot-Dog", "China": "Dim Sum", "South Korea": "Kimchi"}print(d)a: dict = {k: v for k, v in d.items() if "i" in v} # Вернет новый словарь, отфильтрованный по значениюprint(a)b: dict = {k: v for k, v in d.items() if "i" in k} # Вернет новый словарь, отфильтрованный по ключуprint(b)c: dict = {k: v for k, v in d.items() if len(v) >= 7} # Вернет новый словарь, отфильтрованный по длине значенийprint(c) +``` + + + +``` +{'Italy': 'Pizza', 'US': 'Hot-Dog', 'China': 'Dim Sum', 'South Korea': 'Kimchi'} +{'Italy': 'Pizza', 'China': 'Dim Sum', 'South Korea': 'Kimchi'} +{'China': 'Dim Sum'} +{'US': 'Hot-Dog', 'China': 'Dim Sum'} +``` + + + +Попробуйте самостоятельно поиграться с set comprehension. Не забывайте, что set «переваривает» только уникальные значения, поэтому в результате вы можете получить не совсем то, на что рассчитывали. + + + +Попробуйте также освоить nested (вложенный) comprehension, используя конструкции вида [[func(y) for y in x] for x in n]. Для примера создайте двумерный массив, содержащий случайные значения, среднее значение которых плавно нарастает ближе к правому нижнему углу (если не получится, готовый пример есть чуть ниже, в коде, иллюстрирующем применение matplotlib). + + + +### Операции над строками. lower(), upper(), capitalize() и title() + + + +``` +s: str = "camelCase string"print(s.lower())print(s.upper())print(s.capitalize())print(s.title()) +``` + + + +``` +camelcase string +CAMELCASE STRING +Camelcase string +Camelcase String +``` + + + +### strip() + + + +``` +s: str = " ~~##A big blahblahblah##~~ "s = s.strip() # Strips all whitespace characters from both endsprint(s)s = s.strip("~#") # Strips all passed characters from both endsprint(s)s = s.lstrip(" A") # Strips all passed characters from left endprint(s)s = s.rstrip("habl") # Strips all passed characters from right endprint(s) +``` + + + +``` +~~##A big blahblahblah##~~ +A big blahblahblah +big blahblahblah +big +``` + + + +### split() + + + +``` +s1: str = "Follow the white rabbit, Neo"c1 = s1.split() # Splits on one or more whitespace charactersprint(c1)c2 = s1.split(sep=", ", maxsplit=1) # Splits on "sep" str at most "maxsplit" timesprint(c2)s2: str = "Beware the Jabberwock, my son!\n The jaws that bite, the claws that catch!"c3 = s2.splitlines(keepends=False) # On [\n\r\f\v\x1c-\x1e\x85\u2028\u2029] and \r\n.print(c3)# split() vs rsplit()c4 = s2.split(maxsplit=2)c5 = s2.rsplit(maxsplit=2)print(c4, c5) +``` + + + +``` +['Follow', 'the', 'white', 'rabbit,', 'Neo'] +['Follow the white rabbit', 'Neo'] +['Beware the Jabberwock, my son!', ' The jaws that bite, the claws that catch!'] +['Beware', 'the', 'Jabberwock, my son!\n The jaws that bite, the claws that catch!'] ['Beware the Jabberwock, my son!\n The jaws that bite, the claws', 'that', 'catch!'] +``` + + + +### ord(), chr() + + + +``` +s1: str = "abcABC!"for ch in s1: print(f"{ch} -> {ord(ch)}") # Returns an integer representing the Unicode characternums = [72, 101, 108, 108, 111, 33]for num in nums: print(f"{num} -> {chr(num)}") +``` + + + +``` +a -> 97 +b -> 98 +c -> 99 +A -> 65 +B -> 66 +C -> 67 +! -> 33 +72 -> H +101 -> e +108 -> l +108 -> l +111 -> o +33 -> ! +``` + + + +### Regex + + + +Регулярные выражения — отдельная область знаний, и весьма-весьма непростая область. Тут, пожалуй, самое время для бородатой шутки про то, что если вы решили свою проблему при помощи регулярных выражений — теперь у вас две проблемы. + + + +Регулярки похожи на вхождение в воду на пляже острова Гуам в сторону Марианской впадины — даже когда вы думаете, что погрузились _реально_ глубоко, то, скорее всего, вы просто не видите впередилежащей бездны. Но — знать регулярные выражения, хотя бы на начальном уровне, необходимо для решения целого класса задач, а то, что вёрткие регулярки периодически поворачиваются к вам своими, кхм… новыми гранями, придется простить, переварить и принять. + + + +Вот [здесь](https://habr.com/ru/post/349860/) есть грамотное и методически выдержанное введение в тему, пока же окинем взглядом основные возможности регулярных выражений: + + + +``` +import res1: str = "123 abc ABC 456"m1 = re.search("[aA]", s1) # Ищет первое вхождение паттерна, при неудаче возвращает Noneprint(m1, m1.group(0))m2 = re.fullmatch("[aA]", s1) # Проверка, подходит ли строка под шаблонprint(m2)c1: list = re.findall("[aA]", s1) # Найти в строке все непересекающиеся шаблоныprint(c1)def replacer(s): return chr(ord(s[0]) + 1) # Следующий символ из алфавитаs2 = re.sub("\w", replacer, s1) # Вы можете использовать функцию вместо шаблонаprint(s2)c2 = re.split("\d", s1)print(c2)iter = re.finditer("\D", s1) # Итератор по непересекающимся шаблонамfor ch in iter: print(ch.group(0), end= "") +``` + + + +``` +<re.Match object; span=(4, 5), match='a'> a +None +['a', 'A'] +234 bcd BCD 567 +['', '', '', ' abc ABC ', '', '', ''] + abc ABC +``` + + + +### Match Object + + + +``` +import rem3 = re.match(r"(\w+) (\w+)", "John Connor, leader of the Resistance")s3: str = m3.group(0) # Возвращает полное совпадениеs4: str = m3.group(1) # Возвращает часть в первых скобкахt1: tuple = m3.groups()start: int = m3.start() # Возвращает начальный индекс совпаденияend: int = m3.end() # Возвращает конечный индекс совпаденияt2: tuple[int, int] = m3.span() # Кортеж (start, end)print (f"{s3}\n {s4}\n {t1}\n {start}\n {end}\n {t2}\n") +``` + + + +``` +John Connor + John + ('John', 'Connor') + 0 + 11 + (0, 11) +``` + + + +### Создание переменных datetime + + + +Python использует Unix Epoch: "1970-01-01 00:00 UTC" + + + +``` +from datetime import datetimefrom dateutil.tz import tzlocaldt1: datetime = datetime.fromisoformat("2021-10-04 00:05:23.555+00:00") # Может вызвать ValueErrordt2: datetime = datetime.strptime("21/10/04 17:30", "%d/%m/%y %H:%M") # Подробнее про форматы - https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codesdt3: datetime = datetime.fromordinal(100_000) # 100000-й день от 1.1.0001dt4: datetime = datetime.fromtimestamp(20_000_000.01) # Время в секундах с начала Unix Epochtz = tzlocal()dt5: datetime = datetime.fromtimestamp(20_000_000.01, tz) # С учетом часового поясаprint (f"{dt1}\n {dt2}\n {dt3}\n {dt4}\n {dt5}") +``` + + + +``` +2021-10-04 00:05:23.555000+00:00 + 2004-10-21 17:30:00 + 0274-10-16 00:00:00 + 1970-08-20 16:33:20.010000 + 1970-08-20 16:33:20.010000+05:00 +``` + + + +### Преобразование переменных datetime + + + +``` +from datetime import datetimedt1: datetime = datetime.today()s1: str = dt1.isoformat()s2: str = dt1.strftime("%d/%m/%y %H:%M") # https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codesi: int = dt1.toordinal()a: float = dt1.timestamp() # Секунды с начала Unix Epochprint (f"{dt1}\n {s1}\n {s2}\n {i}\n {a}") +``` + + + +``` +2022-09-06 17:50:38.041159 + 2022-09-06T17:50:38.041159 + 06/09/22 17:50 + 738404 + 1662468638.041159 +``` + + + +### Арифметика datetime + + + +``` +from datetime import date, time, datetime, timedeltafrom dateutil.tz import UTC, tzlocal, gettz, datetime_exists, resolve_imaginaryd: date = date.today()dt1: datetime = datetime.today()dt2: datetime = datetime(year=1981, month=12, day=2)td1: timedelta = timedelta(days=5)td2: timedelta = timedelta(days=1)d = d + td1 # date = date ± timedeltadt3 = dt1 - td1 # datetime = datetime ± timedeltatd3 = dt1 - dt2 # timedelta = datetime - datetimetd4 = 10 * td1 # timedelta = const * timedeltac: float = td1/td2 # timedelta/timedeltaprint (f"{d}\n {dt3}\n {td3}\n {td4}\n {c}") +``` + + + +``` +2022-09-11 + 2022-09-01 17:50:38.132916 + 14888 days, 17:50:38.132916 + 50 days, 0:00:00 + 5.0 +``` + + + +### bisect и бинарный поиск + + + +Бинарный поиск существенно быстрее, чем обычный (см. раздел «Алгоритмы»), но требует предварительной сортировки коллекции, по которой осуществляется поиск. + + + +``` +import bisecta: list[int] = [12, 6, 8, 19, 1, 33]a.sort()print(f"Sorted: {a}")print(bisect.bisect(a, 20)) # Найти индекс для потенциальной вставкиbisect.insort(a, 15) # Вставка значения в отсортированную последовательностьprint(a)# Бинарный поискdef binary_search(a, x, lo=0, hi=None): if hi is None: hi = len(a) pos = bisect.bisect_left(a, x, lo, hi) return pos if pos != hi and a[pos] == x else -1print(binary_search(a, 15)) +``` + + + +``` +Sorted: [1, 6, 8, 12, 19, 33] +5 +[1, 6, 8, 12, 15, 19, 33] +4 +``` + + + +### Функциональное программирование (Map, Filter, Reduce, Partial) + + + +На случай, если начиная с этого момента и до конца текущего жизненного цикла вы собираетесь к месту и не месту использовать приёмы функционального программирования, чтобы сделать свой код «воистину крутым», просто процитирую вам Джоэля Граса, автора книги «Data Science: Наука о данных с нуля»: «В первом издании этой книги были представлены функции partial, map, reduce и filter языка Python. На своем пути к просветлению я понял, что этих функций лучше избегать, и их использование в книге было заменено включениями в список, циклами и другими, более Python'овскими конструкциями». Такие дела… + + + +``` +import functools# Преобразует все входящие значения при помощи указанной функцииiter1 = map(lambda x: x + 1, range(10))print(list(iter1))# Передает в выходной итератор только значения, удовлетворяющие условиюiter2 = filter(lambda x: x > 5, range(10))print(list(iter2))# Применяет указанную функцию ко всей последовательности входных данных, сводя их к единственному значениюa = functools.reduce(lambda out, x: out + x, range(10))print(a) +``` + + + +``` +[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +[6, 7, 8, 9] +45 +``` + + + +``` +import functoolsdef sum(a,b): return a + badd_const = functools.partial(sum, 10)print(add_const(5)) +``` + + + +``` +15 +``` + + + +Если вам не сразу станет понятно, как работает функция partial (и зачем она нужна), не расстраивайтесь, вы не одиноки :). Вот, пожалуйста, тема на Stackoverflow: «[I am not able to get my head on how the partial works](https://stackoverflow.com/questions/15331726/how-does-functools-partial-do-what-it-does)». Там, кстати, есть совет, как partial могут быть полезны при организации pipe с включением функций, имеющих разное количество аргументов. + + + +### Any, All + + + +any() вернет True, если хотя бы один элемент итерируемой коллекции истинен, all() вернет True только в случае истинности всех элементов коллекции. + + + +``` +animals = ["Squirrel", "Beaver", "Fox"]sentence = "Bison likes squirrels and beavers"any_animal: bool = any(animal.lower() in sentence.lower() for animal in animals)print(any_animal)all_animal: bool = all(animal.lower() in sentence.lower() for animal in animals)print(all_animal) +``` + + + +``` +True +False +``` + + + +### Файлы + + + +Файловые операции стоят немного особняком от остальных методов обработки данных, как подразумевающие не сиюминутную торопливую обработку информации, а взаимодействие с неким постоянным энергонезависимым хранилищем данных. Так что если вам нужно сохранить данные _на завтра_, или, наоборот, нужно прочитать данные, которые вам предоставили _неделю назад_, то вам, очевидно, нужно будет работать с файлами. В файлах же осядет информация, которую мы передаем базам данных, но эту тему мы рассмотри ниже. + + + +``` +f = open("f.txt", mode='r', encoding="utf-8", newline=None)print(f.read()) +``` + + + +``` +Hello from file! +``` + + + +На всякий случай, если вы испытываете программистский зуд даже небольшой степени выраженности, напоминаю — обязательно прогоняйте в IDE все непонятные куски кода, не надо на них _смотреть_, их надо видоизменять, корректировать, дорабатывать; только когда концы свяжутся, только когда вы поймете, как функционирует этот кусочек кода, только тогда промелькнёт маленькая искорка и ваша квалификация как программиста немного подрастёт. + + + +Режимы (mode): +"r" — чтение (поведение по умолчанию) +"w" — запись (информация, ранее присутствующая в файле, будет стёрта) +"x" — эксклюзивное создание и запись; если файл уже существует, будет выброшено исключение FileExistsError +"a" — открытие с последующим добавлением в конец файла +"w+" — чтение и запись +"r+" — чтение и запись с начала файла +"a+" — чтение и запись с конца файла +"t" — текстовый режим ("rt", "wt" и т. д.; поведение по умолчанию) +"b" — двоичный режим ("rb", "wb", "xb" и т. д.) + + + +encoding=None — будет использована кодировка по умолчанию (зависит от системы, см. getpreferredencoding()). Если нет специальных требований, просто используйте везде encoding="utf-8"; без этого, например, русский текст запишется в текстовый файл в виде человеконечитаемой последовательности. + + + +newline=None — при чтении системные символы конца строки будут конвертированы в "\n"; при записи, наоборот, "\n" будут конвертированы в системные символы конца строки. + + + +Возможные исключения при работе с файлами: +_FileNotFoundError_ при чтении в режиме "r" или "r+". +_FileExistsError_ при записи в режиме "x". +_IsADirectoryError_, _PermissionError_ — в любом режиме. + + + +### Чтение из файла + + + +Открывает файл и возвращает файловый объект. +Для работы с файлами лучше использовать менеджеры контекста (рассмотрены ниже), т. е. конструкции вида "with open...". Даже если что-то пойдет не так, как задумано (например, вы не обработаете исключение во время работы с файлом), менеджер контекста «зачистит хвосты», и ваша оплошность не отразится, например, на файловой системе. + + + +``` +with open("f.txt", encoding="utf-8") as f: chars = f.read(5) # Reads chars/bytes or until EOF print(chars) f.seek(0) # Moves to the start of the file. Also seek(offset) and seek(±offset, anchor), where anchor is 0 for start, 1 for current position and 2 for end lines: list[str] = f.readlines() # Also readline() print(lines) +``` + + + +``` +Hello +['Hello from file!'] +``` + + + +### Запись в файл + + + +``` +with open("f.txt", "w", encoding="utf-8") as f: f.write("Hello from file!") # Или f.writelines(<collection>) +``` + + + +### JSON + + + +Человекочитаемый формат для хранения и передачи данных. + + + +``` +import jsond: dict = {1: "Lemon", 2: "Apple", 3: "Banana!"}object_as_string: str = json.dumps(d, indent=2)print(object_as_string)restored_object = json.loads(object_as_string)# Write object to JSON filewith open("1.json", 'w', encoding='utf-8') as file: json.dump(d, file, indent=2)# Read object from JSON filewith open("1.json", encoding='utf-8') as file: restored_from_file = json.load(file)print(restored_from_file) +``` + + + +``` +{ + "1": "Lemon", + "2": "Apple", + "3": "Banana!" +} +{'1': 'Lemon', '2': 'Apple', '3': 'Banana!'} +``` + + + +### Pickle + + + +Бинарный формат для хранения и передачи данных. + + + +``` +import pickled: dict = {1: "Lemon", 2: "Apple", 3: "Banana!"}# Запись объекта в бинарный файлwith open("1.bin", "wb") as file: pickle.dump(d, file)# Чтение объекта из файлаwith open("1.bin", "rb") as file: restored_from_file = pickle.load(file)print(restored_from_file) +``` + + + +``` +{1: 'Lemon', 2: 'Apple', 3: 'Banana!'} +``` + + + +### Protocol Buffers + + + +Если вы хотите передавать и хранить данные, используя универсальную структуру, одинаково хорошо понимаемую всеми языками программирования (как JSON) и занимающую мало места (как Pickle), то можно посмотреть в сторону Protocol Buffers ([Wikipedia](https://en.wikipedia.org/wiki/Protocol_Buffers), [примеры для Python](https://developers.google.com/protocol-buffers/docs/pythontutorial)). Есть еще альтернативы, например, [FlatBuffers](https://google.github.io/flatbuffers/), [Apache Avro](https://avro.apache.org/) или [Thrift](https://thrift.apache.org/). + + + +### Пути (Paths) + + + +При работе с файлами не обойтись без манипулирования файловыми путями. + + + +``` +from os import getcwd, path, listdirfrom pathlib import Paths1: str = getcwd() # Возвращает текущую рабочую директориюprint(s1)s2: str = path.abspath("f.txt") # Возвращает полный путьprint(s2)s3: str = path.basename(s2) # Возвращает имя файлаs4: str = path.dirname(s2) # Возвращает путь без файлаt1: tuple = path.splitext(s2) # Возвращает кортеж из пути и имени файлаprint(s3, s4, t1)p = Path(s2)st = p.stat()print(st)b1: bool = p.exists()b2: bool = p.is_file()b3: bool = p.is_dir()print(b1, b2, b3)c: list = listdir(path=s1) # Возвращает список имен файлов, находящихся по указанному путиprint(c)s5: str = p.stem # Возвращает имя файла без расширенияs6: str = p.suffix # Возвращает расширение файлаt2: tuple = p.parts # Возвращает все элементы пути как отдельные строкиprint(s5, s6, t2) +``` + + + +``` +c:\Works\amaargiru\pycore +c:\Works\amaargiru\pycore\f.txt +f.txt c:\Works\amaargiru\pycore ('c:\\Works\\amaargiru\\pycore\\f', '.txt') +os.stat_result(st_mode=33206, st_ino=2251799814917120, st_dev=3628794147, st_nlink=1, st_uid=0, st_gid=0, st_size=16, st_atime=1662468638, st_mtime=1662468638, st_ctime=1661089564) +True True False +['.git', '.gitignore', '.pytest_cache', '01_python.ipynb', '01_python.md', '02_postgre.md', '03_architecture.md', '04_algorithms.ipynb', '04_algorithms.md', '05_admin_devops.md', '06_pytest_mock.ipynb', '06_pytest_mock.md', '07_fastapi.md', '08_flask.md', '1.bin', '1.json', 'compose_readme.bat', 'coupling_vs_cohesion.svg', 'f.txt', 'gitflow.svg', 'graph_for_dfs.jpg', 'pycallgraph3.png', 'readme.md'] +f .txt ('c:\\', 'Works', 'amaargiru', 'pycore', 'f.txt') +``` + + + +### Простейшие вычисления — Sum, Count, Min, Max + + + +``` +a: list[int] = [1, 2, 3, 4, 5, 2, 2]s = sum(a)print(s)c = a.count(2) # Вернет количество вхожденийprint(c)mn = min(a)print(mn)mx = max(a)print(mx) +``` + + + +``` +19 +3 +1 +5 +``` + + + +Присмотритесь к [встроенным функциям](https://docs.python.org/3/library/functions.html), там есть еще кое-что, касающееся элементарной математики. + + + +### Базовая математика + + + +``` +from math import pia: float = pi ** 2 # Or pow(pi, 2)print(f"Power: {a}")b: float = round(pi, 2)print(f"Round: {b}")c: int = round(256, -2)print(f"Int round: {c}")d: float = abs(-pi)print(f"Abs: {d}")e: float = abs(10+10j) # Or e: float = abs(complex(real=10, imag=10))print(f"Complex abs: {e}") +``` + + + +``` +Power: 9.869604401089358 +Round: 3.14 +Int round: 300 +Abs: 3.141592653589793 +Complex abs: 14.142135623730951 +``` + + + +### Побитовые операции + + + +``` +a: int = 0b01010101b: int = 0b10101010print(f"And: 0b{a&b:08b}")print(f"Or: 0b{a|b:08b}")print(f"Xor: 0b{a^b:08b}")print(f"Left shift: 0b{a << 4:08b}")print(f"Right shift: 0b{b >> 4:08b}")print(f"Not: 0b{~a:08b}") +``` + + + +``` +And: 0b00000000 +Or: 0b11111111 +Xor: 0b11111111 +Left shift: 0b10101010000 +Right shift: 0b00001010 +Not: 0b-1010110 +``` + + + +### Подсчет битов + + + +``` +a: int = 4242print(f"{a} in binary format: 0b{a:b}")c = a.bit_count() # Returns the number of ones in the binary representation of the absolute value of the integerprint(f"Bit count: {c}") +``` + + + +``` +4242 in binary format: 0b1000010010010 +Bit count: 4 +``` + + + +### Fractions + + + +``` +from fractions import Fractionf = Fraction("0.2").as_integer_ratio()print(f) +``` + + + +``` +(1, 5) +``` + + + +### Евклидово расстояние между двумя точками + + + +``` +import mathp1 = (0.22, 1, 12)p2 = (-0.12, 3, 7)print(math.dist(p1, p2)) +``` + + + +``` +5.39588732276722 +``` + + + +### NumPy + + + +Мини-язык для манипулирования массивами. На удачных сценариях работает в сотни раз быстрее встроенных функций. Еще более быстрая альтернатива работает на GPU, называется [CuPy](https://github.com/cupy/cupy) и опять-таки [обещает](https://medium.com/rapids-ai/single-gpu-cupy-speedups-ea99cbbb0cbb) стократный прирост производительности, только уже по сравнению с NumPy. Так что если вам нужен какой-нибудь быстрый [FFT](https://en.wikipedia.org/wiki/Fast_Fourier_transform) или еще какой числогрыз, то вы знаете, что делать. Если вы дружите с английским, то изучайте [официальный мануал](https://numpy.org/doc/stable/), если нет — на «Хабре» есть [перевод](https://habr.com/ru/post/352678/) (как всегда, читайте комментарии, там немало полезного). + + + +Небольшое отступление. + + + +Во-первых, тут мы переходим границу между встроенной функциональность языка и внешними библиотеками. Надо понимать, что успех Python во многом основан именно на богатстве его экосистемы (хотя, впрочем, то же самое можно сказать и про JavaScript, и про C#); сам язык предоставляет богатую, но всё же ограниченную функциональность, в то время как функционал внешних библиотек практически безграничен; это как бесконечно разнообразные кубики Лего. Соответственно, очень часто для решения задачи не нужно реализовывать алгоритм с нуля на чистом Python'е, достаточно подобрать нужную библиотеку. + + + +Во-вторых, популярность разных библиотек Python (в том числе и конкурирующих) сильно разнится. Например, NumPy — очень популярная библиотека, но в мире существуют буквально миллионы Python-разработчиков, которые _никогда_ не работали с NumPy, просто в силу своего круга функциональных обязанностей. + + + +Для начинающего разработчика это представляет собой довольно нешуточную проблему — как конкретно двигаться вперед, какие библиотеки изучать, ведь знания чистого Python, как правило, недостаточно для формирования актуального резюме. + + + +Дам вам небольшой совет. Ежегодно компания JetBrains (делающая среди прочего очень классную IDE PyCharm) проводит всемирный опрос Python-разработчиков, а потом выкладывает полученные результаты в виде так называемого [Python Developers Survey Results](https://lp.jetbrains.com/python-developers-survey-2021/). Например, если вы почитаете результаты последнего исследования, то найдете там довольно чёткие ориентиры: скажем, в разделе «Data science frameworks and libraries» в топе находятся NumPy, Pandas (рассмотрен ниже) и Matplotlib; в тестировании с большим отрывом лидирует pytest (смотри ниже), в других областях вперед вырываются Flask (Django на втором месте с крошечным отрывом), SQLAlchemy (vs Django ORM) и PostgreSQL (vs SQLite), про них мы тоже еще поговорим. Так что в целом, общее направление развития определить можно. + + + +Однако, вернемся к NumPy. Не забывайте, что в основе NumPy лежат массивы, а все данные в массиве должны быть одинакового типа (просто на случай, если вы уже познали пр-р-рел-л-лесть списков Python). Создание массивов: + + + +``` +import numpy as npa1 = np.array([1, 2, 3, 4, 5], float) # Получение массива из спискаprint(a1[0:2])a2 = np.zeros(5) # Массив, заполненный нулямиprint(a2)a3 = np.arange(0, 6, 1) # Использование диапазона, np.arange(from_inclusive, to_exclusive, step_size)print(a3)a4 = np.random.randint(6, size=10) # Создание массива, содержащего случайные значения, np.random.randint(low_inclusive, high_exclusive=None, size=None, dtype=int)print(a4)a5 = np.random.randint(6, size=(2, 5)) # Создание многомерного массива, содержащего случайные значенияprint(a5)print(a5.shape) # Число строк и столбцов в массивеprint(a5.dtype) # Тип переменныхprint(1 in a5) # Проверка наличия элемента +``` + + + +``` +[1. 2.] +[0. 0. 0. 0. 0.] +[0 1 2 3 4 5] +[2 2 4 0 0 0 0 4 0 5] +[[1 0 3 5 0] + [3 1 4 2 2]] +(2, 5) +int32 +True +``` + + + +Базовые математические операции ([полный список](https://numpy.org/doc/stable/reference/routines.math.html)): + + + +``` +import numpy as npa1 = np.array([1, 2, 3, 4, 5])a2 = np.array([6, 7, 8, 9, 10])a3 = a1 + 1print(a3)a4 = a1 + a2print(a4)a5 = a1 ** 3print(a5)a6 = a1 ** a2print(a6) +``` + + + +``` +[2 3 4 5 6] +[ 7 9 11 13 15] +[ 1 8 27 64 125] +[ 1 128 6561 262144 9765625] +``` + + + +Вообще, можно сказать, что быстрые математические операции над многомерными массивами — это главная «фишка» NumPy. Вы просто говорите: возьми такие-то массивы и проделай над ними такую-то операцию. Далее все эти данные «проваливаются» в высокоскоростное ядро NumPy, где к ним уже можно применить всю мощь вашего процессора, которая раньше была вам недоступна (ну, или доступна не полностью) из-за ограничений Python-интерпретатора. Так что, если вы пытаетесь в цикле итерировать массив NumPy, по факту передавая данные на нижний уровень небольшими порциями (например, объектами row), то имейте в виду, что тем самым используете возможности NumPy недостаточно эффективно; попробуйте решить задачу без итерирования. + + + +### Sum, Min, Max + + + +``` +import numpy as npa1 = np.random.randint(6, size=(2, 10)) # NumPy поддерживает несколько десятков видов распределений, например, Пуассона и Стьюдентаprint(a1)s = np.sum(a1) # Сумма всех элементовprint(s)mn = a1.min(axis=0) # Наименьшие числа в каждом столбцеprint(mn)mx = a1.max(axis=1) # Наибольшие числа в каждой строкеprint(mx)amin = a1.argmin(axis=0) # Индексы минимальных элементов в каждом столбцеprint(amin)amax = a1.argmax(axis=1) # Индексы максимальных элементов в каждой строкеprint(amax)uniq = np.unique(a1) # Извлечение уникальных элементовprint(uniq) +``` + + + +``` +[[3 0 4 1 0 5 4 0 1 3] + [0 3 4 0 0 1 4 0 5 4]] +42 +[0 0 4 0 0 1 4 0 1 3] +[5 5] +[1 0 0 1 0 1 0 0 0 0] +[5 8] +[0 1 3 4 5] +``` + + + +В качестве домашнего задания попробуйте самостоятельно применить prod(), mean(), var(), std(), median(), cov() и corrcoef(). + + + +Форматирование массивов: + + + +``` +import numpy as npa = np.random.randint(6, size=(3, 5))print(a)a1 = a.reshape((5, 3)) # Форматирование. Если есть возможность, создается новый view на те же самые данныеprint(a1)a.shape = (5, 3) # Форматирование in-placeprint(a)print(a.shape)a = a[:, :, np.newaxis] # Увеличение размерности массива с 2 до 3print(a)print(a.shape)a = a.flatten() # Конвертация в одномерный массивprint(a)print(a.shape) +``` + + + +``` +[[5 5 5 1 1] + [0 2 0 5 5] + [0 2 5 4 5]] +[[5 5 5] + [1 1 0] + [2 0 5] + [5 0 2] + [5 4 5]] +[[5 5 5] + [1 1 0] + [2 0 5] + [5 0 2] + [5 4 5]] +(5, 3) +[[[5] + [5] + [5]] + + [[1] + [1] + [0]] + + [[2] + [0] + [5]] + + [[5] + [0] + [2]] + + [[5] + [4] + [5]]] +(5, 3, 1) +[5 5 5 1 1 0 2 0 5 5 0 2 5 4 5] +(15,) +``` + + + +Копирование массивов: + + + +``` +import numpy as npimport copya = np.random.randint(10, size=(4, 4))print(a)# Неглубокая (shallow) копияa1 = np.copy(a)# Глубокая (deep) копияa2 = copy.deepcopy(a)# Копирование ссылкиa3 = aa[0, 0] = 10print(a[0, 0] == a1[0, 0])print(a[0, 0] == a2[0, 0])print(a[0, 0] == a3[0, 0]) +``` + + + +``` +[[5 3 1 4] + [0 8 7 0] + [1 7 4 7] + [5 3 5 2]] +False +False +True +``` + + + +NumPy очень мощный инструмент, не зря же он стоит на первом месте в списке «Data science frameworks and libraries» обзора, который мы упоминали чуть выше. Но углубляться в эту тему очень уж глубоко в рамках нашего достаточно поверхностного очерка, пожалуй, не стоит; вряд ли прямо сейчас вам кровь из носу нужно освоить скалярное, тензорное и внешнее произведение матриц или познать (вспомнить?) специфику линейной алгебры. Думаю, даже если мы сейчас начнем описывать транспонирование или выбор оси, по которой будет произведена конкатенация массивов, то это уже будет, что называется, «не в коня корм». +К тому же, изучая тонкости употребления NumPy, начинает появляться соблазн упоминания SciPy, предоставляющего еще более широкий функционал, а после первого "import scipy" у нас начнётся уже полное непотребство. Давайте пока пройдем мимо этой кроличьей норы, для первого знакомства она слишком глубока. + + + +Единственное, что еще можно освоить в конце ознакомительного курса NumPy — взаимодействие с внешним миром. Изучите для начала load/save/savez (бинарники) и loadtxt/savetxt (человекочитаемый формат). + + + +### Pandas + + + +Библиотека обработки и анализа данных. Работа с данными строится поверх библиотеки NumPy. +В первом, грубом приближении pandas можно воспринимать как связку «Excel + VisualBasic-скрипты», только более гибкую и удобную. Библиотека создает своеобразный мостик между профессиями Python-программиста, дата-сайентиста и аналитика, позволяя сосредоточиться в большей степени именно на очистке и анализе данных, на читабельности отчетов, а не на программировании. Pandas также поддерживает широкий спектр «красивостей» при выводе информации, позволяя, например, добавлять в выводимые данные градиентную подсветку (heatmap) или визуализировать отклонение от среднего (bar chart). + + + +Для того чтобы как следует «распробовать» pandas, по-хорошему надо загрузить какой-нибудь развесистый набор данных, но мы, пожалуй, не будем погружаться в глубины глубин, просто поиграем небольшим самодельным датасетом. + + + +``` +import pandas as pds = pd.Series([0, 1, 4, 7, 8, 10, 12])print(s)print(s[2]) +``` + + + +``` +0 0 +1 1 +2 4 +3 7 +4 8 +5 10 +6 12 +dtype: int64 +4 +``` + + + +Series — базовая структура данных pandas. Вы можете воспринимать её как упорядоченный словарь или как строку Excel, смотря по тому, какая аналогия вам ближе. + + + +``` +import pandas as pds = pd.Series([0, 1, 4, 7, 8, 10, 12], index=["a", "b", "c", "d", "x", "y", "z"]) # Индексы Series можно задавать вручнуюprint(s)print(s["x"])print(s[["x", "y", "z"]]) # Выборкаprint(s[s > 5]) # Фильтрацияprint(s.max()) # Математика, примерно как в NumPyprint(s.sum()) +``` + + + +``` +a 0 +b 1 +c 4 +d 7 +x 8 +y 10 +z 12 +dtype: int64 +8 +x 8 +y 10 +z 12 +dtype: int64 +d 7 +x 8 +y 10 +z 12 +dtype: int64 +12 +42 +``` + + + +При объединении нескольких Series получается DataFrame, вторая базовая структура данных pandas, которую в первом приближении можно рассматривать как лист Excel. + + + +``` +import pandas as pdfrom pandas import DataFrames1 = pd.Series([0, 1, 4, 7, 8, 10, 12])s2 = pd.Series([0, 100, 200, 300, 600, 900, 1200])df = pd.DataFrame([s1, s2])print(df)print(df[1])print(df[2][0])print(df.iloc[0][2:4]) +``` + + + +``` + 0 1 2 3 4 5 6 +0 0 1 4 7 8 10 12 +1 0 100 200 300 600 900 1200 +0 1 +1 100 +Name: 1, dtype: int64 +4 +2 4 +3 7 +Name: 0, dtype: int64 +``` + + + +Давайте сделаем что-то более похожее на реальный анализ данных. При помощи формулы [ИМТ](https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81_%D0%BC%D0%B0%D1%81%D1%81%D1%8B_%D1%82%D0%B5%D0%BB%D0%B0) выясним, кто из знаменитостей не следит за собой и обзавелся лишним весом: + + + +``` +from pandas import DataFrameimport matplotlib.pyplot as pltdef bmi(row): return row["weight"] / row["height"] ** 2if __name__ == '__main__': celebs: dict = {"Britney Spears": {"height": 1.63, "weight": 57}, "Melanie Griffith": {"height": 1.73, "weight": 63}, "Kylie Minogue": {"height": 1.52, "weight": 46}, "Hulk Hogan": {"height": 1.98, "weight": 137}} df = DataFrame(celebs) # Создаем DataFrame df.loc["bmi"] = df.apply(lambda row: bmi(row), axis=0) # Добавлем новую строку с ИМТ df = df.sort_values(by="bmi", ascending=True, axis=1) # Сортируем print(df) df.loc["bmi"].plot.bar() # Визуализация plt.show() +``` + + + +``` + Kylie Minogue Melanie Griffith Britney Spears Hulk Hogan +height 1.520000 1.730000 1.630000 1.980000 +weight 46.000000 63.000000 57.000000 137.000000 +bmi 19.909972 21.049818 21.453574 34.945414 +``` + + + + + + + +На самом деле Халк, конечно, не толстяк, а профессиональный спортсмен, к которым формула ИМТ малоприменима, но крошка Кайли действительно вырывается вперед, даже с учетом своего небольшого роста. + + + +### Matplotlib/Seaborn + + + +Библиотеку визуализации matplotlib мы уже слегка задействовали в примере выше. Прямо здесь и прямо сейчас глубоко погружаться в разбор возможностей matplotlib/seaborn, наверное, особого смысла не имеет; все вы видели примеры иллюстраций в научной и бизнес-литературе и, разумеется, все эти графики и иллюстрации можно повторить при помощи рассматриваемых библиотек. + + + +Давайте просто для затравки нарисуем пару симпатичных визуализаций, чтобы наглядно показать полезность качественного оформления результатов проделанной работы. + + + +Тепловая карта (heatmap), наглядно показывающая достижения отдельных членов команды: + + + +``` +from random import randrangeimport numpy as npimport matplotlib.pyplot as pltimport uuidtargets = ["authorities", "humans", "parrots", "cars", "motorcycles", "buildings", "warehouses"]robots = ["Terminator #" + str(uuid.uuid4())[:5] for _ in range(7)]harvest = np.array([[randrange(i * j) for i in range(10, 80, 10)] for j in range(1, 8)])fig, ax = plt.subplots()im = ax.imshow(harvest)ax.set_xticks(np.arange(len(robots)), labels=robots)ax.set_yticks(np.arange(len(targets)), labels=targets)plt.setp(ax.get_xticklabels(), rotation=60, ha="right", rotation_mode="anchor")for i in range(len(targets)): for j in range(len(robots)): text = ax.text(j, i, harvest[i, j], ha="center", va="center", color="w")ax.set_title("Targets destroyed")fig.tight_layout()plt.rcParams['figure.figsize'] = [4, 4]plt.rcParams['figure.dpi'] = 200plt.show() +``` + + + + + + + +Аналогичная тепловая карта, визуализированная при помощи seaborn: + + + +``` +from random import randrangeimport numpy as npimport matplotlib.pyplot as pltimport uuidimport seaborn as snssns.set_theme()targets = ["authorities", "humans", "parrots", "cars", "motorcycles", "buildings", "warehouses"]robots = ["Terminator #" + str(uuid.uuid4())[:5] for _ in range(7)]harvest = np.array([[randrange(i * j) for i in range(10, 80, 10)] for j in range(1, 8)])fig, ax = plt.subplots()im = ax.imshow(harvest)ax.set_title("Targets destroyed")plt.rcParams['figure.figsize'] = [4, 4]sns.heatmap(harvest, annot=True, fmt="d", linewidths=.5, ax=ax, xticklabels=robots, yticklabels=targets)plt.setp(ax.get_xticklabels(), rotation=60, ha="right", rotation_mode="anchor")plt.xticks(rotation=60)plt.show() +``` + + + + + + + +А вот так будет выглядеть «Доска почёта» при отрисовке в 3D: + + + +``` +import uuidfrom random import randrangeimport matplotlib.cm as cmimport matplotlib.colors as colorsimport matplotlib.pyplot as pltimport numpy as nptargets = ["authorities", "humans", "parrots", "cars", "motorcycles", "buildings", "warehouses"]robots = ["Terminator #" + str(uuid.uuid4())[:5] for _ in range(7)]harvest = np.array([[randrange(i * j) for i in range(10, 80, 10)] for j in range(1, 8)])fig = plt.figure(figsize=(5, 5))ax = fig.add_subplot(projection='3d')ax.set_xticks(np.arange(len(robots)), labels=robots)ax.set_yticks(np.arange(len(targets)), labels=targets)plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")plt.setp(ax.get_yticklabels(), ha="left", rotation_mode="anchor")ax.set_title("Our team")xx, yy = np.meshgrid(range(len(targets)), range(len(robots)))x1d, y1d = xx.ravel(), yy.ravel()harvest1d = harvest.ravel()# Setup color schemeoffset = harvest1d + np.abs(harvest1d.min())fracs = offset.astype(float) / offset.max()norm = colors.Normalize(fracs.min(), fracs.max())colors = cm.jet(norm(fracs))ax.bar3d(x1d, y1d, np.zeros_like(x1d + y1d), 0.7, 0.7, harvest1d, color=colors)plt.show() +``` + + + + + + + +### Нейронные сети + + + +Углубиться достаточно глубоко в тематику глубокого обучения в рамках нашего несколько неглубокого формата, конечно, не получится (каким-нибудь томом «Введение в Deep Learning» вполне можно нанести себе существенные травмы, если неудачно уронить со стола), так что скользнём буквально по верхушкам, рассмотрев основные понятия. + + + +Глубинная нейронная сеть (deep neural network) — это, формально выражаясь, _многослойная_ искусственная нейронная сеть, использующая алгоритмы машинного обучения для моделирования высокоуровневых абстракций с применением нелинейных преобразований. В ходу есть еще такие термины, как «поверхностное машинное обучение», «слабый ИИ», «сильный ИИ»; существует классификация понятий «глубокое обучение», «машинное обучение» и «искусственный интеллект» (вот, например, [вариант](https://learn.microsoft.com/en-us/azure/machine-learning/concept-deep-learning-vs-machine-learning) от Microsoft, такую же структуру-матрёшку демонстрирует Франсуа Шолле в своей книге «Глубокое обучение на Python»), но сейчас, по крайней мере при неформальном общении, всё чаще ставится знак равенства между «машинным обучением» и «искусственным интеллектом»; причём в работе, как правило, используется «машинное обучение», а при подготовке презентаций — «искусственный интеллект» :) Дело, по всей видимости, в том, что глубокое обучение стало самой многообещающей и динамично развивающейся областью машинного обучения, а искусственным интеллектом уже давно именовали всю эту область знаний как популяризаторы науки, так и журналисты. + + + +В общем, при первоначальном знакомстве с TensorFlow или PyTorch можете смело всем говорить, что занимаетесь AI, а если захотите углубиться — уж терминологией-то овладеете. +Что касается сути работы нейронных сетей, то здесь можно выделить следующие основные понятия: + + + +**Датасет** — маркированные данные, используемые для обучения сети и для последующей проверки качества этого обучения. Вариант простого датасета — коллекция изображений одинакового размера с рукописными цифрами и буквами, где про каждое изображение точно известно, какай именно символ в нём содержится; такой датасет нужен для разработки систем распознавания рукописного текста (handwritten text recognition, HTR). Создание качественных датасетов — большая, тяжелая работа, поэтому сейчас идет активная работа по созданию моделей, способных работать с большими объёмами _немаркированных_ данных. Как правило, датасет разбивается на две части — для обучения сети и для проверки качества проведенного обучения. + + + +**Искусственная нейронная сеть** — программный или аппаратный аналог биологической нейронной сети; в самом простом варианте, в сети прямого распространения, это последовательно соединенные слои нейронов. Первые реализации нейронных сетей получили практическое воплощение еще в 60-х годах XX века, хотя существенный прогресс и практическое внедрение приходятся примерно на последние лет пятнадцать. + + + +**Архитектура** искусственной нейронной сети — определяет общие принципы её построения, вот [здесь](https://www.asimovinstitute.org/neural-network-zoo/) есть хорошее введение в тему, наглядно показано, чем, например, GAN отличается от LSTM. + + + +**Обучение** нейронной сети — если в двух словах, то это нахождение коэффициентов связи между нейронами. Применительно к машинному обучению заменяет процесс собственно программирования. Когда мы говорим о DALL-E 2, способном создавать изображения по текстовым описаниям или об AlphaGo, обыгрывающем профессиональных игроков в го — мы говорим в первую очередь об обученных нейронных сетях, создатели которых проявили бездну изобретательности, чтобы все коэффициенты связи были на своём месте. Дообучение — частичная корректировка коэффициентов связи между нейронами при модификации старого или добавлении нового функционала. + + + +Зачем нужно машинное обучение? Сильный ИИ, который будет (скрестим пальцы) решать все наши проблемы в режиме реального времени, еще за горизонтом, а прямо сейчас нейронные сети решают задачи, которые не по зубам классическим алгоритмам. Можно ли при помощи «обычного» программирования решить, например, задачу распознавания отсканированного текста или перевода с одного языка на другой? Да, можно, и такие небезуспешные попытки неоднократно предпринимались. Но, учитывая прорыв в развитии математической базы машинного обучения, наметившийся в последние 10-15 лет, помноженный на гигантский прирост производительности даже обычных повседневных вычислительных устройств, вроде смартфонов, фактически и распознавание, и перевод сейчас реализуют только при помощи машинного обучения. Для некоторых же классов задач, таких как распознавание изображений и видео с последующей классификацией объектов или беспилотная транспортировка, решения на базе классических алгоритмов никогда не заходили дальше вялотекущих концептов. + + + +Мало-помалу машинное обучение делает нашу жизнь лучше. Со временем, надеюсь, каждый из нас сможет воспользоваться плодами работы искусственного интеллекта, хоть мы и величаем его «слабым». И речь идет не только о громких проектах, вроде автопилота «Теслы» (хотя и это крайне немаловажно), но в первую очередь о постоянном сканировании эксабайтных потоков информации, порождаемых современной цифровой цивилизацией — видео с уличных камер, сканов МРТ и КТ, телеметрии с фитнес-браслетов, отчетов об исследовании лекарственных средств и пищевых добавок. И если всё это пройдет перед, может быть, пока не очень умными, но зато неустанными глазами предварительно должным образом обученной нейронной сети, то кратно уменьшится неверных диагнозов, людей, умерших от инсультов или замерзших на улицах, а также лекарств с тяжелыми побочными эффектами. + + + +Но помните, однако — даже если вы полны энтузиазма и готовы хоть прямо сейчас нырнуть во все эти модные, интересные и актуальные сущности вроде бигдаты или ИИ — чем дальше вы будете углубляться в эту тематику, тем меньше в вас будет программиста, тем больше исследователя, учёного, математика. Где-то в самом конце этого длинного-предлинного коридора сидит вовсе не Нео из «Матрицы», а эдакий себе [Григорий Перельман](https://en.wikipedia.org/wiki/Grigori_Perelman), уставший человек с ручкой и листом бумаги, иногда этой ручкой на этой бумаге пишущий вещи, которые потом приходится два года разжевывать лучшим математикам мира. Крепко подумайте, как глубоко вы хотите и можете зайти, взвесьте свои силы. + + + +## 3. Потоки данных + + + + + + + +### Itertools + + + +Методы модуля itertools возвращают _итераторы_. + + + +Итератор — механизм поэлементного обхода данных, который использует метод next() для получения следующего значения последовательности. Подробнее создание итераторов будет рассмотрено ниже, в разделе «ООП / Утиная типизация». В «нормальные» данные итераторы перегоняются при помощи for, next или list(). + + + +Itertools содержит множество готовых итераторов, которые могут быть бесконечными (порождаются при помощи count, cycle или repeat), конечными (accumulate, chain, takewhile и другие) и комбинаторными (product, combinations, combinations_with_replacement, permutations). Лучше изучить их все, хотя бы поверхностно, потому что даже относительно редко употребляемый метод, например, какой-нибудь zip_longest(), иногда весьма и весьма пригождается, идеально ложась на поставленную задачу. + + + +Пример работы с бесконечными итераторами: + + + +``` +from itertools import count, repeat, cycle# Итератор, возвращающий равномерно распределенные значенияi1 = count(start=0, step=.1)print(next(i1))print(next(i1))print(next(i1))# Итератор, циклично и бесконечно возвращающий элементы итерируемого объектаi2 = cycle([1, 2])print(next(i2))print(next(i2))print(next(i2))# Итератор, возвращающий один и тот же объект бесконечно, если не указано значение аргумента timesi3 = repeat("Wow!", times=3)print(list(i3)) +``` + + + +``` +0 +0.1 +0.2 +1 +2 +1 +['Wow!', 'Wow!', 'Wow!'] +``` + + + +Применение некоторых конечных итераторов: + + + +``` +from itertools import accumulate, chain, compress, dropwhile, takewhile, pairwiseimport operator# Итератор, возвращающий накопленный результат выполнения указанной функции (по умолчанию — сложение)i1 = accumulate([1, 2, 3, 4])i2 = accumulate([1, 2, 3, 4], initial=10)print(list(i1), list(i2))i3 = accumulate([ -3, -2, -1, 1, 2, 3, 4], operator.mul)print(list(i3))# Можно использовать свою функциюdef myfunc(accumulated, current): return accumulated + 2 * currenti4 = accumulate([1, 2, 3, 4], func=myfunc)print(list(i4))# Можно использовать лямбду (подробнее рассмотрены ниже)i5 = accumulate([1, 2, 3, 4], lambda accumulated, current: accumulated + 2 * current)print(list(i5))# Итератор, возвращающий только те элементы входной последовательности,# которые имеют соответствующий элемент, равный True или 1 в последовательности selectorsi6 = compress("ABCDEF", [1, 1, 1, 0, 0, 1])print(list(i6))# Итератор, отбрасывающий элементы входной последовательности, если результат выполнения функции равен True.# Как только предикат становится False, то отбрасывание прекращается (предикат больше не применяется)i7 = dropwhile(lambda x: x<5, [1, 4, 6, 4, 1, 1, 1, 0])print(list(i7))# takewhile, в отличие от dropwhile, наоборот, возвращает элементы входной последовательности,# если результат выполнения функции равен Truei8 = takewhile(lambda x: x<5, [1, 4, 6, 0, 4, 1, 2, 1])print(list(i8))# Итератор, формирующий из нескольких входных последовательностей одну общуюi2 = chain(["A", "B", "C"],["D", "E", "F"],["G", "H", "I"])print(list(i2))# Кстати, такой же трюк можно провернуть при помощи обычной sum(), задав ей начальный параметр [] (т. е. пустой список)a = sum([["A", "B", "C"],["D", "E", "F"],["G", "H", "I"]], [])print(a)# Возвращает элементы входной коллекции попарноi6 = pairwise([1, 2, 3, 4, 5])print(list(i6)) +``` + + + +``` +[1, 3, 6, 10] [10, 11, 13, 16, 20] +[-3, 6, -6, -6, -12, -36, -144] +[1, 5, 11, 19] +[1, 5, 11, 19] +['A', 'B', 'C', 'F'] +[6, 4, 1, 1, 1, 0] +[1, 4] +['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] +['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] +[(1, 2), (2, 3), (3, 4), (4, 5)] +``` + + + +Комбинаторика + + + +``` +from itertools import product, combinations, combinations_with_replacement, permutations# Создает множество, содержащее все упорядоченные пары элементов из входных множествa = product("abc", "xyz")print(list(a))b = product([0, 1], repeat=3)print(list(b))# Возвращает подпоследовательности длины r из элементов входного итерируемого объекта, повторяющиеся элементы не допускаютсяc = combinations("abc", r=2)print(list(c))# Выдает перестановки элементов итерируемого объектаd = permutations("abc", r=2)print(list(d))# Возвращает подпоследовательности длины r из элементов входного итерируемого объекта, повторяющиеся элементы допустимыe = combinations_with_replacement("abc", r=2)print(list(e)) +``` + + + +``` +[('a', 'x'), ('a', 'y'), ('a', 'z'), ('b', 'x'), ('b', 'y'), ('b', 'z'), ('c', 'x'), ('c', 'y'), ('c', 'z')] +[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)] +[('a', 'b'), ('a', 'c'), ('b', 'c')] +[('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')] +[('a', 'a'), ('a', 'b'), ('a', 'c'), ('b', 'b'), ('b', 'c'), ('c', 'c')] +``` + + + +### Enumerate + + + +Иногда, при переборе объектов в цикле for, нужно получить не только сам объект, но и его порядковый номер. Разумеется, это можно сделать, создав дополнительную переменную, которая будет инкрементироваться на каждом шаге цикла. Однако, гораздо удобнее это делать с помощью итератора enumerate, введенным в [PEP-279](https://peps.python.org/pep-0279/). Enumerate — синтаксический сахар («introduces… to simplify a commonly used looping idiom»), позволяющий проще и нагляднее работать с объектами, поддерживающими итерацию. Метод __next__() enumerate возвращает кортеж, содержащий значение индекса и соответствующее этому индексу значение. + + + +В документации работа enumerate упрощенно объясняется через генератор: + + + +``` +def enumerate(sequence, start=0): n = start for elem in sequence: yield n, elem n += 1 +``` + + + +На самом деле enumerate — не генератор, а итератор: + + + +``` +import collectionsimport typese = enumerate("abcdef")print(isinstance(e, enumerate))print(isinstance(e, collections.Iterable))print(isinstance(e, collections.Iterator))print(isinstance(e, types.GeneratorType)) +``` + + + +``` +True +True +True +False +``` + + + +Enumerate реализован не на Python, а на C, и в его [исходном коде](https://github.com/python/cpython/blob/master/Objects/enumobject.c#L289), разумеется, нет ключевого слова yield. + + + +Примеры использования enumerate: + + + +``` +values = ["a", "b", "c", "d"]for count, value in enumerate(values): print(count, value)print("\n")for count, value in enumerate(values, start=10 ): print(count, value) +``` + + + +``` +0 a +1 b +2 c +3 d + +10 a +11 b +12 c +13 d +``` + + + +### Генератор (generator) + + + +Любая функция, содержащая ключевое слово _yield_, вернет генератор. Генератор не хранит в памяти все необходимые элементы, а просто содержит метод для вычисления очередного элемента; результат может создаваться на основе математического алгоритма или брать элементы из другого источника данных (коллекция, файл, сетевое подключение и т. д.), при необходимости модифицируя их. + + + +Пройти генератор в цикле можно только один раз, на каждом шаге возможно вычислить только следующий элемент, но не предыдущий. Элемент генератора нельзя извлечь по индексу, будет выброшена ошибка, т. к. генератор не поддерживает метод __getitem__(). + + + +Бесконечный генератор: + + + +``` +def count(start, step): current = start while True: yield current current += stepc = count(100, 10)print(next(c))print(next(c))print(next(c)) +``` + + + +``` +100 +110 +120 +``` + + + +Конечный генератор. +Также, как и конечный итератор, конечный генератор можно превратить в список при помощи list() (вы можете попробовать превратить в list и бесконечный генератор, но процесс рискует несколько затянуться :): + + + +``` +def count(start, stop, step): current = start while current <= stop: yield current current += stepc = count(100, 200, 10)print(next(c))print(next(c))print(next(c))print(list(c)) +``` + + + +``` +100 +110 +120 +[130, 140, 150, 160, 170, 180, 190, 200] +``` + + + +Следует разделять итераторы и генераторы. Итератор — объект, который использует метод __next__() для получения следующего значения последовательности. Генератор — функция, которая позволяет отложено создавать результат при итерации. + + + +#### Объявление генератора + + + +Объявить генератор можно несколькими методами. Первый метод — объявить функцию с yield, как было показано выше. +Второй метод — использовать _генераторное выражение_ (generator expression): + + + +``` +r = range(1, 11)squares = (n**2 for n in r)print(list(squares)) +``` + + + +``` +[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] +``` + + + +Можно объединить генераторы или делегировать часть функционала генератора другому генератору при помощи конструкции _yield from_: + + + +``` +def subg(): yield 'World'def generator(): yield 'Hello' yield from subg() yield '!'for i in generator(): print(i, end = ' ') +``` + + + +``` +Hello World ! +``` + + + +До широкого распространения asyncio конструкция yield from использовалась для создания [корутин на базе генераторов](https://docs.python.org/3.7/library/asyncio-task.html#asyncio.coroutine). + + + +### Декораторы + + + +Что такое декораторы? + + + +Декоратор в широком смысле – паттерн проектирования, когда один объект изменяет поведение другого. Декораторы — это, по сути, своеобразные «обёртки», которые дают нам возможность делать что-либо до или после того, что сделает декорируемая функция, не изменяя её. Можно сказать, что декоратор является просто синтаксическим сахаром для конструкции вида: + + + +``` +my_function = my_decorator(my_function) +``` + + + +``` +def makebold(fn): def wrapped(): return "<b>" + fn() + "</b>" return wrappeddef makeitalic(fn): def wrapped(): return "<i>" + fn() + "</i>" return wrapped# Разумеется, при последовательном применении нескольких декораторов играет роль порядок декорирования.@makebold@makeitalicdef hello(): return "Hello, world!"print(hello()) +``` + + + +``` +<b><i>Hello, world!</i></b> +``` + + + +Декоратор, подсчитывающий время работы оборачиваемой функции: + + + +``` +import timedef perf_counter(function): def counted(*args): start_time = time.perf_counter_ns() res = function(*args) print(f"{time.perf_counter_ns() - start_time} ns") return res return counted@perf_counterdef slow_sum(x, y): time.sleep(1) return x + yprint(slow_sum(1, 2)) +``` + + + +``` +1002478400 ns +3 +``` + + + +### LRU Cache + + + +Декоратор, кеширующий значения, возвращаемые функцией. Все аргументы функции должны быть хэшируемы. + + + +``` +import functoolsdef recursion_sum(n): if n == 1: return n print(n, end=" ") return n + recursion_sum(n - 1)recursion_sum(5)print("\n")recursion_sum(9)print("\n")@functools.lru_cachedef recursion_sum2(n): if n == 1: return n print(n, end=" ") return n + recursion_sum2(n - 1)recursion_sum2(5)print("\n")recursion_sum2(9) +``` + + + +``` +5 4 3 2 + +9 8 7 6 5 4 3 2 + +5 4 3 2 + +9 8 7 6 + +45 +``` + + + +Размер кеша по умолчанию 128 значений. Ограничение можно отменить при помощи 'maxsize=None'. + + + +Пока мы не ушли далеко от тема кеша, погуглите заодно модуль weakref и WeakValueDictionary, позволяющие организовать более гибкую работу с кешем. + + + +### Параметризованный декоратор + + + +В декоратор можно передать и позиционные, и именованные аргументы — args и kwargs соответственно. Синтаксис декораторов с аргументами немного отличается — декоратор с аргументами должен возвращать функцию, которая принимает функцию и возвращает другую функцию: + + + +``` +def text_wrapper(wrap_text): def wrapped(function): def wrapper(*args, **kwargs): result = function(*args, **kwargs) return f"{wrap_text}\n{result}\n{wrap_text}" return wrapper return wrapped@text_wrapper('============')def my_decorated_function(text): return textprint(my_decorated_function('Hello, world!')) +``` + + + +``` +============ +Hello, world! +============ +``` + + + +### Контекстный менеджер + + + +Код, размещенный внутри оператора with выполняется с особенностью: как до, так и после срабатывают события входа в блок with и выхода из него. Объект, который определяет логику событий, называется контекстным менеджером. + + + +На уровне класса события определены методами __enter__ и __exit__. +__enter__ срабатывает в тот момент, когда ход исполнения программы переходит внутрь with. Метод может вернуть значение. Оно будет доступно расположенному внутри блока with коду. +__exit__ срабатывает в момент выхода блока, в т.ч. и в случае исключения. В этом случае в метод будет передана тройка значений (exc_class, exc_instance, traceback). + + + +Самый распространённый контекстный менеджер – класс, порожденный функцией open. Он гарантирует, что файл будет закрыт даже в том случае, если внутри блока возникнет ошибка. + + + +Желательно побыстрее выходить из контекстного менеджера, освобождая контекст и ресурсы. + + + +``` +with open('file.txt') as f: data = f.read()process_data(data) +``` + + + +В примере выше мы вышли из блока with сразу же после прочтения файла. Обработка данных происходит в основном блоке программы. + + + +Контекстные менеджеры можно использовать для временной замены параметров, переменных окружения, транзакций БД. + + + +Напишем свой контекстный менеджер для подключения к БД SQLite: + + + +``` +import sqlite3class db_conn: def __init__(self, db_name): self.db_name = db_name # Открываем подключение к БД def __enter__(self): self.conn = sqlite3.connect(self.db_name) return self.conn # Закрываем подключение к БД def __exit__(self, exc_type, exc_value, exc_traceback): self.conn.close() if exc_value: raiseif __name__ == "__main__": db = "test_context_connect.db" with db_conn(db) as conn: cursor = conn.cursor() +``` + + + +### Контекстный менеджер на базе contextlib + + + +Перепишем наш контекстный менеджер для подключения к БД SQLite при помощи contextlib: + + + +``` +import sqlite3from contextlib import contextmanager# Схема конструирования следующая: всё, что написано до оператора yield - вызывается в рамках функции __enter__,# всё что после – в рамках __exit__.@contextmanagerdef db_conn(db_name): # Открываем подключение к БД conn = sqlite3.connect(db_name) yield conn # Закрываем подключение к БД conn.close()if __name__ == "__main__": db = "test_contextlib_connect.db" with db_conn(db) as conn: cursor = conn.cursor() +``` + + + +## 4. ООП + + + + + + + +### Классы и объекты + + + +Тут, конечно, было бы к месту кратенькое, минут на сорок, введеньице в тему классов и объектов, но в наш текущий формат такая мощная врезка не совсем укладывается. Объясню максимально просто, на доступных примерах из киновселенной «Чужих»: +объект — это один конкретный ксеноморф; +класс — это Королева ксеноморфов. Класс либо рожает ксеноморфа, либо может вступить в бой сам (@staticmethod); +метапрограммирование — это такая Супер-Королева, размером с «Сулако», которая рожает других Королев; +наследование — это ксеноморф из «Воскрешения», помните, миленький такой, взявший лучшее и от собственной генетической программы и от генов Рипли. + + + +Мы попробуем вернуться к теме объектов с чуть более серьезным настроением позже, в главе «Архитектура», но, вообще, в объектно-ориентированном программировании нет ничего особо сложного; просто до него лучше дойти, предварительно немного погрязнув в поддержке обычного процедурного подхода, когда зачастую стоит выбор — попробовать подлечить этот кусок кода или уже усыпить и переписать всё по новой? Когда-то давно, когда я писал относительно несложные программы на ассемблере для микроконтроллеров, то читая Страуструпа, слегка недоумевал — зачем всё это? Чтобы осознать потребность в обуви, надо походить босиком. + + + +### Магические методы + + + +Специальные (называемые также magic или dunder) методы класса — перегрузка, позволяющая классам определять собственное поведение по отношению к операторам языка. +Магические они потому, что почти никогда не вызываются явно. Их вызывают встроенные функции или синтаксические конструкции. Например, функция len() вызывает метод __len__() переданного объекта. Метод __add__(self, other) вызывается автоматически при сложении оператором +. + + + +Примеры магических методы: + + + +__init__: конструктор класса +__add__: сложение с другим объектом +__eq__: проверка на равенство с другим объектом +__cmp__: сравнение (больше, меньше, равно) +__iter__: при подстановке объекта в цикл + + + +``` +print(dir(int), "\n")class A: # An empty class ...a = A()print(dir(a), "\n")print(repr(a), "\n")print(str(a)) +``` + + + +``` +['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes'] + +['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] + +<__main__.A object at 0x000001A1FFF3AF20> + +<__main__.A object at 0x000001A1FFF3AF20> +``` + + + +Особенностью метода __init__ является то, что он не должен ничего возвращать. При попытке возврата данных будет сгенерировано исключение. +__repr__ (representation) возвращает более-менее машино-читаемое представление объекта, полезное для отладки. +_Иногда_ repr может содержать достаточно информации для восстановления объекта. +__str__ возвращает человеко-читаемое сообщение. Если __str__ не определён, то str использует repr. + + + +``` +class Person: # A simple class with init, repr and str methods def __init__(self, name: str): self.name: str = name def __repr__(self): return f"Person '{self.name}'" def __str__(self): return f"{self.name}" def say_hi(self): print("Hi, my name is", self.name)p = Person("Charlie")p.say_hi()print(repr(p))print(str(p)) +``` + + + +``` +Hi, my name is Charlie +Person 'Charlie' +Charlie +``` + + + +### @property + + + +Декоратор [@property](https://docs.python.org/3/library/functions.html?highlight=property#property) используется для определения методов, доступных как поля. Таким образом операции чтения/записи поля можно обрамить дополнительной логикой, например, проверкой допустимых значений входного аргумента. + + + +``` +import mathclass Circle: def __init__(self, radius, max_radius): self._radius = radius self.max_radius = max_radius @property def radius(self): return self._radius @radius.setter def radius(self, value): if value <= self.max_radius: self._radius = value else: raise ValueError @property def area(self): return 2 * self.radius * math.picircle = Circle(10, 100)circle.radius = 20 # OK# circle.radius = 101 # Raises ValueErrorprint(circle.area) +``` + + + +``` +125.66370614359172 +``` + + + +### @staticmethod + + + +Обычный метод (т. е. не помеченный декораторами @staticmethod или @classmethod) имеет доступ к свойствам конкретного экземпляра класса. + + + +@staticmethod — метод, принадлежащий классу, а не экземпляру класса. Можно вызывать без создания экземпляра, т. к. метод не имеет доступа к свойствам экземпляра. При помощи @staticmethod помечают функционал, логически связанный с классом, но не требующий доступа к свойствам экземпляра. + + + +### @classmethod, cls, self + + + +Если метод не должен иметь доступа к свойствам конкретного экземпляра класса (как @staticmethod), но должен иметь доступ к другим методам и переменным класса, то следует использовать @classmethod. + + + +``` +class B(object): def foo(self, x): print(f"Run foo({self}, {x})") @classmethod def class_foo(cls, x): print(f"Run class_foo({cls}, {x})") @staticmethod def static_foo(x): print(f"Run static_foo({x})")b = B()b.foo(1)b.class_foo(1)b.static_foo(1) +``` + + + +``` +Run foo(<__main__.B object at 0x000001A1FFF3A980>, 1) +Run class_foo(<class '__main__.B'>, 1) +Run static_foo(1) +``` + + + +У @classmethod первым параметром должен быть cls (класс), а у обычного метода — self (экземпляр класса). +Для @staticmethod не требуется ни cls, ни self. + + + +### __dict__ + + + +Каждый класс и каждый объект имеет атрибут __dict__. Это «системный», определённый интерпретатором атрибут, его не нужно создавать вручную. __dict__ — словарь, который хранит пользовательские атрибуты, и в котором ключом является _имя атрибута_, значением, соответственно, _значение атрибута_. + + + +``` +class Supercriminal: publisher = 'DC Comics'Riddler = Supercriminal()print(Supercriminal.__dict__)print(Riddler.__dict__)Riddler.name = 'Edward Nygma'print(Riddler.__dict__) # Values from object __dict__print(Riddler.publisher) # Value from class __dict__ +``` + + + +``` +{'__module__': '__main__', 'publisher': 'DC Comics', '__dict__': <attribute '__dict__' of 'Supercriminal' objects>, '__weakref__': <attribute '__weakref__' of 'Supercriminal' objects>, '__doc__': None} +{} +{'name': 'Edward Nygma'} +DC Comics +``` + + + +Каждый раз при запросе пользовательского атрибута Python последовательно обыскивает сам объект, класс объекта и классы, от которых унасаледован класс объекта. + + + +### __slots__ + + + +Если вы припомните разницу между списком и кортежем, а также между множеством и иимутабельным множеством, то заметите, что создатели Python пытаются предоставлять разработчикам выбор между удобством и скоростью (тут Си с ассемблерными вставками слегка напрягается, но потом опадает, как будто хотел что-то сказать, но благоразумно передумал). К списку таких же особенностей языка, заточенных на увеличение производительности и уменьшение занимаемой памяти, относится и __slots__. + + + +Вот [официальная документация](https://docs.python.org/3/reference/datamodel.html?highlight=slots#object.__slots__) по __slots__, а вот [дополнительные разъяснения](https://stackoverflow.com/questions/472000/usage-of-slots/28059785#28059785) от одного из разработчиков официальной документации. При выборе «slots или не slots» не забывайте также про существование [PEP 412 – Key-Sharing Dictionary](https://peps.python.org/pep-0412/), который внёс некоторый раздрай в некогда однозначное отношение к __slots__. + + + +__dict__, рассмотренный чуть выше – изменяемая структура, и вы можете на лету добавлять и удалять поля из класса, что удобно, но порой медленно. Вы можете разменять удобство на скорость и размер занимаемой памяти, создав __slots__ — жестко заданный список предопределенных атрибутов, резервирующий память, создание которого _запрещает дальнейшее создание __dict__ и __weakref___. Слоты можно использовать, когда у класса может быть очень много полей, например, в ORM, либо когда критична производительность, потому что доступ к списку работает быстрее, чем поиск в словаре. + + + +``` +class Clan: __slots__ = ["first", "second"]clan = Clan()clan.first = "Joker"clan.second = "Lex Luthor"# clan.third = "Green Goblin" # Raises AttributeError# print(clan.__dict__) # Raises AttributeError +``` + + + +Слоты используются, скажем, в библиотеках requests (например, __slots__ = ["url", "netloc", "simple_url", "pypi_url", "file_storage_domain"]) или ORM peewee (__slots_\_ = ('stack', '_sql', '_values', 'alias_manager', 'state')). + + + +Наследование __slots__ имеет определенную специфику и будет рассмотрено ниже. + + + +Чтобы было понятно, о каком приросте производительности и снижении потребления памяти идёт речь, сделаем простое сравнение: + + + +``` +import timeitimport pympler.asizeof # В нашем случае sys.getsizeof - не лучший вариант, берем стороннее решениеclass NotSlotted(): passclass Slotted(): __slots__ = 'foo'not_slotted = NotSlotted()slotted = Slotted()def get_set_delete_fn(obj): def get_set_delete(): obj.foo = "Never Ending Song of Love" del obj.foo return get_set_deletens = min(timeit.repeat(get_set_delete_fn(not_slotted)))s = min((timeit.repeat(get_set_delete_fn(slotted))))print(ns, s, f'{(ns - s) / s * 100} %')print(pympler.asizeof.asizeof(not_slotted), 'bytes')print(pympler.asizeof.asizeof(slotted), 'bytes') +``` + + + +``` +0.10838449979200959 0.08712740009650588 24.39772066187959 % +280 bytes +40 bytes +``` + + + +Я в Python 3.10 в Windows вижу 24 % разницы. + + + +На всякий случай напоминаю еще раз — прогняйте все непонятные примеры кода в IDE, их можно и нужно анализировать, корректировать и видоизменять. Попробуйте, например, самостоятельно посмотреть потребление памяти объектов с __dict__ и __slots__. А заодно на практике испытайте давно напрашивающийся, и наконец появившийся в Python 3.10 [симбиоз](https://docs.python.org/3/library/dataclasses.html#module-contents) между __slots__ и dataclass. + + + +### Утиная типизация + + + +[Утиная типизация](https://en.wikipedia.org/wiki/Duck_typing) (duck types) — постулирование реализации интерфейса классом не через явное объявление, а через реализацию методов интерфейса. Так, каждый класс, реализующий методы __next__() и __iter__(), автоматически становится итератором, несмотря на отсутствие явного объявления (что-нибудь вроде [iterator](https://habr.com/ru/users/iterator/)) или, скажем, наследования от класса Iterator. + + + +### Iterator + + + +Итератор — класс, реализующий методы __next__() и __iter__(). +Метод __next__() должен возвращать следующее значение итератора или выкидывать исключение StopIteration, чтобы сигнализировать о том, что итератор исчерпал доступные значения. +Метод __iter__() должен возвращать "self". + + + +``` +class LimitCounter: def __init__(self, max_value: int): self.count = 0 self.max_value = max_value def __next__(self): self.count += 1 if self.count <= self.max_value: return self.count else: raise StopIteration def __iter__(self): return selflimit_counter = LimitCounter(2)print(next(limit_counter))print(next(limit_counter))# print(next(limit_counter)) # Raises StopIteration +``` + + + +``` +1 +2 +``` + + + +### Comparable + + + +Начиная с Python 3.4, для того, чтобы экземпляры метода можно было сравнивать между собой, достаточно определить методы __lt__ (меньше) и __eq__ (равно), а также задействовать декоратор @functools.total_ordering. + + + +``` +from functools import total_ordering@total_orderingclass Person: def __init__(self, firstname: str, lastname: str): self.firstname: str = firstname self.lastname: str = lastname def _is_valid_operand(self, other): return hasattr(other, "lastname") and hasattr(other, "firstname") def __eq__(self, other): if not self._is_valid_operand(other): return NotImplemented return (self.lastname, self.firstname) == (other.lastname, other.firstname) def __lt__(self, other): if not self._is_valid_operand(other): return NotImplemented return (self.lastname, self.firstname) < (other.lastname, other.firstname)Finn = Person("Finn", "the Human")Jake = Person("Jake", "the Dog")print(Finn != Jake) +``` + + + +``` +True +``` + + + +### Hashable + + + +Хешируемые объекты должны реализовывать методы __hash__() и __eq__(). Хеш объекта должен быть неизменен в течении всего жизненного цикла. Хешируемые объекты можно использовать как ключи в словарях и как элементы множеств, так как эти структуры используют хеш-таблицу для внутреннего представления данных. + + + +Hashable objects that compare equal must have the same hash value, meaning default hash() that returns `'id(self)'` will not do. That is why Python automatically makes classes unhashable if you only implement eq(). + + + +``` +class Hero: def __init__(self, name: str, level: int): self.name: str = name self.level: int = level def _is_valid_operand(self, other): return hasattr(other, "name") and hasattr(other, "level") def __eq__(self, other): if not self._is_valid_operand(other): return NotImplemented return (self.name, self.level) == (other.name, other.level) def __hash__(self): return hash((self.name, self.level))Finn = Hero("Finn the Human", 10_000)Jake = Hero("Jake the Dog", 10_000)print(hash(Finn))print(hash(Jake)) +``` + + + +``` +-8707075988359731747 +-2276052447712954388 +``` + + + +### Sortable + + + +Для возможности применения к последовательностям объектов таких методов как sort() или max() необходимо, как и в случае Comparable, определить методы __lt__ (меньше) и __eq__ (равно), а также задействовать декоратор @functools.total_ordering. + + + +Для более предсказумого поведения объекта в условиях различного контекста вы можете определить полное множество функций сравнения (__lt()__, __gt()__, __le__() и __ge__()). + + + +Для примера создадим класс студентов, которых можно будет сортировать не по имени, а по среднему баллу. + + + +``` +from functools import total_orderingfrom statistics import mean@total_orderingclass Student: def __init__(self, name: str, grades: list[int]): self.name: str = name self.grades: list[int] = grades def _is_valid_operand(self, other): return hasattr(other, "name") and hasattr(other, "grades") def __eq__(self, other): if not self._is_valid_operand(other): return NotImplemented return mean(self.grades) == mean(other.grades) def __lt__(self, other): if not self._is_valid_operand(other): return NotImplemented return mean(self.grades) < mean(other.grades) # определим str для человеко-читаемой репрезентации объекта def __str__(self): return self.name + " " + str(mean(self.grades))Melissa = Student("Melissa Andrew", [4, 3, 4, 5, 4])Peter = Student("Peter Shining Jr.", [3, 3, 4, 5, 3])Joe = Student("Just Joe", [5, 5, 4, 5, 5])print([str(stud) for stud in sorted([Peter, Melissa, Joe], reverse=True)]) +``` + + + +``` +['Just Joe 4.8', 'Melissa Andrew 4', 'Peter Shining Jr. 3.6'] +``` + + + +### Callable + + + +Для возможности вызова объекта в качестве функции необходимо реализовать метод __call__. Типы, поддерживающие возможность их вызова в качестве функции, могут принимать набор аргументов. + + + +``` +class Counter: def __init__(self): self.i = 0 def __call__(self): self.i += 1 return self.icounter = Counter()print(counter())print(counter())print(counter()) +``` + + + +``` +1 +2 +3 +``` + + + +@classmethod нельзя вызывать в качестве функции: + + + +``` +class Check(): @classmethod def class_method(cls): pass @staticmethod def static_method(): pass def instance_method(self): pass for attr, val in vars(Check).items(): if not attr.startswith("__"): print (attr, f"{'is' if callable(val) else 'is NOT'} callable") +``` + + + +``` +class_method is NOT callable +static_method is callable +instance_method is callable +``` + + + +Контекстные менеджеры, описанные в предыдущей главе, тоже, как мы теперь видим, определяются через утиную типизацию при помощи методов __enter__ и __exit__. + + + +### Утиная типизация итерируемых объектов + + + +### Iterable + + + +[Iterable](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable) — объект, который для предоставления возможности поочерёдного прохода по всем своим элементам должен реализовывать метод __iter__(), возвращающий итератор. У каждого объекта с методом __iter__() автоматически начинает работать метод __contains__(). + + + +``` +class MyIterable: def __init__(self, *args): self.a = list(args) def __iter__(self): return iter(self.a)mi = MyIterable(1, 2, 3, 4)print([el for el in mi])print(1 in mi) # __contains__() +``` + + + +``` +[1, 2, 3, 4] +True +``` + + + +### Collection + + + +[Collection](https://docs.python.org/3/library/collections.abc.html#collections.abc.Collection) — объект, предоставляющий возможность поочерёдного прохода по всем своим элементам и обладающий конечным размером. +В дополнение к iter() должен быть реализован метод len(), возвращающий размер коллекции. + + + +``` +class MyCollection: def __init__(self, *args): self.a = list(args) def __iter__(self): return iter(self.a) def __len__(self): return len(self.a)mc = MyCollection(1, 2, 3, 4)print([el for el in mc])print(1 in mc)print(len(mc)) +``` + + + +``` +[1, 2, 3, 4] +True +4 +``` + + + +### Sequence + + + +Требует методы len() and getitem(). getitem() должен отдавать элемент с требуемым индексом или вызывать исключение IndexError. +Автоматически будут порождены методы iter(), reversed() и contains(). + + + +``` +class MySequence: def __init__(self, a): self.a = a def __len__(self): return len(self.a) def __getitem__(self, i): return self.a[i] +``` + + + +### ABC Sequence + + + +Коллекция Sequence из [Abstract Base Classes for Containers](https://docs.python.org/3/library/collections.abc.html) предоставляет расширенный интерфейс по сравнению с обычной Sequence. +Всё так же требуя __getitem__ и __len__, предоставляет __contains__, __iter__, __reversed_\_, index и count. + + + +``` +from collections import abcclass MyAbcSequence(abc.Sequence): def __init__(self, a): self.a = a def __len__(self): return len(self.a) def __getitem__(self, i): return self.a[i] +``` + + + +### Таблица требуемых и доступных методов: + + + +``` ++------------+------------+------------+------------+--------------+ +| | Iterable | Collection | Sequence | ABC Sequence | ++------------+------------+------------+------------+--------------+ +| iter() | нужен | нужен | + | + | +| contains() | + | + | + | + | +| len() | | нужен | нужен | нужен | +| getitem() | | | нужен | нужен | +| reversed() | | | + | + | +| index() | | | | + | +| count() | | | | + | ++------------+------------+------------+------------+--------------+ +``` + + + +И вообще, потщательнее присмотритесь с collections.abc, там есть множество заготовок, которые помогут вам сэкономить немало времени. Например, если к упомянутым относительно ABC Sequence __getitem__ и __len__ добавить __setitem__, __delitem__ и insert, то в ответ вы получите коллекцию MutableSequence, которая, кроме возможностей Sequence, имеет еще методы append, reverse, extend, pop, remove и __iadd__. + + + +### Копирование объектов + + + +В Python оператор присваивания (=) не копирует объекты. Вместо этого он создает связь между существующим объектом и именем целевой переменной. Чтобы создать копии объекта в Python, необходимо использовать модуль copy. Более того, существует два способа создания копий для данного объекта с помощью модуля copy. + + + +Shallow Copy – это побитовая копия объекта. Созданный скопированный объект имеет точную копию значений в исходном объекте. Если одно из значений является ссылкой на другие объекты, копируются только адреса ссылок на них. + + + +Deep Copy – рекурсивно копирует все значения от исходного объекта к целевому, т. е. дублирует даже объекты, на которые ссылается исходный объект. + + + +``` +from copy import copy, deepcopyclass A: def __init__(self, val: list): self.val = val def change_val(self, val: list): self.val = vala = A(list("one"))# Просто копирование ссылки на объектb = a # Assignment# Создание нового объекта и копирование ссылок на объекты, найденные в изначальном объектеc = copy(a) # Shallow copy# Создание нового объекта с последующим рекурсивным копированием содержащихся внутри объектовd = deepcopy(a) # Deep Copyb.change_val(list("two"))c.change_val(list("three"))d.change_val(list("four"))print(a.val, b.val, c.val, d.val)print(id(a), id(b), id(c), id(d))print(id(a.val[1]), id(c.val[1])) +``` + + + +``` +['t', 'w', 'o'] ['t', 'w', 'o'] ['t', 'h', 'r', 'e', 'e'] ['f', 'o', 'u', 'r'] +1795295519472 1795295519472 1793149281968 1793149273808 +1795217224688 1795217321264 +``` + + + +### Наследование + + + +``` +class Person: def __init__(self, name, age): self.name = name self.age = ageclass Employee(Person): def __init__(self, name, age, staff_num, email): super().__init__(name, age) self.staff_num = staff_num self.email = email +``` + + + +### Множественное наследование + + + +При множественном наследовании порядок разрешения методов (method resolution order, MRO) позволяет Питону выяснить, из какого класса-предка нужно вызывать метод, если он не обнаружен непосредственно в классе-потомке. + + + +``` +class PrivateStaffData: def __init__(self, private_email): self.private_email = private_emailclass PublicStaffData: def __init__(self, work_email): self.work_email = work_emailclass StaffData(PrivateStaffData, PublicStaffData): def __init__(self, private_email, work_email): super().__init__()print(StaffData.mro()) +``` + + + +``` +[<class '__main__.StaffData'>, <class '__main__.PrivateStaffData'>, <class '__main__.PublicStaffData'>, <class 'object'>] +``` + + + +MRO строит иерархию наследования таким образом, чтобы более специфичные методы класса-потомка перекрывали менее специфичные методы класса-предка. MRO строит упорядоченный список классов, в которых будет производиться поиск метода слева направо (линеаризация класса). + + + +Для решения проблемы ромбовидной структуры (которая неявно присутствует даже в простейшем случае, так как все классы наследуются от object) линеаризация должна быть монотонной. Монотонность — свойство, которое требует соблюдения в линеаризации класса-потомка того же порядка следования классов-прародителей, что и в линеаризации класса-родителя. Линеаризация по сути является [топологической сортировкой](https://en.wikipedia.org/wiki/Topological_sorting). В ранних версиях Python использовался алгоритм DLR, сейчас в ходу [C3-линеаризация](https://en.wikipedia.org/wiki/C3_linearization). + + + +Если после удовлетворения свойства монотонности остаётся больше одного варианта линеаризации, то применяется порядок локального старшинства (local precedence ordering), т. е. порядок соблюдения для классов-родителей в линеаризации класса-потомка того же порядка, что и при его объявлении. Например, если класс объявлен как D(A, B, C), то в линеаризации D класс A должен стоять раньше B, а класс B — раньше C. + + + +Если разрешение всех конфликтов при линеаризации невозможно, то остается три пути: +1 — переменой мест классов-предков в объявлении класса-потомка (но это помогает далеко не всегда); +2 — пересмотр иерархии наследования; +3 — определение своей собственной линеаризации через метаклассы при помощи метода mro(cls). Но при данном подходе надо быть готовым к тому, что будет использован менее специфичный метод класса-родителя вместо более специфичного метода класса-потомка. + + + +При задании своей собственной линеаризации Python отключает встроенные проверки. + + + +### Наследование классов со __slots__ + + + +При одиночном наследовании __slots__ нормально наследуется, но это не предотвращает создание __dict__: + + + +``` +class SlotsClass: __slots__ = 'foo', 'bar'class ChildSlotsClass(SlotsClass): ...obj = ChildSlotsClass()print(obj.__slots__)obj.something_new = "underwater stones"print(obj.__dict__) +``` + + + +``` +('foo', 'bar') +{'something_new': 'underwater stones'} +``` + + + +Для ограничения дочернего класса слотами нужно в нём снова присвоить значение атрибуту __slots__, родительские поля дублировать не нужно. + + + +``` +class SlotsClass: __slots__ = 'foo', 'bar'class ChildSlotsClass(SlotsClass): __slots__ = 'baz'obj = ChildSlotsClass()# obj.something_new = "underwater stones" # Raises AttributeError: 'ChildSlotsClass' object has no attribute 'something_new' +``` + + + +Множественное же наследование классов с _непустыми_ __slots__ невозможно. + + + +### Метапрограммирование + + + +Что такое класс? Это, в принципе, просто кусок кода, описывающий, как создать объект. Но в Python класс — это нечто большее, классы также являются объектами; как только используется ключевое слово class, Python исполняет команду и создаёт объект: + + + +``` +class A: ... +``` + + + +В памяти будет создан объект с именем A. + + + +Классы, как и другие объекты, можно создавать на ходу: + + + +``` +def custom_class(name): if name == "foo": class Foo: ... return Foo # Возвращает именно класс, а не экземпляр else: class Bar: ... return BarMyClass = custom_class("foo")print(MyClass) # Функция возвращает класс, а не экземплярprint(my_class := MyClass()) # Можно создать экземпляр класса +``` + + + +``` +<class '__main__.custom_class.<locals>.Foo'> +<__main__.custom_class.<locals>.Foo object at 0x000001F0ECF97610> +``` + + + +Но это не очень удобно, так как нам до сих пор приходится писать весь код класса. + + + +Основная цель метаклассов — автоматически изменять класс в момент создания, генерируя классы в соответствии с текущим контекстом. +Сами по себе метаклассы достаточно просты и работают примерно следующим образом: +перехватывают создание класса, +изменяют класс, +возвращают модифицированный класс. + + + +Но обычно логику работы метаклассов насыщают вещами вроде [интроспекции](https://en.wikipedia.org/wiki/Type_introspection#Python) или манипуляцией наследованием, поэтому конечный код выглядит достаточно громоздко. + + + +Здесь неплохо было бы добавить еще пару страниц про ньюансы создания и работы метаклассов, но позвольте переадресовать вас на вот эту [прекрасную статью](https://habr.com/ru/post/145835/). + + + +При помощи метаклассов хорошо решаются задачи, например, генерации классов для ORM. Скажем, для + + + +``` +class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField() +``` + + + +код + + + +``` +keanu = Person(name="Keanu Reeves", age=58) print(keanu.age) +``` + + + +распечатает число, взятое из БД, потому что models.Model определяет __metaclass__, который сотворит некоторую магию и превратит класс Person, который мы только что определили достаточно простым выражением, в сложную привязку к базе данных. + + + +Если вы всё еще ломаете голову, где бы вам пришить метапрограммирование в своём текущем проекте, чтобы потом упомянуть об этом в резюме, то вот вам на 147 % уместная цитата из [Тима Питерса](https://en.wikipedia.org/wiki/Tim_Peters_(software_engineer)): «[Metaclasses] are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why)», что в вольном переводе означает «Метаклассы нужны только уверенным в себе людям, которые точно знают, чего хотят от жизни, а вовсе не тебе». + + + +### @abstractmethod + + + +Абстрактный класс в Python — аналог интерфейса в других языках (например, в C#) — класс, содержащий только сигнатуры методов, без реализации. Реализация методов переложена на классы-потомки. Задача абстрактного класса соответствует задаче интерфейса — _обязать_ классы-потомки реализовывать _все_ методы, заложенные в классе-родителе. + + + +``` +import abcclass AbstractClass(metaclass=abc.ABCMeta): @abc.abstractmethod def return_anything(self): returnclass ConcreteClass(AbstractClass): def return_anything(self): return 42c = ConcreteClass()print(c.return_anything()) +``` + + + +``` +42 +``` + + + +Если не специфицировать return_anything() в ConcreteClass, при попытке вызвать c.return_anything() будет выброшено исключение TypeError: Can't instantiate abstract class ConcreteClass with abstract method return_anything. + + + +## Источники + + + +Официальная документация Python [docs.python.org](https://docs.python.org/), включающая [The Python Standard Library](https://docs.python.org/3/library/index.html). +Весьма подробное руководство (совсем уж базовый синтаксис не включен): [Comprehensive Python Cheatsheet](https://github.com/gto76/python-cheatsheet). +Руководство с включением базового синтаксиса: [Python Cheatsheet](https://github.com/wilfredinni/python-cheatsheet). Включает практические Jupiter [Notebooks](https://github.com/wilfredinni/python-cheatsheet/tree/master/jupyter_notebooks). +Сипсок библиотек и фреймворков: [Awesome Python](https://github.com/vinta/awesome-python). +Около-питоновские практические советы (pip, virtualenv, pyInstaller и т. д.): ["The Hitchhiker’s Guide to Python"](https://github.com/realpython/python-guide). +Мануал для начинающих дата-сайентистов: [Joel Grus, "Data Science from Scratch"](https://github.com/joelgrus/data-science-from-scratch). +Руководство для начинающих: ["Python Notes for Professionals"](https://goalkicker.com/PythonBook/). +Руководство для опытных программистов: ["Python 3 Patterns, Recipes and Idioms"](https://python-3-patterns-idioms-test.readthedocs.io/en/latest/index.html). +Архитектурные паттерны: [Harry Percival & Bob Gregory, "Architecture Patterns with Python"](https://www.cosmicpython.com/book/preface.html). + +Теги: + +- [учебник](https://habr.com/ru/search/?target_type=posts&order=relevance&q=[%D1%83%D1%87%D0%B5%D0%B1%D0%BD%D0%B8%D0%BA]) +- [python](https://habr.com/ru/search/?target_type=posts&order=relevance&q=[python]) +- [roadmap](https://habr.com/ru/search/?target_type=posts&order=relevance&q=[roadmap]) +- [career](https://habr.com/ru/search/?target_type=posts&order=relevance&q=[career]) + +Хабы: + +- [Python](https://habr.com/ru/hubs/python/) +- [Программирование](https://habr.com/ru/hubs/programming/) +- [Карьера в IT-индустрии](https://habr.com/ru/hubs/career/)
\ No newline at end of file diff --git a/PYTHON/Password & key = PATH.md b/PYTHON/Password & key = PATH.md new file mode 100644 index 0000000..a5dc455 --- /dev/null +++ b/PYTHON/Password & key = PATH.md @@ -0,0 +1,94 @@ +```python +# Пример куска программы: +from django.conf import settings + +def webhook(request): + ''' Обработчик вебхука от платежной системы ''' + api_token = settigs.MY_API_KEY # ключ берем из файла settings.py + add_mailchimp_email_with_tag(email=request.POST.get('email', + audience_name='DONATES', + tag='DONATE') +``` +==`vim config/settings.py`== +```sh +#.......... + +MY_API_KEY = os.getenv('MY_API_KEY') + +#.......... +``` + +### Уровни переменных окружения: + +##### - OS (операционная система) +```sh +sudo vim /etc/environment +#-------------- +THIRD_VAR="123" +# будет видна после перезагрузки +``` +##### - USER (пользователь) +```sh +vim ~/.bashrc +#-------------- +export USER_VAR=123 +# exit +echo $USER_VAR +``` +##### - SESSION (сессия) +```sh +export SESSION_VAR=777 +echo $SESSION_VAR +# после выхода из сессии переменная пропадает +``` +##### - APPLICATION (приложение) +```sh +vim setenv.sh +#-------------- +#!bash/bin/env python3 + +export VAR1=123 +export VAR1=12345 +export VAR1=123567hbkjbjk +#-------------- + +# запустим скрипт: +. ./setenv.sh + +# или +source ./setenv.sh + +# Также можно сразу запускать приложение с установкой переменных, +> PYTHON_ENV=123 VAR=435 VAR2=jfhfg3 python testenv.py + +``` + +```python +# Использование модуля < python-dotenv > +pip install python-dotenv + +nvim .env +API_KEY='my_secret_key' +# Сразу добавляем этот файл в .gitignore !!! + +# Программа: +from dotenv import load_dotenv + +load_dotenv() + +key = os.getenv('API_KEY') +print(key) + +#################### +# Скрытие пароля при вводе: +from getpass import getpass +import pyAesCrypt + +password = getpass(promt='Пароль для расшифровки') +pyAesCrypt.decryptFile( + "secret_data.aes", + "secret_data.txt", + password +) +``` + diff --git a/PYTHON/PrettyTable.md b/PYTHON/PrettyTable.md new file mode 100644 index 0000000..f344197 --- /dev/null +++ b/PYTHON/PrettyTable.md @@ -0,0 +1,15 @@ +```python +from prettytable import PrettyTable + +table = PrettyTable() +table.field_names = ['Name', 'Password', 'Sex', 'Age'] +table.add_row(['Anna', 'qwerty', 'Female', 24]) +table.add_row(['Victor', '12345', 'Male', 31]) +table.add_row(['Denis', 'mcxc', 'Male', 19]) +table.add_row(['Mia', 'be6djjx', 'Female', 42]) +table.add_row(['Oleg', 'mdso98676', 'Male', 29]) + +print(table) +``` + +![[telegram-cloud-photo-size-2-5413616564063822987-y.jpg]]
\ No newline at end of file diff --git a/PYTHON/Python-cheatsheet.md b/PYTHON/Python-cheatsheet.md new file mode 100644 index 0000000..227e7cb --- /dev/null +++ b/PYTHON/Python-cheatsheet.md @@ -0,0 +1,3717 @@ +--- +title: Python CheatSheet +description: The most commonly used Python concepts are given here. +created: 2022-10-20 +--- + +# Python CheatSheet for Developers + +## Content Outlines + +**1. Collections:** **[`List`](#list)**__,__ **[`Dictionary`](#dictionary)**__,__ **[`Set`](#set)**__,__ **[`Tuple`](#tuple)**__,__ **[`Range`](#range)**__,__ **[`Enumerate`](#enumerate)**__,__ **[`Iterator`](#iterator)**__,__ **[`Generator`](#generator)**__.__ +**2. Types:** **[`Type`](#type)**__,__ **[`String`](#string)**__,__ **[`Regular_Exp`](#regex)**__,__ **[`Format`](#format)**__,__ **[`Numbers`](#numbers-1)**__,__ **[`Combinatorics`](#combinatorics)**__,__ **[`Datetime`](#datetime)**__.__ +**3. Syntax:** **[`Args`](#arguments)**__,__ **[`Inline`](#inline)**__,__ **[`Import`](#imports)**__,__ **[`Decorator`](#decorator)**__,__ **[`Class`](#class)**__,__ **[`Duck_Types`](#duck-types)**__,__ **[`Enum`](#enum)**__,__ **[`Exception`](#exceptions)**__.__ +**4. System:** **[`Exit`](#exit)**__,__ **[`Print`](#print)**__,__ **[`Input`](#input)**__,__ **[`Command_Line_Arguments`](#command-line-arguments)**__,__ **[`Open`](#open)**__,__ **[`Path`](#paths)**__,__ **[`OS_Commands`](#os-commands)**__.__ +**5. Data:** **[`JSON`](#json)**__,__ **[`Pickle`](#pickle)**__,__ **[`CSV`](#csv)**__,__ **[`SQLite`](#sqlite)**__,__ **[`Bytes`](#bytes)**__,__ **[`Struct`](#struct)**__,__ **[`Array`](#array)**__,__ **[`Memory_View`](#memory-view)**__,__ **[`Deque`](#deque)**__.__ +**6. Advanced:** **[`Threading`](#threading)**__,__ **[`Operator`](#operator)**__,__ **[`Introspection`](#introspection)**__,__ **[`Metaprograming`](#metaprogramming)**__,__ **[`Eval`](#eval)**__,__ **[`Coroutines`](#coroutines)**__.__ +**7. Libraries:** **[`Progress_Bar`](#progress-bar)**__,__ **[`Plot`](#plot)**__,__ **[`Table`](#table)**__,__ **[`Curses`](#curses)**__,__ **[`Logging`](#logging)**__,__ **[`Scraping`](#scraping)**__,__ **[`Web`](#web)**__,__ **[`Profile`](#profiling)**__,__ +**[`NumPy`](#numpy)**__,__ **[`Image`](#image)**__,__ **[`Audio`](#audio)**__,__ **[`PyGames`](#pygame)**__,__ **[`Pandas`](#pandas)**__,__ **[`OpenCV`](#opencv)**__,__ **[`DataFrame`](#dataframe)**__.__ + + +Main +---- +```python +if __name__ == '__main__': # Runs main() if file wasn't imported. + main() +``` + +**[🔼Back to Top](#content-outlines)** + +Shortcut to comment/uncomment the selected code +---- + +| Editor | Shortcut Block Comment | Shortcut Block Uncomment | +| :----------: | :----------------------: | :----------------------: | +| Eclipse | `CTRL + /` | `CTRL + /` | +| PyDev | `CTRL + /` | `CTRL + /` | +| PyCharm | `CTRL + /` | `CTRL + /` | +| Notepad++ | `CTRL + K` | `CTRL + SHIFT + K` | +| IDLE | `ALT + 3` | `ALT + 4` | + +**[🔼Back to Top](#content-outlines)** + +List +---- +```python +<list> = <list>[<slice>] # Or: <list>[from_inclusive : to_exclusive : ±step] +``` + +```python +<list>.append(<el>) # Or: <list> += [<el>] | adds a new element to the end of the list +<list>.extend(<collection>) # Or: <list> += <collection> | adds all elements of a collection(list,tuples,etc) to the end of the list +``` + +```python +<list>.sort() # Sorts in ascending order. +<list>.reverse() # Reverses the list in-place. +<list> = sorted(<collection>) # Returns a new sorted list. +<iter> = reversed(<list>) # Returns reversed iterator. +``` + +```python +sum_of_elements = sum(<collection>) +elementwise_sum = [sum(pair) for pair in zip(list_a, list_b)] +sorted_by_second = sorted(<collection>, key=lambda el: el[1]) +sorted_by_both = sorted(<collection>, key=lambda el: (el[1], el[0])) +flatter_list = list(itertools.chain.from_iterable(<list>)) +product_of_elems = functools.reduce(lambda out, el: out * el, <collection>) +list_of_chars = list(<str>) +``` +* **For details about sorted(), min() and max() see [sortable](#sortable).** +* **Module [operator](#operator) provides functions itemgetter() and mul() that offer the same functionality as [lambda](#lambda) expressions above.** + +```python +<list>.insert(<int>, <el>) # Inserts item at index and moves the rest to the right. +<el> = <list>.pop([<int>]) # Removes and returns item at index or from the end. +<int> = <list>.count(<el>) # Returns number of occurrences. Also works on strings. +<int> = <list>.index(<el>) # Returns index of the first occurrence or raises ValueError. +<list>.remove(<el>) # Removes first occurrence of the item or raises ValueError. +<list>.clear() # Removes all items. Also works on dictionary and set. +``` + +**[🔼Back to Top](#content-outlines)** + +Dictionary +---------- +```python +<view> = <dict>.keys() # Coll. of keys that reflects changes. +<view> = <dict>.values() # Coll. of values that reflects changes. +<view> = <dict>.items() # Coll. of key-value tuples that reflects chgs. +``` +```python +value = <dict>.get(key, default=None) # Returns default if key is missing. +value = <dict>.setdefault(key, default=None) # Returns and writes default if key is missing. +<dict> = collections.defaultdict(<type>) # Returns a dict with default value of type. +<dict> = collections.defaultdict(lambda: 1) # Returns a dict with default value 1. +``` + +```python +<dict> = dict(<collection>) # Creates a dict from coll. of key-value pairs. +<dict> = dict(zip(keys, values)) # Creates a dict from two collections. +<dict> = dict.fromkeys(keys [, value]) # Creates a dict from collection of keys. +``` + +```python +<dict>.update(<dict>) # Adds items. Replaces ones with matching keys. +value = <dict>.pop(key) # Removes item or raises KeyError. +{k for k, v in <dict>.items() if v == value} # Returns set of keys that point to the value. +{k: v for k, v in <dict>.items() if k in keys} # Returns a dictionary, filtered by keys. +``` + +### Counter +```python +>>> from collections import Counter +>>> colors = ['blue', 'blue', 'blue', 'red', 'red'] +>>> counter = Counter(colors) +>>> counter['yellow'] += 1 +Counter({'blue': 3, 'red': 2, 'yellow': 1}) +>>> counter.most_common()[0] +('blue', 3) +``` + +**[🔼Back to Top](#content-outlines)** + +Set +--- +```python +<set> = set() # `{}` returns a dictionary. +``` + +```python +<set>.add(<el>) # Or: <set> |= {<el>} +<set>.update(<collection> [, ...]) # Or: <set> |= <set> +``` + +```python +<set> = <set>.union(<coll.>) # Or: <set> | <set> +<set> = <set>.intersection(<coll.>) # Or: <set> & <set> +<set> = <set>.difference(<coll.>) # Or: <set> - <set> +<set> = <set>.symmetric_difference(<coll.>) # Or: <set> ^ <set> +<bool> = <set>.issubset(<coll.>) # Or: <set> <= <set> +<bool> = <set>.issuperset(<coll.>) # Or: <set> >= <set> +``` + +```python +<el> = <set>.pop() # Raises KeyError if empty. +<set>.remove(<el>) # Raises KeyError if missing. +<set>.discard(<el>) # Doesn't raise an error. +``` + +### Frozen Set +* **Is immutable and hashable.** +* **That means it can be used as a key in a dictionary or as an element in a set.** +```python +<frozenset> = frozenset(<collection>) +``` + +**[🔼Back to Top](#content-outlines)** + +Tuple +----- +**Tuple is an immutable and hashable list.** +```python +<tuple> = () # Empty tuple. +<tuple> = (<el>,) # Or: <el>, +<tuple> = (<el_1>, <el_2> [, ...]) # Or: <el_1>, <el_2> [, ...] +``` + +### Named Tuple +**Tuple's subclass with named elements.** + +```python +>>> from collections import namedtuple +>>> Point = namedtuple('Point', 'x y') +>>> p = Point(1, y=2) +Point(x=1, y=2) +>>> p[0] +1 +>>> p.x +1 +>>> getattr(p, 'y') +2 +``` + +**[🔼Back to Top](#content-outlines)** + +Range +----- +**Immutable and hashable sequence of integers.** +```python +<range> = range(stop) # range(to_exclusive) +<range> = range(start, stop) # range(from_inclusive, to_exclusive) +<range> = range(start, stop, ±step) # range(from_inclusive, to_exclusive, ±step_size) +``` + +```python +>>> [i for i in range(3)] +[0, 1, 2] +``` + +**[🔼Back to Top](#content-outlines)** + +Enumerate +--------- +```python +for i, el in enumerate(<collection> [, i_start]): + ... +``` + +**[🔼Back to Top](#content-outlines)** + +Iterator +-------- +```python +<iter> = iter(<collection>) # `iter(<iter>)` returns unmodified iterator. +<iter> = iter(<function>, to_exclusive) # A sequence of return values until 'to_exclusive'. +<el> = next(<iter> [, default]) # Raises StopIteration or returns 'default' on end. +<list> = list(<iter>) # Returns a list of iterator's remaining elements. +``` + +### Itertools +```python +import itertools as it +``` + +```python +<iter> = it.count(start=0, step=1) # Returns updated value endlessly. Accepts floats. +<iter> = it.repeat(<el> [, times]) # Returns element endlessly or 'times' times. +<iter> = it.cycle(<collection>) # Repeats the sequence endlessly. +``` + +```python +<iter> = it.chain(<coll>, <coll> [, ...]) # Empties collections in order (figuratively). +<iter> = it.chain.from_iterable(<coll>) # Empties collections inside a collection in order. +``` + +```python +<iter> = it.islice(<coll>, to_exclusive) # Only returns first 'to_exclusive' elements. +<iter> = it.islice(<coll>, from_inc, …) # `to_exclusive, +step_size`. Indices can be None. +``` + +**[🔼Back to Top](#content-outlines)** + +Generator +--------- +* **Any function that contains a yield statement returns a generator.** +* **Generators and iterators are interchangeable.** + +```python +def count(start, step): + while True: + yield start + start += step +``` + +```python +>>> counter = count(10, 2) +>>> next(counter), next(counter), next(counter) +(10, 12, 14) +``` + +**[🔼Back to Top](#content-outlines)** + +Type +---- +* **Everything is an object.** +* **Every object has a type.** +* **Type and class are synonymous.** + +```python +<type> = type(<el>) # Or: <el>.__class__ +<bool> = isinstance(<el>, <type>) # Or: issubclass(type(<el>), <type>) +``` + +```python +>>> type('a'), 'a'.__class__, str +(<class 'str'>, <class 'str'>, <class 'str'>) +``` + +#### Some types do not have built-in names, so they must be imported: +```python +from types import FunctionType, MethodType, LambdaType, GeneratorType, ModuleType +``` + +### Abstract Base Classes +**Each abstract base class specifies a set of virtual subclasses. These classes are then recognized by isinstance() and issubclass() as subclasses of the ABC, although they are really not. ABC can also manually decide whether or not a specific class is its virtual subclass, usually based on which methods the class has implemented. For instance, Iterable ABC looks for method iter(), while Collection ABC looks for iter(), contains() and len().** + +```python +>>> from collections.abc import Iterable, Collection, Sequence +>>> isinstance([1, 2, 3], Iterable) +True +``` + +```text ++------------------+------------+------------+------------+ +| | Iterable | Collection | Sequence | ++------------------+------------+------------+------------+ +| list, range, str | yes | yes | yes | +| dict, set | yes | yes | | +| iter | yes | | | ++------------------+------------+------------+------------+ +``` + +```python +>>> from numbers import Number, Complex, Real, Rational, Integral +>>> isinstance(123, Number) +True +``` + +```text ++--------------------+----------+----------+----------+----------+----------+ +| | Number | Complex | Real | Rational | Integral | ++--------------------+----------+----------+----------+----------+----------+ +| int | yes | yes | yes | yes | yes | +| fractions.Fraction | yes | yes | yes | yes | | +| float | yes | yes | yes | | | +| complex | yes | yes | | | | +| decimal.Decimal | yes | | | | | ++--------------------+----------+----------+----------+----------+----------+ +``` + +**[🔼Back to Top](#content-outlines)** + +String +------ +```python +<str> = <str>.strip() # Strips all whitespace characters from both ends. +<str> = <str>.strip('<chars>') # Strips all passed characters from both ends. +``` + +```python +<list> = <str>.split() # Splits on one or more whitespace characters. +<list> = <str>.split(sep=None, maxsplit=-1) # Splits on 'sep' str at most 'maxsplit' times. +<list> = <str>.splitlines(keepends=False) # On [\n\r\f\v\x1c-\x1e\x85\u2028\u2029] and \r\n. +<str> = <str>.join(<coll_of_strings>) # Joins elements using string as a separator. +``` + +```python +<bool> = <sub_str> in <str> # Checks if string contains a substring. +<bool> = <str>.startswith(<sub_str>) # Pass tuple of strings for multiple options. +<bool> = <str>.endswith(<sub_str>) # Pass tuple of strings for multiple options. +<int> = <str>.find(<sub_str>) # Returns start index of the first match or -1. +<int> = <str>.index(<sub_str>) # Same, but raises ValueError if missing. +``` + +```python +<str> = <str>.replace(old, new [, count]) # Replaces 'old' with 'new' at most 'count' times. +<str> = <str>.translate(<table>) # Use `str.maketrans(<dict>)` to generate table. +``` + +```python +<str> = chr(<int>) # Converts int to Unicode character. +<int> = ord(<str>) # Converts Unicode character to int. +``` +* **Also: `'lstrip()'`, `'rstrip()'` and `'rsplit()'`.** +* **Also: `'lower()'`, `'upper()'`, `'capitalize()'` and `'title()'`.** + +### Property Methods +```text ++---------------+----------+----------+----------+----------+----------+ +| | [ !#$%…] | [a-zA-Z] | [¼½¾] | [²³¹] | [0-9] | ++---------------+----------+----------+----------+----------+----------+ +| isprintable() | yes | yes | yes | yes | yes | +| isalnum() | | yes | yes | yes | yes | +| isnumeric() | | | yes | yes | yes | +| isdigit() | | | | yes | yes | +| isdecimal() | | | | | yes | ++---------------+----------+----------+----------+----------+----------+ +``` +* **Also: `'isspace()'` checks for `'[ \t\n\r\f\v\x1c-\x1f\x85\u2000…]'`.** + +**[🔼Back to Top](#content-outlines)** + +Regex +----- +```python +import re +<str> = re.sub(<regex>, new, text, count=0) # Substitutes all occurrences with 'new'. +<list> = re.findall(<regex>, text) # Returns all occurrences as strings. +<list> = re.split(<regex>, text, maxsplit=0) # Use brackets in regex to include the matches. +<Match> = re.search(<regex>, text) # Searches for first occurrence of the pattern. +<Match> = re.match(<regex>, text) # Searches only at the beginning of the text. +<iter> = re.finditer(<regex>, text) # Returns all occurrences as Match objects. +``` + +* **Argument 'new' can be a function that accepts a Match object and returns a string.** +* **Search() and match() return None if they can't find a match.** +* **Argument `'flags=re.IGNORECASE'` can be used with all functions.** +* **Argument `'flags=re.MULTILINE'` makes `'^'` and `'$'` match the start/end of each line.** +* **Argument `'flags=re.DOTALL'` makes dot also accept the `'\n'`.** +* **Use `r'\1'` or `'\\1'` for backreference (`'\1'` returns a character with octal code 1).** +* **Add `'?'` after `'*'` and `'+'` to make them non-greedy.** + +### Match Object +```python +<str> = <Match>.group() # Returns the whole match. Also group(0). +<str> = <Match>.group(1) # Returns part in the first bracket. +<tuple> = <Match>.groups() # Returns all bracketed parts. +<int> = <Match>.start() # Returns start index of the match. +<int> = <Match>.end() # Returns exclusive end index of the match. +``` + +### Special Sequences +```python +'\d' == '[0-9]' # Matches decimal characters. +'\w' == '[a-zA-Z0-9_]' # Matches alphanumerics and underscore. +'\s' == '[ \t\n\r\f\v]' # Matches whitespaces. +``` + +* **By default, decimal characters, alphanumerics and whitespaces from all alphabets are matched unless `'flags=re.ASCII'` argument is used.** +* **As shown above, it restricts all special sequence matches to the first 128 characters and prevents `'\s'` from accepting `'[\x1c-\x1f]'` (the so-called separator characters).** +* **Use a capital letter for negation (all non-ASCII characters will be matched when used in combination with ASCII flag).** + +**[🔼Back to Top](#content-outlines)** + +Format +------ +```python +<str> = f'{<el_1>}, {<el_2>}' # Curly brackets can also contain expressions. This is named "Fstring" +<str> = '{}, {}'.format(<el_1>, <el_2>) # Or: '{0}, {a}'.format(<el_1>, a=<el_2>) +<str> = '%s, %s' % (<el_1>, <el_2>) # Redundant and inferior C style formatting. +``` + +### Attributes +```python +>>> Person = collections.namedtuple('Person', 'name height') +>>> person = Person('Jean-Luc', 187) +>>> f'{person.height}' +'187' +>>> '{p.height}'.format(p=person) +'187' +``` + +### General Options +```python +{<el>:<10} # '<el> ' +{<el>:^10} # ' <el> ' +{<el>:>10} # ' <el>' +{<el>:.<10} # '<el>......' +{<el>:0} # '<el>' +``` +* **Options can be generated dynamically: `f'{<el>:{<str/int>}[…]}'`.** +* **Adding `'!r'` before the colon converts object to string by calling its [repr()](#class) method.** + +### Strings +```python +{'abcde':10} # 'abcde ' +{'abcde':10.3} # 'abc ' +{'abcde':.3} # 'abc' +{'abcde'!r:10} # "'abcde' " +``` + +### Numbers +```python +{123456:10} # ' 123456' +{123456:10,} # ' 123,456' +{123456:10_} # ' 123_456' +{123456:+10} # ' +123456' +{123456:=+10} # '+ 123456' +{123456: } # ' 123456' +{-123456: } # '-123456' +``` + +### Floats +```python +{1.23456:10.3} # ' 1.23' +{1.23456:10.3f} # ' 1.235' +{1.23456:10.3e} # ' 1.235e+00' +{1.23456:10.3%} # ' 123.456%' +``` + +#### Comparison of presentation types: +```text ++--------------+----------------+----------------+----------------+----------------+ +| | {<float>} | {<float>:f} | {<float>:e} | {<float>:%} | ++--------------+----------------+----------------+----------------+----------------+ +| 0.000056789 | '5.6789e-05' | '0.000057' | '5.678900e-05' | '0.005679%' | +| 0.00056789 | '0.00056789' | '0.000568' | '5.678900e-04' | '0.056789%' | +| 0.0056789 | '0.0056789' | '0.005679' | '5.678900e-03' | '0.567890%' | +| 0.056789 | '0.056789' | '0.056789' | '5.678900e-02' | '5.678900%' | +| 0.56789 | '0.56789' | '0.567890' | '5.678900e-01' | '56.789000%' | +| 5.6789 | '5.6789' | '5.678900' | '5.678900e+00' | '567.890000%' | +| 56.789 | '56.789' | '56.789000' | '5.678900e+01' | '5678.900000%' | ++--------------+----------------+----------------+----------------+----------------+ +``` + +```text ++--------------+----------------+----------------+----------------+----------------+ +| | {<float>:.2} | {<float>:.2f} | {<float>:.2e} | {<float>:.2%} | ++--------------+----------------+----------------+----------------+----------------+ +| 0.000056789 | '5.7e-05' | '0.00' | '5.68e-05' | '0.01%' | +| 0.00056789 | '0.00057' | '0.00' | '5.68e-04' | '0.06%' | +| 0.0056789 | '0.0057' | '0.01' | '5.68e-03' | '0.57%' | +| 0.056789 | '0.057' | '0.06' | '5.68e-02' | '5.68%' | +| 0.56789 | '0.57' | '0.57' | '5.68e-01' | '56.79%' | +| 5.6789 | '5.7' | '5.68' | '5.68e+00' | '567.89%' | +| 56.789 | '5.7e+01' | '56.79' | '5.68e+01' | '5678.90%' | ++--------------+----------------+----------------+----------------+----------------+ +``` +* **When both rounding up and rounding down are possible, the one that returns result with even last digit is chosen. That makes `'{6.5:.0f}'` a `'6'` and `'{7.5:.0f}'` an `'8'`.** +* **This rule only effects numbers that can be represented exactly by a float (`.5`, `.25`, …).** + +### Ints +```python +{90:c} # 'Z' +{90:b} # '1011010' +{90:X} # '5A' +``` + +**[🔼Back to Top](#content-outlines)** + +Numbers +------- +```python +<int> = int(<float/str/bool>) # Or: math.floor(<float>) +<float> = float(<int/str/bool>) # Or: <real>e±<int> +<complex> = complex(real=0, imag=0) # Or: <real> ± <real>j +<Fraction> = fractions.Fraction(0, 1) # Or: Fraction(numerator=0, denominator=1) +<Decimal> = decimal.Decimal(<str/int>) # Or: Decimal((sign, digits, exponent)) +``` +* **`'int(<str>)'` and `'float(<str>)'` raise ValueError on malformed strings.** +* **Decimal numbers are stored exactly, unlike most floats where `'1.1 + 2.2 != 3.3'`.** +* **Floats can be compared with: `'math.isclose(<float>, <float>)'`.** +* **Precision of decimal operations is set with: `'decimal.getcontext().prec = <int>'`.** + +### Basic Functions +```python +<num> = pow(<num>, <num>) # Or: <num> ** <num> +<num> = abs(<num>) # <float> = abs(<complex>) +<num> = round(<num> [, ±ndigits]) # `round(126, -1) == 130` +``` + +### Math +```python +from math import e, pi, inf, nan, isinf, isnan # `<el> == nan` is always False. +from math import sin, cos, tan, asin, acos, atan # Also: degrees, radians. +from math import log, log10, log2 # Log can accept base as second arg. +``` + +### Statistics +```python +from statistics import mean, median, variance # Also: stdev, quantiles, groupby. +``` + +### Random +```python +from random import random, randint, choice # Also shuffle, gauss, triangular, seed. +<float> = random() # A float inside [0, 1). +<int> = randint(from_inc, to_inc) # An int inside [from_inc, to_inc]. +<el> = choice(<sequence>) # Keeps the sequence intact. +``` + +### Bin, Hex +```python +<int> = ±0b<bin> # Or: ±0x<hex> +<int> = int('±<bin>', 2) # Or: int('±<hex>', 16) +<int> = int('±0b<bin>', 0) # Or: int('±0x<hex>', 0) +<str> = bin(<int>) # Returns '[-]0b<bin>'. +``` + +### Bitwise Operators +```python +<int> = <int> & <int> # And (0b1100 & 0b1010 == 0b1000). +<int> = <int> | <int> # Or (0b1100 | 0b1010 == 0b1110). +<int> = <int> ^ <int> # Xor (0b1100 ^ 0b1010 == 0b0110). +<int> = <int> << n_bits # Left shift. Use >> for right. +<int> = ~<int> # Not. Also -<int> - 1. +``` + +**[🔼Back to Top](#content-outlines)** + +Combinatorics +------------- +* **Every function returns an iterator.** +* **If you want to print the iterator, you need to pass it to the list() function first!** + +```python +import itertools as it +``` + +```python +>>> it.product([0, 1], repeat=3) +[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), + (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)] +``` + +```python +>>> it.product('abc', 'abc') # a b c +[('a', 'a'), ('a', 'b'), ('a', 'c'), # a x x x + ('b', 'a'), ('b', 'b'), ('b', 'c'), # b x x x + ('c', 'a'), ('c', 'b'), ('c', 'c')] # c x x x +``` + +```python +>>> it.combinations('abc', 2) # a b c +[('a', 'b'), ('a', 'c'), # a . x x + ('b', 'c')] # b . . x +``` + +```python +>>> it.combinations_with_replacement('abc', 2) # a b c +[('a', 'a'), ('a', 'b'), ('a', 'c'), # a x x x + ('b', 'b'), ('b', 'c'), # b . x x + ('c', 'c')] # c . . x +``` + +```python +>>> it.permutations('abc', 2) # a b c +[('a', 'b'), ('a', 'c'), # a . x x + ('b', 'a'), ('b', 'c'), # b x . x + ('c', 'a'), ('c', 'b')] # c x x . +``` + +**[🔼Back to Top](#content-outlines)** + +Datetime +-------- +* **Module 'datetime' provides 'date' `<D>`, 'time' `<T>`, 'datetime' `<DT>` and 'timedelta' `<TD>` classes. All are immutable and hashable.** +* **Time and datetime objects can be 'aware' `<a>`, meaning they have defined timezone, or 'naive' `<n>`, meaning they don't.** +* **If object is naive, it is presumed to be in the system's timezone.** + +```python +from datetime import date, time, datetime, timedelta +from dateutil.tz import UTC, tzlocal, gettz, datetime_exists, resolve_imaginary +``` + +### Constructors +```python +<D> = date(year, month, day) # Only accepts valid dates from 1 to 9999 AD. +<T> = time(hour=0, minute=0, second=0) # Also: `microsecond=0, tzinfo=None, fold=0`. +<DT> = datetime(year, month, day, hour=0) # Also: `minute=0, second=0, microsecond=0, …`. +<TD> = timedelta(weeks=0, days=0, hours=0) # Also: `minutes=0, seconds=0, microsecond=0`. +``` +* **Use `'<D/DT>.weekday()'` to get the day of the week as an int, with Monday being 0.** +* **`'fold=1'` means the second pass in case of time jumping back for one hour.** +* **Timedelta normalizes arguments to ±days, seconds (< 86 400) and microseconds (< 1M).** + +### Now +```python +<D/DTn> = D/DT.today() # Current local date or naive datetime. +<DTn> = DT.utcnow() # Naive datetime from current UTC time. +<DTa> = DT.now(<tzinfo>) # Aware datetime from current tz time. +``` +* **To extract time use `'<DTn>.time()'`, `'<DTa>.time()'` or `'<DTa>.timetz()'`.** + +### Timezone +```python +<tzinfo> = UTC # UTC timezone. London without DST. +<tzinfo> = tzlocal() # Local timezone. Also gettz(). +<tzinfo> = gettz('<Continent>/<City>') # 'Continent/City_Name' timezone or None. +<DTa> = <DT>.astimezone(<tzinfo>) # Datetime, converted to the passed timezone. +<Ta/DTa> = <T/DT>.replace(tzinfo=<tzinfo>) # Unconverted object with a new timezone. +``` + +### Encode +```python +<D/T/DT> = D/T/DT.fromisoformat('<iso>') # Object from ISO string. Raises ValueError. +<DT> = DT.strptime(<str>, '<format>') # Datetime from str, according to format. +<D/DTn> = D/DT.fromordinal(<int>) # D/DTn from days since the Gregorian NYE 1. +<DTn> = DT.fromtimestamp(<real>) # Local time DTn from seconds since the Epoch. +<DTa> = DT.fromtimestamp(<real>, <tz.>) # Aware datetime from seconds since the Epoch. +``` +* **ISO strings come in following forms: `'YYYY-MM-DD'`, `'HH:MM:SS.mmmuuu[±HH:MM]'`, or both separated by an arbitrary character. All parts following hours are optional.** +* **Python uses the Unix Epoch: `'1970-01-01 00:00 UTC'`, `'1970-01-01 01:00 CET'`, ...** + +### Decode +```python +<str> = <D/T/DT>.isoformat(sep='T') # Also: `timespec='auto/hours/minutes/seconds/…'`. +<str> = <D/T/DT>.strftime('<format>') # Custom string representation. +<int> = <D/DT>.toordinal() # Days since Gregorian NYE 1, ignoring time and tz. +<float> = <DTn>.timestamp() # Seconds since the Epoch, from DTn in local tz. +<float> = <DTa>.timestamp() # Seconds since the Epoch, from aware datetime. +``` + +### Format +```python +>>> dt = datetime.strptime('2015-05-14 23:39:00.00 +2000', '%Y-%m-%d %H:%M:%S.%f %z') +>>> dt.strftime("%A, %dth of %B '%y, %I:%M%p %Z") +"Thursday, 14th of May '15, 11:39PM UTC+02:00" +``` +* **`'%Z'` only accepts `'UTC/GMT'` and local timezone's code. `'%z'` also accepts `'±HH:MM'`.** +* **For abbreviated weekday and month use `'%a'` and `'%b'`.** + +### Arithmetics +```python +<D/DT> = <D/DT> ± <TD> # Returned datetime can fall into missing hour. +<TD> = <D/DTn> - <D/DTn> # Returns the difference, ignoring time jumps. +<TD> = <DTa> - <DTa> # Ignores time jumps if they share tzinfo object. +<TD> = <TD> * <real> # Also: <TD> = abs(<TD>) and <TD> = <TD> ±% <TD>. +<float> = <TD> / <TD> # How many weeks/years there are in TD. Also //. +``` + +**[🔼Back to Top](#content-outlines)** + +Arguments +--------- +### Inside Function Call +```python +func(<positional_args>) # func(0, 0) +func(<keyword_args>) # func(x=0, y=0) +func(<positional_args>, <keyword_args>) # func(0, y=0) +``` + +### Inside Function Definition +```python +def func(<nondefault_args>): ... # def func(x, y): ... +def func(<default_args>): ... # def func(x=0, y=0): ... +def func(<nondefault_args>, <default_args>): ... # def func(x, y=0): ... +``` +* **Default values are evaluated when function is first encountered in the scope.** +* **Any mutation of a mutable default value will persist between invocations.** + + +Splat Operator +-------------- +### Inside Function Call +**Splat expands a collection into positional arguments, while splatty-splat expands a dictionary into keyword arguments.** +```python +args = (1, 2) +kwargs = {'x': 3, 'y': 4, 'z': 5} +func(*args, **kwargs) +``` + +#### Is the same as: +```python +func(1, 2, x=3, y=4, z=5) +``` + +### Inside Function Definition +**Splat combines zero or more positional arguments into a tuple, while splatty-splat combines zero or more keyword arguments into a dictionary.** +```python +def add(*a): + return sum(a) +``` + +```python +>>> add(1, 2, 3) +6 +``` + +#### Legal argument combinations: +```python +def f(*args): ... # f(1, 2, 3) +def f(x, *args): ... # f(1, 2, 3) +def f(*args, z): ... # f(1, 2, z=3) +``` + +```python +def f(**kwargs): ... # f(x=1, y=2, z=3) +def f(x, **kwargs): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3) +``` + +```python +def f(*args, **kwargs): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3) | f(1, 2, z=3) | f(1, 2, 3) +def f(x, *args, **kwargs): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3) | f(1, 2, z=3) | f(1, 2, 3) +def f(*args, y, **kwargs): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3) +``` + +```python +def f(*, x, y, z): ... # f(x=1, y=2, z=3) +def f(x, *, y, z): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3) +def f(x, y, *, z): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3) | f(1, 2, z=3) +``` + +### Other Uses +```python +<list> = [*<coll.> [, ...]] # Or: list(<collection>) [+ ...] +<tuple> = (*<coll.>, [...]) # Or: tuple(<collection>) [+ ...] +<set> = {*<coll.> [, ...]} # Or: set(<collection>) [| ...] +<dict> = {**<dict> [, ...]} # Or: dict(**<dict> [, ...]) +``` + +```python +head, *body, tail = <coll.> # Head or tail can be omitted. +``` + +**[🔼Back to Top](#content-outlines)** + +Inline +------ +### Lambda +```python +<func> = lambda: <return_value> # A single statement function. +<func> = lambda <arg_1>, <arg_2>: <return_value> # Also accepts default arguments. +``` + +### Comprehensions +```python +<list> = [i+1 for i in range(10)] # Or: [1, 2, ..., 10] +<iter> = (i for i in range(10) if i > 5) # Or: iter([6, 7, 8, 9]) +<set> = {i+5 for i in range(10)} # Or: {5, 6, ..., 14} +<dict> = {i: i*2 for i in range(10)} # Or: {0: 0, 1: 2, ..., 9: 18} +``` + +```python +>>> [l+r for l in 'abc' for r in 'abc'] +['aa', 'ab', 'ac', ..., 'cc'] +``` + +### Map, Filter, Reduce +```python +<iter> = map(lambda x: x + 1, range(10)) # Or: iter([1, 2, ..., 10]) +<iter> = filter(lambda x: x > 5, range(10)) # Or: iter([6, 7, 8, 9]) +<obj> = reduce(lambda out, x: out + x, range(10)) # Or: 45 +``` +* **Reduce must be imported from the functools module.** + +### Any, All +```python +<bool> = any(<collection>) # Is `bool(el)` True for any element. +<bool> = all(<collection>) # Is True for all elements or empty. +``` + +### Conditional Expression +```python +<obj> = <exp> if <condition> else <exp> # Only one expression gets evaluated. +``` + +```python +>>> [a if a else 'zero' for a in (0, 1, 2, 3)] +['zero', 1, 2, 3] +``` + +### Named Tuple, Enum, Dataclass +```python +from collections import namedtuple +Point = namedtuple('Point', 'x y') # Creates a tuple's subclass. +point = Point(0, 0) # Returns its instance. +``` + +```python +from enum import Enum +Direction = Enum('Direction', 'n e s w') # Creates an enum. +direction = Direction.n # Returns its member. +``` + +```python +from dataclasses import make_dataclass +Player = make_dataclass('Player', ['loc', 'dir']) # Creates a class. +player = Player(point, direction) # Returns its instance. +``` + +**[🔼Back to Top](#content-outlines)** + +Imports +------- +```python +import <module> # Imports a built-in or '<module>.py'. +import <package> # Imports a built-in or '<package>/__init__.py'. +import <package>.<module> # Imports a built-in or '<package>/<module>.py'. +``` +* **Package is a collection of modules, but it can also define its own objects.** +* **On a filesystem this corresponds to a directory of Python files with an optional init script.** +* **Running `'import <package>'` does not automatically provide access to the package's modules unless they are explicitly imported in its init script.** + + +Closure +------- +**We have/get a closure in Python when:** +* **A nested function references a value of its enclosing function and then** +* **the enclosing function returns the nested function.** + +```python +def get_multiplier(a): + def out(b): + return a * b + return out +``` + +```python +>>> multiply_by_3 = get_multiplier(3) +>>> multiply_by_3(10) +30 +``` + +* **If multiple nested functions within enclosing function reference the same value, that value gets shared.** +* **To dynamically access function's first free variable use `'<function>.__closure__[0].cell_contents'`.** + +### Partial +```python +from functools import partial +<function> = partial(<function> [, <arg_1>, <arg_2>, ...]) +``` + +```python +>>> import operator as op +>>> multiply_by_3 = partial(op.mul, 3) +>>> multiply_by_3(10) +30 +``` +* **Partial is also useful in cases when function needs to be passed as an argument because it enables us to set its arguments beforehand.** +* **A few examples being: `'defaultdict(<function>)'`, `'iter(<function>, to_exclusive)'` and dataclass's `'field(default_factory=<function>)'`.** + +### Non-Local +**If variable is being assigned to anywhere in the scope, it is regarded as a local variable, unless it is declared as a 'global' or a 'nonlocal'.** + +```python +def get_counter(): + i = 0 + def out(): + nonlocal i + i += 1 + return i + return out +``` + +```python +>>> counter = get_counter() +>>> counter(), counter(), counter() +(1, 2, 3) +``` + +**[🔼Back to Top](#content-outlines)** + +Decorator +--------- +* **A decorator takes a function, adds some functionality and returns it.** +* **It can be any [callable](#callable), but is usually implemented as a function that returns a [closure](#closure).** + +```python +@decorator_name +def function_that_gets_passed_to_decorator(): + ... +``` + +### Debugger Example +**Decorator that prints function's name every time the function is called.** + +```python +from functools import wraps + +def debug(func): + @wraps(func) + def out(*args, **kwargs): + print(func.__name__) + return func(*args, **kwargs) + return out + +@debug +def add(x, y): + return x + y +``` +* **Wraps is a helper decorator that copies the metadata of the passed function (func) to the function it is wrapping (out).** +* **Without it `'add.__name__'` would return `'out'`.** + +### LRU Cache +**Decorator that caches function's return values. All function's arguments must be hashable.** + +```python +from functools import lru_cache + +@lru_cache(maxsize=None) +def fib(n): + return n if n < 2 else fib(n-2) + fib(n-1) +``` +* **Default size of the cache is 128 values. Passing `'maxsize=None'` makes it unbounded.** +* **CPython interpreter limits recursion depth to 1000 by default. To increase it use `'sys.setrecursionlimit(<depth>)'`.** + +### Parametrized Decorator +**A decorator that accepts arguments and returns a normal decorator that accepts a function.** +```python +from functools import wraps + +def debug(print_result=False): + def decorator(func): + @wraps(func) + def out(*args, **kwargs): + result = func(*args, **kwargs) + print(func.__name__, result if print_result else '') + return result + return out + return decorator + +@debug(print_result=True) +def add(x, y): + return x + y +``` +* **Using only `'@debug'` to decorate the add() function would not work here, because debug would then receive the add() function as a 'print_result' argument. Decorators can however manually check if the argument they received is a function and act accordingly.** + +**[🔼Back to Top](#content-outlines)** + +Class +----- +```python +class <name>: + def __init__(self, a): + self.a = a + def __repr__(self): + class_name = self.__class__.__name__ + return f'{class_name}({self.a!r})' + def __str__(self): + return str(self.a) + + @classmethod + def get_class_name(cls): + return cls.__name__ +``` +* **Return value of repr() should be unambiguous and of str() readable.** +* **If only repr() is defined, it will also be used for str().** +* **Methods decorated with `'@staticmethod'` do not receive 'self' nor 'cls' as their first arg.** + +#### Str() use cases: +```python +print(<el>) +f'{<el>}' +logging.warning(<el>) +csv.writer(<file>).writerow([<el>]) +raise Exception(<el>) +``` + +#### Repr() use cases: +```python +print/str/repr([<el>]) +f'{<el>!r}' +Z = dataclasses.make_dataclass('Z', ['a']); print/str/repr(Z(<el>)) +>>> <el> +``` + +### Constructor Overloading +```python +class <name>: + def __init__(self, a=None): + self.a = a +``` + +### Inheritance +```python +class Person: + def __init__(self, name, age): + self.name = name + self.age = age + +class Employee(Person): + def __init__(self, name, age, staff_num): + super().__init__(name, age) + self.staff_num = staff_num +``` + +### Multiple Inheritance +```python +class A: pass +class B: pass +class C(A, B): pass +``` + +**MRO determines the order in which parent classes are traversed when searching for a method or an attribute:** +```python +>>> C.mro() +[<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>] +``` + +### Property +**Pythonic way of implementing getters and setters.** +```python +class Person: + @property + def name(self): + return ' '.join(self._name) + + @name.setter + def name(self, value): + self._name = value.split() +``` + +```python +>>> person = Person() +>>> person.name = '\t Guido van Rossum \n' +>>> person.name +'Guido van Rossum' +``` + +### Dataclass +**Decorator that automatically generates init(), repr() and eq() special methods.** +```python +from dataclasses import dataclass, field + +@dataclass(order=False, frozen=False) +class <class_name>: + <attr_name_1>: <type> + <attr_name_2>: <type> = <default_value> + <attr_name_3>: list/dict/set = field(default_factory=list/dict/set) +``` +* **Objects can be made sortable with `'order=True'` and immutable with `'frozen=True'`.** +* **For object to be hashable, all attributes must be hashable and 'frozen' must be True.** +* **Function field() is needed because `'<attr_name>: list = []'` would make a list that is shared among all instances. Its 'default_factory' argument can be any [callable](#callable).** +* **For attributes of arbitrary type use `'typing.Any'`.** + +#### Inline: +```python +from dataclasses import make_dataclass +<class> = make_dataclass('<class_name>', <coll_of_attribute_names>) +<class> = make_dataclass('<class_name>', <coll_of_tuples>) +<tuple> = ('<attr_name>', <type> [, <default_value>]) +``` + +#### Rest of type annotations (CPython interpreter ignores them all): +```python +def func(<arg_name>: <type> [= <obj>]) -> <type>: ... +<var_name>: typing.List/Set/Iterable/Sequence/Optional[<type>] +<var_name>: typing.Dict/Tuple/Union[<type>, ...] +``` + +### Slots +**Mechanism that restricts objects to attributes listed in 'slots' and significantly reduces their memory footprint.** + +```python +class MyClassWithSlots: + __slots__ = ['a'] + def __init__(self): + self.a = 1 +``` + +### Copy +```python +from copy import copy, deepcopy +<object> = copy(<object>) +<object> = deepcopy(<object>) +``` + +**[🔼Back to Top](#content-outlines)** + +Duck Types +---------- +**A duck type is an implicit type that prescribes a set of special methods. Any object that has those methods defined is considered a member of that duck type.** + +### Comparable +* **If eq() method is not overridden, it returns `'id(self) == id(other)'`, which is the same as `'self is other'`.** +* **That means all objects compare not equal by default.** +* **Only the left side object has eq() method called, unless it returns NotImplemented, in which case the right object is consulted. False is returned if both return NotImplemented.** +* **Ne() automatically works on any object that has eq() defined.** + +```python +class MyComparable: + def __init__(self, a): + self.a = a + def __eq__(self, other): + if isinstance(other, type(self)): + return self.a == other.a + return NotImplemented +``` + +### Hashable +* **Hashable object needs both hash() and eq() methods and its hash value should never change.** +* **Hashable objects that compare equal must have the same hash value, meaning default hash() that returns `'id(self)'` will not do.** +* **That is why Python automatically makes classes unhashable if you only implement eq().** + +```python +class MyHashable: + def __init__(self, a): + self._a = a + @property + def a(self): + return self._a + def __eq__(self, other): + if isinstance(other, type(self)): + return self.a == other.a + return NotImplemented + def __hash__(self): + return hash(self.a) +``` + +### Sortable +* **With 'total_ordering' decorator, you only need to provide eq() and one of lt(), gt(), le() or ge() special methods and the rest will be automatically generated.** +* **Functions sorted() and min() only require lt() method, while max() only requires gt(). However, it is best to define them all so that confusion doesn't arise in other contexts.** +* **When two lists, strings or dataclasses are compared, their values get compared in order until a pair of unequal values is found. The comparison of this two values is then returned. The shorter sequence is considered smaller in case of all values being equal.** + +```python +from functools import total_ordering + +@total_ordering +class MySortable: + def __init__(self, a): + self.a = a + def __eq__(self, other): + if isinstance(other, type(self)): + return self.a == other.a + return NotImplemented + def __lt__(self, other): + if isinstance(other, type(self)): + return self.a < other.a + return NotImplemented +``` + +### Iterator +* **Any object that has methods next() and iter() is an iterator.** +* **Next() should return next item or raise StopIteration.** +* **Iter() should return 'self'.** +```python +class Counter: + def __init__(self): + self.i = 0 + def __next__(self): + self.i += 1 + return self.i + def __iter__(self): + return self +``` + +```python +>>> counter = Counter() +>>> next(counter), next(counter), next(counter) +(1, 2, 3) +``` + +#### Python has many different iterator objects: +* **Sequence iterators returned by the [iter()](#iterator) function, such as list\_iterator and set\_iterator.** +* **Objects returned by the [itertools](#itertools) module, such as count, repeat and cycle.** +* **Generators returned by the [generator functions](#generator) and [generator expressions](#comprehensions).** +* **File objects returned by the [open()](#open) function, etc.** + +### Callable +* **All functions and classes have a call() method, hence are callable.** +* **When this cheatsheet uses `'<function>'` as an argument, it actually means `'<callable>'`.** +```python +class Counter: + def __init__(self): + self.i = 0 + def __call__(self): + self.i += 1 + return self.i +``` + +```python +>>> counter = Counter() +>>> counter(), counter(), counter() +(1, 2, 3) +``` + +### Context Manager +* **Enter() should lock the resources and optionally return an object.** +* **Exit() should release the resources.** +* **Any exception that happens inside the with block is passed to the exit() method.** +* **If it wishes to suppress the exception it must return a true value.** +```python +class MyOpen: + def __init__(self, filename): + self.filename = filename + def __enter__(self): + self.file = open(self.filename) + return self.file + def __exit__(self, exc_type, exception, traceback): + self.file.close() +``` + +```python +>>> with open('test.txt', 'w') as file: +... file.write('Hello World!') +>>> with MyOpen('test.txt') as file: +... print(file.read()) +Hello World! +``` + + +Iterable Duck Types +------------------- +### Iterable +* **Only required method is iter(). It should return an iterator of object's items.** +* **Contains() automatically works on any object that has iter() defined.** +```python +class MyIterable: + def __init__(self, a): + self.a = a + def __iter__(self): + return iter(self.a) + def __contains__(self, el): + return el in self.a +``` + +```python +>>> obj = MyIterable([1, 2, 3]) +>>> [el for el in obj] +[1, 2, 3] +>>> 1 in obj +True +``` + +### Collection +* **Only required methods are iter() and len(). Len() should return the number of items.** +* **This cheatsheet actually means `'<iterable>'` when it uses `'<collection>'`.** +* **I chose not to use the name 'iterable' because it sounds scarier and more vague than 'collection'. The only drawback of this decision is that a reader could think a certain function doesn't accept iterators when it does, since iterators are the only built-in objects that are iterable but are not collections.** +```python +class MyCollection: + def __init__(self, a): + self.a = a + def __iter__(self): + return iter(self.a) + def __contains__(self, el): + return el in self.a + def __len__(self): + return len(self.a) +``` + +### Sequence +* **Only required methods are len() and getitem().** +* **Getitem() should return an item at the passed index or raise IndexError.** +* **Iter() and contains() automatically work on any object that has getitem() defined.** +* **Reversed() automatically works on any object that has len() and getitem() defined.** +```python +class MySequence: + def __init__(self, a): + self.a = a + def __iter__(self): + return iter(self.a) + def __contains__(self, el): + return el in self.a + def __len__(self): + return len(self.a) + def __getitem__(self, i): + return self.a[i] + def __reversed__(self): + return reversed(self.a) +``` + +#### Discrepancies between glossary definitions and abstract base classes: +* **Glossary defines iterable as any object with iter() or getitem() and sequence as any object with getitem() and len(). It does not define collection.** +* **Passing ABC Iterable to isinstance() or issubclass() checks whether object/class has method iter(), while ABC Collection checks for iter(), contains() and len().** + +### ABC Sequence +* **It's a richer interface than the basic sequence.** +* **Extending it generates iter(), contains(), reversed(), index() and count().** +* **Unlike `'abc.Iterable'` and `'abc.Collection'`, it is not a duck type. That is why `'issubclass(MySequence, abc.Sequence)'` would return False even if MySequence had all the methods defined. It however recognizes list, tuple, range, str, bytes, bytearray, memoryview and deque, because they are registered as Sequence's virtual subclasses.** +```python +from collections import abc + +class MyAbcSequence(abc.Sequence): + def __init__(self, a): + self.a = a + def __len__(self): + return len(self.a) + def __getitem__(self, i): + return self.a[i] +``` + +#### Table of required and automatically available special methods: +```text ++------------+------------+------------+------------+--------------+ +| | Iterable | Collection | Sequence | abc.Sequence | ++------------+------------+------------+------------+--------------+ +| iter() | REQ | REQ | Yes | Yes | +| contains() | Yes | Yes | Yes | Yes | +| len() | | REQ | REQ | REQ | +| getitem() | | | REQ | REQ | +| reversed() | | | Yes | Yes | +| index() | | | | Yes | +| count() | | | | Yes | ++------------+------------+------------+------------+--------------+ +``` +* **Other ABCs that generate missing methods are: MutableSequence, Set, MutableSet, Mapping and MutableMapping.** +* **Names of their required methods are stored in `'<abc>.__abstractmethods__'`.** + +**[🔼Back to Top](#content-outlines)** + +Enum +---- +```python +from enum import Enum, auto +``` + +```python +class <enum_name>(Enum): + <member_name_1> = <value_1> + <member_name_2> = <value_2_a>, <value_2_b> + <member_name_3> = auto() +``` +* **If there are no numeric values before auto(), it returns 1.** +* **Otherwise it returns an increment of the last numeric value.** + +```python +<member> = <enum>.<member_name> # Returns a member. +<member> = <enum>['<member_name>'] # Returns a member or raises KeyError. +<member> = <enum>(<value>) # Returns a member or raises ValueError. +<str> = <member>.name # Returns member's name. +<obj> = <member>.value # Returns member's value. +``` + +```python +list_of_members = list(<enum>) +member_names = [a.name for a in <enum>] +member_values = [a.value for a in <enum>] +random_member = random.choice(list(<enum>)) +``` + +```python +def get_next_member(member): + members = list(member.__class__) + index = (members.index(member) + 1) % len(members) + return members[index] +``` + +### Inline +```python +Cutlery = Enum('Cutlery', 'fork knife spoon') +Cutlery = Enum('Cutlery', ['fork', 'knife', 'spoon']) +Cutlery = Enum('Cutlery', {'fork': 1, 'knife': 2, 'spoon': 3}) +``` + +#### User-defined functions cannot be values, so they must be wrapped: +```python +from functools import partial +LogicOp = Enum('LogicOp', {'AND': partial(lambda l, r: l and r), + 'OR': partial(lambda l, r: l or r)}) +``` +* **Member names are in all caps because trying to access a member that is named after a reserved keyword raises SyntaxError.** + +**[🔼Back to Top](#content-outlines)** + +Exceptions +---------- +```python +try: + <code> +except <exception>: + <code> +``` + +### Complex Example +```python +try: + <code_1> +except <exception_a>: + <code_2_a> +except <exception_b>: + <code_2_b> +else: + <code_2_c> +finally: + <code_3> +``` +* **Code inside the `'else'` block will only be executed if `'try'` block had no exceptions.** +* **Code inside the `'finally'` block will always be executed (unless a signal is received).** + +### Catching Exceptions +```python +except <exception>: ... +except <exception> as <name>: ... +except (<exception>, [...]): ... +except (<exception>, [...]) as <name>: ... +``` +* **Also catches subclasses of the exception.** +* **Use `'traceback.print_exc()'` to print the error message to stderr.** +* **Use `'print(<name>)'` to print just the cause of the exception (its arguments).** +* **Use `'logging.exception(<message>)'` to log the exception.** + +### Raising Exceptions +```python +raise <exception> +raise <exception>() +raise <exception>(<el> [, ...]) +``` + +#### Re-raising caught exception: +```python +except <exception> as <name>: + ... + raise +``` + +### Exception Object +```python +arguments = <name>.args +exc_type = <name>.__class__ +filename = <name>.__traceback__.tb_frame.f_code.co_filename +func_name = <name>.__traceback__.tb_frame.f_code.co_name +line = linecache.getline(filename, <name>.__traceback__.tb_lineno) +traceback = ''.join(traceback.format_tb(<name>.__traceback__)) +error_msg = ''.join(traceback.format_exception(exc_type, <name>, <name>.__traceback__)) +``` + +### Built-in Exceptions +```text +BaseException + +-- SystemExit # Raised by the sys.exit() function. + +-- KeyboardInterrupt # Raised when the user hits the interrupt key (ctrl-c). + +-- Exception # User-defined exceptions should be derived from this class. + +-- ArithmeticError # Base class for arithmetic errors. + | +-- ZeroDivisionError # Raised when dividing by zero. + +-- AssertionError # Raised by `assert <exp>` if expression returns false value. + +-- AttributeError # Raised when an attribute is missing. + +-- EOFError # Raised by input() when it hits end-of-file condition. + +-- LookupError # Raised when a look-up on a collection fails. + | +-- IndexError # Raised when a sequence index is out of range. + | +-- KeyError # Raised when a dictionary key or set element is missing. + +-- MemoryError # Out of memory. Could be too late to start deleting vars. + +-- NameError # Raised when an object is missing. + +-- OSError # Errors such as “file not found” or “disk full” (see Open). + | +-- FileNotFoundError # When a file or directory is requested but doesn't exist. + +-- RuntimeError # Raised by errors that don't fall into other categories. + | +-- RecursionError # Raised when the maximum recursion depth is exceeded. + +-- StopIteration # Raised by next() when run on an empty iterator. + +-- TypeError # Raised when an argument is of wrong type. + +-- ValueError # When an argument is of right type but inappropriate value. + +-- UnicodeError # Raised when encoding/decoding strings to/from bytes fails. +``` + +#### Collections and their exceptions: +```text ++-----------+------------+------------+------------+ +| | List | Set | Dict | ++-----------+------------+------------+------------+ +| getitem() | IndexError | | KeyError | +| pop() | IndexError | KeyError | KeyError | +| remove() | ValueError | KeyError | | +| index() | ValueError | | | ++-----------+------------+------------+------------+ +``` + +#### Useful built-in exceptions: +```python +raise TypeError('Argument is of wrong type!') +raise ValueError('Argument is of right type but inappropriate value!') +raise RuntimeError('None of above!') +``` + +### User-defined Exceptions +```python +class MyError(Exception): pass +class MyInputError(MyError): pass +``` + +**[🔼Back to Top](#content-outlines)** + +Exit +---- +**Exits the interpreter by raising SystemExit exception.** +```python +import sys +sys.exit() # Exits with exit code 0 (success). +sys.exit(<el>) # Prints to stderr and exits with 1. +sys.exit(<int>) # Exits with passed exit code. +``` + +**[🔼Back to Top](#content-outlines)** + +Print +----- +```python +print(<el_1>, ..., sep=' ', end='\n', file=sys.stdout, flush=False) +``` +* **Use `'file=sys.stderr'` for messages about errors.** +* **Use `'flush=True'` to forcibly flush the stream.** + +### Pretty Print +```python +from pprint import pprint +pprint(<collection>, width=80, depth=None, compact=False, sort_dicts=True) +``` +* **Levels deeper than 'depth' get replaced by '...'.** + +**[🔼Back to Top](#content-outlines)** + +Input +----- +**Reads a line from user input or pipe if present.** + +```python +<str> = input(prompt=None) +``` +* **Trailing newline gets stripped.** +* **Prompt string is printed to the standard output before reading input.** +* **Raises EOFError when user hits EOF (ctrl-d/ctrl-z⏎) or input stream gets exhausted.** + +**[🔼Back to Top](#content-outlines)** + +Command Line Arguments +---------------------- +```python +import sys +scripts_path = sys.argv[0] +arguments = sys.argv[1:] +``` + +### Argument Parser +```python +from argparse import ArgumentParser, FileType +p = ArgumentParser(description=<str>) +p.add_argument('-<short_name>', '--<name>', action='store_true') # Flag. +p.add_argument('-<short_name>', '--<name>', type=<type>) # Option. +p.add_argument('<name>', type=<type>, nargs=1) # First argument. +p.add_argument('<name>', type=<type>, nargs='+') # Remaining arguments. +p.add_argument('<name>', type=<type>, nargs='*') # Optional arguments. +args = p.parse_args() # Exits on error. +value = args.<name> +``` + +* **Use `'help=<str>'` to set argument description that will be displayed in help message.** +* **Use `'default=<el>'` to set the default value.** +* **Use `'type=FileType(<mode>)'` for files. Accepts 'encoding', but 'newline' is None.** + +**[🔼Back to Top](#content-outlines)** + +Open +---- +**Opens the file and returns a corresponding file object.** + +```python +<file> = open(<path>, mode='r', encoding=None, newline=None) +``` +* **`'encoding=None'` means that the default encoding is used, which is platform dependent. Best practice is to use `'encoding="utf-8"'` whenever possible.** +* **`'newline=None'` means all different end of line combinations are converted to '\n' on read, while on write all '\n' characters are converted to system's default line separator.** +* **`'newline=""'` means no conversions take place, but input is still broken into chunks by readline() and readlines() on every '\n', '\r' and '\r\n'.** + +### Modes +* **`'r'` - Read (default).** +* **`'w'` - Write (truncate).** +* **`'x'` - Write or fail if the file already exists.** +* **`'a'` - Append.** +* **`'w+'` - Read and write (truncate).** +* **`'r+'` - Read and write from the start.** +* **`'a+'` - Read and write from the end.** +* **`'t'` - Text mode (default).** +* **`'b'` - Binary mode (`'br'`, `'bw'`, `'bx'`, …).** + +### Exceptions +* **`'FileNotFoundError'` can be raised when reading with `'r'` or `'r+'`.** +* **`'FileExistsError'` can be raised when writing with `'x'`.** +* **`'IsADirectoryError'` and `'PermissionError'` can be raised by any.** +* **`'OSError'` is the parent class of all listed exceptions.** + +### File Object +```python +<file>.seek(0) # Moves to the start of the file. +<file>.seek(offset) # Moves 'offset' chars/bytes from the start. +<file>.seek(0, 2) # Moves to the end of the file. +<bin_file>.seek(±offset, <anchor>) # Anchor: 0 start, 1 current position, 2 end. +``` + +```python +<str/bytes> = <file>.read(size=-1) # Reads 'size' chars/bytes or until EOF. +<str/bytes> = <file>.readline() # Returns a line or empty string/bytes on EOF. +<list> = <file>.readlines() # Returns a list of remaining lines. +<str/bytes> = next(<file>) # Returns a line using buffer. Do not mix. +``` + +```python +<file>.write(<str/bytes>) # Writes a string or bytes object. +<file>.writelines(<collection>) # Writes a coll. of strings or bytes objects. +<file>.flush() # Flushes write buffer. Runs every 4096/8192 B. +``` +* **Methods do not add or strip trailing newlines, even writelines().** + +### Read Text from File +```python +def read_file(filename): + with open(filename, encoding='utf-8') as file: + return file.readlines() +``` + +### Write Text to File +```python +def write_to_file(filename, text): + with open(filename, 'w', encoding='utf-8') as file: + file.write(text) +``` + +**[🔼Back to Top](#content-outlines)** + +Paths +----- +```python +from os import getcwd, path, listdir, scandir +from glob import glob +``` + +```python +<str> = getcwd() # Returns the current working directory. +<str> = path.join(<path>, ...) # Joins two or more pathname components. +<str> = path.abspath(<path>) # Returns absolute path. +``` + +```python +<str> = path.basename(<path>) # Returns final component of the path. +<str> = path.dirname(<path>) # Returns path without the final component. +<tup.> = path.splitext(<path>) # Splits on last period of the final component. +``` + +```python +<list> = listdir(path='.') # Returns filenames located at path. +<list> = glob('<pattern>') # Returns paths matching the wildcard pattern. +``` + +```python +<bool> = path.exists(<path>) # Or: <Path>.exists() +<bool> = path.isfile(<path>) # Or: <DirEntry/Path>.is_file() +<bool> = path.isdir(<path>) # Or: <DirEntry/Path>.is_dir() +``` + +```python +<stat> = os.stat(<path>) # Or: <DirEntry/Path>.stat() +<real> = <stat>.st_mtime/st_size/… # Modification time, size in bytes, … +``` + +### DirEntry +**Unlike listdir(), scandir() returns DirEntry objects that cache isfile, isdir and on Windows also stat information, thus significantly increasing the performance of code that requires it.** + +```python +<iter> = scandir(path='.') # Returns DirEntry objects located at path. +<str> = <DirEntry>.path # Returns whole path as a string. +<str> = <DirEntry>.name # Returns final component as a string. +<file> = open(<DirEntry>) # Opens the file and returns a file object. +``` + +### Path Object +```python +from pathlib import Path +``` + +```python +<Path> = Path(<path> [, ...]) # Accepts strings, Paths and DirEntry objects. +<Path> = <path> / <path> [/ ...] # First or second path must be a Path object. +``` + +```python +<Path> = Path() # Returns relative cwd. Also Path('.'). +<Path> = Path.cwd() # Returns absolute cwd. Also Path().resolve(). +<Path> = Path.home() # Returns user's home directory (absolute). +<Path> = Path(__file__).resolve() # Returns script's path if cwd wasn't changed. +``` + +```python +<Path> = <Path>.parent # Returns Path without the final component. +<str> = <Path>.name # Returns final component as a string. +<str> = <Path>.stem # Returns final component without extension. +<str> = <Path>.suffix # Returns final component's extension. +<tup.> = <Path>.parts # Returns all components as strings. +``` + +```python +<iter> = <Path>.iterdir() # Returns directory contents as Path objects. +<iter> = <Path>.glob('<pattern>') # Returns Paths matching the wildcard pattern. +``` + +```python +<str> = str(<Path>) # Returns path as a string. +<file> = open(<Path>) # Also <Path>.read/write_text/bytes(). +``` + +**[🔼Back to Top](#content-outlines)** + +OS Commands +----------- +```python +import os, shutil, subprocess +``` + +```python +os.chdir(<path>) # Changes the current working directory. +os.mkdir(<path>, mode=0o777) # Creates a directory. Permissions are in octal. +os.makedirs(<path>, mode=0o777) # Creates all path's dirs. Also: `exist_ok=False`. +``` + +```python +shutil.copy(from, to) # Copies the file. 'to' can exist or be a dir. +shutil.copytree(from, to) # Copies the directory. 'to' must not exist. +``` + + +```python +os.rename(from, to) # Renames/moves the file or directory. +os.replace(from, to) # Same, but overwrites 'to' if it exists. +``` + +```python +os.remove(<path>) # Deletes the file. +os.rmdir(<path>) # Deletes the empty directory. +shutil.rmtree(<path>) # Deletes the directory. +``` +* **Paths can be either strings, Paths or DirEntry objects.** +* **Functions report OS related errors by raising either OSError or one of its [subclasses](#exceptions-1).** + +### Shell Commands +```python +<pipe> = os.popen('<command>') # Executes command in sh/cmd. Returns its stdout pipe. +<str> = <pipe>.read(size=-1) # Reads 'size' chars or until EOF. Also readline/s(). +<int> = <pipe>.close() # Closes the pipe. Returns None on success. +``` + +#### Sends '1 + 1' to the basic calculator and captures its output: +```python +>>> subprocess.run('bc', input='1 + 1\n', capture_output=True, text=True) +CompletedProcess(args='bc', returncode=0, stdout='2\n', stderr='') +``` + +#### Sends test.in to the basic calculator running in standard mode and saves its output to test.out: +```python +>>> from shlex import split +>>> os.popen('echo 1 + 1 > test.in') +>>> subprocess.run(split('bc -s'), stdin=open('test.in'), stdout=open('test.out', 'w')) +CompletedProcess(args=['bc', '-s'], returncode=0) +>>> open('test.out').read() +'2\n' +``` + +**[🔼Back to Top](#content-outlines)** + +JSON +---- +**Text file format for storing collections of strings and numbers.** + +```python +import json +<str> = json.dumps(<object>) # Converts object to JSON string. +<object> = json.loads(<str>) # Converts JSON string to object. +``` + +### Read Object from JSON File +```python +def read_json_file(filename): + with open(filename, encoding='utf-8') as file: + return json.load(file) +``` + +### Write Object to JSON File +```python +def write_to_json_file(filename, an_object): + with open(filename, 'w', encoding='utf-8') as file: + json.dump(an_object, file, ensure_ascii=False, indent=2) +``` + +**[🔼Back to Top](#content-outlines)** + +Pickle +------ +**Binary file format for storing Python objects.** + +```python +import pickle +<bytes> = pickle.dumps(<object>) # Converts object to bytes object. +<object> = pickle.loads(<bytes>) # Converts bytes object to object. +``` + +### Read Object from File +```python +def read_pickle_file(filename): + with open(filename, 'rb') as file: + return pickle.load(file) +``` + +### Write Object to File +```python +def write_to_pickle_file(filename, an_object): + with open(filename, 'wb') as file: + pickle.dump(an_object, file) +``` + +**[🔼Back to Top](#content-outlines)** + +CSV +--- +**Text file format for storing spreadsheets.** + +```python +import csv +``` + +### Read +```python +<reader> = csv.reader(<file>) # Also: `dialect='excel', delimiter=','`. +<list> = next(<reader>) # Returns next row as a list of strings. +<list> = list(<reader>) # Returns a list of remaining rows. +``` +* **File must be opened with a `'newline=""'` argument, or newlines embedded inside quoted fields will not be interpreted correctly!** +* **To print the spreadsheet to the console use [Tabulate](#table) library.** +* **For XML and binary Excel files (xlsx, xlsm and xlsb) use [Pandas](#dataframe-plot-encode-decode) library.** + +### Write +```python +<writer> = csv.writer(<file>) # Also: `dialect='excel', delimiter=','`. +<writer>.writerow(<collection>) # Encodes objects using `str(<el>)`. +<writer>.writerows(<coll_of_coll>) # Appends multiple rows. +``` +* **File must be opened with a `'newline=""'` argument, or '\r' will be added in front of every '\n' on platforms that use '\r\n' line endings!** + +### Parameters +* **`'dialect'` - Master parameter that sets the default values. String or a Dialect object.** +* **`'delimiter'` - A one-character string used to separate fields.** +* **`'quotechar'` - Character for quoting fields that contain special characters.** +* **`'doublequote'` - Whether quotechars inside fields are/get doubled or escaped.** +* **`'skipinitialspace'` - Is space character at the start of the field stripped by the reader.** +* **`'lineterminator'` - How writer terminates rows. Reader is hardcoded to '\n', '\r', '\r\n'.** +* **`'quoting'` - 0: As necessary, 1: All, 2: All but numbers which are read as floats, 3: None.** +* **`'escapechar'` - Character for escaping quotechars if doublequote is False.** + +### Dialects +```text ++------------------+--------------+--------------+--------------+ +| | excel | excel-tab | unix | ++------------------+--------------+--------------+--------------+ +| delimiter | ',' | '\t' | ',' | +| quotechar | '"' | '"' | '"' | +| doublequote | True | True | True | +| skipinitialspace | False | False | False | +| lineterminator | '\r\n' | '\r\n' | '\n' | +| quoting | 0 | 0 | 1 | +| escapechar | None | None | None | ++------------------+--------------+--------------+--------------+ +``` + +### Read Rows from CSV File +```python +def read_csv_file(filename, dialect='excel'): + with open(filename, encoding='utf-8', newline='') as file: + return list(csv.reader(file, dialect)) +``` + +### Write Rows to CSV File +```python +def write_to_csv_file(filename, rows, dialect='excel'): + with open(filename, 'w', encoding='utf-8', newline='') as file: + writer = csv.writer(file, dialect) + writer.writerows(rows) +``` + +**[🔼Back to Top](#content-outlines)** + +SQLite +------ +**A server-less database engine that stores each database into a separate file.** + +```python +import sqlite3 +<conn> = sqlite3.connect(<path>) # Opens existing or new file. Also ':memory:'. +<conn>.close() # Closes the connection. +``` + +### Read +```python +<cursor> = <conn>.execute('<query>') # Can raise a subclass of sqlite3.Error. +<tuple> = <cursor>.fetchone() # Returns next row. Also next(<cursor>). +<list> = <cursor>.fetchall() # Returns remaining rows. Also list(<cursor>). +``` + +### Write +```python +<conn>.execute('<query>') # Can raise a subclass of sqlite3.Error. +<conn>.commit() # Saves all changes since the last commit. +<conn>.rollback() # Discards all changes since the last commit. +``` + +#### Or: +```python +with <conn>: # Exits the block with commit() or rollback(), + <conn>.execute('<query>') # depending on whether any exception occurred. +``` + +### Placeholders +```python +<conn>.execute('<query>', <list/tuple>) # Replaces '?'s in query with values. +<conn>.execute('<query>', <dict/namedtuple>) # Replaces ':<key>'s with values. +<conn>.executemany('<query>', <coll_of_above>) # Runs execute() multiple times. +``` +* **Passed values can be of type str, int, float, bytes, None, bool, datetime.date or datetime.datetime.** +* **Bools will be stored and returned as ints and dates as [ISO formatted strings](#encode).** + +### Example +**Values are not actually saved in this example because `'conn.commit()'` is omitted!** +```python +>>> conn = sqlite3.connect('test.db') +>>> conn.execute('CREATE TABLE person (person_id INTEGER PRIMARY KEY, name, height)') +>>> conn.execute('INSERT INTO person VALUES (NULL, ?, ?)', ('Jean-Luc', 187)).lastrowid +1 +>>> conn.execute('SELECT * FROM person').fetchall() +[(1, 'Jean-Luc', 187)] +``` + +### SqlAlchemy +```python +# $ pip3 install sqlalchemy +from sqlalchemy import create_engine, text +<engine> = create_engine('<url>').connect() # Url: 'dialect://user:password@host/dbname'. +<conn> = <engine>.connect() # Creates a connection. Also <conn>.close(). +<cursor> = <conn>.execute(text('<query>'), …) # Replaces ':<key>'s with keyword arguments. +with <conn>.begin(): ... # Exits the block with commit or rollback. +``` + +```text ++------------+--------------+-----------+-----------------------------------+ +| Dialects | pip3 install | import | Dependencies | ++------------+--------------+-----------+-----------------------------------+ +| mysql | mysqlclient | MySQLdb | www.pypi.org/project/mysqlclient | +| postgresql | psycopg2 | psycopg2 | www.psycopg.org/docs/install.html | +| mssql | pyodbc | pyodbc | apt install g++ unixodbc-dev | +| oracle | cx_oracle | cx_Oracle | Oracle Instant Client | ++------------+--------------+-----------+-----------------------------------+ +``` + +**[🔼Back to Top](#content-outlines)** + +Bytes +----- +**Bytes object is an immutable sequence of single bytes. Mutable version is called bytearray.** + +```python +<bytes> = b'<str>' # Only accepts ASCII characters and \x00-\xff. +<int> = <bytes>[<index>] # Returns an int in range from 0 to 255. +<bytes> = <bytes>[<slice>] # Returns bytes even if it has only one element. +<bytes> = <bytes>.join(<coll_of_bytes>) # Joins elements using bytes as a separator. +``` + +### Encode +```python +<bytes> = bytes(<coll_of_ints>) # Ints must be in range from 0 to 255. +<bytes> = bytes(<str>, 'utf-8') # Or: <str>.encode('utf-8') +<bytes> = <int>.to_bytes(n_bytes, …) # `byteorder='little/big', signed=False`. +<bytes> = bytes.fromhex('<hex>') # Hex pairs can be separated by whitespaces. +``` + +### Decode +```python +<list> = list(<bytes>) # Returns ints in range from 0 to 255. +<str> = str(<bytes>, 'utf-8') # Or: <bytes>.decode('utf-8') +<int> = int.from_bytes(<bytes>, …) # `byteorder='little/big', signed=False`. +'<hex>' = <bytes>.hex() # Returns hex pairs. Accepts `sep=<str>`. +``` + +### Read Bytes from File +```python +def read_bytes(filename): + with open(filename, 'rb') as file: + return file.read() +``` + +### Write Bytes to File +```python +def write_bytes(filename, bytes_obj): + with open(filename, 'wb') as file: + file.write(bytes_obj) +``` + +**[🔼Back to Top](#content-outlines)** + +Struct +------ +* **Module that performs conversions between a sequence of numbers and a bytes object.** +* **System’s type sizes, byte order, and alignment rules are used by default.** + +```python +from struct import pack, unpack +<bytes> = pack('<format>', <el_1> [, ...]) # Packages arguments into bytes object. +<tuple> = unpack('<format>', <bytes>) # Use iter_unpack() for iterator of tuples. +``` + +```python +>>> pack('>hhl', 1, 2, 3) +b'\x00\x01\x00\x02\x00\x00\x00\x03' +>>> unpack('>hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03') +(1, 2, 3) +``` + +### Format +#### For standard type sizes and manual alignment (padding) start format string with: +* **`'='` - System's byte order (usually little-endian).** +* **`'<'` - Little-endian.** +* **`'>'` - Big-endian (also `'!'`).** + +#### Besides numbers, pack() and unpack() also support bytes objects as part of the sequence: +* **`'c'` - A bytes object with a single element. For pad byte use `'x'`.** +* **`'<n>s'` - A bytes object with n elements.** + +#### Integer types. Use a capital letter for unsigned type. Minimum and standard sizes are in brackets: +* **`'b'` - char (1/1)** +* **`'h'` - short (2/2)** +* **`'i'` - int (2/4)** +* **`'l'` - long (4/4)** +* **`'q'` - long long (8/8)** + +#### Floating point types: +* **`'f'` - float (4/4)** +* **`'d'` - double (8/8)** + +**[🔼Back to Top](#content-outlines)** + +Array +----- +**List that can only hold numbers of a predefined type. Available types and their minimum sizes in bytes are listed above. Sizes and byte order are always determined by the system.** + +```python +from array import array +<array> = array('<typecode>', <collection>) # Array from collection of numbers. +<array> = array('<typecode>', <bytes>) # Array from bytes object. +<array> = array('<typecode>', <array>) # Treats array as a sequence of numbers. +<bytes> = bytes(<array>) # Or: <array>.tobytes() +<file>.write(<array>) # Writes array to the binary file. +``` + +**[🔼Back to Top](#content-outlines)** + +Memory View +----------- +* **A sequence object that points to the memory of another object.** +* **Each element can reference a single or multiple consecutive bytes, depending on format.** +* **Order and number of elements can be changed with slicing.** +* **Casting only works between char and other types and uses system's sizes.** +* **Byte order is always determined by the system.** + +```python +<mview> = memoryview(<bytes/bytearray/array>) # Immutable if bytes, else mutable. +<real> = <mview>[<index>] # Returns an int or a float. +<mview> = <mview>[<slice>] # Mview with rearranged elements. +<mview> = <mview>.cast('<typecode>') # Casts memoryview to the new format. +<mview>.release() # Releases the object's memory buffer. +``` + +### Decode +```python +<bytes> = bytes(<mview>) # Returns a new bytes object. +<bytes> = <bytes>.join(<coll_of_mviews>) # Joins mviews using bytes object as sep. +<array> = array('<typecode>', <mview>) # Treats mview as a sequence of numbers. +<file>.write(<mview>) # Writes mview to the binary file. +``` + +```python +<list> = list(<mview>) # Returns a list of ints or floats. +<str> = str(<mview>, 'utf-8') # Treats mview as a bytes object. +<int> = int.from_bytes(<mview>, …) # `byteorder='little/big', signed=False`. +'<hex>' = <mview>.hex() # Treats mview as a bytes object. +``` + +**[🔼Back to Top](#content-outlines)** + +Deque +----- +**A thread-safe list with efficient appends and pops from either side. Pronounced "deck".** + +```python +from collections import deque +<deque> = deque(<collection>, maxlen=None) +``` + +```python +<deque>.appendleft(<el>) # Opposite element is dropped if full. +<deque>.extendleft(<collection>) # Collection gets reversed. +<el> = <deque>.popleft() # Raises IndexError if empty. +<deque>.rotate(n=1) # Rotates elements to the right. +``` + +**[🔼Back to Top](#content-outlines)** + +Threading +--------- +* **CPython interpreter can only run a single thread at a time.** +* **That is why using multiple threads won't result in a faster execution, unless at least one of the threads contains an I/O operation.** +```python +from threading import Thread, RLock, Semaphore, Event, Barrier +from concurrent.futures import ThreadPoolExecutor +``` + +### Thread +```python +<Thread> = Thread(target=<function>) # Use `args=<collection>` to set the arguments. +<Thread>.start() # Starts the thread. +<bool> = <Thread>.is_alive() # Checks if the thread has finished executing. +<Thread>.join() # Waits for the thread to finish. +``` +* **Use `'kwargs=<dict>'` to pass keyword arguments to the function.** +* **Use `'daemon=True'`, or the program will not be able to exit while the thread is alive.** + +### Lock +```python +<lock> = RLock() # Lock that can only be released by acquirer. +<lock>.acquire() # Waits for the lock to be available. +<lock>.release() # Makes the lock available again. +``` + +#### Or: +```python +with <lock>: # Enters the block by calling acquire(), + ... # and exits it with release(). +``` + +### Semaphore, Event, Barrier +```python +<Semaphore> = Semaphore(value=1) # Lock that can be acquired by 'value' threads. +<Event> = Event() # Method wait() blocks until set() is called. +<Barrier> = Barrier(n_times) # Wait() blocks until it's called n_times. +``` + +### Thread Pool Executor +* **Object that manages thread execution.** +* **An object with the same interface called ProcessPoolExecutor provides true parallelism by running a separate interpreter in each process. All arguments must be [pickable](#pickle).** + +```python +<Exec> = ThreadPoolExecutor(max_workers=None) # Or: `with ThreadPoolExecutor() as <name>: …` +<Exec>.shutdown(wait=True) # Blocks until all threads finish executing. +``` + +```python +<iter> = <Exec>.map(<func>, <args_1>, ...) # A multithreaded and non-lazy map(). +<Futr> = <Exec>.submit(<func>, <arg_1>, ...) # Starts a thread and returns its Future object. +<bool> = <Futr>.done() # Checks if the thread has finished executing. +<obj> = <Futr>.result() # Waits for thread to finish and returns result. +``` + +### Queue +**A thread-safe FIFO queue. For LIFO queue use LifoQueue.** +```python +from queue import Queue +<Queue> = Queue(maxsize=0) +``` + +```python +<Queue>.put(<el>) # Blocks until queue stops being full. +<Queue>.put_nowait(<el>) # Raises queue.Full exception if full. +<el> = <Queue>.get() # Blocks until queue stops being empty. +<el> = <Queue>.get_nowait() # Raises queue.Empty exception if empty. +``` + +**[🔼Back to Top](#content-outlines)** + +Operator +-------- +**Module of functions that provide the functionality of operators.** +```python +import operator as op +<el> = op.add/sub/mul/truediv/floordiv/mod(<el>, <el>) # +, -, *, /, //, % +<int/set> = op.and_/or_/xor(<int/set>, <int/set>) # &, |, ^ +<bool> = op.eq/ne/lt/le/gt/ge(<sortable>, <sortable>) # ==, !=, <, <=, >, >= +<func> = op.itemgetter/attrgetter/methodcaller(<obj>) # [index/key], .name, .name() +``` + +```python +elementwise_sum = map(op.add, list_a, list_b) +sorted_by_second = sorted(<collection>, key=op.itemgetter(1)) +sorted_by_both = sorted(<collection>, key=op.itemgetter(1, 0)) +product_of_elems = functools.reduce(op.mul, <collection>) +union_of_sets = functools.reduce(op.or_, <coll_of_sets>) +first_element = op.methodcaller('pop', 0)(<list>) +``` +* **Binary operators require objects to have and(), or(), xor() and invert() special methods, unlike logical operators that work on all types of objects.** +* **Also: `'<bool> = <bool> &|^ <bool>'` and `'<int> = <bool> &|^ <int>'`.** + +**[🔼Back to Top](#content-outlines)** + +Introspection +------------- +**Inspecting code at runtime.** + +### Variables +```python +<list> = dir() # Names of local variables (incl. functions). +<dict> = vars() # Dict of local variables. Also locals(). +<dict> = globals() # Dict of global variables. +``` + +### Attributes +```python +<list> = dir(<object>) # Names of object's attributes (incl. methods). +<dict> = vars(<object>) # Dict of writable attributes. Also <obj>.__dict__. +<bool> = hasattr(<object>, '<attr_name>') # Checks if getattr() raises an AttributeError. +value = getattr(<object>, '<attr_name>') # Raises AttributeError if attribute is missing. +setattr(<object>, '<attr_name>', value) # Only works on objects with '__dict__' attribute. +delattr(<object>, '<attr_name>') # Same. Also `del <object>.<attr_name>`. +``` + +### Parameters +```python +<Sig> = inspect.signature(<function>) # Function's Signature object. +<dict> = <Sig>.parameters # Dict of Parameter objects. +<memb> = <Param>.kind # Member of ParameterKind enum. +<obj> = <Param>.default # Default value or <Param>.empty. +<type> = <Param>.annotation # Type or <Param>.empty. +``` + +**[🔼Back to Top](#content-outlines)** + +Metaprogramming +--------------- +**Code that generates code.** + +### Type +**Type is the root class. If only passed an object it returns its type (class). Otherwise it creates a new class.** + +```python +<class> = type('<class_name>', <tuple_of_parents>, <dict_of_class_attributes>) +``` + +```python +>>> Z = type('Z', (), {'a': 'abcde', 'b': 12345}) +>>> z = Z() +``` + +### Meta Class +**A class that creates classes.** + +```python +def my_meta_class(name, parents, attrs): + attrs['a'] = 'abcde' + return type(name, parents, attrs) +``` + +#### Or: +```python +class MyMetaClass(type): + def __new__(cls, name, parents, attrs): + attrs['a'] = 'abcde' + return type.__new__(cls, name, parents, attrs) +``` +* **New() is a class method that gets called before init(). If it returns an instance of its class, then that instance gets passed to init() as a 'self' argument.** +* **It receives the same arguments as init(), except for the first one that specifies the desired type of the returned instance (MyMetaClass in our case).** +* **Like in our case, new() can also be called directly, usually from a new() method of a child class (**`def __new__(cls): return super().__new__(cls)`**).** +* **The only difference between the examples above is that my\_meta\_class() returns a class of type type, while MyMetaClass() returns a class of type MyMetaClass.** + +### Metaclass Attribute +**Right before a class is created it checks if it has the 'metaclass' attribute defined. If not, it recursively checks if any of his parents has it defined and eventually comes to type().** + +```python +class MyClass(metaclass=MyMetaClass): + b = 12345 +``` + +```python +>>> MyClass.a, MyClass.b +('abcde', 12345) +``` + +### Type Diagram +```python +type(MyClass) == MyMetaClass # MyClass is an instance of MyMetaClass. +type(MyMetaClass) == type # MyMetaClass is an instance of type. +``` + +```text ++-------------+-------------+ +| Classes | Metaclasses | ++-------------+-------------| +| MyClass --> MyMetaClass | +| | v | +| object -----> type <+ | +| | ^ +--+ | +| str ----------+ | ++-------------+-------------+ +``` + +### Inheritance Diagram +```python +MyClass.__base__ == object # MyClass is a subclass of object. +MyMetaClass.__base__ == type # MyMetaClass is a subclass of type. +``` + +```text ++-------------+-------------+ +| Classes | Metaclasses | ++-------------+-------------| +| MyClass | MyMetaClass | +| v | v | +| object <----- type | +| ^ | | +| str | | ++-------------+-------------+ +``` + +**[🔼Back to Top](#content-outlines)** + +Eval +---- +```python +>>> from ast import literal_eval +>>> literal_eval('[1, 2, 3]') +[1, 2, 3] +>>> literal_eval('1 + 2') +ValueError: malformed node or string +``` + +**[🔼Back to Top](#content-outlines)** + +Coroutines +---------- +* **Coroutines have a lot in common with threads, but unlike threads, they only give up control when they call another coroutine and they don’t use as much memory.** +* **Coroutine definition starts with `'async'` and its call with `'await'`.** +* **`'asyncio.run(<coroutine>)'` is the main entry point for asynchronous programs.** +* **Functions wait(), gather() and as_completed() start multiple coroutines at the same time.** +* **Asyncio module also provides its own [Queue](#queue), [Event](#semaphore-event-barrier), [Lock](#lock) and [Semaphore](#semaphore-event-barrier) classes.** + +#### Runs a terminal game where you control an asterisk that must avoid numbers: + +```python +import asyncio, collections, curses, curses.textpad, enum, random + +P = collections.namedtuple('P', 'x y') # Position +D = enum.Enum('D', 'n e s w') # Direction +W, H = 15, 7 # Width, Height + +def main(screen): + curses.curs_set(0) # Makes cursor invisible. + screen.nodelay(True) # Makes getch() non-blocking. + asyncio.run(main_coroutine(screen)) # Starts running asyncio code. + +async def main_coroutine(screen): + moves = asyncio.Queue() + state = {'*': P(0, 0), **{id_: P(W//2, H//2) for id_ in range(10)}} + ai = [random_controller(id_, moves) for id_ in range(10)] + mvc = [human_controller(screen, moves), model(moves, state), view(state, screen)] + tasks = [asyncio.create_task(cor) for cor in ai + mvc] + await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) + +async def random_controller(id_, moves): + while True: + d = random.choice(list(D)) + moves.put_nowait((id_, d)) + await asyncio.sleep(random.triangular(0.01, 0.65)) + +async def human_controller(screen, moves): + while True: + ch = screen.getch() + key_mappings = {258: D.s, 259: D.n, 260: D.w, 261: D.e} + if ch in key_mappings: + moves.put_nowait(('*', key_mappings[ch])) + await asyncio.sleep(0.005) + +async def model(moves, state): + while state['*'] not in (state[id_] for id_ in range(10)): + id_, d = await moves.get() + x, y = state[id_] + deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)} + state[id_] = P((x + deltas[d].x) % W, (y + deltas[d].y) % H) + +async def view(state, screen): + offset = P(curses.COLS//2 - W//2, curses.LINES//2 - H//2) + while True: + screen.erase() + curses.textpad.rectangle(screen, offset.y-1, offset.x-1, offset.y+H, offset.x+W) + for id_, p in state.items(): + screen.addstr(offset.y + (p.y - state['*'].y + H//2) % H, + offset.x + (p.x - state['*'].x + W//2) % W, str(id_)) + await asyncio.sleep(0.005) + +if __name__ == '__main__': + curses.wrapper(main) +``` +<br> + +**[🔼Back to Top](#content-outlines)** + +Libraries +========= + +Progress Bar +------------ +```python +# $ pip3 install tqdm +>>> from tqdm import tqdm +>>> from time import sleep +>>> for el in tqdm([1, 2, 3], desc='Processing'): +... sleep(1) +Processing: 100%|████████████████████| 3/3 [00:03<00:00, 1.00s/it] +``` + +**[🔼Back to Top](#content-outlines)** + +Plot +---- +```python +# $ pip3 install matplotlib +import matplotlib.pyplot as plt +plt.plot(<x_data>, <y_data> [, label=<str>]) # Or: plt.plot(<y_data>) +plt.legend() # Adds a legend. +plt.savefig(<path>) # Saves the figure. +plt.show() # Displays the figure. +plt.clf() # Clears the figure. +``` + +**[🔼Back to Top](#content-outlines)** + +Table +----- +#### Prints a CSV file as an ASCII table: +```python +# $ pip3 install tabulate +import csv, tabulate +with open('test.csv', encoding='utf-8', newline='') as file: + rows = csv.reader(file) + header = next(rows) + table = tabulate.tabulate(rows, header) +print(table) +``` + +**[🔼Back to Top](#content-outlines)** + +Curses +------ +#### Runs a basic file explorer in the terminal: +```python +import curses, curses.ascii, os +from curses import A_REVERSE, KEY_DOWN, KEY_UP, KEY_LEFT, KEY_RIGHT, KEY_ENTER + +def main(screen): + ch, first, selected, paths = 0, 0, 0, os.listdir() + while ch != curses.ascii.ESC: + height, _ = screen.getmaxyx() + screen.erase() + for y, filename in enumerate(paths[first : first+height]): + screen.addstr(y, 0, filename, A_REVERSE * (selected == first + y)) + ch = screen.getch() + selected += (ch == KEY_DOWN) - (ch == KEY_UP) + selected = max(0, min(len(paths)-1, selected)) + first += (first <= selected - height) - (first > selected) + if ch in [KEY_LEFT, KEY_RIGHT, KEY_ENTER, 10, 13]: + new_dir = '..' if ch == KEY_LEFT else paths[selected] + if os.path.isdir(new_dir): + os.chdir(new_dir) + first, selected, paths = 0, 0, os.listdir() + +if __name__ == '__main__': + curses.wrapper(main) +``` + +**[🔼Back to Top](#content-outlines)** + +Logging +------- +```python +# $ pip3 install loguru +from loguru import logger +``` + +```python +logger.add('debug_{time}.log', colorize=True) # Connects a log file. +logger.add('error_{time}.log', level='ERROR') # Another file for errors or higher. +logger.<level>('A logging message.') # Logs to file/s and prints to stderr. +``` +* **Levels: `'debug'`, `'info'`, `'success'`, `'warning'`, `'error'`, `'critical'`.** + +### Exceptions +**Exception description, stack trace and values of variables are appended automatically.** + +```python +try: + ... +except <exception>: + logger.exception('An error happened.') +``` + +### Rotation +**Argument that sets a condition when a new log file is created.** +```python +rotation=<int>|<datetime.timedelta>|<datetime.time>|<str> +``` +* **`'<int>'` - Max file size in bytes.** +* **`'<timedelta>'` - Max age of a file.** +* **`'<time>'` - Time of day.** +* **`'<str>'` - Any of above as a string: `'100 MB'`, `'1 month'`, `'monday at 12:00'`, ...** + +### Retention +**Sets a condition which old log files get deleted.** +```python +retention=<int>|<datetime.timedelta>|<str> +``` +* **`'<int>'` - Max number of files.** +* **`'<timedelta>'` - Max age of a file.** +* **`'<str>'` - Max age as a string: `'1 week, 3 days'`, `'2 months'`, ...** + +**[🔼Back to Top](#content-outlines)** + +Scraping +-------- +#### Scrapes Python's URL, version number and logo from its Wikipedia page: +```python +# $ pip3 install requests beautifulsoup4 +import requests, bs4, os, sys + +WIKI_URL = 'https://en.wikipedia.org/wiki/Python_(programming_language)' +try: + html = requests.get(WIKI_URL).text + document = bs4.BeautifulSoup(html, 'html.parser') + table = document.find('table', class_='infobox vevent') + python_url = table.find('th', text='Website').next_sibling.a['href'] + version = table.find('th', text='Stable release').next_sibling.strings.__next__() + logo_url = table.find('img')['src'] + logo = requests.get(f'https:{logo_url}').content + filename = os.path.basename(logo_url) + with open(filename, 'wb') as file: + file.write(logo) + print(f'{python_url}, {version}, file://{os.path.abspath(filename)}') +except requests.exceptions.ConnectionError: + print("You've got problems with connection.", file=sys.stderr) +``` + +**[🔼Back to Top](#content-outlines)** + +Web +--- +```python +# $ pip3 install bottle +from bottle import run, route, static_file, template, post, request, response +import json +``` + +### Run +```python +run(host='localhost', port=8080) # Runs locally. +run(host='0.0.0.0', port=80) # Runs globally. +``` + +### Static Request +```python +@route('/img/<filename>') +def send_file(filename): + return static_file(filename, root='img_dir/') +``` + +### Dynamic Request +```python +@route('/<sport>') +def send_html(sport): + return template('<h1>{{title}}</h1>', title=sport) +``` + +### REST Request +```python +@post('/<sport>/odds') +def send_json(sport): + team = request.forms.get('team') + response.headers['Content-Type'] = 'application/json' + response.headers['Cache-Control'] = 'no-cache' + return json.dumps({'team': team, 'odds': [2.09, 3.74, 3.68]}) +``` + +#### Test: +```python +# $ pip3 install requests +>>> import threading, requests +>>> threading.Thread(target=run, daemon=True).start() +>>> url = 'http://localhost:8080/football/odds' +>>> request_data = {'team': 'arsenal f.c.'} +>>> response = requests.post(url, data=request_data) +>>> response.json() +{'team': 'arsenal f.c.', 'odds': [2.09, 3.74, 3.68]} +``` + +**[🔼Back to Top](#content-outlines)** + +Profiling +--------- +### Stopwatch +```python +from time import perf_counter +start_time = perf_counter() +... +duration_in_seconds = perf_counter() - start_time +``` + +### Timing a Snippet +```python +>>> from timeit import timeit +>>> timeit("''.join(str(i) for i in range(100))", +... number=10000, globals=globals(), setup='pass') +0.34986 +``` + +### Profiling by Line +```python +# $ pip3 install line_profiler memory_profiler +@profile +def main(): + a = [*range(10000)] + b = {*range(10000)} +main() +``` + +```text +$ kernprof -lv test.py +Line # Hits Time Per Hit % Time Line Contents +======================================================= + 1 @profile + 2 def main(): + 3 1 955.0 955.0 43.7 a = [*range(10000)] + 4 1 1231.0 1231.0 56.3 b = {*range(10000)} +``` + +```text +$ python3 -m memory_profiler test.py +Line # Mem usage Increment Line Contents +======================================================= + 1 37.668 MiB 37.668 MiB @profile + 2 def main(): + 3 38.012 MiB 0.344 MiB a = [*range(10000)] + 4 38.477 MiB 0.465 MiB b = {*range(10000)} +``` + +### Call Graph +#### Generates a PNG image of the call graph with highlighted bottlenecks: +```python +# $ pip3 install pycallgraph2; apt/brew install graphviz +import pycallgraph2 as cg, datetime + +filename = f'profile-{datetime.datetime.now():%Y%m%d_%H%M%S}.png' +drawer = cg.output.GraphvizOutput(output_file=filename) +with cg.PyCallGraph(drawer): + <code_to_be_profiled> +``` + +**[🔼Back to Top](#content-outlines)** + +NumPy +----- +**Array manipulation mini-language. It can run up to one hundred times faster than the equivalent Python code. An even faster alternative that runs on a GPU is called CuPy.** + +```python +# $ pip3 install numpy +import numpy as np +``` + +```python +<array> = np.array(<list/list_of_lists>) # Returns 1d/2d NumPy array. +<array> = np.zeros/ones(<shape>) # Also np.full(<shape>, <el>). +<array> = np.arange(from_inc, to_exc, ±step) # Also np.linspace(start, stop, num). +<array> = np.random.randint(from_inc, to_exc, <shape>) # Also np.random.random(<shape>). +``` + +```python +<view> = <array>.reshape(<shape>) # Also `<array>.shape = <shape>`. +<array> = <array>.flatten() # Collapses array into one dimension. +<view> = <array>.squeeze() # Removes dimensions of length one. +``` + +```python +<array> = <array>.sum/min/mean/var/std(axis) # Passed dimension gets aggregated. +<array> = <array>.argmin(axis) # Returns indexes of smallest elements. +<array> = np.apply_along_axis(<func>, axis, <array>) # Func can return a scalar or array. +``` + +* **Shape is a tuple of dimension sizes. A 100x50 RGB image has shape (50, 100, 3).** +* **Axis is an index of the dimension that gets aggregated. Leftmost dimension has index 0. Summing the RGB image along axis 2 will return a greyscale image with shape (50, 100).** +* **Passing a tuple of axes will chain the operations like this: `'<array>.<method>(axis_1, keepdims=True).<method>(axis_2).squeeze()'`.** + +### Indexing +```bash +<el> = <2d_array>[row_index, column_index] # <3d_a>[table_i, row_i, column_i] +<1d_view> = <2d_array>[row_index] # <3d_a>[table_i, row_i] +<1d_view> = <2d_array>[:, column_index] # <3d_a>[table_i, :, column_i] +``` + +```bash +<1d_array> = <2d_array>[row_indexes, column_indexes] # <3d_a>[table_is, row_is, column_is] +<2d_array> = <2d_array>[row_indexes] # <3d_a>[table_is, row_is] +<2d_array> = <2d_array>[:, column_indexes] # <3d_a>[table_is, :, column_is] +``` + +```bash +<2d_bools> = <2d_array> ><== <el> # <3d_array> ><== <1d_array> +<1d_array> = <2d_array>[<2d_bools>] # <3d_array>[<2d_bools>] +``` +* **All examples also allow assignments.** + +### Broadcasting +**Broadcasting is a set of rules by which NumPy functions operate on arrays of different sizes and/or dimensions.** + +```python +left = [[0.1], [0.6], [0.8]] # Shape: (3, 1) +right = [ 0.1 , 0.6 , 0.8 ] # Shape: (3,) +``` + +#### 1. If array shapes differ in length, left-pad the shorter shape with ones: +```python +left = [[0.1], [0.6], [0.8]] # Shape: (3, 1) +right = [[0.1 , 0.6 , 0.8]] # Shape: (1, 3) <- ! +``` + +#### 2. If any dimensions differ in size, expand the ones that have size 1 by duplicating their elements: +```python +left = [[0.1, 0.1, 0.1], # Shape: (3, 3) <- ! + [0.6, 0.6, 0.6], + [0.8, 0.8, 0.8]] + +right = [[0.1, 0.6, 0.8], # Shape: (3, 3) <- ! + [0.1, 0.6, 0.8], + [0.1, 0.6, 0.8]] +``` + +#### 3. If neither non-matching dimension has size 1, raise an error. + + +### Example +#### For each point returns index of its nearest point (`[0.1, 0.6, 0.8] => [1, 2, 1]`): + +```python +>>> points = np.array([0.1, 0.6, 0.8]) + [ 0.1, 0.6, 0.8] +>>> wrapped_points = points.reshape(3, 1) +[[ 0.1], + [ 0.6], + [ 0.8]] +>>> distances = wrapped_points - points +[[ 0. , -0.5, -0.7], + [ 0.5, 0. , -0.2], + [ 0.7, 0.2, 0. ]] +>>> distances = np.abs(distances) +[[ 0. , 0.5, 0.7], + [ 0.5, 0. , 0.2], + [ 0.7, 0.2, 0. ]] +>>> i = np.arange(3) +[0, 1, 2] +>>> distances[i, i] = np.inf +[[ inf, 0.5, 0.7], + [ 0.5, inf, 0.2], + [ 0.7, 0.2, inf]] +>>> distances.argmin(1) +[1, 2, 1] +``` + +**[🔼Back to Top](#content-outlines)** + +Image +----- +```python +# $ pip3 install pillow +from PIL import Image +``` + +```python +<Image> = Image.new('<mode>', (width, height)) # Also: `color=<int/tuple/str>`. +<Image> = Image.open(<path>) # Identifies format based on file contents. +<Image> = <Image>.convert('<mode>') # Converts image to the new mode. +<Image>.save(<path>) # Selects format based on the path extension. +<Image>.show() # Opens image in default preview app. +``` + +```python +<int/tuple> = <Image>.getpixel((x, y)) # Returns a pixel. +<Image>.putpixel((x, y), <int/tuple>) # Writes a pixel to the image. +<ImagingCore> = <Image>.getdata() # Returns a flattened sequence of pixels. +<Image>.putdata(<list/ImagingCore>) # Writes a flattened sequence of pixels. +<Image>.paste(<Image>, (x, y)) # Writes passed image to the image. +``` + +```bash +<2d_array> = np.array(<Image_L>) # Creates NumPy array from greyscale image. +<3d_array> = np.array(<Image_RGB/A>) # Creates NumPy array from color image. +<Image> = Image.fromarray(np.uint8(<array>)) # Use <array>.clip(0, 255) to clip the values. +``` + +### Modes +* **`'1'` - 1-bit pixels, black and white, stored with one pixel per byte.** +* **`'L'` - 8-bit pixels, greyscale.** +* **`'RGB'` - 3x8-bit pixels, true color.** +* **`'RGBA'` - 4x8-bit pixels, true color with transparency mask.** +* **`'HSV'` - 3x8-bit pixels, Hue, Saturation, Value color space.** + +### Examples +#### Creates a PNG image of a rainbow gradient: +```python +WIDTH, HEIGHT = 100, 100 +n_pixels = WIDTH * HEIGHT +hues = (255 * i/n_pixels for i in range(n_pixels)) +img = Image.new('HSV', (WIDTH, HEIGHT)) +img.putdata([(int(h), 255, 255) for h in hues]) +img.convert('RGB').save('test.png') +``` + +#### Adds noise to a PNG image: +```python +from random import randint +add_noise = lambda value: max(0, min(255, value + randint(-20, 20))) +img = Image.open('test.png').convert('HSV') +img.putdata([(add_noise(h), s, v) for h, s, v in img.getdata()]) +img.convert('RGB').save('test.png') +``` + +### Image Draw +```python +from PIL import ImageDraw +<ImageDraw> = ImageDraw.Draw(<Image>) +``` + +```python +<ImageDraw>.point((x, y)) # Truncates floats into ints. +<ImageDraw>.line((x1, y1, x2, y2 [, ...])) # To get anti-aliasing use Image's resize(). +<ImageDraw>.arc((x1, y1, x2, y2), deg1, deg2) # Always draws in clockwise direction. +<ImageDraw>.rectangle((x1, y1, x2, y2)) # To rotate use Image's rotate() and paste(). +<ImageDraw>.polygon((x1, y1, x2, y2, ...)) # Last point gets connected to the first. +<ImageDraw>.ellipse((x1, y1, x2, y2)) # To rotate use Image's rotate() and paste(). +``` +* **Use `'fill=<color>'` to set the primary color.** +* **Use `'width=<int>'` to set the width of lines or contours.** +* **Use `'outline=<color>'` to set the color of the contours.** +* **Color can be an int, tuple, `'#rrggbb[aa]'` string or a color name.** + + +Animation +--------- +#### Creates a GIF of a bouncing ball: +```python +# $ pip3 install imageio +from PIL import Image, ImageDraw +import imageio + +WIDTH, HEIGHT, R = 126, 126, 10 +frames = [] +for velocity in range(1, 16): + y = sum(range(velocity)) + frame = Image.new('L', (WIDTH, HEIGHT)) + draw = ImageDraw.Draw(frame) + draw.ellipse((WIDTH/2-R, y, WIDTH/2+R, y+R*2), fill='white') + frames.append(frame) +frames += reversed(frames[1:-1]) +imageio.mimsave('test.gif', frames, duration=0.03) +``` + +**[🔼Back to Top](#content-outlines)** + +Audio +----- +```python +import wave +``` + +```python +<Wave_read> = wave.open('<path>', 'rb') # Opens the WAV file. +framerate = <Wave_read>.getframerate() # Number of frames per second. +nchannels = <Wave_read>.getnchannels() # Number of samples per frame. +sampwidth = <Wave_read>.getsampwidth() # Sample size in bytes. +nframes = <Wave_read>.getnframes() # Number of frames. +<params> = <Wave_read>.getparams() # Immutable collection of above. +<bytes> = <Wave_read>.readframes(nframes) # Returns next 'nframes' frames. +``` + +```python +<Wave_write> = wave.open('<path>', 'wb') # Truncates existing file. +<Wave_write>.setframerate(<int>) # 44100 for CD, 48000 for video. +<Wave_write>.setnchannels(<int>) # 1 for mono, 2 for stereo. +<Wave_write>.setsampwidth(<int>) # 2 for CD quality sound. +<Wave_write>.setparams(<params>) # Sets all parameters. +<Wave_write>.writeframes(<bytes>) # Appends frames to the file. +``` +* **Bytes object contains a sequence of frames, each consisting of one or more samples.** +* **In a stereo signal, the first sample of a frame belongs to the left channel.** +* **Each sample consists of one or more bytes that, when converted to an integer, indicate the displacement of a speaker membrane at a given moment.** +* **If sample width is one byte, then the integer should be encoded unsigned.** +* **For all other sizes, the integer should be encoded signed with little-endian byte order.** + +### Sample Values +```text ++-----------+-----------+------+-----------+ +| sampwidth | min | zero | max | ++-----------+-----------+------+-----------+ +| 1 | 0 | 128 | 255 | +| 2 | -32768 | 0 | 32767 | +| 3 | -8388608 | 0 | 8388607 | ++-----------+-----------+------+-----------+ +``` + +### Read Float Samples from WAV File +```python +def read_wav_file(filename): + def get_int(bytes_obj): + an_int = int.from_bytes(bytes_obj, 'little', signed=(sampwidth != 1)) + return an_int - 128 * (sampwidth == 1) + with wave.open(filename, 'rb') as file: + sampwidth = file.getsampwidth() + frames = file.readframes(-1) + bytes_samples = (frames[i : i+sampwidth] for i in range(0, len(frames), sampwidth)) + return [get_int(b) / pow(2, sampwidth * 8 - 1) for b in bytes_samples] +``` + +### Write Float Samples to WAV File +```python +def write_to_wav_file(filename, float_samples, nchannels=1, sampwidth=2, framerate=44100): + def get_bytes(a_float): + a_float = max(-1, min(1 - 2e-16, a_float)) + a_float += sampwidth == 1 + a_float *= pow(2, sampwidth * 8 - 1) + return int(a_float).to_bytes(sampwidth, 'little', signed=(sampwidth != 1)) + with wave.open(filename, 'wb') as file: + file.setnchannels(nchannels) + file.setsampwidth(sampwidth) + file.setframerate(framerate) + file.writeframes(b''.join(get_bytes(f) for f in float_samples)) +``` + +### Examples +#### Saves a 440 Hz sine wave to a mono WAV file: +```python +from math import pi, sin +samples_f = (sin(i * 2 * pi * 440 / 44100) for i in range(100000)) +write_to_wav_file('test.wav', samples_f) +``` + +#### Adds noise to a mono WAV file: +```python +from random import random +add_noise = lambda value: value + (random() - 0.5) * 0.03 +samples_f = (add_noise(f) for f in read_wav_file('test.wav')) +write_to_wav_file('test.wav', samples_f) +``` + +#### Plays a WAV file: +```python +# $ pip3 install simpleaudio +from simpleaudio import play_buffer +with wave.open('test.wav', 'rb') as file: + p = file.getparams() + frames = file.readframes(-1) + play_buffer(frames, p.nchannels, p.sampwidth, p.framerate) +``` + +### Text to Speech +```python +# $ pip3 install pyttsx3 +import pyttsx3 +engine = pyttsx3.init() +engine.say('Sally sells seashells by the seashore.') +engine.runAndWait() +``` + + +Synthesizer +----------- +#### Plays Popcorn by Gershon Kingsley: +```python +# $ pip3 install simpleaudio +import itertools as it, math, struct, simpleaudio + +F = 44100 +P1 = '71♩,69♪,,71♩,66♪,,62♩,66♪,,59♩,,' +P2 = '71♩,73♪,,74♩,73♪,,74♪,,71♪,,73♩,71♪,,73♪,,69♪,,71♩,69♪,,71♪,,67♪,,71♩,,' +get_pause = lambda seconds: it.repeat(0, int(seconds * F)) +sin_f = lambda i, hz: math.sin(i * 2 * math.pi * hz / F) +get_wave = lambda hz, seconds: (sin_f(i, hz) for i in range(int(seconds * F))) +get_hz = lambda key: 8.176 * 2 ** (int(key) / 12) +parse_note = lambda note: (get_hz(note[:2]), 1/4 if '♩' in note else 1/8) +get_samples = lambda note: get_wave(*parse_note(note)) if note else get_pause(1/8) +samples_f = it.chain.from_iterable(get_samples(n) for n in f'{P1},{P1},{P2}'.split(',')) +samples_b = b''.join(struct.pack('<h', int(f * 30000)) for f in samples_f) +simpleaudio.play_buffer(samples_b, 1, 2, F) +``` + +**[🔼Back to Top](#content-outlines)** + +Pygame +------ +```python +# $ pip3 install pygame +import pygame as pg + +pg.init() +screen = pg.display.set_mode((500, 500)) +rect = pg.Rect(240, 240, 20, 20) +while all(event.type != pg.QUIT for event in pg.event.get()): + deltas = {pg.K_UP: (0, -1), pg.K_RIGHT: (1, 0), pg.K_DOWN: (0, 1), pg.K_LEFT: (-1, 0)} + for ch, is_pressed in enumerate(pg.key.get_pressed()): + rect = rect.move(deltas[ch]) if ch in deltas and is_pressed else rect + screen.fill((0, 0, 0)) + pg.draw.rect(screen, (255, 255, 255), rect) + pg.display.flip() +``` + +### Rectangle +**Object for storing rectangular coordinates.** +```python +<Rect> = pg.Rect(x, y, width, height) # Floats get truncated into ints. +<int> = <Rect>.x/y/centerx/centery/… # Top, right, bottom, left. Allows assignments. +<tup.> = <Rect>.topleft/center/… # Topright, bottomright, bottomleft. Same. +<Rect> = <Rect>.move((x, y)) # Use move_ip() to move in-place. +``` + +```python +<bool> = <Rect>.collidepoint((x, y)) # Checks if rectangle contains a point. +<bool> = <Rect>.colliderect(<Rect>) # Checks if two rectangles overlap. +<int> = <Rect>.collidelist(<list_of_Rect>) # Returns index of first colliding Rect or -1. +<list> = <Rect>.collidelistall(<list_of_Rect>) # Returns indexes of all colliding rectangles. +``` + +### Surface +**Object for representing images.** +```python +<Surf> = pg.display.set_mode((width, height)) # Returns a display surface. +<Surf> = pg.Surface((width, height)) # New RGB surface. RGBA if `flags=pg.SRCALPHA`. +<Surf> = pg.image.load('<path>') # Loads the image. Format depends on source. +<Surf> = <Surf>.subsurface(<Rect>) # Returns a subsurface. +``` + +```python +<Surf>.fill(color) # Tuple, Color('#rrggbb[aa]') or Color(<name>). +<Surf>.set_at((x, y), color) # Updates pixel. +<Surf>.blit(<Surf>, (x, y)) # Draws passed surface to the surface. +``` + +```python +from pygame.transform import scale, ... +<Surf> = scale(<Surf>, (width, height)) # Returns scaled surface. +<Surf> = rotate(<Surf>, anticlock_degrees) # Returns rotated and scaled surface. +<Surf> = flip(<Surf>, x_bool, y_bool) # Returns flipped surface. +``` + +```python +from pygame.draw import line, ... +line(<Surf>, color, (x1, y1), (x2, y2), width) # Draws a line to the surface. +arc(<Surf>, color, <Rect>, from_rad, to_rad) # Also: ellipse(<Surf>, color, <Rect>, width=0) +rect(<Surf>, color, <Rect>, width=0) # Also: polygon(<Surf>, color, points, width=0) +``` + +### Font +```python +<Font> = pg.font.SysFont('<name>', size) # Loads the system font or default if missing. +<Font> = pg.font.Font('<path>', size) # Loads the TTF file. Pass None for default. +<Surf> = <Font>.render(text, antialias, color) # Background color can be specified at the end. +``` + +### Sound +```python +<Sound> = pg.mixer.Sound('<path>') # Loads the WAV file. +<Sound>.play() # Starts playing the sound. +``` + +### Basic Mario Brothers Example +```python +import collections, dataclasses, enum, io, itertools as it, pygame as pg, urllib.request +from random import randint + +P = collections.namedtuple('P', 'x y') # Position +D = enum.Enum('D', 'n e s w') # Direction +W, H, MAX_S = 50, 50, P(5, 10) # Width, Height, Max speed + +def main(): + def get_screen(): + pg.init() + return pg.display.set_mode((W*16, H*16)) + def get_images(): + url = 'https://gto76.github.io/python-cheatsheet/web/mario_bros.png' + img = pg.image.load(io.BytesIO(urllib.request.urlopen(url).read())) + return [img.subsurface(get_rect(x, 0)) for x in range(img.get_width() // 16)] + def get_mario(): + Mario = dataclasses.make_dataclass('Mario', 'rect spd facing_left frame_cycle'.split()) + return Mario(get_rect(1, 1), P(0, 0), False, it.cycle(range(3))) + def get_tiles(): + border = [(x, y) for x in range(W) for y in range(H) if x in [0, W-1] or y in [0, H-1]] + platforms = [(randint(1, W-2), randint(2, H-2)) for _ in range(W*H // 10)] + return [get_rect(x, y) for x, y in border + platforms] + def get_rect(x, y): + return pg.Rect(x*16, y*16, 16, 16) + run(get_screen(), get_images(), get_mario(), get_tiles()) + +def run(screen, images, mario, tiles): + clock = pg.time.Clock() + while all(event.type != pg.QUIT for event in pg.event.get()): + keys = {pg.K_UP: D.n, pg.K_RIGHT: D.e, pg.K_DOWN: D.s, pg.K_LEFT: D.w} + pressed = {keys.get(ch) for ch, is_prsd in enumerate(pg.key.get_pressed()) if is_prsd} + update_speed(mario, tiles, pressed) + update_position(mario, tiles) + draw(screen, images, mario, tiles, pressed) + clock.tick(28) + +def update_speed(mario, tiles, pressed): + x, y = mario.spd + x += 2 * ((D.e in pressed) - (D.w in pressed)) + x -= (x > 0) - (x < 0) + y += 1 if D.s not in get_boundaries(mario.rect, tiles) else (D.n in pressed) * -10 + mario.spd = P(x=max(-MAX_S.x, min(MAX_S.x, x)), y=max(-MAX_S.y, min(MAX_S.y, y))) + +def update_position(mario, tiles): + x, y = mario.rect.topleft + n_steps = max(abs(s) for s in mario.spd) + for _ in range(n_steps): + mario.spd = stop_on_collision(mario.spd, get_boundaries(mario.rect, tiles)) + x, y = x + mario.spd.x / n_steps, y + mario.spd.y / n_steps + mario.rect.topleft = x, y + +def get_boundaries(rect, tiles): + deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)} + return {d for d, delta in deltas.items() if rect.move(delta).collidelist(tiles) != -1} + +def stop_on_collision(spd, bounds): + return P(x=0 if (D.w in bounds and spd.x < 0) or (D.e in bounds and spd.x > 0) else spd.x, + y=0 if (D.n in bounds and spd.y < 0) or (D.s in bounds and spd.y > 0) else spd.y) + +def draw(screen, images, mario, tiles, pressed): + def get_marios_image_index(): + if D.s not in get_boundaries(mario.rect, tiles): + return 4 + return next(mario.frame_cycle) if {D.w, D.e} & pressed else 6 + screen.fill((85, 168, 255)) + mario.facing_left = (D.w in pressed) if {D.w, D.e} & pressed else mario.facing_left + screen.blit(images[get_marios_image_index() + mario.facing_left * 9], mario.rect) + for t in tiles: + screen.blit(images[18 if t.x in [0, (W-1)*16] or t.y in [0, (H-1)*16] else 19], t) + pg.display.flip() + +if __name__ == '__main__': + main() +``` + +**[🔼Back to Top](#content-outlines)** + +Pandas +------ +```python +# $ pip3 install pandas matplotlib +import pandas as pd +from pandas import Series, DataFrame +import matplotlib.pyplot as plt +``` + +### Series +**Ordered dictionary with a name.** + +```python +>>> Series([1, 2], index=['x', 'y'], name='a') +x 1 +y 2 +Name: a, dtype: int64 +``` + +```python +<Sr> = Series(<list>) # Assigns RangeIndex starting at 0. +<Sr> = Series(<dict>) # Takes dictionary's keys for index. +<Sr> = Series(<dict/Series>, index=<list>) # Only keeps items with keys specified in index. +``` + +```python +<el> = <Sr>.loc[key] # Or: <Sr>.iloc[index] +<Sr> = <Sr>.loc[keys] # Or: <Sr>.iloc[indexes] +<Sr> = <Sr>.loc[from_key : to_key_inclusive] # Or: <Sr>.iloc[from_i : to_i_exclusive] +``` + +```python +<el> = <Sr>[key/index] # Or: <Sr>.key +<Sr> = <Sr>[keys/indexes] # Or: <Sr>[<key_range/range>] +<Sr> = <Sr>[bools] # Or: <Sr>.i/loc[bools] +``` + +```python +<Sr> = <Sr> ><== <el/Sr> # Returns a Series of bools. +<Sr> = <Sr> +-*/ <el/Sr> # Items with non-matching keys get value NaN. +``` + +```python +<Sr> = <Sr>.append(<Sr>) # Or: pd.concat(<coll_of_Sr>) +<Sr> = <Sr>.combine_first(<Sr>) # Adds items that are not yet present. +<Sr>.update(<Sr>) # Updates items that are already present. +``` + +```python +<Sr>.plot.line/area/bar/pie/hist() # Generates a Matplotlib plot. +plt.show() # Displays the plot. Also plt.savefig(<path>). +``` + +#### Series — Aggregate, Transform, Map: +```python +<el> = <Sr>.sum/max/mean/idxmax/all() # Or: <Sr>.agg(lambda <Sr>: <el>) +<Sr> = <Sr>.rank/diff/cumsum/ffill/interpl() # Or: <Sr>.agg/transform(lambda <Sr>: <Sr>) +<Sr> = <Sr>.fillna(<el>) # Or: <Sr>.agg/transform/map(lambda <el>: <el>) +``` + +```python +>>> sr = Series([1, 2], index=['x', 'y']) +x 1 +y 2 +``` + +```text ++-----------------+-------------+-------------+---------------+ +| | 'sum' | ['sum'] | {'s': 'sum'} | ++-----------------+-------------+-------------+---------------+ +| sr.apply(…) | 3 | sum 3 | s 3 | +| sr.agg(…) | | | | ++-----------------+-------------+-------------+---------------+ +``` + +```text ++-----------------+-------------+-------------+---------------+ +| | 'rank' | ['rank'] | {'r': 'rank'} | ++-----------------+-------------+-------------+---------------+ +| sr.apply(…) | | rank | | +| sr.agg(…) | x 1 | x 1 | r x 1 | +| sr.transform(…) | y 2 | y 2 | y 2 | ++-----------------+-------------+-------------+---------------+ +``` +* **Last result has a hierarchical index. Use `'<Sr>[key_1, key_2]'` to get its values.** + +**[🔼Back to Top](#content-outlines)** + +## OpenCV + +```python +# Importing an Image & Viewing it +import cv2 +image = cv2.imread("./path/to/image.jpg") +cv2.imshow("Image", image) +cv2.waitKey(0) +cv2.destroyAllWindows() +``` + +Croping an image +```python +import cv2 +image = cv2.imread("./path/to/image.jpg") +crop_img = image[y:y+h, x:x+w] +cv2.imshow("cropped", crop_img) +cv2.waitKey(0) +cv2.destroyAllWindows() +``` +Resizing an image +```python +import cv2 +image = cv2.imread("./path/to/image.jpg") +resized_image = cv2.resize(image, (100, 100)) +cv2.imshow("Resized image", resized_image) +cv2.waitKey(0) +cv2.destroyAllWindows() +``` +Saving an image +```python +import cv2 +image = cv2.imread("./path/to/image.jpg") +cv2.imwrite("./path/to/save/image.jpg", image) +``` +Converting an image to grayscale +```python +import cv2 +image = cv2.imread("./path/to/image.jpg") +gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) +cv2.imshow("Grayscale image", gray_image) +cv2.waitKey(0) +cv2.destroyAllWindows() +``` +Converting an image to HSV +```python +import cv2 +image = cv2.imread("./path/to/image.jpg") +hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) +cv2.imshow("HSV image", hsv_image) +cv2.waitKey(0) +cv2.destroyAllWindows() +``` +Converting an image to LAB +```python +import cv2 +image = cv2.imread("./path/to/image.jpg") +lab_image = cv2.cvtColor(image, cv2.COLOR_BGR2LAB) +cv2.imshow("LAB image", lab_image) +cv2.waitKey(0) +cv2.destroyAllWindows() +``` +Converting an image to RGB +```python +import cv2 +image = cv2.imread("./path/to/image.jpg") +rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) +cv2.imshow("RGB image", rgb_image) +cv2.waitKey(0) +cv2.destroyAllWindows() +``` + +**[🔼Back to Top](#content-outlines)** + +## DataFrame +**Table with labeled rows and columns.** + +```python +>>> DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['x', 'y']) + x y +a 1 2 +b 3 4 +``` + +```python +<DF> = DataFrame(<list_of_rows>) # Rows can be either lists, dicts or series. +<DF> = DataFrame(<dict_of_columns>) # Columns can be either lists, dicts or series. +``` + +```python +<el> = <DF>.loc[row_key, column_key] # Or: <DF>.iloc[row_index, column_index] +<Sr/DF> = <DF>.loc[row_key/s] # Or: <DF>.iloc[row_index/es] +<Sr/DF> = <DF>.loc[:, column_key/s] # Or: <DF>.iloc[:, column_index/es] +<DF> = <DF>.loc[row_bools, column_bools] # Or: <DF>.iloc[row_bools, column_bools] +``` + +```python +<Sr/DF> = <DF>[column_key/s] # Or: <DF>.column_key +<DF> = <DF>[row_bools] # Keeps rows as specified by bools. +<DF> = <DF>[<DF_of_bools>] # Assigns NaN to False values. +``` + +```python +<DF> = <DF> ><== <el/Sr/DF> # Returns DF of bools. Sr is treated as a row. +<DF> = <DF> +-*/ <el/Sr/DF> # Items with non-matching keys get value NaN. +``` + +```python +<DF> = <DF>.set_index(column_key) # Replaces row keys with values from a column. +<DF> = <DF>.reset_index() # Moves row keys to a column named index. +<DF> = <DF>.sort_index(ascending=True) # Sorts rows by row keys. +<DF> = <DF>.sort_values(column_key/s) # Sorts rows by the passed column/s. +``` + +#### DataFrame — Merge, Join, Concat: +```python +>>> l = DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['x', 'y']) + x y +a 1 2 +b 3 4 +>>> r = DataFrame([[4, 5], [6, 7]], index=['b', 'c'], columns=['y', 'z']) + y z +b 4 5 +c 6 7 +``` + +```text ++------------------------+---------------+------------+------------+--------------------------+ +| | 'outer' | 'inner' | 'left' | Description | ++------------------------+---------------+------------+------------+--------------------------+ +| l.merge(r, on='y', | x y z | x y z | x y z | Joins/merges on column. | +| how=…) | 0 1 2 . | 3 4 5 | 1 2 . | Also accepts left_on and | +| | 1 3 4 5 | | 3 4 5 | right_on parameters. | +| | 2 . 6 7 | | | Uses 'inner' by default. | ++------------------------+---------------+------------+------------+--------------------------+ +| l.join(r, lsuffix='l', | x yl yr z | | x yl yr z | Joins/merges on row keys.| +| rsuffix='r', | a 1 2 . . | x yl yr z | 1 2 . . | Uses 'left' by default. | +| how=…) | b 3 4 4 5 | 3 4 4 5 | 3 4 4 5 | If r is a Series, it is | +| | c . . 6 7 | | | treated as a column. | ++------------------------+---------------+------------+------------+--------------------------+ +| pd.concat([l, r], | x y z | y | | Adds rows at the bottom. | +| axis=0, | a 1 2 . | 2 | | Uses 'outer' by default. | +| join=…) | b 3 4 . | 4 | | A Series is treated as a | +| | b . 4 5 | 4 | | column. Use l.append(sr) | +| | c . 6 7 | 6 | | to add a row instead. | ++------------------------+---------------+------------+------------+--------------------------+ +| pd.concat([l, r], | x y y z | | | Adds columns at the | +| axis=1, | a 1 2 . . | x y y z | | right end. Uses 'outer' | +| join=…) | b 3 4 4 5 | 3 4 4 5 | | by default. A Series is | +| | c . . 6 7 | | | treated as a column. | ++------------------------+---------------+------------+------------+--------------------------+ +| l.combine_first(r) | x y z | | | Adds missing rows and | +| | a 1 2 . | | | columns. Also updates | +| | b 3 4 5 | | | items that contain NaN. | +| | c . 6 7 | | | R must be a DataFrame. | ++------------------------+---------------+------------+------------+--------------------------+ +``` + +#### DataFrame — Aggregate, Transform, Map: +```python +<Sr> = <DF>.sum/max/mean/idxmax/all() # Or: <DF>.apply/agg(lambda <Sr>: <el>) +<DF> = <DF>.rank/diff/cumsum/ffill/interpl() # Or: <DF>.apply/agg/transfrm(lambda <Sr>: <Sr>) +<DF> = <DF>.fillna(<el>) # Or: <DF>.applymap(lambda <el>: <el>) +``` +* **All operations operate on columns by default. Pass `'axis=1'` to process the rows instead.** + +```python +>>> df = DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['x', 'y']) + x y +a 1 2 +b 3 4 +``` + +```text ++-----------------+-------------+-------------+---------------+ +| | 'sum' | ['sum'] | {'x': 'sum'} | ++-----------------+-------------+-------------+---------------+ +| df.apply(…) | | x y | | +| df.agg(…) | x 4 | sum 4 6 | x 4 | +| | y 6 | | | ++-----------------+-------------+-------------+---------------+ +``` + +```text ++-----------------+-------------+-------------+---------------+ +| | 'rank' | ['rank'] | {'x': 'rank'} | ++-----------------+-------------+-------------+---------------+ +| df.apply(…) | x y | x y | x | +| df.agg(…) | a 1 1 | rank rank | a 1 | +| df.transform(…) | b 2 2 | a 1 1 | b 2 | +| | | b 2 2 | | ++-----------------+-------------+-------------+---------------+ +``` +* **Use `'<DF>[col_key_1, col_key_2][row_key]'` to get the fifth result's values.** + +#### DataFrame — Plot, Encode, Decode: +```python +<DF>.plot.line/bar/hist/scatter/box() # Also: `x=column_key, y=column_key/s`. +plt.show() # Displays the plot. Also plt.savefig(<path>). +``` + +```python +<DF> = pd.read_json/html('<str/path/url>') # Run `$ pip3 install beautifulsoup4 lxml`. +<DF> = pd.read_csv/pickle/excel('<path/url>') # Use `sheet_name=None` to get all Excel sheets. +<DF> = pd.read_sql('<table/query>', <conn.>) # Accepts SQLite3 or SQLAlchemy connection. +<DF> = pd.read_clipboard() # Reads a copied table from the clipboard. +``` + +```python +<dict> = <DF>.to_dict(['d/l/s/…']) # Returns columns as dicts, lists or series. +<str> = <DF>.to_json/html/csv([<path>]) # Also to_markdown/latex([<path>]). +<DF>.to_pickle/excel(<path>) # Run `$ pip3 install openpyxl` for xlsx files. +<DF>.to_sql('<table_name>', <connection>) # Accepts SQLite3 or SQLAlchemy connection. +``` + +### GroupBy +**Object that groups together rows of a dataframe based on the value of the passed column.** + +```python +>>> df = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 6]], index=list('abc'), columns=list('xyz')) +>>> df.groupby('z').get_group(6) + x y +b 4 5 +c 7 8 +``` + +```python +<GB> = <DF>.groupby(column_key/s) # Splits DF into groups based on passed column. +<DF> = <GB>.apply(<func>) # Maps each group. Func can return DF, Sr or el. +<GB> = <GB>[column_key] # Single column GB. All operations return a Sr. +``` + +#### GroupBy — Aggregate, Transform, Map: +```python +<DF> = <GB>.sum/max/mean/idxmax/all() # Or: <GB>.agg(lambda <Sr>: <el>) +<DF> = <GB>.rank/diff/cumsum/ffill() # Or: <GB>.transform(lambda <Sr>: <Sr>) +<DF> = <GB>.fillna(<el>) # Or: <GB>.transform(lambda <Sr>: <Sr>) +``` + +```python +>>> gb = df.groupby('z') + x y z +3: a 1 2 3 +6: b 4 5 6 + c 7 8 6 +``` + +```text ++-----------------+-------------+-------------+-------------+---------------+ +| | 'sum' | 'rank' | ['rank'] | {'x': 'rank'} | ++-----------------+-------------+-------------+-------------+---------------+ +| gb.agg(…) | x y | x y | x y | x | +| | z | a 1 1 | rank rank | a 1 | +| | 3 1 2 | b 1 1 | a 1 1 | b 1 | +| | 6 11 13 | c 2 2 | b 1 1 | c 2 | +| | | | c 2 2 | | ++-----------------+-------------+-------------+-------------+---------------+ +| gb.transform(…) | x y | x y | | | +| | a 1 2 | a 1 1 | | | +| | b 11 13 | b 1 1 | | | +| | c 11 13 | c 2 2 | | | ++-----------------+-------------+-------------+-------------+---------------+ +``` + +### Rolling +**Object for rolling window calculations.** + +```python +<RSr/RDF/RGB> = <Sr/DF/GB>.rolling(win_size) # Also: `min_periods=None, center=False`. +<RSr/RDF/RGB> = <RDF/RGB>[column_key/s] # Or: <RDF/RGB>.column_key +<Sr/DF> = <R>.mean/sum/max() # Or: <R>.apply/agg(<agg_func/str>) +``` + + +Plotly +------ +```python +# $ pip3 install plotly kaleido +from plotly.express import line +<Figure> = line(<DF>, x=<col_name>, y=<col_name>) # Or: line(x=<list>, y=<list>) +<Figure>.update_layout(margin=dict(t=0, r=0, b=0, l=0)) # Or: paper_bgcolor='rgba(0, 0, 0, 0)' +<Figure>.write_html/json/image('<path>') # Also: <Figure>.show() +``` + +#### Covid deaths by continent: + + +<div id="2a950764-39fc-416d-97fe-0a6226a3095f" class="plotly-graph-div" style="height:340px; width:100%;"></div> + +```python +covid = pd.read_csv('https://covid.ourworldindata.org/data/owid-covid-data.csv', + usecols=['iso_code', 'date', 'total_deaths', 'population']) +continents = pd.read_csv('https://gist.githubusercontent.com/stevewithington/20a69c0b6d2ff' + '846ea5d35e5fc47f26c/raw/country-and-continent-codes-list-csv.csv', + usecols=['Three_Letter_Country_Code', 'Continent_Name']) +df = pd.merge(covid, continents, left_on='iso_code', right_on='Three_Letter_Country_Code') +df = df.groupby(['Continent_Name', 'date']).sum().reset_index() +df['Total Deaths per Million'] = df.total_deaths * 1e6 / df.population +df = df[df.date > '2020-03-14'] +df = df.rename({'date': 'Date', 'Continent_Name': 'Continent'}, axis='columns') +line(df, x='Date', y='Total Deaths per Million', color='Continent').show() +``` + +#### Confirmed covid cases, Dow Jones, Gold, and Bitcoin price: + + +<div id="e23ccacc-a456-478b-b467-7282a2165921" class="plotly-graph-div" style="height:315px; width:100%;"></div> + +```python +import pandas as pd +import plotly.graph_objects as go + +def main(): + display_data(wrangle_data(*scrape_data())) + +def scrape_data(): + def scrape_covid(): + url = 'https://covid.ourworldindata.org/data/owid-covid-data.csv' + df = pd.read_csv(url, usecols=['location', 'date', 'total_cases']) + return df[df.location == 'World'].set_index('date').total_cases + def scrape_yahoo(slug): + url = f'https://query1.finance.yahoo.com/v7/finance/download/{slug}' + \ + '?period1=1579651200&period2=9999999999&interval=1d&events=history' + df = pd.read_csv(url, usecols=['Date', 'Close']) + return df.set_index('Date').Close + out = scrape_covid(), scrape_yahoo('BTC-USD'), scrape_yahoo('GC=F'), scrape_yahoo('^DJI') + return map(pd.Series.rename, out, ['Total Cases', 'Bitcoin', 'Gold', 'Dow Jones']) + +def wrangle_data(covid, bitcoin, gold, dow): + df = pd.concat([bitcoin, gold, dow], axis=1) # Joins columns on dates. + df = df.sort_index().interpolate() # Sorts by date and interpolates NaN-s. + df = df.loc['2020-02-23':] # Discards rows before '2020-02-23'. + df = (df / df.iloc[0]) * 100 # Calculates percentages relative to day 1. + df = df.join(covid) # Adds column with covid cases. + return df.sort_values(df.index[-1], axis=1) # Sorts columns by last day's value. + +def display_data(df): + figure = go.Figure() + for col_name in reversed(df.columns): + yaxis = 'y1' if col_name == 'Total Cases' else 'y2' + trace = go.Scatter(x=df.index, y=df[col_name], name=col_name, yaxis=yaxis) + figure.add_trace(trace) + figure.update_layout( + yaxis1=dict(title='Total Cases', rangemode='tozero'), + yaxis2=dict(title='%', rangemode='tozero', overlaying='y', side='right'), + legend=dict(x=1.1), + height=450 + ).show() + +if __name__ == '__main__': + main() +``` + + +PySimpleGUI +----------- +```python +# $ pip3 install PySimpleGUI +import PySimpleGUI as sg +layout = [[sg.Text("What's your name?")], [sg.Input()], [sg.Button('Ok')]] +window = sg.Window('Window Title', layout) +event, values = window.read() +print(f'Hello {values[0]}!' if event == 'Ok' else '') +``` + + +Appendix +-------- +### Cython +**Library that compiles Python code into C.** + +```python +# $ pip3 install cython +import pyximport; pyximport.install() +import <cython_script> +<cython_script>.main() +``` + +#### Definitions: +* **All `'cdef'` definitions are optional, but they contribute to the speed-up.** +* **Script needs to be saved with a `'pyx'` extension.** + +```python +cdef <ctype> <var_name> = <el> +cdef <ctype>[n_elements] <var_name> = [<el_1>, <el_2>, ...] +cdef <ctype/void> <func_name>(<ctype> <arg_name>): ... +``` + +```python +cdef class <class_name>: + cdef public <ctype> <attr_name> + def __init__(self, <ctype> <arg_name>): + self.<attr_name> = <arg_name> +``` + +```python +cdef enum <enum_name>: <member_name_1>, <member_name_2>, ... +``` + +### PyInstaller +```bash +$ pip3 install pyinstaller +$ pyinstaller script.py # Compiles into './dist/script' directory. +$ pyinstaller script.py --onefile # Compiles into './dist/script' console app. +$ pyinstaller script.py --windowed # Compiles into './dist/script' windowed app. +$ pyinstaller script.py --add-data '<path>:.' # Adds file to the root of the executable. +``` +* **File paths need to be updated to `'os.path.join(sys._MEIPASS, <path>)'`.** + +### Basic Script Template +```python +#!/usr/bin/env python3 +# +# Usage: .py +# + +from sys import argv, exit +from collections import defaultdict, namedtuple +from dataclasses import make_dataclass +from enum import Enum +import functools as ft, itertools as it, operator as op, re + + +def main(): + pass + + +### +## UTIL +# + +def read_file(filename): + with open(filename, encoding='utf-8') as file: + return file.readlines() + + +if __name__ == '__main__': + main() +``` + + +Index +----- +* **Only available in the [PDF](https://transactions.sendowl.com/products/78175486/4422834F/view).** +* **Ctrl+F / ⌘F is usually sufficient.** +* **Searching `'#<title>'` on the [webpage](https://gto76.github.io/python-cheatsheet/) will limit the search to the titles.** + +**[🔼Back to Top](#content-outlines)** diff --git a/PYTHON/ROUND, CEIL, FLOOR.md b/PYTHON/ROUND, CEIL, FLOOR.md new file mode 100644 index 0000000..6a9bf93 --- /dev/null +++ b/PYTHON/ROUND, CEIL, FLOOR.md @@ -0,0 +1,20 @@ +👩💻 **Как округлять числа в Python?** + +Python предоставляет гибкие способы округления чисел: + +➡️ **Округление до ближайшего целого:** + +print(round(3.14159)) # 3 + +➡️ **Округление с точностью до 2 знаков:** + +print(round(3.14159, 2)) # 3.14 + +**➡️ Округление вверх или вниз:** + +import math + +print(math.ceil(3.1)) # 4 +print(math.floor(3.9)) # 3 + +🗣 Используйте round(), когда важна точность, и math.ceil()/math.floor() для контроля направления округления.
\ No newline at end of file diff --git a/PYTHON/SET, DICT, LIST, TUPLE, RANGE.md b/PYTHON/SET, DICT, LIST, TUPLE, RANGE.md new file mode 100644 index 0000000..4d58f5e --- /dev/null +++ b/PYTHON/SET, DICT, LIST, TUPLE, RANGE.md @@ -0,0 +1,219 @@ +### SET - набор неупорядоченная последовательность элементов +Может содержать ТОЛЬКО уникальные элементы ! +По сути тот же список, но только с уникальными элементами ! + +```python +S = {'Moscow', 'Riga', ''Dolgoprudny', 'Piter'} + +S.add('Rostov') + +if 'Riga' in S : + print('.......') + +for el in S : + print( el ) +``` + +### DICT - словарь (набор элементов) +ключ : значение (нет уникальных индексов) + +```python +D = { + 'Moscow': 12547, + 'Dolgoprudny': 3562, + 'Piter': 15478 + 'Rostov': 333 +} +print(D['Moscow']) +# 12547 + +D['Rostov'] = 756 +print(D['Rostov']) +# 756 + + +# с python 3.9 новый оператор слияния словарей +# создается новый словарь: +x = {1: 'qwe', 2: 'ggfd'} +y = {3: 'qwjhgv', 4: 'gfffgfd'} +z = {5: 'q234we', 6: 'gg45fd'} + +d = x | y | z +print(d) +``` +**Удаление дубликатов в словаре** + +Стратегия заключается в преобразовании списка словарей в список кортежей, где кортежи содержат элементы словаря. +Поскольку кортежи могут быть хешированы, вы можете удалить дубликаты с помощью set(используя здесь понимание множества , более старой альтернативой Python будет set(tuple(d.items()) for d in l)) и после этого заново создать словари из кортежей с помощью dict. +![[telegram-cloud-photo-size-2-5280728042542581828-y.jpg]] + +** - можно распаковать словарь +### LIST - список (изменяемый) +Упорядоченная последовательность любых элементов. +Может содержать ЛЮБЫЕ (даже повторяющиеся - НЕ уникальные) элементы. +Можно изменять, удалять, добавлять. +```python +A = [1,2,3,4,5] +A = [(1,10),(2,20),(3,30), {'den': 345}] + +# копирование списка (создание нового, еще одного): +B = A[:] +B = A.copy() +B = list(A) +``` + +### TUPLE - кортеж (не изменяемый) +последовательность элементов - ВАЖНА ! +```python +T = (1, 2, 3, 4, 5) +a, b, *rest = T +*rest, a, b = T + +print(T) +print(*T) +print(*T, sep=":", end=" !\n") + + +def hello_n(name: str, n: int): + for i in range(n): + print('Привет ', name) + +hello_n('Алеша', 5) +hello_n('Петя', 4) +vasya = 'Вася', 3 +hello_n(*vasya) + +######################################################## + +my_object = { + 'x': 10, + 'y': True +} + +for item in my_object.items(): + key, value = item + print(key, value) + +for key, value in my_object.items(): + print(key, value) +``` + +### RANGE - упорядоченная НЕизменяемая последовательность элементов +Обычно используются в циклах. +```python +my_range = range(20) # до +my_range = range(10, 20) # от : до +my_range = range(10, 20, 3) # от : до : шаг + +print(my_range) +# range(10, 20, 3) + +print(list(my_range)) +# [10, 13, 16, 19] + +for n in my_range: + print(n) +# 10 +# 13 +# 16 +# 19 +``` + +![[Снимок экрана 2024-03-10 в 20.55.05.png]] + +### ZIP - встроенная функция + +```python +fruits = ['apple', 'banana', 'lime'] +quantities = [100, 70, 50] +availabiliti = [True, False, False, True] + +fruit_qtys_zip = zip(fruits, quantities, availabiliti) + +print(fruit_qtys_zip) + +print(type(fruit_qtys_zip)) + +fruit_qtys_list = list(fruit_qtys_zip) +print(fruit_qtys_list) # создается список кортежей + +# fruit_qtys_list = tuple(fruit_qtys_zip) +# print(fruit_qtys_list) # создается список кортежей + +# fruit_qtys_list = dict(fruit_qtys_zip) +# print(fruit_qtys_list) # создается список словарей +``` + +```python +from copy import deepcopy # глубокая копия + + +info = { + 'name': 'vlapa', + 'is_instructor': True, + 'reviews': [] +} + + +info_deepcopy = info.copy() # deepcopy(info) +info_deepcopy['reviews'].append('Great_course') +info['reviews'].append('Super') + +print('\n-1-') +print(info) + +print('\n-2-') +print(info_deepcopy) +``` + +```python +# Фильтрация элементов по типу: +def filter_list(list_to_filter, value_type): + filtered_list = [] + for element in list_to_filter: + if type(element) == value_type: + filtered_list.append(element) + + # Not recomended! Because bool is subclass of int ! + # if isinstance(element, value_type): + # filtered_list.append(element) + + return filtered_list + + +print(filter_list([35, True, 'abc', 10], int)) +print(filter_list([35, True, 'abc', 10], str)) +print(filter_list([35, True, 'abc', 10], bool)) + +################################################ +# Фильтрация элементов с помощью <filter>: + +def filter_list(list_to_filter, value_type): + def check_element_type(elem): + return isinstance(elem, value_type) + return list(filter(check_element_type, list_to_filter)) + +res = filter_list([1, 10, 'abc', 5.5], int) +print(res) + +################################################ +# Фильтрация элементов с помощью <filter> и <lambda>: + +def filter_list(list_to_filter, value_type): + return list(filter(lambda elem: type(elem) is value_type, list_to_filter)) + +res = filter_list([1, 10, 'abc', 5.5], int) +print(res) +``` + +**Соединение списка и множества в словарь** + +![[telegram-cloud-photo-size-2-5249336126576118394-y.jpg]] + +Для соединения списка и множества в словарь можно использовать встроенную функцию `zip()`. Эта функция объединяет элементы нескольких последовательностей (`списков`, множеств, кортежей и т.д.) в кортежи. + +Для создания словаря из списков и множеств нужно сначала объединить элементы в кортежи при помощи функции zip(), а затем создать словарь из этих кортежей при помощи функции dict(). + +`fruit_prices +будет содержать словарь +`{'apple': 1.20, 'banana': 0.80, 'orange': 1.50}.
\ No newline at end of file diff --git a/PYTHON/SPEEDTEST python.md b/PYTHON/SPEEDTEST python.md new file mode 100644 index 0000000..1a0f86e --- /dev/null +++ b/PYTHON/SPEEDTEST python.md @@ -0,0 +1,51 @@ +⏲ Проверка скорости интернета с Python + +`import speedtest + +`st = speedtest.Speedtest() +`print("Загрузка:", round(st.download() / 1_000_000, 2), "Мбит/с") +`print("Выгрузка:", round(st.upload() / 1_000_000, 2), "Мбит/с") +`print("Пинг:", st.results.ping, "мс") + +📝 Пример результата + +Загрузка: 55.23 Мбит/с +Выгрузка: 33.66 Мбит/с +Пинг: 42.241 мс + +✔️ Установка библиотеки + +`pip install speedtest-cli + +Можно использовать для быстрой проверки интернет-соединение или автоматизации мониторинга сети. + + +![[telegram-cloud-photo-size-2-5316919099131030050-y.jpg]] + +======================================= + +⚙️ **Проверка скорости интернета в Linux** + +Если интернет работает медленно, важно быстро протестировать его скорость. В Linux это можно сделать прямо из терминала. +# Установить Speedtest CLI (если не установлен) + +```sh +sudo apt install speedtest-cli # Для Debian/Ubuntu +sudo pacman -S speedtest-cli # Для Arch +sudo dnf install speedtest-cli # Для Fedora + +# Проверить скорость интернета +speedtest + +# Проверить только скорость загрузки и отдачи +speedtest --simple + +# Получить результат в виде JSON (можно использовать для автоматизации) +speedtest --json +``` +🟢 Показывает реальную скорость загрузки и отдачи. +🟢 Подходит для диагностики интернет-соединения. +🟢 Можно встроить в мониторинг серверов + +**🗣 Эти команды помогут быстро определить, есть ли проблемы со скоростью сети.** + diff --git a/PYTHON/SPLIT, JOIN.md b/PYTHON/SPLIT, JOIN.md new file mode 100644 index 0000000..1ee06b8 --- /dev/null +++ b/PYTHON/SPLIT, JOIN.md @@ -0,0 +1,2 @@ +![[telegram-cloud-photo-size-2-5253592580900375225-x.jpg]] + diff --git a/PYTHON/STRING.md b/PYTHON/STRING.md new file mode 100644 index 0000000..6e7d4f9 --- /dev/null +++ b/PYTHON/STRING.md @@ -0,0 +1,121 @@ +```python +my_str = 'Vlapa' +print(len(my_str)) +# 5 + +print(my_str[0]) +# V + +print(my_str[2:5]) +# apa + + +my_str.upper() +my_str.replace('__old', '__new') # само значение не изменяется ! +my_str.count('__x') # считает кол-во вхождений + + +``` +![[Снимок экрана 2025-02-11 в 01.15.10.png]] + +Создать 1000 папок со случайными именами: +![[Снимок экрана 2025-02-11 в 01.17.25.png]] + + +**Работа с регистрами** + +![[telegram-cloud-photo-size-4-5863854399405801203-w.jpg]] + +_Строки в Python имеют свойство регистра — прописные и строчные буквы считаются разными символами._ + +Для преобразования регистра используются методы строк: +str.upper() — преобразует строку к верхнему регистру. +str.lower() — преобразует строку к нижнему регистру. +str.swapcase() — меняет регистр каждого символа на противоположный. +str.capitalize() — делает первую букву заглавной, остальные строчными. + +Регистр конкретного символа можно проверить в строке методами: +str.isupper() — проверка на заглавный регистр. +str.islower() — проверка на строчный регистр. + +Регистр важно учитывать при сравнении строк на равенство. + + +### Полезные строковые методы + +1️⃣ .upper() **– переводит строку в ВЕРХНИЙ РЕГИСТР** +text = "hello world" +print(text.upper()) # "HELLO WORLD" + +2️⃣ .lower() **– переводит строку в нижний регистр** +text = "HELLO WORLD" +print(text.lower()) # "hello world" + +3️⃣ .strip() **– удаляет пробелы и символы переноса с начала и конца строки** +text = " hello world " +print(text.strip()) # "hello world" + +Можно указать символы, которые нужно удалить: +text = "##hello##" +print(text.strip("#")) # "hello" + +4️⃣ .replace(old, new) **– заменяет подстроку в строке** +text = "Hello, world!" +print(text.replace("world", "Python")) # "Hello, Python!" + +5️⃣ .split(separator) **– разделяет строку по разделителю и возвращает список** +text = "apple,banana,orange" +print(text.split(",")) # ['apple', 'banana', 'orange'] + +Если разделитель не указан, удаляются пробелы: +text = "Hello world" +print(text.split()) # ['Hello', 'world'] + +Эти методы часто используются вместе, например: +text = " hello, world! " +clean_text = text.strip().replace(",", "").upper() +print(clean_text) # "HELLO WORLD" + + +### f-строки с выражениями в форматировании + +Да, ты можешь использовать **выражения прямо внутри f-строк**! +```python +value = 3.14159 +print(f"Округлено: {value:.2f}") # Округлит до 2 знаков +print(f"В квадрате: {value**2:.1f}") # Сразу вычисляет и форматирует +``` + +🧠 **А можно и условие:** +```python +name = None +print(f"Привет, {name or 'гость'}!") +``` + +🗣 **При генерации отчётов, логов, CLI-вывода, шаблонов — везде, где важна читаемость и компактность.** + + + +PEP 750 вводят t-строки — это как f"..." но: +- интерполяция ограничена и контролируема; +- поддерживается строгое соответствие шаблону; +- можно передавать переменные явно, предотвращая **SQL-инъекции** и XSS. + +Для чего? Для того, чтобы писать shell-команды, SQL, HTML и т.д. без риска инъекций и без ручного экранирования + +**Пример**: + +```PYTHON +from string.templatelib import Template + +name = "World" +template: Template = t"Hello, {name}!" + +safe_output = html(template) +``` + +Здесь html() — это функция, которая принимает объект Template и возвращает безопасную строку, экранируя потенциально опасные вставки + +PEP 787 предлагает добавить это прямо в subprocess и shlex (но пока в статусе "отложен") + +Следим за развитием 😊
\ No newline at end of file diff --git a/PYTHON/SnakeViz.md b/PYTHON/SnakeViz.md new file mode 100644 index 0000000..eb624ff --- /dev/null +++ b/PYTHON/SnakeViz.md @@ -0,0 +1,15 @@ +⚙️ Библиотека Python: SnakeViz + +SnakeViz — незаменимый инструмент, если ваш Python-скрипт работает медленно. Он позволяет наглядно увидеть, где "утекает" производительность, и понять, какие функции требуют оптимизации. + +`pip install snakeviz + +• Профилируйте код с помощью cProfile: + +`python -m cProfile -o output.prof script.py + +• Визуализируйте результаты: + +`snakeviz output.prof + +Откроется удобный интерфейс в браузере, где будет ясно видно, какие функции "съедают" больше всего времени.
\ No newline at end of file diff --git a/PYTHON/TELEGRAMM BOT.md b/PYTHON/TELEGRAMM BOT.md new file mode 100644 index 0000000..50a2ae1 --- /dev/null +++ b/PYTHON/TELEGRAMM BOT.md @@ -0,0 +1,59 @@ +Создаем бота в BotFather: + +==/newbot = Work_Server== +t.me/vlapa_bot +7286073488:AAGzCTkYPI8rleSXpZxRm5isckgN09YVhiM +'id': 949969015 + +==/newbot = Home_server== +t.me/vlapaHome_bot +7423942020:AAGw0w75nFIkMGLDRSk2Spx1wq_-dlJOPeM +'id': 949969015 + +==/newbot = mqtt== +t.me/vlapa_mqtt_log_Bot +7238364847:AAGbx7RwzPaTpirzttnLQT9FPWL7DmvyWDs +'id': 949969015 + + +Запускаем боты: +- /start + +```python +# python -m pip install requests + +import requests +TOKEN = "Сюда вставьте свой токен" +url = f"https://api.telegram.org/bot{TOKEN}/getUpdates" +print(requests.get(url).json()) +``` + +{'ok': True, 'result': [{'update_id': 552144411, 'message': {'message_id': 1, 'from': {'id': 949969015, 'is_bot': False, 'first_name': 'Vlapa', 'language_code': 'ru'}, 'chat': {'id': 949969015, 'first_name': 'Vlapa', 'type': 'private'}, 'date': 1725095952, 'text': '/start', 'entities': [{'offset': 0, 'length': 6, 'type': 'bot_command'}]}}]} + +```python +import requests +TOKEN = "Сюда вставьте свой токен" +chat_id = "Сюда вставьте ID чата" +message = "Здесь напишите свое сообщение" +url = f"https://api.telegram.org/bot{TOKEN}/sendMessage?chat_id={chat_id}&text={message}" +print(requests.get(url).json()) # Эта строка отсылает сообщение +``` + + +```python +disk_list = '/dev/sdd5 /dev/md127p1 /dev/md126p1 /dev/sdc' +disk_name = '300Gb 1Tb-a 1Tb-b 1Tb-c' + + + +for disk in disk_list + command = f"df {disk} | grep / | awk 'print $5' | sed 's/%//g' >> size.txt" + current = os.system(command) + + threshold = 5 + if current > threshold + f = open('size.txt', w+) + + f.close() + +```
\ No newline at end of file diff --git a/PYTHON/THREADING.md b/PYTHON/THREADING.md new file mode 100644 index 0000000..8ebc631 --- /dev/null +++ b/PYTHON/THREADING.md @@ -0,0 +1,128 @@ +**Как работать с многопоточностью в Python** + +Python поддерживает многопоточность, но из-за **GIL (Global Interpreter Lock)** потоки работают не параллельно на CPU. + +✅ Использование потоков: +```python +import threading + +def worker(): + print("Работает поток") + +t = threading.Thread(target=worker) +t.start() +t.join() # Ждем завершения потока +``` + +**⚙️ Когда использовать?** +🟢 Для задач ввода-вывода (запросы в сеть, работа с файлами) +🟠 Для вычислений лучше использовать multiprocessing, а не threading +🗣 **Потоки полезны, но знай их ограничения!** + + + +# Python: как сделать многопоточную программу + +Когда-то давно мы делали [простой таймер с напоминанием на Python](https://thecode.media/python-time/). Он работал так: + +1. Мы спрашивали пользователя, о чём ему напомнить и через сколько минут. +2. Программа на это время засыпала и ничего не делала. +3. Как только время сна заканчивалось, программа просыпалась и выводила напоминание. + +У такой схемы есть минус: мы не можем пользоваться программой и выделенными на неё ресурсами до тех пор, пока она не проснётся. Процессор по кругу гоняет пустые команды и ждёт, когда можно будет продолжить полезную работу. Чтобы процессор и программа могли во время работы таймера делать что-то ещё, используют потоки. + +## Что такое поток + +В упрощённом виде потоки — это параллельно выполняемые задачи. По умолчанию используется один поток — это значит, что программа делает всё по очереди, линейно, без возможности делать несколько дел одновременно. + +Но если мы сделаем в программе два потока задач, то они будут работать параллельно и независимо друг от друга. Одному потоку не нужно будет становиться на паузу, когда в другом что-то происходит. + +👉 Важно понимать, что поток — это высокоуровневое понятие из области программирования. На уровне вашего «железа» эти потоки всё ещё могут обсчитываться последовательно. Но благодаря тому, что они будут обсчитываться быстро, вам может показаться, что они работают параллельно. + +## Многопоточность + +Представим такую ситуацию: + +- У вас на руке смарт-часы, которые собирают данные о вашем пульсе, УФ-излучении и движениях. На смарт-часах работает программа, которая обрабатывает эти данные. +- Программа состоит из четырёх функций. Первая собирает данные с датчиков. Три другие обрабатывают эти данные и делают выводы. +- Пока первая функция не собрала нужные данные, ничего другого не происходит. +- Как только данные введены, запускаются три оставшиеся функции. Они не зависят друг от друга и каждая считает своё. +- Как только все три функции закончат работу, программа выдаёт нужный результат. + +А теперь давайте посмотрим, как это выглядит в однопоточной и многопоточной системе. Видно, что если процессор позволяет делать несколько дел одновременно, в многопоточном режиме программа будет работать быстрее: + + +## Многопоточность на Python + +За потоки в Python отвечает модуль threading, а сам поток можно создать с помощью класса Thread из этого модуля. Подключается он так: + +```markup +from threading import Thread +``` + +После этого с помощью функции Thread () мы сможем создать столько потоков, сколько нам нужно. Логика работы такая: + +1. Подключаем нужный модуль и класс Thread. +2. Пишем функции, которые нам нужно выполнять в потоках. +3. Создаём новую переменную — поток, и передаём в неё название функции и её аргументы. Один поток = одна функция на входе. +4. Делаем так столько потоков, сколько требует логика программы. +5. Потоки сами следят за тем, закончилась в них функция или нет. Пока работает функция — работает и поток. +6. Всё это работает параллельно и (в теории) не мешает друг другу. + +Для иллюстрации запустим такой код: + +```python +import time +from threading import Thread + +def sleepMe(i): + print("Поток %i засыпает на 5 секунд.\n" % i) + time.sleep(5) + print("Поток %i сейчас проснулся.\n" % i) +for i in range(10): + th = Thread(target=sleepMe, args=(i, )) + th.start() +``` + +А вот как выглядит результат. Обратите внимание — потоки просыпаются не в той последовательности, как мы их запустили, а в той, в какой их выполнил процессор. Иногда это может помешать работе программы, но про это мы поговорим отдельно в другой статье. + + +## Добавляем потоки в таймер + +Первое, что нам нужно сделать, — вынести код таймера-напоминания в отдельную функцию, чтобы создать с ней поток. Для этого используем команду `def`: + +```python +# Делаем отдельную функцию с напоминанием +def remind(): + # Спрашиваем текст напоминания, который нужно потом показать пользователю + print("О чём вам напомнить?") + # Ждём ответа пользователя и результат помещаем в строковую переменную text + text = str(input()) + # Спрашиваем про время + print("Через сколько минут?") + # Тут будем хранить время, через которое нужно показать напоминание + local_time = float(input()) + # Переводим минуты в секунды + local_time = local_time * 60 + # Ждём нужное количество секунд, программа в это время ничего не делает + time.sleep(local_time) + # Показываем текст напоминания + print(text) +``` + +Теперь сделаем новый поток, в который отправим выполняться нашу новую функцию. Так как аргументов у нас нет, то и аргументы передавать не будем, а напишем args=(). + +```markup +# Создаём новый поток +th = Thread(target=remind, args=()) +# И запускаем его +th.start() +``` + +Нам осталось убедиться в том, что пока поток работает, мы можем выполнять в программе что-то ещё и наше засыпание в потоке на это не повлияет. Для этого мы через 20 секунд после запуска выведем сообщение на экран. За 20 секунд пользователь успеет ввести напоминание и время, после чего таймер уйдёт в новый поток и там уснёт на нужное количество минут. Но это не помешает работе основной программы — она тоже будет выполнять свои команды параллельно с потоком. + +```markup +# Пока работает поток, выведем что-то на экран через 20 секунд после запуска +time.sleep(20) +print("Пока поток работает, мы можем сделать что-нибудь ещё.\n") +``` diff --git a/PYTHON/TKINTER.md b/PYTHON/TKINTER.md new file mode 100644 index 0000000..23b9658 --- /dev/null +++ b/PYTHON/TKINTER.md @@ -0,0 +1,52 @@ +```python +from tkinter import * + +root = Tk() +root.title("Hello!") +root.iconbitmap(default="favicon.ico") # определим в одной папке с файлом приложения какой-нибудь файл с иконкой, допустип, он называется "favicon.ico". через параметр `default` в метод iconbitmap передается путь к иконки. В данном случае файл иконки располагается с файлом приложения в одной папке, поэтому в качестве пути указывается просто имя файла. + +root.geometry("300x250+400+200") +# Теперь строка в методе geometry имеет следующий формат: "Ширина x Высота + координатаX + координатаY". То есть при запуске окно шириной в 300 единиц и высотой 250 единиц будет находиться на 400 пикселей вправо и на 200 пикселей вниз от верхнего левого угла экрана. + +root.update_idletasks() +print(root.geometry()) # Для получения данных о размере и позиции также можно использовать метод geometry(), который возвращает данные значения в виде строки в формате "widthxheight+x+y". Чтобы приложение еще до метода `mainloop()` принименило для окна переданные ему значения по ширине, высоте и позиции, вызывается метод `root.update_idletasks()`. В итоге вызов `root.geometry()` возвратить строку "300x250+400+200" + +root.resizable(False,False) # Можно-ли растягивать: ширина - высота + +root.mainloop() + +``` +### Виджеты. Основные: + +- Button: кнопка +- Label: текстовая метка +- Entry: однострочное текстовое поле +- Text: многострочное текстовое поле +- Checkbutton: флажок +- Radiobutton: переключатель или радиокнопка +- Frame: фрейм, который организует виджеты в группы +- Listbox: список +- Combobox: выпадающий список +- Menu: элемент меню +- Scrollbar: полоса прокрутки +- Treeview: позволяет создавать древовидные и табличные элементы +- Scale: текстовая метка +- Spinbox: список значений со стрелками для перемещения по элементам +- Progressbar: текстовая метка +- Canvas: текстовая метка +- Notebook: панель вкладок + +### Получение информации о виджете + +Для получения информации о виджете можно использовать ряд его атрибутов. Рассмотрим некоторые из них: + +- winfo_class: возвращает класс виджета, например, для кнопки это класс `TButton` +- winfo_children: возвращает для текущего виджета список вложенных виджетов +- winfo_parent: возвращает родительский виджет +- winfo_toplevel: возвращает окно, которое содержит данный виджет +- winfo_width и winfo_height: текущая ширина и высота виджета +- winfo_reqwidth и winfo_reqheight: запрошенная виджетом ширина и высота +- winfo_x и winfo_y: x и y координаты верхнего левого угла виджета относительно родительского элемента +- winfo_rootx и winfo_rooty: x и y координаты верхнего левого угла виджета относительно экрана +- winfo_viewable: указывает, отображается ли виджет или скрыт + diff --git a/PYTHON/TRY ... EXCEPT.md b/PYTHON/TRY ... EXCEPT.md new file mode 100644 index 0000000..087abe3 --- /dev/null +++ b/PYTHON/TRY ... EXCEPT.md @@ -0,0 +1,269 @@ +## исключения и какие они бывают + +В Python исключение — это событие, которое возникает при выполнении программы и меняет его нормальный поток. Также это объект, который обозначает различные типы состояний отказа программы и содержит информацию об ошибке: + +- тип ошибки; +- состояние программы на момент, когда ошибка возникла; +- описание ошибки. + +При этом в Python много встроенных исключений, которые охватывают стандартные типы ошибок: + +- `AssertionError` — возникает, когда оператор assert терпит неудачу. +- `AttributeError` — попытка доступа к атрибуту объекта, которого не существует. +- `EOFError` — возникает, когда функция input () достигает конца файла. +- `FileNotFoundError` — программа пытается открыть несуществующий файл. +- `FloatingPointError` — сбой операции с плавающей запятой. +- `ImportError` — неудачная попытка импорта модуля. +- `IndexError` — попытка доступа к элементу списка по недопустимому индексу, например когда индекс выходит за пределы допустимого диапазона. +- `IndentationError` — неправильные отступы в коде. +- `KeyError` — ключ не найден в словаре. +- `KeyboardInterrupt` — пользователь нажал клавишу прерывания. +- `MemoryError` — операции не хватает памяти. +- `NameError` — попытка использовать переменную или функцию, которая не определена. +- `OSError` — ошибка, связанная с операционной системой, например при проблеме с файловой системой. +- `RuntimeError` — общая ошибка, которая не подпадает под другие категории. +- `SyntaxError` — ошибка в синтаксисе кода. +- `TabError` — ошибка смешивания табуляции и пробелов для отступов. +- `TypeError` — ошибка, которая возникает, когда операция или функция применяются к объекту неподходящего типа. +- `ValueError` — возникает, когда функция получает аргумент правильного типа, но некорректного знания. +- `ZeroDivisionError` — ошибка при делении на ноль. + +## Обработка исключений по умолчанию + +По умолчанию исключения в Python обрабатываются с помощью блоков `try`, `except`, `else` и `finally`. Стандартная структура этой конструкции такая: + +```python +try: + # код, который может вызвать исключение +except SomeException: + # код для обработки исключения, которое может возникнуть в блоке try +else: + # код, который выполняется, если исключения не было +finally: + # код, который выполняется в любом случае +``` + +Сначала Python выполняет код в блоке `try`. Если в блоке `try` возникает исключение, Python останавливает выполнение кода в этом блоке и начинает искать блок `except`, который соответствует типу возникшего исключения. Если соответствующий блок найден, выполняет код в этом блоке, если нет — исключение передаётся выше по цепочке вызовов (это может привести к завершению программы, если исключение не будет нигде поймано). Если в блоке `try` не возникло исключение, код выполняется в блоке `else` (если он есть). Затем, наконец, выполняется код в блоке `finally`, независимо от того, было исключение или нет. + +Блок `else` бывает полезен, когда нам нужно выполнить какой-то код только в том случае, если блок try завершился успешно. Например, когда нам нужно разделить логику обработки ошибок и основную логику, чтобы код был более читаемым и структурированным. Также это может быть полезно, если мы хотим выполнить какую-то операцию только после успешного выполнения блока try, например когда она требует много ресурсов. + +Рассмотрим простой пример с операцией деления на 0: + +```python +# выполняем попытку деления +try: + result = 10 / 0 +# исключение при делении на 0 +except ZeroDivisionError: + print("Деление на ноль!") +# этот блок не будет выполнен, поскольку возникло исключение +else: + print("Ошибок нет, результат:", result) +# этот блок выполнится в любом случае +finally: + print("Блок finally выполнен.") +``` + +Блок `finally` гарантирует, что критические операции, такие как освобождение ресурсов или закрытие файлов, будут выполнены независимо от того, произошла ошибка или нет. + +## Обработка нескольких типов исключений + +Для обработки различных типов исключений, которые могут возникнуть в блоке `try`, можно использовать несколько блоков `except`. Это позволит обрабатывать каждое исключение по-своему, в зависимости от типа исключения. + +Например, у нас есть операция деления на какое-то число, которое может ввести пользователь, в том числе из заданного списка. Мы можем предусмотреть исключения для ввода, который не может быть преобразован в целое число, для деления на 0 и для случаев, когда пользователь указывает индекс за пределами списка: + +```python +try: + # код, который может вызвать различные исключения + x = int(input("Введите число: ")) + result = 10 / x + my_list = [0, 1, 2, 3] + print(10 / my_list[x]) +# это исключение будет вызвано, если пользователь введёт некорректное значение +except ValueError: + print("Это не число! Пожалуйста, введите корректное число.") +# это исключение будет вызвано, если пользователь введёт 0 +except ZeroDivisionError: + print("Деление на 0 невозможно! Пожалуйста, введите число, отличное от 0.") +# это исключение будет вызвано, если пользователь введёт число за пределами индексов списка my_list +except IndexError: + print("Индекс выходит за пределы списка! Пожалуйста, введите число от 0 до 3.") +``` + +Если мы хотим обработать все три типа исключений при помощи одного блока, это можно сделать при помощи переменной, которая будет содержать объект исключения и может быть использована, чтобы получить дополнительную информацию об ошибке. + +```python +try: + # код, который может вызвать различные исключения + x = int(input("Введите число: ")) + result = 10 / x + my_list = [0, 1, 2, 3] + print(10 / my_list[x]) +# это исключение будет вызвано, если пользователь введёт некорректное значение, 0 или число за пределами индексов списка my list +except (ValueError, ZeroDivisionError, IndexError) as e: + print(f"Произошла ошибка: {e}") +``` +## Обработка всех типов исключений + +Если мы хотим обработать все возможные исключения, можно использовать блок except, не указывая конкретный тип исключения: + +```python +try: + # код, который может вызвать различные исключения + x = int(input("Введите число: ")) + result = 10 / x + my_list = [0, 1, 2, 3] + print(10 / my_list[x]) +except Exception as e: + print(f"Произошла ошибка: {e}") +``` + +Общее исключение без конкретизации типа нужно применять с осторожностью, поскольку это может затруднить отладку и скрыть реальные ошибки, которые лучше обрабатывать отдельно. + +## Использование оператора raise + +Оператор raise используется для генерации существующих встроенных исключений, для создания пользовательских исключений или для повторного вызова исключения, которое было перехвачено. С его помощью можно узнавать о проблемах и ошибках, которые возникли в коде, и контролировать то, как эти ошибки обрабатываются. + +Генерация встроенных исключений будет выглядеть так: + +```python +def divide(a, b): + if b == 0: + # генерируем встроенное исключение + raise ZeroDivisionError("Деление на ноль невозможно!") + return a / b + +# исключение перехватывается в этом блоке +try: + result = divide(10, 0) +except ZeroDivisionError as e: + print(f"Ошибка: {e}") +``` + +Собственные исключения бывают полезны для точного описания определённых сценариев ошибок или нештатных ситуаций, которые могут возникнуть в коде. С помощью собственных исключений можно повысить читаемость кода, сделать его надёжнее и обеспечить более точную обработку ошибок. При этом можно определить специализированные обработчики для различных типов ошибок, так что это упрощает отладку и управление ошибками. Создать пользовательское исключение можно так: + +```python +# объявляем класс пользовательского исключения +class NegativeNumberError(Exception): + """Класс для пользовательского исключения при отрицательном числе.""" + pass + +def check_positive(number): + if number < 0: + # генерируем пользовательское исключение + raise NegativeNumberError("Число не должно быть отрицательным!") + return number + +try: + check_positive(-5) +except NegativeNumberError as e: + print(f"Ошибка: {e}") +``` + +Можно повторно вызвать исключение, которое было перехвачено, чтобы оно могло быть обработано на более высоком уровне: + +```python +def process_value(value): + try: + if value < 0: + # генерируем исключение + raise ValueError("Значение не должно быть отрицательным!") + except ValueError as e: + print(f"Перехвачено исключение: {e}") + # повторно генерируем исключение + raise + +try: + process_value(-10) +except ValueError as e: + print(f"Повторно перехвачено исключение: {e}") +``` +## Наследование от классов исключений + +Пользовательские исключения можно создавать путём наследования от встроенных классов исключений или от любого другого пользовательского исключения. Таким образом можно описывать различные сценарии ошибок, что делает код более управляем. Пользовательские исключения могут содержать дополнительные атрибуты и методы для предоставления информации об ошибке, за счёт чего можно проще отлаживать код и быстрее исправлять проблемы. + +```python +class MyCustomError(Exception): + """Пользовательское исключение для представления специфического типа ошибки.""" + + def __init__(self, message): + self.message = message + super().__init__(self.message) + +try: + # генерируем пользовательское исключение + raise MyCustomError("Это моё пользовательское исключение!") +except MyCustomError as e: + print(f"Поймано пользовательское исключение: {e}") +``` +## Логирование исключений + +С помощью модуля `logging` бывает полезно вести журнал исключений для обнаружения проблем и отладки приложений. При этом исключения могут быть записаны в файл, выведены в консоль, отправлены на электронную почту и так далее. + +```python +import logging + +# настраиваем конфигурацию логирования +logging.basicConfig(filename='app.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s') + +try: + # код, который может вызывать исключения + result = 10 / 0 +except Exception as e: + # записываем сообщение об ошибке в журнал + logging.error("Произошло исключение: %s", e) +``` + +#============================================== +```python +try: + print('10' / 0) +except Exception as e: + print(e) +except ZeroDivisionError as e: + print(type(e)) + print(e) +except TypeError as e: + print(type(e)) + print(e) +else: # выполняется если ошибок нет + print("There was no ERROR !") +finally: # выполняется в любом случае + print('Continue...') + +######################################################## + +def divide_nums(a, b): + if b == 0: + raise ValueError("Second argument can't be 0") + return a / b + + +try: + divide_nums(10, 0) +except Exception as e: + print(isinstance(e, Exception)) + print(e) + + +print('Continue...') + +######################################################## + +def image_info(img): + if ('image_id' not in img) or ('image_title' not in img): + raise TypeError("Keys image_id and image_title must be present") + return f"Image '{img['image_title']}' has id {img['image_id']}" + + +print(image_info({'image_title': 'My cat', 'image_id': 123})) + + +try: + print(image_info({'image_id': 123})) +except TypeError as e: + print(e) +``` + + + diff --git a/PYTHON/Translators.md b/PYTHON/Translators.md new file mode 100644 index 0000000..c0d001e --- /dev/null +++ b/PYTHON/Translators.md @@ -0,0 +1,20 @@ + +**Translators** — это лёгкая и мощная библиотека для Python, которая позволяет выполнять переводы текста через популярные онлайн-сервисы, такие как Google Translate, Microsoft Translator, DeepL и многие другие. + +— Поддержка множества переводчиков: Google Translate, DeepL, Microsoft Translator, и т.д. +— Перевод текста на более чем 100 языков. +— Простота в использовании: подключение к API не требуется! +— Возможность указания источника и целевого языка. + +```python +import translators as ts + +# Перевод через DeepL +result = ts.deepl("Как дела?", from_language="ru", to_language="en") +print(result) + +# Вывод: How are you? +``` +✔️ **Установка** + +`pip install translators
\ No newline at end of file diff --git a/PYTHON/WITH.md b/PYTHON/WITH.md new file mode 100644 index 0000000..437e03d --- /dev/null +++ b/PYTHON/WITH.md @@ -0,0 +1,28 @@ +Ещё одна мощная и идиоматичная конструкция в Python — это менеджеры контекста и оператор ==with==. Они кардинально упрощают работу с ресурсами, которые требуют обязательной "очистки" после использования, например, файлы, сетевые соединения или блокировки потоков. + +Представьте, что вы открываете файл для записи. Крайне важно его потом закрыть, иначе данные могут не сохраниться, а файловый дескриптор останется занят. Классический, но многословный способ гарантировать закрытие — использовать `try...finally: +```sh +f = open('my_file.txt', 'w') +try: + f.write('Важные данные!') + # ... тут может произойти ошибка +finally: + f.close() # Закроется в любом случае +``` + +Работает, но выглядит громоздко. Python предлагает элегантную альтернативу с with: +```sh +with open('my_file.txt', 'w') as f: + f.write('Важные данные!') + # ... тут тоже может произойти ошибка +# Как только блок `with` завершается (нормально или из-за ошибки), +# Python АВТОМАТИЧЕСКИ вызывает f.close()! +``` + +**В чём магия with?** + +Объекты, используемые с with (как тот, что возвращает open()), реализуют так называемый "протокол менеджера контекста". У них есть специальные методы (__enter__ и __exit__). Оператор with вызывает __enter__ при входе в блок и гарантированно вызывает __exit__ при выходе из блока, передавая туда информацию об исключениях, если они были. Именно в __exit__ и происходит освобождение ресурса (закрытие файла). + + + +Оператор with — это стандарт де-факто для управления ресурсами в Python. Он делает код не только короче, но и значительно безопаснее и понятнее.
\ No newline at end of file diff --git a/PYTHON/any, all.md b/PYTHON/any, all.md new file mode 100644 index 0000000..56a60e6 --- /dev/null +++ b/PYTHON/any, all.md @@ -0,0 +1,22 @@ +Когда нужно проверить список условий, any и all делают код более читаемым и удобным. + +```python +numbers = [2, 4, 6, 8] + +# Проверяем, есть ли хотя бы одно нечётное число +print(any(n % 2 != 0 for n in numbers)) # False + +# Проверяем, все ли числа чётные +print(all(n % 2 == 0 for n in numbers)) # True +``` + +**📌 Почему это удобно?** + +🟢 any() возвращает True, если хотя бы одно условие выполняется + +🟢 all() возвращает True, если выполняются все условия + +🟢 Упрощает код вместо громоздких циклов + + +🗣️ **Используйте** any() **и** all()**, когда нужно быстро проверить списки условий без явных циклов**.
\ No newline at end of file diff --git a/PYTHON/argparse.md b/PYTHON/argparse.md new file mode 100644 index 0000000..b5019b5 --- /dev/null +++ b/PYTHON/argparse.md @@ -0,0 +1,5 @@ +Библиотека argparse является стандартным инструментом для обработки аргументов командной строки в Python. **Она позволяет легко определить и обработать аргументы, передаваемые программе при запуске из командной строки.** + +В данном примере мы просто выводим значения аргументов на экран, но в реальном приложении вы можете использовать их для выполнения конкретных действий, например, **чтения входного файла, обработки данных и записи результата в выходной файл.** + +![[telegram-cloud-photo-size-4-6016931255788092228-y.jpg]] diff --git a/PYTHON/dir().md b/PYTHON/dir().md new file mode 100644 index 0000000..1fb2745 --- /dev/null +++ b/PYTHON/dir().md @@ -0,0 +1,17 @@ +**Функция dir()** + +![[telegram-cloud-photo-size-4-5850234924966199387-w.jpg]] + +_Функция dir() используется для получения списка атрибутов и методов объекта._ + +Она принимает объект в качестве аргумента и возвращает список его атрибутов и методов в виде строк. + + + +Для модулей dir() возвращает список определенных в нем имен. Для классов возвращаются его атрибуты, методы и внутренние функции. + + + +С помощью dir() можно получить список ключевых слов языка, передав builtin или builtins в качестве аргумента. + +Без аргументов dir() возвращает список имен, определенных в текущей области видимости.
\ No newline at end of file diff --git a/PYTHON/exec.md b/PYTHON/exec.md new file mode 100644 index 0000000..90c622f --- /dev/null +++ b/PYTHON/exec.md @@ -0,0 +1,6 @@ +**Как обратиться к коду из другого файла** + +Если вам нужно выполнить код, содержащийся в другом файле, вы можете использовать встроенную функцию exec. + +![[telegram-cloud-photo-size-2-5269638479933982570-y.jpg]] + diff --git a/PYTHON/hash() - хэширование.md b/PYTHON/hash() - хэширование.md new file mode 100644 index 0000000..6f9e143 --- /dev/null +++ b/PYTHON/hash() - хэширование.md @@ -0,0 +1,19 @@ +Хэш — это целое число фиксированного размера, которое идентифицирует определенное значение. Каждое уникальное значение должно иметь свой собственный хэш. + +Для хэширования значений есть встроенная (её не надо устанавливать или импортировать) функция hash(). Используется в основном для сравнения разных объектов — сравнивать хэши легче и выгоднее. + +Однако изменяемые объекты по типу списков и словарей нельзя хэшировать — интерпретатор выдаст ошибку. + +Кстати, есть две пасхалки. Хэш бесконечности равен первым цифрам числа Пи, а хэш Not a Number равен нулю. + +А ещё бывают коллизии: например, хэши чисел -1 и -2 одинаковы. + +```python +hash(42) # 42 +hash(1) # 1 +hash((1, 2, 3, 4)) # 590899387183067792 +hash('hi') # 2211653411235407625 +hash(float('inf')) # 314159 +hash(float 'nan' )) # 0 +hash(-1) == hash(-2) # True +``` diff --git a/PYTHON/itertools.md b/PYTHON/itertools.md new file mode 100644 index 0000000..b4b4141 --- /dev/null +++ b/PYTHON/itertools.md @@ -0,0 +1,8 @@ +**Перебираем все возможные 8 значные пароли состоящие из букв и цифр с помощью Python** + +В этом примере используется модуль itertools, который позволяет генерировать все возможные комбинации символов из списка characters длиной password_length. Затем перебираются все комбинации и выводятся на экран. + +Обратите внимание, что перебор всех возможных 8-значных паролей может занять большое количество времени, особенно если количество символов в characters большое. Будьте осторожны и убедитесь, что ваше намерение соответствует законам и этическим правилам. + +![[telegram-cloud-photo-size-2-5255812146984502723-y.jpg]] + diff --git a/PYTHON/keys() + &.md b/PYTHON/keys() + &.md new file mode 100644 index 0000000..b9de8ab --- /dev/null +++ b/PYTHON/keys() + &.md @@ -0,0 +1,8 @@ +**Находим общие ключи в двух словарях** + +В этом примере мы используем метод keys() для получения наборов ключей из словарей dict1 и dict2, а затем применяем оператор & для нахождения общих ключей. + +Обратите внимание, что как множества, так и словари не сохраняют порядок элементов/ключей, поэтому порядок результатов может быть произвольным. + +![[telegram-cloud-photo-size-2-5264765212471446942-y.jpg]] + diff --git a/PYTHON/nmcli - Работа с сетевыми интерфейсами.md b/PYTHON/nmcli - Работа с сетевыми интерфейсами.md new file mode 100644 index 0000000..4a8c1b5 --- /dev/null +++ b/PYTHON/nmcli - Работа с сетевыми интерфейсами.md @@ -0,0 +1,56 @@ + +`nmcli` — это удобный инструмент командной строки для управления сетевыми интерфейсами в Linux. С помощью Python его можно использовать для автоматизации таких задач, как: подключение к Wi-Fi, работа с Ethernet или настройки VPN. + +Для начала, убедимся, что nmcli установлен. Проверим его версию: + +`nmcli --version + +Затем установим библиотеку subprocess для работы с командами оболочки: + +`pip install subprocess + +➡️ **1. Получение информации о сетях:** + +```python +import subprocess + +def list_wifi(): + + result = subprocess.run(['nmcli', '-t', '-f', 'SSID,SIGNAL', 'dev', 'wifi'], + + stdout=subprocess.PIPE, text=True) + + print("Доступные сети:") + + print(result.stdout) + + + +list_wifi() +``` +Данный пример выведет список SSID и уровень сигнала доступных Wi-Fi сетей. + +➡️ **2. Подключение к WiFi с паролем:** + +```python +def connect_wifi(ssid, password): + + command = ['nmcli', 'dev', 'wifi', 'connect', ssid, 'password', password] + + result = subprocess.run(command, stdout=subprocess.PIPE, text=True) + + if result.returncode == 0: + + print(f"Успешно подключено к {ssid}") + + else: + + print(f"Ошибка подключения: {result.stdout}") + + + +connect_wifi("PyLinux_WIFI", "CoolPassword") +``` +В этом же примере, автоматически подключаемся к указанной сети. + +Это лишь пару примеров, которые призваны показать принцип работы. А дальше, вы можете использовать это для автоматизации подключения к сетям в скриптах, разработки своей утилиты для управления сетями или мониторинга состояния сети.
\ No newline at end of file diff --git a/PYTHON/pandas.md b/PYTHON/pandas.md new file mode 100644 index 0000000..748f66b --- /dev/null +++ b/PYTHON/pandas.md @@ -0,0 +1,2 @@ +![[telegram-cloud-photo-size-2-5350524228924991162-y.jpg]] + diff --git a/PYTHON/partition().md b/PYTHON/partition().md new file mode 100644 index 0000000..a7aa52a --- /dev/null +++ b/PYTHON/partition().md @@ -0,0 +1,8 @@ +Разбивает стороку по заданной подстроке. +После этого результат возвращается в виде кортежа. +При этом подстрока по которой осуществлялась разбика, тоже входит в кортеж. +```python +str1 = "I want to become a Python developer" +print(str1.partition(' want ')) +# ('I', ' want ', 'to become a Python developer') +``` diff --git a/PYTHON/poyo.md b/PYTHON/poyo.md new file mode 100644 index 0000000..b8028cc --- /dev/null +++ b/PYTHON/poyo.md @@ -0,0 +1,35 @@ +⚙️ **Библиотека Python: Poyo** + +**Poyo** — это лёгкая библиотека для разбора (парсинга) YAML-файлов в виде словарей Python. Она особенно полезна для простых конфигурационных файлов и легковесных проектов. + +Подходит для разработчиков, которым нужно быстро и без сложных зависимостей читать YAML-файлы в Python. + +⚙️ **Пример использования** +```python +import poyo + +yaml_content = """ +name: PyLinux +version: 1.0 +features: + - Linux + - Python + - DevOps +""" + +# Разбор YAML в словарь +parsed_data = poyo.parse_string(yaml_content) + +print(parsed_data) + +# Вывод: {'name': 'PyLinux', 'version': 1.0, 'features': ['Linux', 'Python', 'DevOps']} +``` +**➕ Преимущества:** + +🟢 Минималистичный парсер YAML +🟢 Без сложных зависимостей (в отличие от PyYAML) +🟢 Удобно для небольших конфигурационных файлов + +✔️ **Установка** + +`pip install poyo diff --git a/PYTHON/pprint().md b/PYTHON/pprint().md new file mode 100644 index 0000000..9fb735a --- /dev/null +++ b/PYTHON/pprint().md @@ -0,0 +1,17 @@ +_Функция pprint() предназначена для красивого форматированного вывода данных._ + +![[telegram-cloud-photo-size-4-5850234924966199390-w.jpg]] + +pprint() форматирует произвольные структуры данных, такие как вложенные списки, словари, кортежи для удобочитаемого вывода. + +По умолчанию выводит данные с отступами и переносами, чтобы структура была наглядной. + + + +Может принимать ширину вывода в количестве символов для лучшего форматирования. + +Имеет параметры depth и compact для настройки уровня вложенности и режима компактного вывода. + + + +Функционал pprint() доступен через модуль pprint в стандартной библиотеке.
\ No newline at end of file diff --git a/PYTHON/sleep().md b/PYTHON/sleep().md new file mode 100644 index 0000000..3c91c5e --- /dev/null +++ b/PYTHON/sleep().md @@ -0,0 +1,8 @@ +**Усыпляем программу** + +Иногда возникает необходимость остановить выполнение программы, чтобы можно было выполнить несколько других операций, или просто из-за требуемой утилиты. В такой ситуации может пригодиться функция sleep(), которая обеспечивает точный и гибкий способ остановки потока кода на любой период времени. + +В нашем примере время начала и время окончания будут напечатаны с задержкой в 6 секунд. + +![[telegram-cloud-photo-size-2-5363884033936834909-y.jpg]] + diff --git a/PYTHON/tabulate.md b/PYTHON/tabulate.md new file mode 100644 index 0000000..ad838f7 --- /dev/null +++ b/PYTHON/tabulate.md @@ -0,0 +1,12 @@ +**Библиотека Tabulate предназначена для красивого вывода табличных данных.** Она поддерживает различные форматы таблиц и позволяет легко выводить данные в виде таблицы с заголовками и разделителями. + +**Основные возможности библиотеки Tabulate:** + +- Вывод табличных данных в различных форматах (plain, simple, grid, pipe, orgtbl, tsv, html, latex, ts, и др.). +- Автоматическое определение заголовков таблицы. +- Поддержка разных типов данных (числа, строки, списки, кортежи, словари и др.). + +В результате работы кода из примера выводится таблица с данными о фруктах, их цене и количестве, оформленная в формате _"grid"_. + +![[telegram-cloud-photo-size-4-5776315316576566340-y.jpg]] + diff --git a/PYTHON/Библиотеки для ленивых.md b/PYTHON/Библиотеки для ленивых.md new file mode 100644 index 0000000..66f7096 --- /dev/null +++ b/PYTHON/Библиотеки для ленивых.md @@ -0,0 +1,83 @@ +👩💻 когда хочется больше делать с меньшими усилиями + +Каждый программист знает: кодить можно быстрее, если использовать проверенные решения. В Python куча библиотек, которые делают работу за тебя — не нужно изобретать велосипед. + + 1. **`pathlib` — путь к лёгкости работы с путями** + +Ты всё ещё юзаешь os.path? Пора остановиться. pathlib — это объектно-ориентированная альтернатива, которая позволяет работать с путями проще и чище. + +```python +from pathlib import Path + +p = Path("/home/user/docs") +print(p / "file.txt") # /home/user/docs/file.txt +``` + +👉 Это значительно читаемее и проще, чем старые подходы с os.path. + + 2. **`requests` — HTTP без головной боли** + +HTTP-запросы — одна из самых часто используемых операций, но с ними можно запутаться. requests делает этот процесс понятным и удобным. + +```python +import requests + +response = requests.get('https://api.example.com/data') +print(response.json()) +``` + +👉 Всё! Нет лишнего кода, никаких ненужных сложностей. + + 3. **`pydantic` — валидация данных с минимальными усилиями** + +Если тебе нужно проверять данные, а писать вручную кучу кода — не хочется, используй pydantic. Эта библиотека позволяет быстро проверять и валидировать данные, обрабатывая JSON, параметры и модели. + +```python +from pydantic import BaseModel + +class User(BaseModel): + name: str + age: int + +user = User(name="Alice", age=30) +print(user) +``` + +👉 Валидация без лишних усилий и работы с типами. Всё автоматически. + + 4. **`attrs` — создание классов без ненужного кода** + 5. +Если ты создаёшь классы, но не хочешь писать лишние методы вроде __init__, __repr__, __eq__, то тебе в помощь attrs. Эта библиотека позволяет легко создавать классы с автоматическим заполнением всех необходимых методов. + +```python +import attr + +@attr.s +class Point: + x = attr.ib() + y = attr.ib() + +p = Point(3, 4) +print(p) # Point(x=3, y=4) +``` + +👉 Быстро, удобно, без лишних усилий. + + 5. **`click` — консольные приложения без мук** + +Создание CLI-интерфейсов может быть утомительным, но с click это делается быстро и без боли. Ты можешь создавать простые и мощные консольные команды с минимальными усилиями. + +```python +import click + +@click.command() +@click.argument('name') +def greet(name): + click.echo(f"Hello, {name}!") + +greet() +``` + +👉 Лёгкая настройка, удобная обработка аргументов и флагов. + +🗣️ **Запомни**: используй библиотеки, чтобы минимизировать шаблонный код, повысить читаемость и улучшить производительность разработки.
\ No newline at end of file diff --git a/PYTHON/Возврат нескольких значений.md b/PYTHON/Возврат нескольких значений.md new file mode 100644 index 0000000..b1e54e0 --- /dev/null +++ b/PYTHON/Возврат нескольких значений.md @@ -0,0 +1,5 @@ +из функции с помощью именованных кортежей: + +![[telegram-cloud-photo-size-2-5406976059292904279-x.jpg]] + +По умолчанию функция возвращает одно значение и останавливает выполнение функции. Есть несколько способов вернуть несколько значений. Например, вы можете использовать именованный кортеж из модуля collections. Это поможет вам определить структуру возвращаемых значений. В коде на картинке мы создаем именованный кортеж и назначаем поля: имя, возраст и автомобиль, а затем присваиваем эти значения переменным вне функции!
\ No newline at end of file diff --git a/PYTHON/Генерируем QR-код.md b/PYTHON/Генерируем QR-код.md new file mode 100644 index 0000000..d5dde9c --- /dev/null +++ b/PYTHON/Генерируем QR-код.md @@ -0,0 +1,10 @@ +Необходимый пакет ставится командой +`pip install qrcode +. Также должна быть установлена библиотека pillow. + +Программа с картинки создаёт QR-код, который при сканировании ведёт пользователя на сайт google, и сохраняет его в новую картинку. + +Теперь вы можете генерировать QR-коды в своих приложениях. + +![[Pasted image 20250501031129.png]] + diff --git a/PYTHON/Контекстные менеджеры.md b/PYTHON/Контекстные менеджеры.md new file mode 100644 index 0000000..f685d3b --- /dev/null +++ b/PYTHON/Контекстные менеджеры.md @@ -0,0 +1,8 @@ +Контекстные менеджеры позволяют задать поведение при работе с конструкцией with: при входе и выходе из блока. Это упрощает работу с ресурсами в части их захвата и освобождения; транзакциями, когда нужно либо полностью закончить транзакцию, либо откатить ее целиком. + +Простейшей функцией, использующей данный принцип, является функция open. Каждый раз, когда мы открываем файл, нам необходимо его закрыть, чтобы вытолкнуть выходные данные на диск (на самом деле Python вызывает метод close автоматически, но явное его использование является хорошим тоном). + +Чтобы каждый раз не вызывать метод close, мы можем воспользоваться контекстным менеджером функции open, который автоматически закроет файл после выхода из блока. + +![[Pasted image 20250501031423.png]] + diff --git a/PYTHON/Копируем файл в другой каталог.md b/PYTHON/Копируем файл в другой каталог.md new file mode 100644 index 0000000..75026af --- /dev/null +++ b/PYTHON/Копируем файл в другой каталог.md @@ -0,0 +1,4 @@ +Метод ==Shutil.copy()== в Python используется для копирования содержимого исходного файла в целевой файл или каталог. Он также сохраняет режим разрешений файла, но другие метаданные файла, такие как время создания и изменения файла, не сохраняются. + +![[Pasted image 20250501030617.png]] + diff --git a/PYTHON/Множественный ввод.md b/PYTHON/Множественный ввод.md new file mode 100644 index 0000000..3098ec8 --- /dev/null +++ b/PYTHON/Множественный ввод.md @@ -0,0 +1,2 @@ +![[2025-02-20 04.18.59.jpg]] + diff --git a/PYTHON/Моржовый оператор.md b/PYTHON/Моржовый оператор.md new file mode 100644 index 0000000..23697c9 --- /dev/null +++ b/PYTHON/Моржовый оператор.md @@ -0,0 +1,17 @@ +```python +# Добавлен в Python 3.8, позволяет присваивать значение прямо внутри выражения. + +# Без := : + +data = input("Введите число: ") +if data.isdigit(): + print(int(data) * 2) + +# С := становится короче: + +if (data := input("Введите число: ")).isdigit(): + print(int(data) * 2) + +# Полезно для сокращения кода в if и while! +``` + diff --git a/PYTHON/ОБНОВЛЕНИЕ до последней версии.md b/PYTHON/ОБНОВЛЕНИЕ до последней версии.md new file mode 100644 index 0000000..7297876 --- /dev/null +++ b/PYTHON/ОБНОВЛЕНИЕ до последней версии.md @@ -0,0 +1,98 @@ +```shell +python3 --version +sudo apt update && sudo apt upgrade -y +sudo add-apt-repository ppa:deadsnakes/ppa +sudo apt update +apt list | grep python3.12 + +sudo apt install python3.12 +python3.12 --version +# правильно создать вирт среду в приложении: +python3.12 -m venv env +source env/bin/activate +# можно создать псевдоним: +echo "alias py=/usr/bin/python3" >> ~/.bashrc # ~/.zshrc +echo "alias python=/usr/bin/python3" >> ~/.bashrc # ~/.zshrc +``` + +```shell +# Установка зависимостей Python: +sudo apt-get install build-essential checkinstall +sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev \ libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev zlib1g-dev + +# Скачивание Python: +cd /opt +sudo wget https://www.python.org/ftp/python/3.9.2/Python-3.9.2.tgz +sudo tar xzf Python-3.9.2.tgz + +# Установка из исходных кодов: +cd Python-3.9.2$ sudo ./configure —enable-optimizations +sudo make altinstall + +# После того, как команды будут выполнены вы можете увидеть python3.9 в вашей директории /usr/local/bin/ +# Проверка версии Python +cd /usr/local/bin/ +ls + +# Включение альтернативной версии Python +update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1 +update-alternatives --install /usr/bin/python3 python3 /usr/local/bin/python3.9 2 + +# Список установленных версий Python +update-alternatives --list python3 +/usr/bin/python3.6 +/usr/local/bin/python3.8 + +# Переключение между версиями +update-alternatives --config python3 + + +# CLI для переключения активной версии Python +Selection Path Priority Status +------------------------------------------------------------ + 0 /usr/bin/python3.8 3 auto mode +* 1 /usr/bin/python3.8 3 manual mode + 2 /usr/local/bin/python3.10 2 manual mode + +Press <enter> to keep the current choice[*], or type selection number: + + +# Установка pip3 +apt install python3-pip +python3.9 -m pip install --upgrade pip + +# Скажите Ubuntu что python это python3 +apt-get install python-is-python3 +``` + +```shell +# Запускайте скрипты на Python с помощью Shebang: + +#!/usr/bin/env python +print("Привет, мир!") + +chmod +x hello.py + +./hello.py + +# Запускать скрипты с аргументами скрипта: + +#!/usr/bin/env python +import argparse + +def main(): + parser = argparse.ArgumentParser(description='Скрипт, который приветствует пользователя.') + parser.add_argument('name', help='Имя человека, которого нужно поприветствовать') + args = parser.parse_args() + greet(args.name) + +def greet(name): + print(f'Hello, {name}!') + +if __name__ == '__main__': + main() +``` + +```shell +python hello.py John +``` diff --git a/PYTHON/ОТЛАДКА КОДА.md b/PYTHON/ОТЛАДКА КОДА.md new file mode 100644 index 0000000..df19336 --- /dev/null +++ b/PYTHON/ОТЛАДКА КОДА.md @@ -0,0 +1,32 @@ +🖥 **Как отлаживать Python-код, не выходя из редактора — с помощью встроенного модуля** pdb**.** + +Часто, когда код не работает как надо, мы начинаем закидывать print()-ами. Но это неудобно, медленно и мусорит код. Вместо этого вставь в нужное место строчку: + +```python +import pdb; pdb.set_trace() +``` +Когда выполнение дойдет до этой строки, ты попадешь в интерактивную консоль отладчика прямо в терминале. Дальше можно: + +- n (next) — перейти к следующей строке; +- s (step) — зайти внутрь функции; +- c (continue) — продолжить выполнение; +- l (list) — показать текущий контекст; +- p var — вывести значение переменной var. + +```python +def calc(a, b): + import pdb; pdb.set_trace() + result = a + b + return result + +calc(2, 3) +``` +На строке с ==`pdb.set_trace()`== ты остановишься и сможешь изучить, что происходит внутри. + +Зачем это нужно? + +- Понять, **почему что-то идет не так**. +- Посмотреть, **какие значения у переменных** прямо в момент ошибки. +- Быстро отладить **без запуска IDE** — удобно в Docker, SSH или при работе с cron. + +Попробуй — один раз освоишь, и уже не захочешь возвращаться к print().
\ No newline at end of file diff --git a/PYTHON/Программа-напоминалка на Python.md b/PYTHON/Программа-напоминалка на Python.md new file mode 100644 index 0000000..d0c28e1 --- /dev/null +++ b/PYTHON/Программа-напоминалка на Python.md @@ -0,0 +1,156 @@ +### Установка и импорт библиотек + +Перед написанием кода нам понадобится установить библиотеку **schedule** для планирования напоминаний и **plyer** для отображения системных уведомлений Windows. Сделать это можно через **pip**. Для этого нужно перейти в терминал/командную строку, прописать **pip install schedule plyer**, нажать Enter и ждать установки. + +```bash +pip install schedule plyer +``` + +После инсталляции импортируем все необходимые библиотеки: + +```python +import schedule +import time +from plyer import notification +``` + +- **schedule** — для планирования напоминаний; +- **time** — для работы с задержками; +- **plyer** — для отображения системных уведомлений. + +### Написание кода программы-напоминалки на Python + +Первым делом создадим функцию **show_notification()**, которая будет вызывать системное уведомление. + +У функции будет присутствовать один параметр, а именно: + +- message — текст оповещения, переданный пользователем. + +```python +import schedule +import time +from plyer import notification + + +def show_notification(message): +``` + +Внутри функции через объект **notification** обратимся к методу **notify()**, чтобы создать уведомление с заголовком «Напоминание», переданным пользователем текстом и длительностью равной 10 секунд: + + +```python +import schedule +import time +from plyer import notification + + +def show_notification(message): + notification.notify( + title="Напоминание", + message=message, + timeout=10 + ) +``` + +Теперь создадим функцию **add_reminder()**, которая будет создавать напоминания. + +У функции будет присутствовать два параметра, а именно: + +- text — текст напоминания; +- reminder_time — время, в которое должно сработать напоминание. + +```python +import schedule +import time +from plyer import notification + + +def show_notification(message): + notification.notify( + title="Напоминание", + message=message, + timeout=10 + ) + + +def add_reminder(text, reminder_time): +``` + +Внутри функции будет добавляться задача в планировщик, чтобы каждый день в указанное время **reminder_time** вызывалась функция **show_notification()** с переданным текстом: + +```python +import schedule +import time +from plyer import notification + + +def show_notification(message): + notification.notify( + title="Напоминание", + message=message, + timeout=10 + ) + + +def add_reminder(text, reminder_time): + schedule.every().day.at(reminder_time).do(show_notification, message=text) + print(f"Напоминание добавлено на {reminder_time}: {text}") +``` + +Теперь добавим точку входа, внутри которой будет находиться бесконечный цикл: + +```python +import schedule +import time +from plyer import notification + + +def show_notification(message): + notification.notify( + title="Напоминание", + message=message, + timeout=10 + ) + + +def add_reminder(text, reminder_time): + schedule.every().day.at(reminder_time).do(show_notification, message=text) + print(f"Напоминание добавлено на {reminder_time}: {text}") + + +if __name__ == "__main__": + while True: +``` + +Внутри бесконечного цикла пользователю будет предложено ввести текст напоминания и время, в которое должно произойти напоминание. Далее будет идти вложенный бесконечный цикл, который ожидает выполнения напоминания. + +```python +import schedule +import time +from plyer import notification + + +def show_notification(message): + notification.notify( + title="Напоминание", + message=message, + timeout=10 + ) + + +def add_reminder(text, reminder_time): + schedule.every().day.at(reminder_time).do(show_notification, message=text) + print(f"Напоминание добавлено на {reminder_time}: {text}") + + +if __name__ == "__main__": + while True: + text = input("Введите текст напоминания: ") + reminder_time = input("Введите время (формат ЧЧ:ММ): ") + add_reminder(text, reminder_time) + print("Ожидание напоминания...") + + while True: + schedule.run_pending() + time.sleep(1) +```
\ No newline at end of file diff --git a/PYTHON/Протоколы.md b/PYTHON/Протоколы.md new file mode 100644 index 0000000..cb5465a --- /dev/null +++ b/PYTHON/Протоколы.md @@ -0,0 +1,13 @@ +![[telegram-cloud-photo-size-4-5850234924966199391-w.jpg]] + +_Протоколы — это соглашения, которые определяют интерфейс класса и поведение его объектов._ + +Протоколы задаются с помощью специальных методов, таких как str, len и другие. + +Они позволяют классам работать с различными функциями и операторами языка. + +Реализуя протоколы можно интегрировать классы в языке и делать их поведение естественным и интуитивным. + +В Python есть протоколы для чисел, итераторов, контекстных менеджеров, атрибутов и других областей. + +Встроенные и сторонние библиотеки полагаются на стандартные протоколы.
\ No newline at end of file diff --git a/PYTHON/Своя библиотека для Python.md b/PYTHON/Своя библиотека для Python.md new file mode 100644 index 0000000..d1c365a --- /dev/null +++ b/PYTHON/Своя библиотека для Python.md @@ -0,0 +1,161 @@ +Часто в проектах на Python мы используем внешние библиотеки — уже готовые скрипты с нужными нам функциями, которые упрощают разработку. Теперь настало время сделать свою первую библиотеку на Python с нуля. + +Есть несколько способов собрать свою библиотеку — и все они рабочие. Мы выбрали способ, который рекомендует Гвидо ван Россум — автор языка Python, но вы можете выбрать любой другой подход. + +## Подготовка + +Чтобы сборка библиотеки прошла без проблем, обновим менеджер пакетов pip. Для этого в командной строке выполним команду: + +```markup +pip install --upgrade pip +``` + +Теперь установим пакет twine — он нам поможет опубликовать свою библиотеку в сети, чтобы её мог установить любой желающий: + +```markup +pip install --upgrade twine +``` + +Затем поставим `hatchling` — пакет для сборки серверной части библиотеки: + +```markup +pip install hatchling +``` + +И наконец, установим пакет `build`, чтобы создать загрузочные архивы с нашей библиотекой: + +```markup +pip install --upgrade build +``` + +## Структура библиотеки + +Библиотека в Python — это не просто один скрипт, а целый проект, в котором есть много разных файлов, каждый из которых отвечает за что-то своё. + +Первое, что делаем, — создаём папку `src`, в которой будет лежать наша новая библиотека. После этого внутри этой папки создаём ещё одну — с именем создаваемой библиотеки. Как мы назовём эту вложенную папку, так и будет называться наша библиотека. Для проекта мы выбрали название `beginning_package`. + +Теперь внутри папки с названием создаём два файла: + +- __init__.py — файл, который покажет компьютеру, что beginning_package — это не просто папка, а целый модуль с определённой функциональностью; +- main.py — тут будет код нашей библиотеки. + +Теперь делаем остальной обвес и внутри основной папки создаём такое: + +- папку tests — если к библиотеке делаются тесты, они отправляются сюда. У нас их пока не будет, но папка, даже пустая, всё равно нужна, таковы правила; +- LICENSE — файл с лицензией. Расширения нет, только имя; +- pyproject.toml — отвечает за конфигурацию и серверную часть библиотеки; +- README.md — информация о библиотеке: что она делает, зачем нужна и какие у неё особенности. + +В итоге по структуре должно получиться что-то вроде этого: + + + +Тут ещё есть папка venv для виртуального окружения, но нам она сегодня не понадобится + +## Оформляем лицензию и файл README.md + +Наша библиотека будет относиться к свободному ПО, поэтому в файл с лицензией мы вставляем стандартный текст MIT License. В первой строке указываем копирайт и название проекта: + +Текст лицензии + + + +Точно так же заполняем файл README.md. Обычно здесь показывают, какие функции доступны в пакете, причём здесь уже можно использовать маркдаун для оформления текста. У нас учебная библиотека, поэтому сделаем максимально простое описание: + +```markup +This library is not that useful. Perhaps someday it will become great. +``` + +(Это не очень полезная библиотека. Возможно, когда-нибудь она станет лучше) + + + +## Заполняем pyproject.toml + +Рyproject.toml — это файл, который отвечает за служебную часть библиотеки: как она называется, какая версия, какая версия Python нужна, по какой лицензии распространяется и так далее. Когда мы хотим установить новую библиотеку, система сначала смотрит в этот файл и проверяет, соответствует ли ваша версия Python той, что нужна для работы. + +Файл состоит из блоков, каждый из которых отвечает за что-то своё. Начнём с блока `build-system` — он отвечает за параметры сборки загрузочного архива. В нашем проекте за серверную часть отвечает `Hatchling`, поэтому указываем его: + +```python +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" +``` + +Python + +Copy + +Дальше идут основные параметры проекта: название, выпуск, авторы, описание, имя файла readme, требования к версии Python, язык программирования, тип лицензии и совместимость с ОС: + +```python +[project] +name = "" +version = "0.0.1" +authors = [ + { name="", email="" }, +] +description = "" +readme = "README.md" +requires-python = ">=3.10.0" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +``` + +Python + +Copy + +В конце можно добавить разные ссылки, например, на репозиторий в GitHub, домашнюю страницу проекта или на дополнительные материалы: + +```python +[project.urls] +Homepage = "" +Issues = "" +``` + +Python + +Copy + +## Пишем библиотеку + +В папке с библиотекой два файла — `__init.py__` и `main.py`. Файл `__init__.py` можно оставить пустым — он нужен для того, чтобы сборщик понял, что это именно цельный проект и его нужно собрать из других файлов в той же папке. + +В `main.py` пишем сам код библиотеки. Так как это самая первая и учебная наша библиотека, сделаем там только одну функцию с приветствием: + +```python +def greeting(): + return "Hello, I'm your first library!" +``` + +Python + +Copy + +## Создаём загрузочные архивы + +Цель этого этапа — собрать и подготовить библиотеку к загрузке в менеджер пакетов `pip`, откуда каждый сможет её установить. Для этого открываем командную строку, переходим в папку с проектом и собираем архив: + +```markup +py -m build +``` + + + +Если в проводнике мы посмотрим на структуру папок, то увидим, что там появилась новая папка — `dist`, в которой и хранятся наши подготовленные для загрузки архивы: + + + +Заглянем внутрь: + + + +## Создаём токен для подписи библиотеки + +PyPi — это хранилище открытого программного обеспечения для Python. Чтобы опубликовать там библиотеку, нужно иметь аккаунт и получить уникальный API-токен. + +Для этого заходим на официальный сайт [pypi.org](https://pypi.org/), заводим новый аккаунт, привязываем Гугл-аутентификатор для безопасности и переходим [в настройки аккаунта](https://pypi.org/manage/account/). Там прокручиваем страницу вниз до блока API tokens:
\ No newline at end of file diff --git a/PYTHON/Строим линейные графики с помощью Python.md b/PYTHON/Строим линейные графики с помощью Python.md new file mode 100644 index 0000000..e8e887c --- /dev/null +++ b/PYTHON/Строим линейные графики с помощью Python.md @@ -0,0 +1,6 @@ +Визуализация данных в виде линейного графика — максимально простая задача. Достаточно передать объект в качестве аргумента функции plot() для получения графика с несколькими линиями. + +![[Pasted image 20250501031545.png]] + +![[Pasted image 20250501031558.png]] + diff --git a/PYTHON/Тест кода в терминале.md b/PYTHON/Тест кода в терминале.md new file mode 100644 index 0000000..f541790 --- /dev/null +++ b/PYTHON/Тест кода в терминале.md @@ -0,0 +1,27 @@ +⚙️ **Тестируй код прямо в терминале с** python -i + +🟢 **Запуск файла Python с режимом интерактивного тестирования:** + +```sh +python -i script.py +``` + +После выполнения script.py ты можешь вручную проверять переменные и вызывать функции прямо в консоли! + +⚙️ **Пример использования:** + +x = 10 +y = x * 2 + +**Запускаем:** + +```sh +python -i script.py +``` + +➡️ **Теперь в консоли доступна переменная** y**, можно тестировать:** + +```sh +print(y) # 20 +``` +🗣 **Отличный способ отладки кода на лету!** diff --git a/PYTHON/Улучшаем код на Python.md b/PYTHON/Улучшаем код на Python.md new file mode 100644 index 0000000..0336296 --- /dev/null +++ b/PYTHON/Улучшаем код на Python.md @@ -0,0 +1,650 @@ +![[telegram-cloud-photo-size-2-5382031937209757544-y.jpg]] + +![[telegram-cloud-photo-size-2-5382031937209757546-y.jpg]] + +![[telegram-cloud-photo-size-2-5399847483028401010-y.jpg]] + + +Уже почти никто не называет свои переменные непонятными буквами и имена функциям стараются давать начинающиеся с глагола, однако из раза в раз замечаю общие для многих ~~ошибки~~ зоны роста. + +======================================= +Например, до сих пор встречается такой зверь: + +```python +admins = [] +for user in get_users(): + if user.is_admin: + admins.append(user) +``` + +### 1. Использовать comprehension + +Код выше можно написать короче, используя генератор списка. + +```python +admins = [user for user in get_users() if user.is_admin] +``` + +Более того, такое решение будет работать немного быстрее, так как comprehension-ы - оптимизированы для создания объектов. Не забываем также, что подобным образом можно создавать словари и множества. + +Эта же конструкция признается сообществом как более _pythonic way_ для фильтрации + +```python +# Вместо +active_users = list(filter(lambda user: user.is_active, get_users())) + +# Лучше +active_users = [user for user in get_users() if user.is_active] +``` + +### 2. Вложенные циклы for в comprehension + +Предположим, у нас есть задача сформировать список комбинаций элементов двух коллекций. + +```python +numbers = ["1", "2", "3"]letters = ["A", "B", "C"] +# Хотим ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'] +``` + +Даже знакомые с генераторами списков могу не знать о небольшом сахаре и написать вложенный цикл. + +```python +spots = []for letter in letters: + for number in numbers: + spots.append(letter + number) + +# Хотя и можно +такspots = [letter + number for letter in letters for number in numbers] +``` + +### 3. Логические операторы вместо тернарного + +Логические операторы часто можно использовать для задания значения переменной. Многие знакомы с тернарным оператором и избегают лишних if-else, однако и от первого можно уйти в некоторых случаях. + +```python +# Есть переменная, которая может быть не заполнена +user: User | None + +# Задаем админа тернарником +admin = user if user else get_user() + +# Но можно и короче +admin = user or get_user() + +# Цепочку можно продолжать. Вернется первое приводимое к True значение +admin = get_admin() or user or get_user() +``` + +### 4. Использование оптимизации проверки условий + +Если python явно узнаёт значение условия, он не производит дальнейших проверок. + +Пример. Мы хотим убедиться, что последний из аппрувнувших людей обладает админскими правами. Но список аппрувнувших может быть и пустым. + +Чтобы не получить `"IndexError: list index out of range"`, +часто вижу, что начинающие разработчики сначала проверяют список на пустоту + +```python +approvers: list[User] = []if approvers: + if approvers[-1].is_admin: + return Truereturn False +``` + +Однако мы можем и совместить условия, упростив наш код: + +```python +approvers = [] +if approvers and approvers[-1].is_admin: + return Truereturn False +``` + +Здесь, если `approvers` пустой, то общее условие уже никак не может быть `True`. Поэтому дальнейшие проверки не производятся, и за границы списка мы не выходим. + +Работает также и в обратную сторону для `or` + +```python +approvers = [] +# Если первое условие уже `True`, то часть с `or` не имеет значения +approved_by_admin = True +if approved_by_admin == True or approvers[-1].is_admin: + return Truereturn False +``` + +### 5. Использование магии bool + +Как ни странно, в python `True` и `1` равны. Как и `False` c `0` + +```python +>>> True == 1 +>>> True +>>> False == 0 +>>> True +``` + +Помимо багов, это может и здорово сыграть нам на руку. Снова пример. + +Представим, что мы решили бросить программирование и ударились ростовщичество. Математическое прошлое подсказывает, что нужна какая-то система принятия решения о выдаче кредита, и мы выбираем самую передовую из известных нам. +А именно: мы собираемся анализировать три показателя у заемщика. + +- наличие дома +- наличие машины +- наличие работы + +Если какие-либо два из них положительны, то клиент, скорее всего, кредит отдаст, поэтому мы дадим ему деньги. Приступаем! + +```python +has_a_house: + boolhas_as_car: + boolhas_a_job: + boolif has_a_house and has_a_car: + return Trueif has_a_house and has_a_job: + return Trueif has_a_car and has_a_job: + return True +``` + +Вроде работает, но смущает нездоровое количество if-ов... А если факторов будет не три, а 100, и для положительного ответа нужно будет набрать минимум 60 очков? Переписываем. + +```python +factors = [has_a_house, has_as_car, has_a_job] +total = 0 +for factor in factors: + if factor: + total += 1return total >= 2 +``` + +Уже лучше, но если мы воспользуемся тем, что булево `True` равняется единице, то получим изящный однострочник + +```python +return sum(factors) >= 2 +# здесь sum сложит все единицы. По сути, посчитает количество True. +``` + +### 6. Использование any и all + +Если бы в предыдущем примере мы захотели проверить условия на +- хотя бы одно +- все + +Мы бы просто могли воспользоваться конструкциями `any` и `all`соответственно + +```python +any([True, False, False]) +# Trueany([False, False, False]) +# Falseall([True, True, True]) +# Trueall([True, True, False]) +# False +``` + +### 7. Сниппет для проверки пути в JSON-е + +Когда мы работаем с JSON-ами внешних сервисов, элементы в них могут быть опциональны. Причем бывает так, что дерево пути может оборваться в любой момент. + +В данном примере хотим проверить наличие прав у пользователя на создание других пользователей. Для таких случаев сталкивался с таким вот кодом: + +```python +user_dict = { + "user": { + "username": "PetyaNagibator228", + "permissions": { + "create_troubles": True, + "create_users": True + } + } + } +if user := user_dict.get("user"): + if permissions := user.get("permissions"): + if permissions.get("create_users"): + return Truereturn False +``` + +Громоздко. Еще моржовые операторы эти. Было бы здорово обращаться к элементу прям по пути + +`path = "user.permissions.create_users"` + +Такое можно реализовать при помощи однострочника + +```python +path = "user.permissions.create_users" +reduce(dict.get, path.split('.'), user_dict) +# True +``` + +Однако у него есть несколько недостатков. + +- reduce у новичков вызывает недопонимания + +- решение не гибкое, если мы хотим иметь больше возможностей по обработке элементов + + +Возможно, более адаптируемым под Ваши нужды окажется прямолинейное решение: + +```python +def find(path: str, dict_: dict): + keys = path.split(".") + data = dict_ + for key in keys: + try: + data = data.get(key) + except AttributeError: + return None + return datafind("user.permissions.create_users", user_dict) + +# Truefind("user.permissions.no_key.create_users", user_dict)# None +``` + +### 8. Генераторы для экономии памяти и кода + +Уверен, что в любом уважающем себя курсе рассказывают про генераторы и про то, зачем они нужны. Однако это не мешает многим начинающим ребятам обходить пагинацию следующим образом: + +```python +j = { + "pagination": { + "page": 0, # текущая страница + "limit": 3 # кол-во элементов на странице + }, + "user_ids": [1, 2, 3] +} + +users = [] +limit = 3 +page = 0 +while True: + rsp = client.get_all_users(page=page, limit=limit) + if not rsp.get("user_ids"): + break + users.extend(rsp["user_ids"]) + page += 1return users +``` + +Здесь создается список пользователей и наполняется до тех пор, пока внешний сервис отдает данные. Как только пагинация будет исчерпана, возвращается созданный список. + +Используя подобную конструкцию, мы должны понимать, что весь итоговый список пользователей будет храниться в памяти. Что как минимум не очень эффективно. К тому же, существуют расходы на постоянное расширение данного списка на каждой итерации. + +Здесь как нельзя лучше находят свое применение те самые генераторы. + +``` +limit = 3page = 0while True: rsp = client.get_all_users(page=page, limit=limit) if not rsp.get("user_ids"): break yield rsp["user_ids"] page += 1 +``` + +Так мы возвращаем пользователей пачками, сэкономив ресурсы машины. + +### 9. Ограничение пагинации + +Существует такая вещь, как ограничение глубины рекурсии. Она нужна для избегания бесконечной вложенности. К сожалению, работая с внешними API, я не один раз сталкивался с багами, приводящими к бесконечным обходам пагинации. В примере выше цикл так и будет крутиться, если по какой-то причине не выполнится условие `break`. Например, если API будет всегда возвращать данные, не зависимо от передаваемого `page`. + +Для таких случаев не лишним было бы иметь некий предохранитель, которым часто пренебрегают. + +``` +max_pages = 5for i in range(max_pages + 1): rsp = client.get_all_users(page=page, limit=limit) if not rsp.get("user_ids"): break if i >= max_pages: raise RuntimeError("Too many pagination elements") yield rsp["user_ids"] +``` + +### 10. А так же... + +Последним пунктом хотел бы привести похожую [статью](https://habr.com/ru/articles/466441/), обозревающую частые ошибочные решения разработчиков. + +### Итог + +Конечно же, данный список не является исчерпывающим. Он также ни в коем разе не является никаким «топом самых страшных вещей в мире программирования» и весьма субъективен. Даже наоборот, я старался собрать больше «мелочи», незаслуженно обделяемые вниманием начинающих разработчиков. + +Благодарю прочитавших статью и желаю всем никогда не останавливаться на пути развития! + +========================== + +# Глючный код на Python: 10 самых распространенных ошибок, которые допускают разработчики + +6 сен 2019 в 15:49 + +### О Python + + +Python — это интерпретируемый, объектно-ориентированный язык программирования высокого уровня с динамической семантикой. Встроенные структуры данных высокого уровня в сочетании с динамической типизацией и динамическим связыванием делают его очень привлекательным для БРПС (быстрой разработки прикладных средств), а также для использования в качестве скриптового и связующего языка для подключения существующих компонентов или сервисов. Python поддерживает модули и пакеты, тем самым поощряя модульность программы и повторное использование кода. + + +### О данной статье + + +Простота и легкость в освоении данного языка может ввести разработчиков в заблуждение (особенно тех, кто еще только начинает изучать Python), так что можно упустить из виду некоторые важные тонкости и недооценить силу разнообразия возможных решений с помощью Python. + +Имея это в виду, в этой статье представлен «топ-10» тонких, трудных для обнаружения ошибок, которые могут допустить даже продвинутые разработчики Python. + + +### Ошибка № 1: неправильное использование выражений в качестве значений по умолчанию для аргументов функций + + +Python позволяет указывать, что у функции могут быть необязательные аргументы, путем задания для них значения по умолчанию. Это, конечно, очень удобная особенность языка, но может привести к неприятным последствиям, если тип такого значения будет изменяемым. Например, рассмотрим следующее определение функции: + + +``` +>>> def foo(bar=[]): # bar - это необязательный аргумент # и по умолчанию равен пустому списку.... bar.append("baz") # эта строка может стать проблемой...... return bar +``` + + +Распространенная ошибка в данном случае — это думать, что значение необязательного аргумента будет устанавливаться в значение по умолчанию каждый раз, как функция будет вызываться без значения для этого аргумента. В приведенном выше коде, например, можно предположить, что повторно вызывая функцию foo() (то есть без указания значения для агрумента bar), она всегда будет возвращать «baz», поскольку предполагается, что каждый раз, когда вызывается foo () (без указания аргумента bar), bar устанавливается в [ ] (т. е. новый пустой список). + +Но давайте посмотрим что же будет происходить на самом деле: + + +``` +>>> foo()["baz"]>>> foo()["baz", "baz"]>>> foo()["baz", "baz", "baz"] +``` + + +А? Почему функция продолжает добавлять значение по умолчанию «baz» к существующему списку каждый раз, когда вызывается foo(), вместо того, чтобы каждый раз создавать новый список? + +Ответом на данный вопрос будет более глубокое понимание того, что творится у Python «под капотом». А именно: значение по умолчанию для функции инициализируется только один раз, во время определения функции. Таким образом, аргумент bar инициализируется по умолчанию (т. е. пустым списком) только тогда, когда foo() определен впервые, но последующие вызовы foo() (т. е. без указания аргумента bar) продолжат использовать тот же список, который был создан для аргумента bar в момент первого определения функции. + +Для справки, распространенным «обходным путем» для этой ошибки является следующее определение: + + +``` +>>> def foo(bar=None):... if bar is None: # or if not bar:... bar = []... bar.append("baz")... return bar...>>> foo()["baz"]>>> foo()["baz"]>>> foo()["baz"] +``` + + + +### Ошибка № 2: неправильное использование переменных класса + + +Рассмотрим следующий пример: + + +``` +>>> class A(object):... x = 1...>>> class B(A):... pass...>>> class C(A):... pass...>>> print A.x, B.x, C.x1 1 1 +``` + + +Вроде все в порядке. + + +``` +>>> B.x = 2>>> print A.x, B.x, C.x1 2 1 +``` + + +Ага, все как и ожидалось. + + +``` +>>> A.x = 3>>> print A.x, B.x, C.x3 2 3 +``` + + +Что за черт?! Мы же только изменили A.x. Почему же C.x тоже изменилось? + +В Python переменные класса обрабатываются как словари и следуют тому, что часто называют Порядком разрешения методов (MRO). Таким образом, в приведенном выше коде, поскольку атрибут x не найден в классе C, он будет найден в его базовых классах (только A в приведенном выше примере, хотя Python поддерживает множественное наследование). Другими словами, C не имеет своего собственного свойства x, независимого от A. Таким образом, ссылки на C.x фактически являются ссылками на A.x. Это будет вызывать проблемы, если не обрабатывать такие случаи должным образом. Так что при изучении Python обратите особое внимание на аттрибуты класса и работу с ними. + + +### Ошибка № 3: неправильное указание параметров для блока исключения + + +Предположим, что у вас есть следующий кусок кода: + + +``` +>>> try:... l = ["a", "b"]... int(l[2])... except ValueError, IndexError: # To catch both exceptions, right?... pass...Traceback (most recent call last): File "<stdin>", line 3, in <module>IndexError: list index out of range +``` + + +Проблема здесь заключается в том, что выражение except не принимает список исключений, указанных таким образом. Скорее, в Python 2.x выражение «except Exception, e» используется для привязки исключения к необязательному второму заданному второму параметру (в данном случае e), чтобы сделать его доступным для дальнейшей проверки. В результате в приведенном выше коде исключение IndexError не перехватывается выражением except; скорее, вместо этого исключение заканчивается привязкой к параметру с именем IndexError. + +Правильный способ перехвата нескольких исключений с помощью выражения except — указать первый параметр в виде кортежа, содержащего все исключения, которые нужно перехватить. Кроме того, для максимальной совместимости используйте ключевое слово as, так как этот синтаксис поддерживается как в Python 2, так и в Python 3: + + +``` +>>> try:... l = ["a", "b"]... int(l[2])... except (ValueError, IndexError) as e: ... pass...>>> +``` + + + +### Ошибка № 4: непонимание правил области видимости Python + + +Область видимости в Python основана на так называемом правиле LEGB, которое является аббревиатурой Local (имена, назначенные любым способом внутри функции (def или lambda), и не объявленные глобальными в этой функции), Enclosing (имя в локальной области действия любых статически включающих функций (def или lambda), от внутреннего к внешнему), Global (имена, назначенные на верхнем уровне файла модуля, или путем выполнения global инструкции в def внутри файла), Built-in (имена, предварительно назначенные в модуле встроенных имен: open, range, SyntaxError ,...). Кажется достаточно просто, верно? Ну, на самом деле, есть некоторые тонкости в том, как это работает в Python, что подводит нас к общей более сложной проблеме программирования на Python ниже. Рассмотрим следующей пример: + + +``` +>>> x = 10>>> def foo():... x += 1... print x...>>> foo()Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fooUnboundLocalError: local variable 'x' referenced before assignment +``` + + +В чем проблема? + +Вышеуказанная ошибка возникает потому, что, когда вы присваиваете переменную в области видимости, Python автоматически считает ее локальной для этой области и скрывает любую переменную с аналогичным именем в любой вышестоящей области. + +Таким образом, многие удивляются, когда получают UnboundLocalError в ранее работающем коде, когда он модифицируется путем добавления оператора присваивания где-нибудь в теле функции. + +Эта особенность особенно сбивает разработчиков с толку при использовании списков. Рассмотрим следующий пример: + + +``` +>>> lst = [1, 2, 3]>>> def foo1():... lst.append(5) # Это работает нормально......>>> foo1()>>> lst[1, 2, 3, 5]>>> lst = [1, 2, 3]>>> def foo2():... lst += [5] # ... а вот это падает!...>>> foo2()Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fooUnboundLocalError: local variable 'lst' referenced before assignment +``` + + +А? Почему foo2 падает, в то время как foo1 работает нормально? + +Ответ такой же, как в предыдущем примере, но, по распространенному мнению, здесь ситуация более тонкая. foo1 не применяет оператор присваивания к lst, тогда как foo2 — да. Помня, что lst + = [5] на самом деле является просто сокращением для lst = lst + [5], мы видим, что мы пытаемся присвоить значение lst (поэтому Python предполагает, что он находится в локальной области видимости). Однако значение, которое мы хотим присвоить lst, основано на самом lst (опять же, теперь предполагается, что он находится в локальной области видимости), который еще не был определен. И мы получаем ошибку. + + +### Ошибка № 5: изменение списка во время итерации по нему + + +Проблема в следующем куске кода должна быть достаточно очевидной: + + +``` +>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> for i in range(len(numbers)):... if odd(numbers[i]):... del numbers[i] # BAD: Deleting item from a list while iterating over it...Traceback (most recent call last): File "<stdin>", line 2, in <module>IndexError: list index out of range +``` + + +Удаление элемента из списка или массива во время итерации по нему — это проблема Python, которая хорошо известна любому опытному разработчику программного обеспечения. Но, хотя приведенный выше пример может быть достаточно очевидным, даже опытные разработчики могут встать на эти грабли в гораздо более сложном коде. + +К счастью, Python включает в себя ряд элегантных парадигм программирования, которые при правильном использовании могут привести к значительному упрощению и оптимизации кода. Дополнительным приятным следствием этого является то, что в более простом коде вероятность попасться на ошибку случайного удаления элемента списка во время итерации по нему значительно меньше. Одна из таких парадигм — генераторы списков. Кроме того, понимание работы генераторов списков особенно полезны для избежания этой конкретной проблемы, как показано в этой альтернативной реализацией приведенного выше кода, которая прекрасно работает: + + +``` +>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all>>> numbers[0, 2, 4, 6, 8] +``` + + + +### Ошибка № 6: непонимание того, как Python связывает переменные в замыканиях + + +Рассмотрим следующий пример: + + +``` +>>> def create_multipliers():... return [lambda x : i * x for i in range(5)]>>> for multiplier in create_multipliers():... print multiplier(2)... +``` + + +Вы можете ожидать следующий вывод: + + +``` +02468 +``` + + +Но на самом деле вы получите вот что: + + +``` +88888 +``` + + +Сюрприз! + +Это происходит из-за поздней привязки в Python, которое заключается в том, что значения переменных, используемых в замыканиях, ищутся во время вызова внутренней функции. Таким образом, в приведенном выше коде всякий раз, когда вызывается какая-либо из возвращаемых функций, значение i ищется в окружающей области видимости во время ее вызова (а к тому времени цикл уже завершился, поэтому i уже был присвоен конечный результат — значение 4). + +Решение этой распространенной проблемы с Python будет таким: + + +``` +>>> def create_multipliers():... return [lambda x, i=i : i * x for i in range(5)]...>>> for multiplier in create_multipliers():... print multiplier(2)...02468 +``` + + +Вуаля! Мы используем здесь аргументы по умолчанию для генерации анонимных функций для достижения желаемого поведения. Некоторые назвали бы это решение элегантным. Некоторые — +тонким. Некоторые ненавидят подобные штуки. Но если вы разработчик Python, в любом случае, это важно понимать. + + +### Ошибка № 7: создание циклических зависимостей модуля + + +Допустим, у вас есть два файла, a.py и b.py, каждый из которых импортирует другой, следующим образом: + +В a.py: + + +``` +import bdef f(): return b.x print f() +``` + + +В b.py: + + +``` +import ax = 1def g(): print a.f() +``` + + +Сначала попробуем импортировать a.py: + + +``` +>>> import a1 +``` + + +Сработало просто отлично. Возможно, это вас удивляет. В конце концов, модули циклически импортируют друг друга и это, вероятно, должено быть проблемой, не так ли? + +Ответ заключается в том, что простое наличие циклического импорта модулей само по себе не является проблемой в Python. Если модуль уже был импортирован, Python достаточно умен, чтобы не пытаться повторно импортировать его. Однако, в зависимости от точки, в которой каждый модуль пытается получить доступ к функциям или переменным, определенным в другом, вы действительно можете столкнуться с проблемами. + +Итак, возвращаясь к нашему примеру, когда мы импортировали a.py, у него не было проблем с импортом b.py, поскольку b.py не требует, чтобы что-либо из a.py было определено во время его импорта. Единственная ссылка в b.py на a — это вызов a.f(). Но этот вызов в g() и ничего в a.py или b.py не вызывает g(). Так что все работает прекрасно. + +Но что произойдет, если мы попытаемся импортировать b.py (без предварительного импорта a.py, то есть): + + +``` +>>> import bTraceback (most recent call last): File "<stdin>", line 1, in <module> File "b.py", line 1, in <module> import a File "a.py", line 6, in <module> print f() File "a.py", line 4, in f return b.xAttributeError: 'module' object has no attribute 'x' +``` + + +Ой-ой. Это не хорошо! Проблема здесь в том, что в процессе импорта b.py он пытается импортировать a.py, который, в свою очередь, вызывает f(), который пытается получить доступ к b.x. Но b.x еще не было определено. Отсюда исключение AttributeError. + +По крайней мере, одно из решений этой проблемы довольно тривиально. Просто измените b.py, чтобы импортировать a.py в g(): + + +``` +x = 1def g(): import a # This will be evaluated only when g() is called print a.f() +``` + + +Теперь, когда мы его импортируем, все нормально: + + +``` +>>> import b>>> b.g()1 # Printed a first time since module 'a' calls 'print f()' at the end1 # Printed a second time, this one is our call to 'g' +``` + + + +### Ошибка № 8: пересечение имен с именами модулями стандартной библиотеки Python + + +Одна из прелестей Python — это множество модулей, которые поставляются «из коробки». Но в результате, если вы сознательно не будете за этим следить, можно столкнуться с тем, что имя вашего модуля может быть с тем же именем, что и модуль в стандартной библиотеке, поставляемой с Python (например, в вашем коде может быть модуль с именем email.py, который будет конфликтовать со модулем стандартной библиотеки с таким же именем). + +Это может привести к серьезным проблемам. Например, если какой-нибудь из модулей будет пытаться импортировать версию модуля из стандартной библиотеки Python, а у вас в проекте будет модуль с таким же именем, который и будет по ошибке импортирован вместо модуля из стандартной библиотеки. + +Поэтому следует проявлять осторожность, чтобы не использовать те же имена, что и в модулях стандартной библиотеки Python. Гораздо проще изменить название модуля в своем проекте, нежели подать запрос на изменение имени модуля в стандартной библиотеке и получить на него одобрение. + + +### Ошибка № 9: неспособность учесть различия Python 2 и Python 3 + + +Рассмотрим следующий файл foo.py: + + +``` +import sysdef bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2)def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e)bad() +``` + + +На Python 2 он отработает нормально: + + +``` +$ python foo.py 1key error1$ python foo.py 2value error2 +``` + + +Но теперь давайте посмотрим как он будет работать в Python 3: + + +``` +$ python3 foo.py 1key errorTraceback (most recent call last): File "foo.py", line 19, in <module> bad() File "foo.py", line 17, in bad print(e)UnboundLocalError: local variable 'e' referenced before assignment +``` + + +Что здесь только что произошло? «Проблема» в том, что в Python 3 объект в блоке исключения недоступен за его пределами. (Причина этого заключается в том, что в противном случае объекты в этом блоке будут сохраняться в памяти до тех пор, пока сборщик мусора не запустится и не удалит ссылки на них оттуда). + +Один из способов избежать этой проблемы — сохранить ссылку на объект блока исключения за пределами этого блока, чтобы он оставался доступным. Вот версия предыдущего примера, которая использует эту технику, тем самым получая код, который подходит как для Python 2, так и для Python 3: + + +``` +import sysdef bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2)def good(): exception = None try: bar(int(sys.argv[1])) except KeyError as e: exception = e print('key error') except ValueError as e: exception = e print('value error') print(exception)good() +``` + + +Запустим его в Python 3: + + +``` +$ python3 foo.py 1key error1$ python3 foo.py 2value error2 +``` + + +Ураааа! + + +### Ошибка № 10: неправильное использование метода __del__ + + +Допустим, у вас есть вот такой файл mod.py: + + +``` +import fooclass Bar(object): ... def __del__(self): foo.cleanup(self.myhandle) +``` + + +И вы пытаетесь сделать вот такое из другого another_mod.py: + + +``` +import modmybar = mod.Bar() +``` + + +И получите ужасный AttributeError. + +Почему? Потому что, как сообщается [здесь](https://mail.python.org/pipermail/python-bugs-list/2009-January/069209.html), когда интерпретатор отключается, глобальные переменные модуля все имеют значение None. В результате в приведенном выше примере, в момент вызова __del__, имя foo уже было установлено в None. + +Решением этой «задачи со звездочкой» будет использование atexit.register(). Таким образом, когда ваша программа завершает выполнение (то есть при нормальном выходе из нее), ваши handle'ы удаляются до того, как интерпретатор звершает работу. + +С учетом этого, исправление для приведенного выше кода mod.py может выглядеть примерно так: + + +``` +import fooimport atexitdef cleanup(handle): foo.cleanup(handle)class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle) +``` + + +Подобная реализация обеспечивает простой и надежный способ вызова любой необходимой очистки после обычного завершения программы. Очевидно, что решение о том, как поступить с объектом, который связан с имненем self.myhandle, остается за foo.cleanup, но, думаю, идею вы поняли. + + +![[telegram-cloud-photo-size-2-5386535536837126461-y.jpg]]
\ No newline at end of file diff --git a/PYTHON/ФУНКЦИИ.md b/PYTHON/ФУНКЦИИ.md new file mode 100644 index 0000000..73d132c --- /dev/null +++ b/PYTHON/ФУНКЦИИ.md @@ -0,0 +1,192 @@ +```python +def foo() -> int : + return x ** 2 + + +x = foo( 2 ) +print(x) # 5 + + +def foo( x : str, y : int ) -> str : + ........... + + +name = input('Ведите имя:') # - всегда строка! +print(dir(name)) # - вывод атрибутов + +# ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] +# +``` + +```python +# собираем все параметры в кортеж: + +def sum_nums(*args): + print(args) + print(type(args)) + print(args[0]) + return sum(args) + + +print(sum_nums(2, 3, 7)) +``` + +```python +# аргументы с ключевыми словами - порядок не важен: + +def get_post_info(name, post_qty): + info = f"{name} wrote {post_qty} posts" + return info + + +info = get_post_info(name='Vlapa', post_qty=50) +print(info) +``` + +```python +# объединение аргументов в DICT (только именованные): + +def get_post_info(**person): + print(person) + print(type(person)) + info = ( + f"{person['name']} wrote " + f"{person['post_qty']} posts" + ) + return info + +info = get_post_info(name='Vlapa', post_qty=50) +print(info) +``` + +```python +# значение по умолчанию: + +def mult_by(value, multiplier=1): + return value * multiplier + +print(mult_by(10, 2)) +print(mult_by(5)) + +``` + +```python +# + +from datetime import date + + +def get_weekday(): + return date.today().strftime('%A') + + +def create_new_post(post, weekday=get_weekday()): + post_copy = post.copy() + post_copy['created_on_weekday'] = weekday + return post_copy + + +initial_post = { + 'id': 234, + 'author': 'Vlapa', +} + +post_with_weekday = create_new_post(initial_post) +``` + +```python +# callback function + +def print_number_info(num): + """ + Print num information + Args: + num (int): Integer number + """ + if (num % 2) == 0: + print("Число четное") +else: + print("Число НЕ четное") + + +def print_square_num(num): + print("Square of the num is", num * num) + + +def process_number(num, callback_fn): + callback_fn(num) + + +entered_num = int(input("Введите любое целое число: ")) + +process_number(entered_num, print_number_info) +process_number(entered_num, print_square_num) +``` + +```python +print(dir()) # все переменные области +``` + +```python +def greeting(greet): + return lambda name: f"{greet}, {name}!" + + # def info(name): + # return f"{greet}, {name}!" + # return info + +morning_greeting = greeting("Good Morning") + +print(morning_greeting('Vlapa')) + +evening_greeting = greeting("Good Evening") + +print(evening_greeting('Vlapa')) +``` +====================================================== +### Голобурдин: + +```python +def chain_sum0(number): + result = number + def wrapper(number2=None): + nonlocal result + if number2 is None: + return result + result += number2 + return wrapper + return wrapper + + +def chain_sum00(number): + result = number + def wrapper(number2=None): + nonlocal result + try: + number2 = int(number2) + except TypeError: + return result + result += number2 + return wrapper + return wrapper + + +def chain_sum(number): + def wrapper(number2=None): + def inner(): + wrapper.result += number2 + return wrapper + logic = { + type(None): lambda: wrapper.result, + int: inner + } + return logic[type(number2)]() + wrapper.result = number + return wrapper + + +print(chain_sum(5)()) +print(chain_sum(5)(2)()) +print(chain_sum(5)(100)(-10)()) +``` + diff --git a/PyCharm.md b/PyCharm.md new file mode 100644 index 0000000..f89368c --- /dev/null +++ b/PyCharm.md @@ -0,0 +1,26 @@ +- JetBrains Toolbox App +- скачиваем и устанавливаем последний Python + - settings: +- editor - настраиваем шрифт размер и отступ между строками +- tools - включаем black и ставим все галки +- editior tabs - appearance - left +- tab limit = 50 + +```bash +pip list +pip install --upgrade pip +# покажет все пакеты +pip freeze +# копируем все зависимости в файл: +pip freeze > requirements.txt +# устанавиваем все зависимости из файла: +pip install -r requirements.txt + +``` +==ОТСТУПЫ:== +- Настройки - Editor - Code Style - HTML - ставим вместо 4 = 2 +- Применить: SHIFT_left х 2 = reformat (или: OPTION+COMMAND+L) + +==GIT:== +Настройки-Version Control-GIT +-Update : REBASE !!!
\ No newline at end of file diff --git a/RADIO/ds18b20.md b/RADIO/ds18b20.md new file mode 100644 index 0000000..199bfd3 --- /dev/null +++ b/RADIO/ds18b20.md @@ -0,0 +1,493 @@ +# Подключаем датчик температуры DS18B20 + +Продолжаем изучать датчики компании **DALLAS**. + +И сегодняшним представителем будет **DS18B20**, который является датчиком температуры. + +Измеряет температуру данный датчик в градусах по цельсию. Измерения могут быть как 9-битной дискретности, так и 12-битной. + +Распределяются данные биты следующим образом. + +Мы будем измерять температуру в 12-битном виде и самые младшие 4 бита будут хранить показания долей градуса, а остальные старшие 8 — сами градусы. + +Подключается данный датчик по однопроводной технологии (1-wire). + +Диапазон измерений — от -55 0C до +125 0C, но наивысшая точность показаний достигается в диапазоне от -10 0C до +85 0C/ + +Погрешность данного датчика — 0,5 градуса, поэтому нам нет смысла использовать все младшие биты и мы их будем просто отсекать. + +Режим 1-wire у нас не организован аппаратно в контроллере, поэтому мы будем весь протокол программировать. + +Чем удобен для нас данный датчик? Удобен он тем, что мы можем его в часовой модуль, использованный на прошлых занятиях просто впаять и использовать. Для этого на модуле выведен отдельный контакт. У датчика всего 3 ножки. 2 из них ножки питания и одна — ножка данных. + +Данный датчик существует в двух видах корпуса — Dip и TO-92. Мы будем использовать второй тип. + +Таких датчиков, как пишут, на 1 провод можно повесить несколько, но я не пробовал. + +В связи с этим у каждого датчика есть уникальный 64-битный код, записанный в его ROM, записанного постредством лазерных технологий. + +Но, так как мы будем использовать только один датчик, то мы будем к нему обращаться другим способом, не используя ROM. + +Мы подаём на датчик команду 44h последовательным кодом, тем самым заставляя датчик конвертировать температуру. + +Посмотрим регистры, в которых хранится значение температуры после преобразования + + + +В четырех младших битах младшего регистра хранятся доли градусов, в четырех старших, а также в трех младших битах старшего регистра — целые значения градусов. + +В остальных битах — знак. Если 0, то плюс, если 1 — то минус. + +Также в технической документации написано, что нужно обязательно подтянуть резистор на информационную ножку датчика к питанию. + +Также написан ряд временных и других характеристик, которые мы уже будем рассматривать по мере того, как будем писать код. + +Проект был создан новый и назван **My1820LCD**, все файлы проекта [**прошлого занятия**](https://narodstream.ru/avr-urok-19-sobiraem-chasy-na-mikrosxeme-ds1307-i-lcd-1602/) **MyClock1307LCD** были вставлены в проект, а код главного модуля также был скопирован в главный модуль нового проекта. Всё это сделано потому, что мы работаем с тем же модулем, просто мы также впаяли туда датчик температуры, а время мы считывать не перестанем, пусть часы также ходят. + +Добавим ещё файлы **DS18B20.c** и **DS18B20.h** для созадния библиотеки работы именно с датчиком температуры и заголовочный файл также подключим в main.h по нашему обыкновению, ну и его же подключим в одноименный c-файл. + +Сначала создадим функцию конвертирования температуры в файле DS18B20.c. Надобность данной функции обусловлена тем, что датчик нам передаёт показания в двух байтах в определённом виде, рассмотренном нами выше, который без преобразования будет непонятен + +//функция преобразования показаний датчика в температуру + +**int dt_check(void)** + +**{** + +**}** + +Создадим две переменных, которые нам пригодятся в дальнейшем в теле нашей функции + +int dt_check(void) + +{ + + **unsigned char bt;//переменная для считывания байта** + + **unsigned int tt=0;** + +Сразу сделаем прототип нашей функции в заголовочном файле + +#include «main.h» + +**int dt_check(void); //функция преобразования показаний датчика в температуру** + +Чтобы нам дальше писать тело функции, нам будет нужна ещё функция. Напишем её выше, чтобы не создавать прототип. Данная функция будет узнавать, есть ли датчик на шине + +//функция определения датчика на шине + +**char dt_testdevice(void) //dt — digital termomether | определим, есть ли устройство на шине** + +**{** + +**}** + +Теперь, прежде чем писать тело данной функции, самое время посмотреть схему подключения датчика к контроллера. Посмотрим опять же упрощённый вариант в протеусе (нажмите на картинку для увеличения изображения) + +[](https://narodstream.ru///wp-content/uploads/2016/12/Image02-11.png) + +Мы видим, что датчик своим информационным портом подключен к ножке порта МК PD1, также мы видим подтягивающее сопротивление на 4,7 килоом с данного провода на шину питания. + +И вот теперь на данной ножке мы должны как-то иммитировать определённые имплуьсы. То есть мы как-то должны инициализировать то ноль, то единицу. Делать мы это сможем благодаря тому, что у нас есть подтягивающий резистор. Мы будем её то отпускать от общей шины, то притягивать к ней. Соответственно, когда мы её отпустим, то через резистор на ней окажется высокий уровень, а когда подтянем к земле, то низкий. + +Теперь вопрос, как это всё сделать. А сделать это можно вполне с помощью определённого бита регистра DDRB, который отвечает, как мы знаем, за направление работы определённых ножек порта. Но мы воспользуемся тем, что когда мы объявляем ножку на выход, она у нас притягивается к земле, понятно что при условии, что соответствующий бит регистра PORTD будет также нулём. А если мы уже в соответствующий бит регистра DDRB записываем значение, при котором соответствующая этому биту ножка объявится на вход, то, соответственно, от общей шины она. ясное дело отпустится, иначе как контроллер определит её состояние. Ну и, как и было написано выше, на этой ножке засчет подтягивающего резистора установится высокий логический уровень. + +Также, начиная процесс обмена с датчиком, мы должны запретить прерывания, чтобы не получить искаженных данных. Также, прежде чем запретить прерывания, нам необходимо сохранить регистр статуса контроллера, или стек. Это регистр SREG. Поэтому в самом начале тела нашей функции проверки присутствии датчика на шине мы это и сделаем, сохранив его в определённую переменную, ну и затем уже запретим прерывания + +char dt_testdevice(void) //dt — digital termomether | определим, есть ли устройство на шине + +{ + + **char stektemp=SREG;// сохраним значение стека** + + **cli(); //запрещаем прерывания** + +Добавим ещё одну переменную в теле данной функции для возврата результата функции + +cli(); //запрещаем прерывание + +**char dt;** + +Теперь для удобства работы с ножками порта давайте напишем некоторые дефайновые замены в заголовочном файле DS18B20.h + +#include «main.h» + +**#define PORTTEMP PORTD** + +**#define DDRTEMP DDRD** + +**#define PINTEMP PIND** + +**#define BITTEMP 1** + +Также здесь давайте сделаем ещё некоторые макроподставновки + +#include «main.h» + +**#define NOID 0xCC //Пропустить идентификацию** + +**#define T_CONVERT 0x44 //Код измерения температуры** + +**#define READ_DATA 0xBE //Передача байтов ведущему** + +Здесь мы присваиваем удобные имена командам датчикам. + +Ну давайте их посмотрим. + +Первая команда **0xCC** + +Посмотрим теперь все тайминги инициализации датчика + + + +Здесь видно что мы здесь притягиваем шину, держим её в таком состоянии как минимум 480 микросекунд, затем отпускаем её, ждём максимум 60 мкс (мы подождём побольше — 65 мкс) ответа датчика, датчик должен нам ответить таким же притягиванием шины к земле за данное время. Если обнаружится ноль, то значит устройство на шине есть, а если шина так и останется висеть в воздухе, то данного датчика на шине нет. + +Ну. и на остове вышепрочитанного продолжим нашу функцию определения наличия датчика на шине + +char dt; + +**DDRTEMP |= 1<<BITTEMP; //притягиваем шину** + +**_delay_us(485); //задержка как минимум на 480 микросекунд** + +**DDRTEMP &= ~(1<<BITTEMP); //отпускаем шину** + +**_delay_us(65); //задержка как минимум на 60 микросекунд** + +**if ((PINTEMP & (1<<BITTEMP))==0)//проверяем, ответит ли устройство** + +**{** + + **dt=1;//устройство есть** + +**}** + +**else dt=0;//устройства нет** + +В условии у нас идёт проверка, опустилась ли шина. + +Затем мы стеку вернём его первоначальное состояние + +else dt=0;//устройства нет + +**SREG = stektemp;// вернем значение стека** + +Отдельной командой после этого разрешать прерывания не требуется, так как все эти команды как раз и управляют определёнными битами регистра **SREG**. Если прерывания были разрешены до сохранения стека, то они, ясное дело, опять разрешатся, так как все биты регистра мы возвращаем в прежнее положение. + +Дальше. судя по диаграмме, которая у нас находится выше, мы должны выдержать время максимум 480 микросекунд, но так как после последней задержки у нас уже прошло некоторое количество времени, то хватит и 420, тем более задержка эта не фиксированная, а максимальная. Ну, и добавим эту задержку в тело функции далее, и в оконцовке вернём результат функции. + + **SREG = stektemp;// вернем значение стека** + + **_delay_us(420); //задержка как минимум на 480 микросекунд, но хватит и 420, тк это с учетом времени прошедших команд** + + **return dt; //вернем результат** + +} + +Теперь по окончании написания кода данной функции, мы можем продолжить начатую в предыдущей части урока функцию преобразования. Но, всё-таки, чтобы её продолжить, нам нужна будет ещё одна немаловажная, а может и вообще самая важная функция в нашем проекте — функция считывания байта с датчика температуры. Создадим данную функцию, а также в её теле сразу объявим две локальные переменные + +**//функция чтения байта с устройства** + +**unsigned char dt_readbyte(void)** + +**{** + + **char c=0;** + + **char i;** + +**}** + +Прежде чем писать дальше тело данной функции, мы должны вспомнить что каждый байт у нас состоит из восьми бит, поэтому целесообразно для того, чтобы не нагромождать много кода в одну функцию, создать ещё одну выше для чтения отдельного бита с датчика температуры. Создадим пока каркас данной функции с комментарием, объясняющем назначение этой функции + +**//функция чтения бита с устройства** + +**char dt_readbit(void)** + +**{** + +**}** + +В данной функции мы также сохраним регистр статусов микроконтроллера, запретим прерывания и создадим переменную для дальнейшего возврата результата. Результат у нас будет в виде обычной знаковой 8-битной величины, по большому счёту мы в ней будем использовать только младший бит + +//функция чтения бита с устройства + +char dt_readbit(void) + +{ + + **char stektemp=SREG;// сохраним значение стека** + + **cli(); //запрещаем прерывание** + + **char bt; //переменная хранения бита** + +Для написания дальнейшего кода посмотрим временную диаграмму, как вообще читать бит с данного датчика. Данная диаграмма в технической документации универсальна и для чтения и для записи бита в устройство + + + +Мы здесь видим, что после притягивания шины к земле мы ждём минимум одну микросекунду, ну, подождём две микросекунды на всякий случай, перед этим притянув шину к земле + +char bt; //переменная хранения бита + +**DDRTEMP |= 1<<BITTEMP; //притягиваем шину** + +**_delay_us(2); //задержка как минимум на 2 микросекунды** + +Смотрим дальше. Мы должны шину отпустить от земли, а затем задержка для считывания результата. Здесь стоит общая задержка 15 микросекунд, так как мы уже две подождали, осталось нам 13 + +_delay_us(2); //задержка как минимум на 2 микросекунды + +**DDRTEMP &= ~(1<<BITTEMP); //отпускаем шину** + +**_delay_us(13);** + +После данной задержки результат уже на шине должен быть. Считаем его аналогично, как в предыдущей функции мы узнавали присутствие усройства на шине + +_delay_us(13); + +**bt = (PINTEMP & (1<<BITTEMP))>>BITTEMP; //читаем бит** + +До считывания следующего байта, судя по диаграмме мы ждём 45 микросекунд, затем возвращаем значение стека в стек и возвращаем результат функции считывания бита на шине + + **bt = (PINTEMP & (1<<BITTEMP))>>BITTEMP; //читаем бит** + + **_delay_us(45);** + + **SREG = stektemp;// вернем значение стека** + + **return bt; //вернем результат** + +} + +Теперь, используя данную функцию, допишем тело функции считывания с датчика целого байта, последовательно занося байты в переменную **c**, применяя битовый сдвиг, а затем ещё же и возвращая. + + **char i;** + + **for(i=0;i<8;i++)** + + **c|=dt_readbit()<<i; //читаем бит** + + **return c;** + +} + +Ну вот. Самая, можно сказать, важная функция написана. + +Продолжим функцию конвертирования считанных байтов + + **unsigned int tt=0;** + + **if(dt_testdevice()==1) //если устройство нашлось** + + **{** + + **}** + + **return tt;** + +} + +В теле условия по определению присутствия датчика на шине, прежде чем что-то считать с нашего датчика, мы должны ему будем передать команды. А чтобы их передать, мы должны будем написать также функцию отправки байта в устройство. + +давайте напишем функцию передачи байта в датчик, в её теле мы созадим локальную переменную для дальнейшего цикла передачи каждого байта + +**//функция записи байта на устройство** + +**void dt_sendbyte(unsigned char bt)** + +**{** + + **char i;** + +**}** + +Ну и, соответственно, чтобы нам передать байт, нам нужно сначала научиться передавать бит, так как без бита не бывает байта. Для этого выше создадим другую функцию, написав её теле пока стандартный код сохранения стека и запрета прерываний + +**//функция записи бита на устройство** + +**void dt_sendbit(char bt)** + +**{** + + **char stektemp=SREG;// сохраним значение стека** + + **cli(); //запрещаем прерывание** + +**}** + +Затем смотрим в диаграмму записи бита в устройство, которую мы смотрели в даташите датчика в предыдущей части. В ней видно, что мы должны также притянуть шину к земле, подождать минимум 1 микросекунду, мы опять же подождём две. Это нужно для того, чтобы датчик «успел понять» что шина притянулась. + +cli(); //запрещаем прерывание + +**DDRTEMP |= 1<<BITTEMP; //притягиваем шину** + +**_delay_us(2); //задержка как минимум на 2 микросекунды** + +Дальше мы уже будем решать, что именно мы будем посылать. если будем посылать 0, то мы шину отпускать не будем, а, если нам нужно послать 1, то будем шину отпускать + +_delay_us(2); //задержка как минимум на 2 микросекунды + +**if(bt)** + + **DDRTEMP &= ~(1<<BITTEMP); //отпускаем шину** + +Дальше мы ждём от 60 до 120 микросекунд, ну, подождём 65, как обычно. То есть если мы предаём ноль, то мы просто выжидаем с притянутой шиной данный временной интервал, а если передаём единицу, то делаем то же самое, но отпустив перед этим шину, так как сработает условие в коде. Затем мы отпускаем шину. То есть, если мы передаём единицу, то она уже у нас отпущена, если ноль, то отпускаем. ну и затем возвращаем сохраненное значение в стек + + if(bt) + + DDRTEMP &= ~(1<<BITTEMP); //отпускаем шину + + **_delay_us(65); //задержка как минимум на 60 микросекунд** + + **DDRTEMP &= ~(1<<BITTEMP); //отпускаем шину** + + **SREG = stektemp;// вернем значение стека** + +} + +Теперь продолжим, используя данную функцию, функцию записи (или отправки) байта в шину + + char i; + + **for(i=0;i<8;i++)//посылаем отдельно каждый бит на устройство** + + **{** + + **if((bt & (1<<i)) == 1<<i)//посылаем 1** + + **dt_sendbit(1);** + + **else //посылаем 0** + + **dt_sendbit(0);** + + **}** + +} + +Здесь мы также используем цикл и в цикле мы, используя также битовый сдвиг, постепенно по одному биту считываем посланный в функцию в качестве входного аргумента байт и посылаем каждый считанный бит в функцию передачи бита в виде переменной, равной 0 или 1. + +Теперь, написав все эти рутинные функции, мы смело можем продолжить так и не дописанную в предыдущих частях функцию конвертирования показания температуры. + +Как мы помним, мы написали в данной функции код условия наличия датчика на шине, и теперь можем писать тело данного условия. Начнём с передачи команды разрешения на то, чтобы нам не считывать идентификатор датчика + +if(dt_testdevice()==1) //если устройство нашлось + +{ + + **dt_sendbyte(NOID); //пропустить идентификацию, тк у нас только одно устройство на шине** + +Затем мы уже отправляем команду на считывание температуры + +dt_sendbyte(NOID); //пропустить идентификацию, тк у нас только одно устройство на шине + +**dt_sendbyte(T_CONVERT); //измеряем температуру** + +Данная команда — это только команда, а не само считывание температуры. По данной команде датчик начнёт измерение температуры и конвертацию её в цифровой код и занесение его в регистровую пару. Данный процесс у датчика длится значительно долго, около 750 милисекунд, не микро. Данное время необходимо, чтобы датчик успел записать все 12 бит + + + +Здесь мы видим, что если нам особая точность не нужна, то мы можем считать другое количество бит, тем самым сэкономив значительное количество времени. Но мы напишем идеально 750, так как мы никуда не спешим + +dt_sendbyte(T_CONVERT); //измеряем температуру + +**_delay_ms(750); //в 12битном режиме преобразования — 750 милисекунд** + +Далее мы опять проделываем те же манипуляции с шиной. как и при проверке наличия устройства на ней, и опять посылаем команду работы без идентификации + +_delay_ms(750); //в 12битном режиме преобразования — 750 милисекунд + +**dt_testdevice(); //снова используем те же манипуляции с шиной что и при проверке ее присутствия** + +**dt_sendbyte(NOID); //пропустить идентификацию, тк у нас только одно устройство на шине** + +И затем мы уже даем команду датчику считывать байты с регистра. Судя по временным диаграммам, они там уже должны быть + +dt_sendbyte(NOID); //пропустить идентификацию, тк у нас только одно устройство на шине + +**dt_sendbyte(READ_DATA); //даем команду на чтение данных с устройства** + +Затем мы считываем байты с регистра и укладываем их в переменную tt, потом, выйдя из тела условия, возвращаем их. + + dt_sendbyte(READ_DATA); //даем команду на чтение данных с устройства bt = dt_readbyte(); //читаем младший бит + + **tt = dt_readbyte(); //читаем старший бит MS** + + **tt = (tt<<8)|bt;//сдвигаем старший влево, младший пишем на его место, тем самым получаем общий результат** + + **}** + + **return tt;** + +**}** + +Мы получили хоть и не раздельный результат в виде двух различных байтов, но все равно результат этот сырой, так как в нём есть и биты знака, и также шестнадцатые доли градуса, которые мы не можем показать на дисплее. Поэтому нужна будет ещё одна функция конвертирования результата в удобный для глаза вид. + +Мы вообще упростим нашу задачу и не будем использовать доли, а будем использовать только целые показания, и бит отрицания мы также не будем использовать, мы будем измерять только положительные температуры. Вот такая поэтому будет простая у нас функция + +**//преобразование температуры в единицы** + +**char converttemp (unsigned int tt)** + +**{** + + **char t = tt>>4;//сдвиг и отсечение части старшего байта** + + **return t;** + +**}** + +Напишем на данную функцию прототип в хедер-файле + +int dt_check(void); //функция преобразования показаний датчика в температуру + +**char converttemp (unsigned int tt); //преобразование температуры в единицы** + +Теперь мы, наконец-то вернёмся в наш главный модуль в его главную функцию **main()**, а то мы уже тут очень давно не были. Создадим здесь локальную переменную для хранения показаний температуры + +int main(void) + +{ + + **unsigned int tt=0; //переменная для хранения температуры** + +Напишем код для вызова функции измерения температуры в бесконечном цикле + +sendcharlcd(sec%10+0x30);//Преобразуем число в код числа + +sendcharlcd(' '); + +**tt = converttemp(dt_check()); //измеряем температуру** + +Отобразим показания температуры на дисплее + + tt = converttemp(dt_check()); //измеряем температуру + + **sendcharlcd(tt/10+0x30);//Преобразуем число в код числа** + + **sendcharlcd(tt%10+0x30);//Преобразуем число в код числа** + + **sendcharlcd('*');** + + **sendcharlcd('C');** + +} + +Прежде чем собирать код и прошивать контроллер, посмотрим подключение нашей всей схемы (нажмите на картинку для увеличения изображения) + +[](https://narodstream.ru///wp-content/uploads/2016/12/Image10-10.png) + +Посмотрим также поближе модуль с припаянным датчиком + + + +Вот так выглядит модуль с другой стороны, желтым прямоугольником отмечен контакт данных термодатчика + + + +Соберём код и прошьём контроллер. А вот и результат + + + +Так как при измерении температуры у нас происходит очень большая задержка, а мы всё равно отсекаем считанные биты, впоследствии мы. когда будем писать часы с термометром, то мы данную задержку значительно убавим.
\ No newline at end of file diff --git a/RADIO/РЕЛЕ понижение потребления.md b/RADIO/РЕЛЕ понижение потребления.md new file mode 100644 index 0000000..265dc49 --- /dev/null +++ b/RADIO/РЕЛЕ понижение потребления.md @@ -0,0 +1,16 @@ +| | | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [](https://www.rlocman.ru/shem/pdf.html?di=175183) | | +| _Рисунок 1._ | **_RC-цепочка, включенная последовательно с обмоткой, обеспечивает надежное <br>включение реле полным током, а затем снижает ток для экономии энергии._** | + +Хорошо известно, что возбужденная катушка реле будет поддерживать замкнутое состояние контактов даже после некоторого снижения управляющего напряжения. В течение многих лет мы использовали очень простой подход к снижению управляющего напряжения реле, не требующий ни неиспользуемых контактов, ни сложных схем. Все, что нужно сделать, это добавить два простых компонента – включенные параллельно резистор и конденсатор. + +Пара резистор/конденсатор разрывает обычное соединение с «землей» эмиттерного вывода управляющего транзистора (Рисунок 1). Когда управляющий транзистор выключен, резистор R2 гарантирует, что конденсатор разряжен. Когда внешняя команда включает управляющий транзистор, этот незаряженный электролитический конденсатор в течение некоторого времени представляет собой короткое замыкание, обеспечивая протекание максимального тока через обмотку реле и бездребезговое замыкание его контактов. + +Однако по мере того, как конденсатор заряжается, напряжение на обмотке и ток через нее уменьшаются. Схема достигает установившегося состояния, когда конденсатор заряжается до такой степени, что весь ток обмотки реле проходит через резистор R2. Контакты реле будут оставаться замкнутыми до тех пор, пока не будет снято внешнее управляющее напряжение. + +В этом примере сопротивление резистора примерно в два раза превышает сопротивление обмотки реле: 390 Ом для сопротивления обмотки 200 Ом. Выбранный конденсатор 150 мкФ/25 В образует комбинацию, которая хорошо работает при номинальном напряжении обмотки 12 В. Если бы эмиттер управляющего транзистора был просто подключен к «земле», цепь реле потребляла бы около 58 мА. С добавлением резистора/конденсатора установившийся ток составляет всего 20 мА, что дает хорошую экономию энергии. Дополнительные контакты реле не требуются, а два добавочных компонента стоят всего несколько центов. + +![[Снимок экрана 2025-05-14 в 00.35.18.png]] + +![[Снимок экрана 2025-05-14 в 00.38.05.png]]
\ No newline at end of file diff --git a/SQL/MySQL.md b/SQL/MySQL.md new file mode 100644 index 0000000..1fb4179 --- /dev/null +++ b/SQL/MySQL.md @@ -0,0 +1,86 @@ +```bash +# Удаление: +sudo apt remove --purge mysql\* -y +sudo apt autoremove +sudo apt autoclean + +# проверка "хвостов": +sudo find / -name mysql + +# Установка: +sudo apt install mysql mysql-server + +sudo apt update +sudo apt upgrad -y +``` + +### Создадим базу данных в MySQL + +Войдем в командную строку MySQL: + +``` +sudo mysql +``` + +Создадим новую базу данных: + +``` +CREATE DATABASE НАЗВАНИЕ_САЙТА; +``` + +Создадим нового пользователя: + +``` +CREATE USER 'НАЗВАНИЕ_САЙТА'@'localhost' IDENTIFIED BY 'aef@234R345t2iJhfvar'; +``` + +«aef@234R345t2iJhfvar» — это пароль пользователя НАЗВАНИЕ_САЙТА. + +Дадим пользователю НАЗВАНИЕ_САЙТА права на базу данных НАЗВАНИЕ_САЙТА: + +``` +GRANT ALL ON НАЗВАНИЕ_САЙТА.* TO 'НАЗВАНИЕ_САЙТА'@'localhost'; +``` + +Выйдем из командной строки MySQL: + +``` +exit +``` + +Теперь давайте войдем в командную строку MySQL под новым MySQL-пользователем: + +``` +mysql -u НАЗВАНИЕ_САЙТА -p +``` + +Вас попросят ввести пароль, введите его. Если Вы не меняли его в примере выше, то он будет такой: + +``` +aef@234R345t2iJhfvar +``` + +Вот мы и вошли под новым пользователем. Посмотрим, какие базы данных ему видны, введем: + +``` +SHOW DATABASES; +``` + +Результат будет такой: + +``` ++---------------------------+ +| Database | ++---------------------------+ +| information_schema | +| НАЗВАНИЕ_САЙТА | ++---------------------------+ +2 rows in set (0.06 sec) +``` + +Видим нашу базу НАЗВАНИЕ_САЙТА, отлично, выйдем из командной строки MySQL: + +``` +exit +``` + diff --git a/SQL/SQL Cheatsheet.md b/SQL/SQL Cheatsheet.md new file mode 100644 index 0000000..175b0a0 --- /dev/null +++ b/SQL/SQL Cheatsheet.md @@ -0,0 +1,313 @@ +# Quick SQL Cheatsheet + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#quick-sql-cheatsheet) + +A quick reminder of all relevant SQL queries and examples on how to use them. + +This repository is constantly being updated and added to by the community. Pull requests are welcome. Enjoy! + +# Table of Contents + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#table-of-contents) + +1. [Finding Data Queries.](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#find) +2. [Data Modification Queries.](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#modify) +3. [Reporting Queries.](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#report) +4. [Join Queries.](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#joins) +5. [View Queries.](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#view) +6. [Altering Table Queries.](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#alter) +7. [Creating Table Query.](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#create) + +# 1. Finding Data Queries + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#1-finding-data-queries) + +### **SELECT**: used to select data from a database + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#select-used-to-select-data-from-a-database) + +- `SELECT` * `FROM` table_name; + +### **DISTINCT**: filters away duplicate values and returns rows of specified column + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#distinct-filters-away-duplicate-values-and-returns-rows-of-specified-column) + +- `SELECT DISTINCT` column_name; + +### **WHERE**: used to filter records/rows + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#where-used-to-filter-recordsrows) + +- `SELECT` column1, column2 `FROM` table_name `WHERE` condition; +- `SELECT` * `FROM` table_name `WHERE` condition1 `AND` condition2; +- `SELECT` * `FROM` table_name `WHERE` condition1 `OR` condition2; +- `SELECT` * `FROM` table_name `WHERE NOT` condition; +- `SELECT` * `FROM` table_name `WHERE` condition1 `AND` (condition2 `OR` condition3); +- `SELECT` * `FROM` table_name `WHERE EXISTS` (`SELECT` column_name `FROM` table_name `WHERE` condition); + +### **ORDER BY**: used to sort the result-set in ascending or descending order + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#order-by-used-to-sort-the-result-set-in-ascending-or-descending-order) + +- `SELECT` * `FROM` table_name `ORDER BY` column; +- `SELECT` * `FROM` table_name `ORDER BY` column `DESC`; +- `SELECT` * `FROM` table_name `ORDER BY` column1 `ASC`, column2 `DESC`; + +### **SELECT TOP**: used to specify the number of records to return from top of table + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#select-top-used-to-specify-the-number-of-records-to-return-from-top-of-table) + +- `SELECT TOP` number columns_names `FROM` table_name `WHERE` condition; +- `SELECT TOP` percent columns_names `FROM` table_name `WHERE` condition; +- Not all database systems support `SELECT TOP`. The MySQL equivalent is the `LIMIT` clause +- `SELECT` column_names `FROM` table_name `LIMIT` offset, count; + +### **LIKE**: operator used in a WHERE clause to search for a specific pattern in a column + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#like-operator-used-in-a-where-clause-to-search-for-a-specific-pattern-in-a-column) + +- % (percent sign) is a wildcard character that represents zero, one, or multiple characters +- _ (underscore) is a wildcard character that represents a single character +- `SELECT` column_names `FROM` table_name `WHERE` column_name `LIKE` pattern; +- `LIKE` ‘a%’ (find any values that start with “a”) +- `LIKE` ‘%a’ (find any values that end with “a”) +- `LIKE` ‘%or%’ (find any values that have “or” in any position) +- `LIKE` ‘_r%’ (find any values that have “r” in the second position) +- `LIKE` ‘a_%_%’ (find any values that start with “a” and are at least 3 characters in length) +- `LIKE` ‘[a-c]%’ (find any values starting with “a”, “b”, or “c” + +### **IN**: operator that allows you to specify multiple values in a WHERE clause + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#in-operator-that-allows-you-to-specify-multiple-values-in-a-where-clause) + +- essentially the IN operator is shorthand for multiple OR conditions +- `SELECT` column_names `FROM` table_name `WHERE` column_name `IN` (value1, value2, …); +- `SELECT` column_names `FROM` table_name `WHERE` column_name `IN` (`SELECT STATEMENT`); + +### **BETWEEN**: operator selects values within a given range inclusive + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#between-operator-selects-values-within-a-given-range-inclusive) + +- `SELECT` column_names `FROM` table_name `WHERE` column_name `BETWEEN` value1 `AND` value2; +- `SELECT` * `FROM` Products `WHERE` (column_name `BETWEEN` value1 `AND` value2) `AND NOT` column_name2 `IN` (value3, value4); +- `SELECT` * `FROM` Products `WHERE` column_name `BETWEEN` #01/07/1999# AND #03/12/1999#; + +### **NULL**: values in a field with no value + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#null-values-in-a-field-with-no-value) + +- `SELECT` * `FROM` table_name `WHERE` column_name `IS NULL`; +- `SELECT` * `FROM` table_name `WHERE` column_name `IS NOT NULL`; + +### **AS**: aliases are used to assign a temporary name to a table or column + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#as-aliases-are-used-to-assign-a-temporary-name-to-a-table-or-column) + +- `SELECT` column_name `AS` alias_name `FROM` table_name; +- `SELECT` column_name `FROM` table_name `AS` alias_name; +- `SELECT` column_name `AS` alias_name1, column_name2 `AS` alias_name2; +- `SELECT` column_name1, column_name2 + ‘, ‘ + column_name3 `AS` alias_name; + +### **UNION**: set operator used to combine the result-set of two or more SELECT statements + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#union-set-operator-used-to-combine-the-result-set-of-two-or-more-select-statements) + +- Each SELECT statement within UNION must have the same number of columns +- The columns must have similar data types +- The columns in each SELECT statement must also be in the same order +- `SELECT` columns_names `FROM` table1 `UNION SELECT` column_name `FROM` table2; +- `UNION` operator only selects distinct values, `UNION ALL` will allow duplicates + +### **INTERSECT**: set operator which is used to return the records that two SELECT statements have in common + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#intersect-set-operator-which-is-used-to-return-the-records-that-two-select-statements-have-in-common) + +- Generally used the same way as **UNION** above +- `SELECT` columns_names `FROM` table1 `INTERSECT SELECT` column_name `FROM` table2; + +### **EXCEPT**: set operator used to return all the records in the first SELECT statement that are not found in the second SELECT statement + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#except-set-operator-used-to-return-all-the-records-in-the-first-select-statement-that-are-not-found-in-the-second-select-statement) + +- Generally used the same way as **UNION** above +- `SELECT` columns_names `FROM` table1 `EXCEPT SELECT` column_name `FROM` table2; + +### **ANY|ALL**: operator used to check subquery conditions used within a WHERE or HAVING clauses + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#anyall-operator-used-to-check-subquery-conditions-used-within-a-where-or-having-clauses) + +- The `ANY` operator returns true if any subquery values meet the condition +- The `ALL` operator returns true if all subquery values meet the condition +- `SELECT` columns_names `FROM` table1 `WHERE` column_name operator (`ANY`|`ALL`) (`SELECT` column_name `FROM` table_name `WHERE` condition); + +### **GROUP BY**: statement often used with aggregate functions (COUNT, MAX, MIN, SUM, AVG) to group the result-set by one or more columns + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#group-by-statement-often-used-with-aggregate-functions-count-max-min-sum-avg-to-group-the-result-set-by-one-or-more-columns) + +- `SELECT` column_name1, COUNT(column_name2) `FROM` table_name `WHERE` condition `GROUP BY` column_name1 `ORDER BY` COUNT(column_name2) DESC; + +### **HAVING**: this clause was added to SQL because the WHERE keyword could not be used with aggregate functions + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#having-this-clause-was-added-to-sql-because-the-where-keyword-could-not-be-used-with-aggregate-functions) + +- `SELECT` `COUNT`(column_name1), column_name2 `FROM` table `GROUP BY` column_name2 `HAVING` `COUNT(`column_name1`)` > 5; + +### **WITH**: often used for retrieving hierarchical data or re-using temp result set several times in a query. Also referred to as "Common Table Expression" + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#with-often-used-for-retrieving-hierarchical-data-or-re-using-temp-result-set-several-times-in-a-query-also-referred-to-as-common-table-expression) + +- `WITH RECURSIVE` cte `AS` ( + `SELECT` c0.* `FROM` categories `AS` c0 `WHERE` id = 1 `# Starting point` + `UNION ALL` + `SELECT` c1.* `FROM` categories `AS` c1 `JOIN` cte `ON` c1.parent_category_id = cte.id + ) + `SELECT` * + `FROM` cte + +# 2. Data Modification Queries + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#2-data-modification-queries) + +### **INSERT INTO**: used to insert new records/rows in a table + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#insert-into-used-to-insert-new-recordsrows-in-a-table) + +- `INSERT INTO` table_name (column1, column2) `VALUES` (value1, value2); +- `INSERT INTO` table_name `VALUES` (value1, value2 …); + +### **UPDATE**: used to modify the existing records in a table + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#update-used-to-modify-the-existing-records-in-a-table) + +- `UPDATE` table_name `SET` column1 = value1, column2 = value2 `WHERE` condition; +- `UPDATE` table_name `SET` column_name = value; + +### **DELETE**: used to delete existing records/rows in a table + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#delete-used-to-delete-existing-recordsrows-in-a-table) + +- `DELETE FROM` table_name `WHERE` condition; +- `DELETE` * `FROM` table_name; + +# 3. Reporting Queries + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#3-reporting-queries) + +### **COUNT**: returns the # of occurrences + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#count-returns-the--of-occurrences) + +- `SELECT COUNT (DISTINCT` column_name`)`; + +### **MIN() and MAX()**: returns the smallest/largest value of the selected column + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#min-and-max-returns-the-smallestlargest-value-of-the-selected-column) + +- `SELECT MIN (`column_names`) FROM` table_name `WHERE` condition; +- `SELECT MAX (`column_names`) FROM` table_name `WHERE` condition; + +### **AVG()**: returns the average value of a numeric column + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#avg-returns-the-average-value-of-a-numeric-column) + +- `SELECT AVG (`column_name`) FROM` table_name `WHERE` condition; + +### **SUM()**: returns the total sum of a numeric column + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#sum-returns-the-total-sum-of-a-numeric-column) + +- `SELECT SUM (`column_name`) FROM` table_name `WHERE` condition; + +# 4. Join Queries + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#4-join-queries) + +### **INNER JOIN**: returns records that have matching value in both tables + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#inner-join-returns-records-that-have-matching-value-in-both-tables) + +- `SELECT` column_names `FROM` table1 `INNER JOIN` table2 `ON` table1.column_name=table2.column_name; +- `SELECT` table1.column_name1, table2.column_name2, table3.column_name3 `FROM` ((table1 `INNER JOIN` table2 `ON` relationship) `INNER JOIN` table3 `ON` relationship); + +### **LEFT (OUTER) JOIN**: returns all records from the left table (table1), and the matched records from the right table (table2) + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#left-outer-join-returns-all-records-from-the-left-table-table1-and-the-matched-records-from-the-right-table-table2) + +- `SELECT` column_names `FROM` table1 `LEFT JOIN` table2 `ON` table1.column_name=table2.column_name; + +### **RIGHT (OUTER) JOIN**: returns all records from the right table (table2), and the matched records from the left table (table1) + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#right-outer-join-returns-all-records-from-the-right-table-table2-and-the-matched-records-from-the-left-table-table1) + +- `SELECT` column_names `FROM` table1 `RIGHT JOIN` table2 `ON` table1.column_name=table2.column_name; + +### **FULL (OUTER) JOIN**: returns all records when there is a match in either left or right table + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#full-outer-join-returns-all-records-when-there-is-a-match-in-either-left-or-right-table) + +- `SELECT` column_names `FROM` table1 `FULL OUTER JOIN` table2 `ON` table1.column_name=table2.column_name; + +### **Self JOIN**: a regular join, but the table is joined with itself + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#self-join-a-regular-join-but-the-table-is-joined-with-itself) + +- `SELECT` column_names `FROM` table1 T1, table1 T2 `WHERE` condition; + +# 5. View Queries + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#5-view-queries) + +### **CREATE**: create a view + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#create-create-a-view) + +- `CREATE VIEW` view_name `AS SELECT` column1, column2 `FROM` table_name `WHERE` condition; + +### **SELECT**: retrieve a view + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#select-retrieve-a-view) + +- `SELECT` * `FROM` view_name; + +### **DROP**: drop a view + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#drop-drop-a-view) + +- `DROP VIEW` view_name; + +# 6. Altering Table Queries + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#6-altering-table-queries) + +### **ADD**: add a column + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#add-add-a-column) + +- `ALTER TABLE` table_name `ADD` column_name column_definition; + +### **MODIFY**: change data type of column + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#modify-change-data-type-of-column) + +- `ALTER TABLE` table_name `MODIFY` column_name column_type; + +### **DROP**: delete a column + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#drop-delete-a-column) + +- `ALTER TABLE` table_name `DROP COLUMN` column_name; + +# 7. Creating Table Query + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#7-creating-table-query) + +### **CREATE**: create a table + +[](https://github.com/enochtangg/quick-SQL-cheatsheet?tab=readme-ov-file#create-create-a-table) + +- `CREATE TABLE` table_name `(` + `column1` `datatype`, + `column2` `datatype`, + `column3` `datatype`, + `column4` `datatype`, + `);`
\ No newline at end of file diff --git a/SQL/SQL. Базы Данных.md b/SQL/SQL. Базы Данных.md new file mode 100644 index 0000000..48e9bc5 --- /dev/null +++ b/SQL/SQL. Базы Данных.md @@ -0,0 +1,172 @@ +```sh +sudo apt install postgres +sudo apt install-contrib # Debian +sudo systemctl start posgresql.service +sudo systemctl status posgresql.service + +sudo -u postgres createuser --interactive +# вводим имя +# даем права sudo + +man createuser + +sudo -u postgres createdb metall +sudo adduser metall +sudo -u metall psql + +# переключиться на другую БД: +psql -d postgres +``` +### Установка из исходников: +```sh +# качаем с сайта нужную версию: +# https://www.postgresql.org/ftp/source/v16.2/postgresql-16.2.tar.gz + +mkdir Downloads +cd Downloads + +# Качаем: +wget https://ftp.postgresql.org/pub/source/v16.2/postgresql-16.2.tar.gz +# Скачать скрипт и сразу выполнить: +wget -O - http://www.tecmint.com/wp-content/scripts/Colorfull.sh | bash +# Также можно использовать curl: +curl -O https://github.com/torvalds/linux/archive/v4.11-rc6.tar.gz + +tar -xzvf postgresql-16.2.tar.gz +cd postgresql-16.2 +./configure --prefix=/var/lib/postgresql + +# Поиск пакетов в Linux: +sudo apt-cache search lib<пакет> # нужен пакет <name>-dev +sudo apt install <name>-dev + +brew install pkg-config + +./configure --prefix=/var/lib/postgresql +sudo make install-world +cd /var/lib/postgresql +sudo chown -R vlapa ../postgresql +./bin/pg_ctl initdb +export PGDATA=/var/lib/postgresql/main +./bin/pg_ctl initdb +./bin/pg_ctl -D /var/lib/postgresql/main start +./bin/psql -h localhost -U vlapa. postgres +# Создать БД: +CREATE DATABASE <name_db> + +#=========================================================== +# создадим конфигурацию перед сборкой. Для этого используем команду configure с тремя опциями: + +# prefix — укажем каталог для установки PostgreSQL; +# libdir — укажем каталог с библиотеками PostgreSQL, это необходимо, так как PostgreSQL устанавливается в нестандартное расположение и не будет знать где ему искать библиотеки; +# with-pgport — укажем нестандартный порт на котором будет работать этот экземпляр PostgreSQL. + +./configure --prefix=/home/alex/pgsql-16 --libdir=/home/alex/pgsql-16/lib/ --with-pgport=5555 +mkdir ~/pgsql-16/data/ +echo "export PGDATA=~/pgsql-16/data/" >> .bashrc +. .bashrc +ls -d $PGDATA + +echo "export PGDATA=~/pgsql-16/data/" >> .bashrc +. .bashrc +whereis psql + +initdb -k + +pg_ctl -l ~/pgsql-16/postgres.log start # -l -файл логов +pg_ctl status +psql -d postgres -c 'select now()' +pg_ctl stop +``` + +### Создание и запуск второго кластера +```sh +mkdir ~/pgsql-16/data2/ +initdb -k -D ~/pgsql-16/data2/ + +pg_ctl -D /home/alex/pgsql-16/data2/ -l logfile start +# При инициализация кластера, если путь к каталогу PGDATA отличается от переменной $PGDATA или если такая переменная не задана. То нужно с помощью ключа -D указать путь к этому каталогу. + +# Чтобы запустить второй кластер одновременно с первым, нужно поменять порт, на котором работает PostgreSQL. Изменим этот порт для второго кластера: +echo port = 5556 >> ~/pgsql-16/data2/postgresql.conf +pg_ctl -D ~/pgsql-16/data2/ -l ~/pgsql-16/data2/postgres.log start + +ps ax | grep postgresql +# Первый кластер запущен без ключа -D и используется переменная $PGDATA, а во втором кластере путь к PGDATA прописан в строке запуска. +# Перед дальнейшей работой выключим оба кластера: +pg_ctl stop + +pg_ctl -D /home/alex/pgsql-16/data2 stop +``` + +# 📑 Шпаргалка по основным командам PostgreSQL + +Вся работа с PostgreSQL осуществляется под пользователем `postgres` + +`$sudo su postgres` + +Работать с `PosgreSQL` можно как в интерактивном режиме, так и из командной строки. Программа — `psql` + +## Основные команды PostgreSQL в интерактивном режиме: + +- `\connect db_name` – подключиться к базе с именем db_name +- `\du` – список пользователей +- `\dp` (или `\z`) – список таблиц, представлений, последовательностей, прав доступа к ним +- `\di` – индексы +- `\ds` – последовательности +- `\dt` – список таблиц +- `\dt+` — список всех таблиц с описанием +- `\dt *s*` — список всех таблиц, содержащих s в имени +- `\dv` – представления +- `\dS` – системные таблицы +- `\d+` – описание таблицы +- `\o` – пересылка результатов запроса в файл +- `\l` – список баз данных +- `\i` – читать входящие данные из файла +- `\e` – открывает текущее содержимое буфера запроса в редакторе (если иное не указано в окружении переменной _EDITOR_, то будет использоваться по умолчанию vi) +- `\d` “table_name” – описание таблицы +- `\i` запуск команды из внешнего файла, например \i /my/directory/my.sql +- `\pset` – команда настройки параметров форматирования +- `\echo` – выводит сообщение +- `\set` – устанавливает значение переменной среды. Без параметров выводит список текущих переменных (\unset – удаляет). +- `\?` – справочник psql +- `\help` – справочник SQL +- `\q` (или `Ctrl+D`) – выход с программы + +## Удаленное подключение к Postgres: +- **postgresql.conf** +- **pg_hba.conf** + +Вы можете найти эти два файла в папке: +- **/etc/postgresql/13/main** (Linux) +- **C:/Program Files/PostgreSQL/13/data** (Windows) + + + + +![[Снимок экрана 2024-03-14 в 13.21.49.png]] + +![[Снимок экрана 2024-03-14 в 13.22.17.png]] + + +![[Снимок экрана 2024-03-14 в 13.25.59.png]] + +![[Снимок экрана 2024-03-14 в 13.27.58.png]] + + +![[Снимок экрана 2024-03-14 в 13.29.48.png]] +![[Снимок экрана 2024-03-14 в 13.32.01.png]] + +###################################################################### + +### Первичный ключ: + +_должен быть: +- уникальным +- заполненным (not Null) +_бывают: +- простые +- составные +- естественные +- искусственные +- diff --git a/SQL/SQLite Commands.md b/SQL/SQLite Commands.md new file mode 100644 index 0000000..16f4cc5 --- /dev/null +++ b/SQL/SQLite Commands.md @@ -0,0 +1,384 @@ +Ray Ferrell + +September 9, 2023 + +SQLite is an open-source, embedded relational database management system. It is a popular choice for local data storage in applications due to its lightweight nature, simplicity, and robustness. + +SQLite uses SQL syntax and provides developers with powerful commands to create, query, and manage databases. + +This comprehensive guide covers the most important SQLite commands for creating and managing database schemas, manipulating data, running queries, and handling connections. + +We’ll explore the SQL standard commands supported by SQLite and special dot commands offered by the SQLite3 command line shell. + +**_Also read: [Create SQLite Database – Step-by-Step Guide for Beginners](https://sqldocs.org/sqlite/sqlite-create-database/)_** + +1. [SQLite Data Definition Language (DDL) Commands](https://sqldocs.org/sqlite/sqlite-commands/#sqlite-data-definition-language-ddl-commands "SQLite Data Definition Language (DDL) Commands") + 1. [CREATE](https://sqldocs.org/sqlite/sqlite-commands/#create "CREATE") + 2. [ALTER](https://sqldocs.org/sqlite/sqlite-commands/#alter "ALTER") + 3. [DROP](https://sqldocs.org/sqlite/sqlite-commands/#drop "DROP") +2. [SQLite Data Manipulation Language (DML) Commands](https://sqldocs.org/sqlite/sqlite-commands/#sqlite-data-manipulation-language-dml-commands "SQLite Data Manipulation Language (DML) Commands") + 1. [INSERT](https://sqldocs.org/sqlite/sqlite-commands/#insert "INSERT") + 2. [UPDATE](https://sqldocs.org/sqlite/sqlite-commands/#update "UPDATE") + 3. [DELETE](https://sqldocs.org/sqlite/sqlite-commands/#delete "DELETE") + 4. [REPLACE](https://sqldocs.org/sqlite/sqlite-commands/#replace "REPLACE") +3. [SQLite Data Query Language (DQL) Commands](https://sqldocs.org/sqlite/sqlite-commands/#sqlite-data-query-language-dql-commands "SQLite Data Query Language (DQL) Commands") + 1. [SELECT](https://sqldocs.org/sqlite/sqlite-commands/#select "SELECT") +4. [SQLite Transaction Control Commands](https://sqldocs.org/sqlite/sqlite-commands/#sqlite-transaction-control-commands "SQLite Transaction Control Commands") + 1. [BEGIN TRANSACTION](https://sqldocs.org/sqlite/sqlite-commands/#begin-transaction "BEGIN TRANSACTION") + 2. [COMMIT](https://sqldocs.org/sqlite/sqlite-commands/#commit "COMMIT") + 3. [ROLLBACK](https://sqldocs.org/sqlite/sqlite-commands/#rollback "ROLLBACK") +5. [SQLite Administration Commands](https://sqldocs.org/sqlite/sqlite-commands/#sqlite-administration-commands "SQLite Administration Commands") +6. [SQLite Dot Commands for sqlite3](https://sqldocs.org/sqlite/sqlite-commands/#sqlite-dot-commands-for-sqlite3 "SQLite Dot Commands for sqlite3") +7. [Using Transactions in SQLite](https://sqldocs.org/sqlite/sqlite-commands/#using-transactions-in-sqlite "Using Transactions in SQLite") +8. [Making SQLite Backups](https://sqldocs.org/sqlite/sqlite-commands/#making-sqlite-backups "Making SQLite Backups") +9. [Importing and Exporting Data](https://sqldocs.org/sqlite/sqlite-commands/#importing-and-exporting-data "Importing and Exporting Data") + 1. [Importing Data](https://sqldocs.org/sqlite/sqlite-commands/#importing-data "Importing Data") + 2. [Exporting Data](https://sqldocs.org/sqlite/sqlite-commands/#exporting-data "Exporting Data") +10. [Full-text Search in SQLite](https://sqldocs.org/sqlite/sqlite-commands/#full-text-search-in-sqlite "Full-text Search in SQLite") +11. [User Authentication Methods in SQLite](https://sqldocs.org/sqlite/sqlite-commands/#user-authentication-methods-in-sqlite "User Authentication Methods in SQLite") +12. [Statistical and Diagnostic Functions](https://sqldocs.org/sqlite/sqlite-commands/#statistical-and-diagnostic-functions "Statistical and Diagnostic Functions") + 1. [Statistical Aggregates](https://sqldocs.org/sqlite/sqlite-commands/#statistical-aggregates "Statistical Aggregates") +13. [Advanced Topics](https://sqldocs.org/sqlite/sqlite-commands/#advanced-topics "Advanced Topics") + 1. [SQLite Extensions](https://sqldocs.org/sqlite/sqlite-commands/#sqlite-extensions "SQLite Extensions") + 2. [JSON Support](https://sqldocs.org/sqlite/sqlite-commands/#json-support "JSON Support") + 3. [Database Encryption](https://sqldocs.org/sqlite/sqlite-commands/#database-encryption "Database Encryption") + 4. [Multi-Threading](https://sqldocs.org/sqlite/sqlite-commands/#multi-threading "Multi-Threading") + 5. [And More](https://sqldocs.org/sqlite/sqlite-commands/#and-more "And More") + +## SQLite Data Definition Language (DDL) Commands + +Data Definition Language (DDL) commands allow you to create and modify database structures. The key DDL commands in SQLite are: + +### CREATE + +The CREATE command creates new database objects like tables, indexes, views, and triggers. + +For example, to [create a new table](https://sqldocs.org/sqlite/sqlite-create-table/): + +```sql +CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE ); +``` + +You can also use CREATE to define indexes, views, and triggers. + +### ALTER + +The ALTER command modifies the structure of existing database objects. For example, to add a new column to a table: + +```sql +ALTER TABLE users ADD COLUMN phone TEXT; +``` + +Other usages include renaming or dropping columns, adding table constraints, and renaming tables. + +### DROP + +The DROP command removes objects from the database. For example, to remove a table: + +```sql +DROP TABLE users; +``` + +You can also DROP indexes, views, and triggers. + +## SQLite Data Manipulation Language (DML) Commands + +Data Manipulation Language (DML) commands allow inserting, modifying, and deleting data within schema objects like tables. Key commands include: + +### INSERT + +INSERT adds new rows of data into a table. For example: + +```sql +INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com'); +``` + +And you can insert multiple rows at once: + +```sql +INSERT INTO users (name, email) VALUES ('Mary', 'mary@example.com'), ('Lee', 'lee@example.com'); +``` + +### UPDATE + +UPDATE modifies existing data in a table. For example: + +```sql +UPDATE users SET name = 'John Taylor' WHERE id = 123; +``` + +Updates can affect multiple table rows at once. + +### DELETE + +DELETE removes rows from a table. For example: + +```sql +DELETE FROM users WHERE id = 123; +``` + +Similar to UPDATE, DELETE can remove multiple rows in one statement. + +### REPLACE + +The REPLACE command does an INSERT or UPDATE depending on whether the row already exists. From the SQLite documentation: + +> For each row proposed for insertion, if a row with the same primary key already exists in the database, delete it first and then insert the new row. + +This provides UPSERT functionality in a single statement. + +**_Also read: [SQLite Operators: A Comprehensive Guide](https://sqldocs.org/sqlite/sqlite-operators/)_** + +## SQLite Data Query Language (DQL) Commands + +Data Query Language (DQL) commands allow you to query, search, and filter data stored in the database. The most important command is: + +### SELECT + +SELECT retrieves data from one or more tables. For example: + +```sql +SELECT id, name FROM users; +``` + +This query selects the id and name columns from the users table. + +SELECT supports powerful filtering capabilities using WHERE clauses: + +```sql +SELECT * FROM users WHERE age >= 18 AND city = 'Boston'; +``` + +And advanced JOIN operations allow selecting data across multiple related tables. + +SQLite supports nearly all standard [SQL SELECT](https://sqldocs.org/sqlite/sqlite-select-statement/) syntax like subqueries, UNIONs, GROUP BY, ORDER BY, [LIMIT](https://sqldocs.org/sqlite/sqlite-limit/) and much more. Familiarity with SELECT is a must for working effectively with SQLite. + +**_Also read: [SQLite Data Types: An In-Depth Guide](https://sqldocs.org/sqlite/sqlite-data-types/)_** + +## SQLite Transaction Control Commands + +Transactions allow you to bundle multiple related operations into an atomic unit. The key commands are: + + + +### BEGIN TRANSACTION + +This starts a new transaction block. All subsequent DML commands execute within the context of the transaction. + +### COMMIT + +This commits (saves) the current transaction. All changes within the block become permanent. + +### ROLLBACK + +This rolls back (undoes) the current transaction. All changes within the block are discarded. + +Transactions provide consistency, atomicity, isolation, and durability. They are crucial for the correct operation of SQLite databases in concurrent environments. + +## SQLite Administration Commands + +Administrative commands handle tasks like user management, backups, and database maintenance. Important commands include: + +- **CREATE USER**: Creates a new user account for authentication. Users can be granted privileges and roles. +- **ALTER USER**: Modifies properties of an existing user, such as changing their password. +- **DROP USER**: Deletes a user account. +- **PRAGMA**: The PRAGMA command controls various configuration settings of the SQLite library. For example, `PRAGMA journal_mode` sets the journaling mode. +- **VACUUM**: The VACUUM command rebuilds and cleans the database file, reclaiming unused space. Recommended for occasional maintenance. +- **BACKUP**: Makes a backup copy of an SQLite database. Useful for tasks like archiving transaction logs. More details below. + +## SQLite Dot Commands for sqlite3 + +The sqlite3 command line shell provides special “dot commands” that facilitate common tasks. These commands are prefixed with a `.` and are not standard SQL. Some important sqlite3 dot commands include: + +- **.databases**: Lists names and files of attached databases; allows viewing of database connections. +- **.mode**: Sets output modes for displaying query results (e.g., list, column, csv). +- **.headers**: Toggles column headers on or off for query output. +- **.tables**: Displays names of tables in the connected database; useful for exploring schemas. +- **.schema**: Outputs the CREATE statements for database schemas; accepts regex patterns. +- **.indexes**: Lists names of indexes for connected databases; optional filtering available. +- **.dump**: Converts the database or specific tables to an SQL text file; useful for backups. +- **.output FILE**: Redirects output to a specified file. +- **.once FILE**: Similar to .output, but applies only to one query before reverting to stdout. +- **.open FILE**: Closes current database and opens specified file; facilitates database switching. +- **.backup**: Creates a backup copy of the opened database. +- **.exit**: Exits the sqlite3 shell program. + +There are many more handy dot commands and `.help` will show you a full list. The dot commands provide useful functionality beyond plain SQL. + +## Using Transactions in SQLite + +As introduced earlier, transactions are key to managing consistency in SQLite. Let’s look at a simple example: + +```sql +-- Start a transaction BEGIN TRANSACTION; -- Insert transfer amount in accounts UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- Commit changes COMMIT; +``` + +This safely transfers $100 from account 1 to account 2 as a single atomic operation. If either UPDATE fails, the whole transaction will roll back and protect data integrity. + +Transactions have huge benefits for performance, safety, and correctness in concurrent systems. Use them liberally when multiple operations need to act as one. + +## Making SQLite Backups + +It’s important to routinely backup critical SQLite databases in case of corruption, hardware failure, or human error. The `.backup` and `BACKUP` commands help automate this. + +For example, from within the sqlite3 shell: + +```sql +sqlite> .backup main main.bak +``` + +This creates a copy of the currently open “main” database to a file named main.bak. + +You can also backup within a live application using the BACKUP command: + +```sql +BACKUP main TO 'main.bak'; +``` + +This copies the “main” database to the named backup file. + +Make sure to periodically backup SQLite databases using either of these approaches. The database files are portable and easily restored. + +## Importing and Exporting Data + +SQLite provides handy ways to move data in and out of databases. + +### Importing Data + +The `.import` command loads data into a table from a file. For example: + +```sql +sqlite> .mode csv sqlite> .import users.csv users +``` + +This imports the CSV file into the “users” table. + +Within an application, you can also import data using the sqlite3_load_extension() function to load a custom import module. + +### Exporting Data + +To export data, switch to the desired output mode like CSV and run a query: + +```sql +sqlite> .headers on sqlite> .mode csv sqlite> .once dump.csv sqlite> SELECT * FROM users; +``` + +This exports the users table to a CSV file. + +The `.once` dot command sends the output to a file instead of `stdout`. + +You can also implement custom export handlers by calling sqlite3_export_extensions() and registering callback functions. + +## Full-text Search in SQLite + +SQLite’s FTS modules enable powerful full-text searches on text data. For example: + +```sql +-- Create virtual FTS table CREATE VIRTUAL TABLE docs USING fts4(title, content); -- Populate the FTS table INSERT INTO docs(title, content) VALUES ('SQLite Guide', 'Intro to using SQLite commands...'), ('SQLite Tips', 'Optimizing performance of SQLite queries...'); -- Full text search SELECT * FROM docs WHERE docs MATCH 'guide tips'; +``` + +This performs a full-text search across the title and content columns, making it easy to build search functionality within applications. + +SQLite FTS provides fast and flexible textual searches. Refer to the documentation for the available FTS versions and customization options. + +## User Authentication Methods in SQLite + +SQLite provides ways to implement user authentication when opening database connections. For example: + +- Using the sqlite3_user_authenticate() interface within a custom authentication handler. This allows username/password checking. +- Attaching a user-defined authorization function using sqlite3_set_authorizer(). Can authorize on a per-command basis. +- Encrypting the database file with SQLite Encryption Extension (SEE). Decryption requires user-supplied passphrase, providing authentication. + +Depending on your specific needs, one of these approaches can be used to restrict and authenticate database access properly. + +## Statistical and Diagnostic Functions + +SQLite offers several functions to support statistical analysis and database diagnostics: + +### Statistical Aggregates + +Aggregates like STATS, STAT4 provide median, variance, mode calculations. Useful for analysis. + +- **rowid**: Special hidden column returning 64-bit signed row ID integer; useful for diagnostics. +- **last_insert_rowid()**: Function that returns last inserted row ID; captures autogenerated IDs. +- **total_changes()**: Shows the number of row changes from the most recent SQL statement; aids in auditing. +- **integrity_check**: PRAGMA command for database integrity check; detects corruption. +- **And more**: Includes additional statistical, diagnostic, and introspection capabilities. + +## Advanced Topics + +There are a few more advanced topics worth mentioning: + +### SQLite Extensions + +SQLite allows developers to create custom SQL functions, aggregates, collating sequences, and virtual table implementations. These “extensions” can be compiled into shared libraries and loaded at runtime to expand SQLite’s capabilities. + +### JSON Support + +SQLite has native support for JavaScript Object Notation (JSON) with functions like json_extract(), json_array(), and json_object(). This allows efficient storage and querying of JSON documents within an SQLite database. + +### Database Encryption + +SQLite Encryption Extension (SEE) can encrypt database files to protect sensitive data. Makes full-disk encryption easy. + + + +### Multi-Threading + +SQLite supports multi-threaded access with proper locking and isolation. This allows concurrent use by multiple processes and threads. + +### And More + +Spatialite support, alternative storage engines like LMDB, external content tables, C/C++ usage guides… the list goes on! SQLite is a versatile database. + +This covers the most essential SQLite commands and features, but there is always more to learn. SQLite’s documentation is excellent and fills in many additional details. With this foundation, you should feel comfortable using SQLite in your own projects. + + + +The key is to start simple, learn by example, and gradually work up to more advanced capabilities over time. Mastering both the SQL standard and sqlite3 dot commands will make you a pro at working with SQLite databases. + + + + + +Ray Ferrell + +Ray is a data nerd and a technical content creator for multiple websites. He enjoys writing on topics related to SQL databases, data science, machine learning, and more. When not working, you can find him spending quality time with his family and friends. + +**Previous:** [SQLite Create Table](https://sqldocs.org/sqlite/sqlite-create-table/) + +**Read next:** [SQLite Attach Database](https://sqldocs.org/sqlite/sqlite-attach-database/) + +**Important Links** + +[Terms of Use](https://sqldocs.org/terms/) + +[Privacy Policy](https://sqldocs.org/privacy) + +[Advertise](https://sqldocs.org/advertise/) + +**Company** + +[Home](https://sqldocs.org/) + +[About Us](https://sqldocs.org/about/) + +[Write for Us](https://sqldocs.org/write-for-us) + +[Careers](https://www.linkedin.com/company/sql-docs/jobs/) + +**Resources** + +[SQLite](https://sqldocs.org/sqlite/) + +[SQLite vs MySQL](https://sqldocs.org/sqlite/sqlite-vs-mysql/) + +[SQLite vs. PostgrSQL](https://sqldocs.org/sqlite/sqlite-vs-postgresql/) + +**Follow SQLDocs** + +[Twitter (X)](https://x.com/SQLDocsOrg) + +[LinkedIn](https://www.linkedin.com/company/sql-docs/) + +Copyright © 2022 – [SQL Documentation](https://sqldocs.org/) diff --git a/SQL/SQLite.md b/SQL/SQLite.md new file mode 100644 index 0000000..fa98f5d --- /dev/null +++ b/SQL/SQLite.md @@ -0,0 +1,556 @@ +```python +# Создание таблицы +with con: + con.execute(""" + CREATE TABLE USER ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name TEXT, + age INTEGER + ); + """) + +# Вставка записей в таблицу +sql = 'INSERT INTO USER (id, name, age) values(?, ?, ?)' +data = [ + (1, 'Alice', 21), + (2, 'Bob', 22), + (3, 'Chris', 23) +] + +# Выполнение запросов к базе данных +with con: + data = con.execute("SELECT * FROM USER WHERE age <= 22") + for row in data: + print(row) +``` +### Интеграция с pandas +```python +# Объявим датафрейм: +df_skill = pd.DataFrame({ + 'user_id': [1,1,2,2,3,3,3], + 'skill': ['Network Security', 'Algorithm Development', 'Network Security', 'Java', 'Python', 'Data Science', 'Machine Learning'] +}) + +# Сохранение датафрейма в БД +df_skill.to_sql('SKILL', con) +``` +Вот и всё! Нам даже не нужно заранее создавать таблицу. Типы данных и характеристики полей будут настроены автоматически, на основании характеристик датафрейма. Конечно, вы, если надо, можете настроить всё самостоятельно. + +Теперь, предположим, нам нужно получить объединение таблиц `USER` и `SKILL` и записать полученные данные в датафрейм pandas. Это тоже очень просто: +```python +df = pd.read_sql(''' + SELECT s.user_id, u.name, u.age, s.skill + FROM USER u LEFT JOIN SKILL s ON u.id = s.user_id +''', con) +``` +Здесь нам нужно определить SQL-выражение со знаками вопроса (`?`) в виде местозаполнителей. Учитывая то, что в нашем распоряжении есть объект подключения к базе данных, мы, подготовив выражение и данные, можем вставить записи в таблицу: +```python +with con: + con.executemany(sql, data) +``` +Сообщений об ошибках после выполнения этого кода не поступает, а это значит, что данные успешно добавлены в таблицу. + +Замечательно! А теперь давайте запишем то, что у нас получилось, в новую таблицу с именем `USER_SKILL`: +```python +df.to_sql('USER_SKILL', con) +``` +#### Чтение данных SQLite с помощью Pandas + +С pandas управление базой данных SQLite превращается в увлекательный процесс. Она предоставляет функцию `read_sql`, позволяющую напрямую выполнять инструкции SQL, не заботясь о внутренней инфраструктуре. + +```python +import pandas as pd +df = pd.read_sql("select * from transcript", con) +df name grade0 Mike 951 Jane 922 Bella 98 +``` +#### Обратная запись DataFrame в SQLite + +После обработки данных с помощью pandas самое время осуществить обратную запись DataFrame в БД SQLite для долгосрочного хранения. На этот случай в pandas есть метод `to_sql`. Обратимся к соответствующему примеру: + +```python +df['gpa'] = [4.0, 3.8, 3.9] +df.to_sql("transcript", con, if_exists="replace", index=False) +list(cur.execute("select * from transcript order by grade desc"))[('Bella', 98, 3.9), ('Mike', 95, 4.0), ('Jane', 92, 3.8)] +``` + +- В отличие от ==`read_sql`==, функции из библиотеки pandas, ==`to_sql`== является методом класса ==`DataFrame`==, вследствие чего он непосредственно вызывается объектом ==`DataFrame`==. +- В методе ==`to_sql`== указывается таблица, в которую сохраняется ==`DataFrame`==. +- Отметим важность параметра ==`if_exists`==, так как по умолчанию ему задается значение ==`“fail”`==. Это значит, что если таблица уже существует, вы не сможете записать в нее текущий `DataFrame`, поскольку будет вызвана ошибка ==`ValueError`==. В рассматриваемом примере требуется заменить существующую таблицу из-за изменения средних баллов оценок, поэтому параметру ==`if_exisits`== устанавливается значение ==`“replace”`==. +- В результате установки ==`index=False`== индекс объекта ==`DataFrame`== просто игнорируется при сохранении в таблицу. По своему принципу данное действие аналогично методу ==`to_csv`==, с которым вы наверняка знакомы лучше. + + +############################################# +```bash +import sqlite3 +``` +### Подключение к базе данных (или создание новой) +```python +import sqlite3 + +try: + with sqlite3.connect('file:sensors.db?mode=rw', uri=True) as conn: + print('sqlite3.db is OK !') +except: + with sqlite3.connect('sensors.db') as conn: + cursor = conn.cursor() + query = """CREATE TABLE sensors ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + sens_loc TEXT(32) NOT NULL, + sens_name TEXT(32) NOT NULL, + sens_date TEXT(10) NOT NULL, + sens_time TEXT(8) NOT NULL, + sens_temp TEXT(8) NOT NULL, + sens_pres TEXT(8) NOT NULL, + sens_hum TEXT(3) NOT NULL, + sens_vcc TEXT(8) NOT NULL, + sens_other TEXT(255) + )""" + cursor.execute(query) + conn.commit() + print('sqlite3.db is NOT !') + + +with sqlite3.connect('example.db') as db + cursor = db.cursor() + query = """ CREATE TABLE IF NOT EXISTS expenses(id INTEGER, name TEXT)""" + cursor.execute(query) +``` + + +```bash +conn = sqlite3.connect('example.db') +cursor = conn.cursor() + +# Создание таблицы +cursor.execute(''' +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT, + age INTEGER +) +''') + +# Вставка данных +cursor.execute(''' +INSERT INTO users (name, age) VALUES (?, ?) +''', ('Alice', 30)) + +# Сохранение изменений +conn.commit() + +# Получение данных +cursor.execute('SELECT * FROM users') +rows = cursor.fetchall() +for row in rows: + print(row) + +# Закрытие соединения +conn.close() +``` +Этот скрипт создает базу данных SQLite, создает таблицу users, добавляет в неё запись и выводит все строки таблицы. Команда cursor.execute используется для выполнения SQL-запросов, а conn.commit сохраняет изменения. +### Обновление возраста пользователя с именем Alice +```python +cursor.execute(''' +UPDATE users SET age = ? WHERE name = ? +''', (31, 'Alice')) +conn.commit() +``` +В этом примере возраст пользователя с именем Alice обновляется до 31 года. После выполнения запроса изменения сохраняются с помощью conn.commit. +### Удаление пользователя с именем Alice ### +```python +cursor.execute(''' +DELETE FROM users WHERE name = ? +''', ('Alice',)) +conn.commit() +``` +Этот пример показывает, как удалить запись из таблицы по условию. В данном случае удаляется пользователь с именем Alice. +### Работа с параметризованными запросами: ### +Для защиты от SQL-инъекций рекомендуется использовать параметризованные запросы. +```bash +name = 'Bob' +cursor.execute('SELECT * FROM users WHERE name = ?', (name,)) +rows = cursor.fetchall() +``` +Использование параметризованных запросов помогает избежать уязвимостей, связанных с SQL-инъекциями, передавая параметры запроса через кортеж или список. + +======================================== +```python +# извлечь один/три/все столбец +SELECT prod_name FROM Products +SELECT prod_id, prod_name, prod price FROM Products +SELECT * FROM Products; + +# извлечь уникальные строки +SELECT DISTINCT vend_id +FROM Products; + +# извлечь 5 строк начиная с 2ой +SELECT prod_name +FROM Products +LIMIT 5 OFFSET 2; + +# комментарии +# этокомментарий +/* + это комментарий +*/ +SELECT DISTINCT vend_id -- это комментарий +FROM Products; + +# сортировка 1 или нескольким выбранным/невыбранным столбцам +SELECT prod_name +FROM Products +ORDER BY prod_name, prod_price; (DESC) -- сортировка наоборот + +# можно сортировать по положению столбца (только выбранному) +SELECT prod_name +FROM Products +ORDER BY 2, 3; (DESC) -- сортировка наоборот + +# критерий отбора +SELECT DISTINCT vend_id +FROM Products +WHERE prod_price = 3.49; + +/* + если оба: WHERE и ( ORDER BY - должно быть последним ) + */ + +# сравнение с диапазоном значений +SELECT DISTINCT vend_id +FROM Products +WHERE prod_price = 3.49 BETWEEN 5 AND 10 +( WHERE prod_price IS NULL ) --проверка на NULL + +# фильтр по нескольким столбцам (+ можно объединять и много + скобки) +SELECT prod_id, prod_price, prod_name +FROM Products +WHERE vend_id = 'DLL01' AND prod_price <= 4 -- И то И то +( WHERE vend_id = 'DLL01' OR vend_id = 'BRS01' -- ИЛИ то ИЛИ то ) + +# сравнение +SELECT prod_name, prod_price +FROM Products +WHERE vend_id IN ('DLL01', 'BRS01') -- это лучше! +( WHERE vend_id = 'DLL01' OR vend_id = 'BRS01' ) -- то же самое +ORDER BY prod_name; + +# выбор все КРОМЕ +SELECT prod_name +FROM Products +WHERE NOT vend_id = 'DLL01' +ORDER BY prod_name; + +# поиск с использованием МЕТАСИМВОЛОВ (только текстовые поля) и любое кол-во +SELECT prod_id, prod_name +FROM Products +WHERE prod_name LIKE 'Fish%'; -- % +( WHERE email LIKE 'b%@forta.com'; ) -- % может стоять в любом месте +WHERE prod_name LIKE '__ inch teddy bear%'; -- __(2) % (_один символ) + +# конкатенация (+) или (||) +SELECT vend_name || ' ()' || vend_country || ')' +FROM Vendors +ORDER BY vend_name; + +# убрать все пробелы справа +SELECT vend_name || ' ()' || +RTRIM(vend_country) || ')' +FROM Vendors +ORDER BY vend_name; + +# псевдонимы (переименовать столбец (если недопустимые символы, или длинное)) +SELECT RTRIM(vend_name) || ' ()' || + RTRIM(vend_country) || ')' + AS vend_title -- псевдоним +FROM Vendors +ORDER BY vend_name; + +# арифметические операции +SELECT prod_id, + quantity, + item_price + quantity*item_price AS expanded_price -- нов колонка (*) +FROM OrderItems +WHERE order_nam = 20008; + +# функции обработки данных +# DATE() +SELECT order_num +FROM Orders +WHERE strftime('%Y', order_date) = 2020; -- извлекается только год +-- добавить AND для сравнения месяца и года + +# ИТОГОВЫЕ ФУНКЦИИ +# AVG() +SELECT AVG(prod_price) AS avg_price -- строки NULL - игнорируются +FROM Products +WHERE vend_id = 'DLL01'; + +SELECT AVG(DISTINCT prod_price) AS avg_price -- только уникальные цены +FROM Products +WHERE vend_id = 'DLL01'; + +# COUNT() +SELECT COUNT(*) AS num_cust -- подсчет всех строк независимо от значений +FROM Customers; + +SELECT COUNT(cust_email) AS num_cust -- подсчет только где есть email +FROM Customers; + +# MAX() +# MIN() + +# SUM() - строки с NULL - игнорируются ! +SELECT SUM(item_price*quantity) AS total_price +FROM OrderItems +WHERE order_item = 20005; + +# комбинирование итоговых функций +SELECT COUNT(*) AS num_items, + MIN(prod_price) AS price_min, + MAX(prod_price) AS price_max, + AVG(prod_price) AS price_avg +FROM Products; + +# СОЗДАНИЕ ГРУПП +SELECT vend_id, COUNT(*) AS num_prods +FROM Products +GROUP BY vend_id; +# WHERE -> GROUP BY -> ORDER BY -- только такой порядок ! + +# HAVING - фильтр группы. (WHERE - фильтр строк) +SELECT cust_id, COUNT(*) AS orders +FROM Orders +GROUP BY cust_id +HAVING COUNT (*) >= 2; + +SELECT order_num, COUNT(*) AS items +FROM OrderItems +GROUP BY order_num +HAVING COUNT (*) >= 3 +ORDER BY items, order_num; + +# ПОРЯДОК СЛЕДОВАНИЯ: ################################### +SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY +######################################################### + +# ФИЛЬТРАЦИЯ с помощью ПОДЗАПРОСОВ : +SELECT cust_name, cust_contact +FROM Customers +WHERE cust_id IN (SELECT cust_id + FROM Orders + WHERE order_num IN (SELECT order_num + FROM OrderItems + WHERE prod_id = 'RGAN01')); + +# SELECT в подзапросах могут возвращать только один столбец !!! + +# СОЗДАНИЕ СОЕДИНЕНИЯ: +SELECT vend_name, prod_name, prod_price +FROM Vendors, Products -- ( Vendors INNER JOIN Products ) +WHERE Vendors.vend_id = Products.vend_id; + +SELECT vend_name, prod_name, prod_price +FROM Vendors INNER JOIN Products + ON Vendors.vend_id = Products.vend_id; + +SELECT vend_name, prod_name, prod_price, quantity +FROM OrderItems, Products, Vendors +WHERE Products.vend_id = Vendors.vend_id +AND OrderItems.prod_id = Products.prod_id +AND order_num = 20007; + +SELECT cust_name, cust_contact +FROM Customers, Orders, OrderItems +WHERE Customers.cust_id = Orders.cust_id +AND OrderItems.order_num = Orders.order_num +AND prod_id = 'RGAN01')); + +# Псевдонимы ТАБЛИЦ - только во время выполнения запроса +SELECT cust_name, cust_contact +FROM Customers AS C, Orders AS O, OrderItems AS OI +WHERE C.cust_id = O.cust_id +AND OI.order_num = O.order_num +AND prod_id = 'RGAN01')); + +# САМО_СОЕДИНЕНИЯ +SELECT c1.cust_id, c1.cust_name, c1.cust_contact +FROM Customers AS c1, Customers AS c2 +WHERE c1.cust_name = c2.cust_name + AND c2.cust_contact = 'Jim Jones'; + +# ЕСТЕСТВЕННЫЕ СОЕДИНЕНИЯ +SELECT C.*, O.order_num, O.order_date, + OI.prod_id, OI.quantity, OI.item_price +FROM Customers AS C, Orders AS O, OrderItems AS OI +WHERE C.cust_id = O.cust_id + AND OI.order_num = O.order_num + AND prod_id = 'RGAN01'; + +# ВНЕШНИЕ СОЕДИНЕНИЯ +SELECT Customers.cust_id, Orders.order_num +FROM Customers +INNER JOIN Orders -- LEFT OUTER JOIN + ON Customers.cust_id = Orders.cust_id; + +# ИТОГОВЫЕ СОЕДИНЕНИЯ +SELECT Customers.cust_id, + COUNT(Orders.order_num) AS num_ord +FROM Customers +INNER JOIN Orders -- LEFT OUTER JOIN + ON Customers.cust_id = Orders.cust_id +GROUP BY Customers.cust_id; + +# КОМБИНИРОВАННЫЕ ЗАПРОСЫ +# -- UNION - повторяющиеся строки - удаляются!!! UNION ALL - НЕТ !!! +SELECT cust_name, cust_contact, cust_email +FROM Customers +WHERE cust_state IN ('IL', 'IN', 'MI') +UNION +SELECT cust_name, cust_contact, cust_email +FROM Customers +WHERE cust_name = 'Fun4All'; + +# тот же самый запрос: +SELECT cust_name, cust_contact, cust_email +FROM Customers +WHERE cust_state IN ('IL', 'IN', 'MI') + OR cust_name = 'Fun4All'; + +# сортировка результатов КОМБИНИРОВАННЫХ ЗАПРОСОВ +SELECT cust_name, cust_contact, cust_email +FROM Customers +UNION +SELECT cust_name, cust_contact, cust_email +FROM Customers +WHERE cust_name = 'Fun4All' +ORDER BY cust_name, cust_contact; -- сортировка ОБОИХ инструкций !!! + +#================================================= +# ДОБАВЛЕНИЕ ДАННЫХ -- INSERT +INSERT INTO Customers +VALUES(100000006, + 'Toy Land', + '123 Any Street', + 'New York', + 'NY', + '11111', + 'USA', + 'NULL', + 'NULL'); + +# то же самое только более правильно: +# столбцы можно пропускать если он: +# по умолчанию = NULL, или = какое-то значение по умолчанию +# добавляется только ОДНА ! строка: +INSERT INTO Customers(cust_id, + cust_name, + cust_address, + cust_city, + cust_state, + cust_zip, + cust_country, + cust_contact, + cust_email) +VALUES(100000006, + 'Toy Land', + '123 Any Street', + 'New York', + 'NY', + '11111', + 'USA', + 'NULL', + 'NULL'); + +# можно брать значения из другой таблицы с помощью SELECT +# учитывается только положение столбцов инструкции INSERT, +# а SELECT - нет ! +# добавляются ВСЕ! строки из SELECT: +INSERT INTO Customers(cust_id, + cust_name, + cust_address, + cust_city, + cust_state, + cust_zip, + cust_country, + cust_contact, + cust_email) +SELECT cust_id, + cust_name, + cust_address, + cust_city, + cust_state, + cust_zip, + cust_country, + cust_contact, + cust_email +FROM CustNew; + +# ОБНОВЛЕНИЕ ЗАПИСЕЙ в БД +UPDATE Customers +SET cust_email = 'kim@thetoystore.com', + cust_contact = 'Sam Roberts' +WHERE cust_id = 1000000005; + +# удаление значение из столбца +UPDATE Customers +SET cust_email = NULL -- отсутствие хоть какого-то значения +WHERE cust_id = 1000000005; + +# УДАЛЕНИЕ ЗАПИСЕЙ (СТРОК полностью, не удаляет таблицу) +DELETE FROM Customers +WHERE cust_id = 1000000005; -- если WHERE нет - удаление ВСЕХ! строк таблицы + +# СОЗДАНИЕ ТАБЛИЦ (значение NULL - по умолчанию) +CREATE TABLE Products +( + prod_id CHAR(10) NOT NULL PRIMARY KEY, + vend_id CHAR(10) NOT NULL, + prod_name CHAR(254) NOT NULL, + prod_price DECIMAL(8, 2) NOT NULL, + prod_desk VARCHAR(1000) NULL +); + +CREATE TABLE Orders +( + order_num INTEGER NOT NULL, + order_date DATETIME NOT NULL DEFAULT date('now'), + cust_id CHAR(10) NOT NULL DEFAULT 1 +); + +# ОБНОВЛЕНИЕ ТАБЛИЦ +# добавление столбца: +ALTER TABLE Vendors +ADD vend_phone CHAR(20); + +# удаление столбца: +ALTER TABLE Vendors +DROP COLUMN vend_phone; + +# удаление таблицы (безвозвратно !!!): +DROP TABLE CustCopy; + +# ПРЕДСТАВЛЕНИЯ (ВИРТУАЛЬНЫЕ ТАБЛИЦЫ - запросы вместо данных) +CREATE VIEW ProductCustomers AS +SELECT cust_name, cust_contact, prod_id +FROM Customers, Orders, OrderItems +WHERE Customers.cust_id = Orders.cust_id + AND OrderItems.order_num = Orders.order_num; + +SELECT * FROM ProductCustomers; -- вернет список всех клиентов сделавших заказы +WHERE prod_id = 'RGAN01'; -- все клиенты заказавшие товар 'RGAN01' + +............. + +# ТРАНЗАКЦИИ - единый набор SQL запросов (механизм для управления наборами SQL запросов, которые должны быть выполнены ТОЛЬКО целиком) +BEGIN TRANSACTION +...... +COMMIT TRANSACTION +...... +ROLLBACK; + +# КУРСОРЫ (шаги) + +``` + diff --git a/SQL/НОРМАЛЬНЫЕ ФОРМЫ.md b/SQL/НОРМАЛЬНЫЕ ФОРМЫ.md new file mode 100644 index 0000000..5a18e5e --- /dev/null +++ b/SQL/НОРМАЛЬНЫЕ ФОРМЫ.md @@ -0,0 +1,41 @@ +#### *==Реляционная модель + +- Отношения -- таблицы +- Атрибуты -- поля таблицы +- Кортежи -- строки таблицы + + +#### *==Первая нормальная форма + +- нет дублирующихся строк +- все атрибуты атомарны +- нет повторяющихся атрибутоов с одинаковым смыслом + +![[Снимок экрана 2024-09-15 в 18.55.25.png]] + +![[Снимок экрана 2024-09-16 в 21.31.06.png]] + +#### *==Вторая нормальная форма + +- отношение находится в первой НФ +- есть первичный ключ +- все неключевые атрибуты функционально зависят от ключа целиком, но не от его части + +![[Снимок экрана 2024-09-15 в 19.02.02.png]] + +![[Снимок экрана 2024-09-16 в 21.36.18.png]] + +#### *==Третья нормальная форма + +- отношение находится во второй НФ +- неключевые атрибуты напрямую зависят только от РК, но не от других атрибутов + +![[Снимок экрана 2024-09-15 в 19.10.29.png]] + + +=========================================== + +![[Снимок экрана 2024-09-16 в 21.45.31.png]] + + +![[Снимок экрана 2024-09-16 в 21.45.54.png]]
\ No newline at end of file diff --git a/SQL/ШПАРГАЛКА SQLite.md b/SQL/ШПАРГАЛКА SQLite.md new file mode 100644 index 0000000..d6107c2 --- /dev/null +++ b/SQL/ШПАРГАЛКА SQLite.md @@ -0,0 +1,4139 @@ +- 09.02.2024 +- [DATA ANALYST](https://uproger.com/category/data-analyst/), [SQL](https://uproger.com/category/sql/) + +") + +Все примеры изначально писались для СУБД SQLite, но почти всё из этого применимо также и к другим СУБД. + +Вначале идут очень простые запросы, с них можно начать новичкам. Если хочется чего-то более интересного — листайте вниз. Здесь есть и примеры довольно сложных запросов с агрегирующими функциями, триггерами, длинными подзапросами, с оконными функциями. Помимо этого, часть примеров посвящена работе с SQL в Python, используя `sqlite3`, `pandas`, `polars`. Этот список запросов с комментариями можно использовать как наглядное пособие для изучения SQL. + +Большинство советов я публиковал в своем [канале по анализу данных](https://t.me/+dXZpK8lQ0lY4YjAy), где вы найдете большое количество советов, инструментов и примеров с кодом. А [здесь большая полезная папка,](https://t.me/addlist/qht-ouKSGaQwNjcy) которую я собрал в которой куча полезного для работы с данными. + +Кстати, все эти примеры SQL заботливо собраны в одном архиве, вы можете [скачать](https://github.com/gvwilson/sql-tutorial/raw/main/sql-tutorial.zip) его и экспериментировать локально. После скачивания и разархивирования, у вас будет 3 группы файлов: + +- `./db/*.db` — базы данных SQLite, которые используются в примерах ниже +- `./src/*.*` — SQL-запросы, сценарии Python +- `./out/*.*` — ожидаемый результат для примеров + +### Выбираем все значения из таблички + +``` +SELECT * +FROM little_penguins; +``` + +``` +Adelie|Dream|37.2|18.1|178|3900|MALE +Adelie|Dream|37.6|19.3|181|3300|FEMALE +Gentoo|Biscoe|50|15.3|220|5550|MALE +Adelie|Torgersen|37.3|20.5|199|3775|MALE +Adelie|Biscoe|39.6|17.7|186|3500|FEMALE +Gentoo|Biscoe|47.7|15|216|4750|FEMALE +Adelie|Dream|36.5|18|182|3150|FEMALE +Gentoo|Biscoe|42|13.5|210|4150|FEMALE +Adelie|Torgersen|42.1|19.1|195|4000|MALE +Gentoo|Biscoe|54.3|15.7|231|5650|MALE +``` + +- ничего особенного, выбираем все записи из таблички `little_penguins` + +### Дополнительные команды SQL + +[src/admin_commands.sql](https://gvwilson.github.io/sql-tutorial/src/admin_commands.sql) + +``` +.headers on +.mode markdown +SELECT * +FROM little_penguins; +``` + +[out/admin_commands.out](https://gvwilson.github.io/sql-tutorial/out/admin_commands.out) + +``` +| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | +|---------|-----------|----------------|---------------|-------------------|-------------|--------| +| Adelie | Dream | 37.2 | 18.1 | 178 | 3900 | MALE | +| Adelie | Dream | 37.6 | 19.3 | 181 | 3300 | FEMALE | +| Gentoo | Biscoe | 50 | 15.3 | 220 | 5550 | MALE | +| Adelie | Torgersen | 37.3 | 20.5 | 199 | 3775 | MALE | +| Adelie | Biscoe | 39.6 | 17.7 | 186 | 3500 | FEMALE | +| Gentoo | Biscoe | 47.7 | 15 | 216 | 4750 | FEMALE | +| Adelie | Dream | 36.5 | 18 | 182 | 3150 | FEMALE | +| Gentoo | Biscoe | 42 | 13.5 | 210 | 4150 | FEMALE | +| Adelie | Torgersen | 42.1 | 19.1 | 195 | 4000 | MALE | +| Gentoo | Biscoe | 54.3 | 15.7 | 231 | 5650 | MALE | +``` + +- включаем заголовки и режим markdown; в `SQLite` подобные команды начинаются с `.`, а в PostgreSQL с `\` +- кстати, для просмотра дополнительной инфы или чтобы узнать, какие команды есть, используйте `.help` + +### Выбираем нужные столбцы + +[src/specify_columns.sql](https://gvwilson.github.io/sql-tutorial/src/specify_columns.sql) + +``` +SELECT species, + island, + sex +FROM little_penguins; +``` + +[out/specify_columns.out](https://gvwilson.github.io/sql-tutorial/out/specify_columns.out) + +``` +| species | island | sex | +|---------|-----------|--------| +| Adelie | Dream | MALE | +| Adelie | Dream | FEMALE | +| Gentoo | Biscoe | MALE | +| Adelie | Torgersen | MALE | +| Adelie | Biscoe | FEMALE | +| Gentoo | Biscoe | FEMALE | +| Adelie | Dream | FEMALE | +| Gentoo | Biscoe | FEMALE | +| Adelie | Torgersen | MALE | +| Gentoo | Biscoe | MALE | +``` + +- выбираем колонки `species`, `island`, `sex` из таблички `little_penguins` + +### Сортировка + +[src/sort.sql](https://gvwilson.github.io/sql-tutorial/src/sort.sql) + +``` +SELECT species, + sex, + island +FROM little_penguins +ORDER BY island ASC, + sex DESC; +``` + +[out/sort.out](https://gvwilson.github.io/sql-tutorial/out/sort.out) + +``` +| species | sex | island | +|---------|--------|-----------| +| Gentoo | MALE | Biscoe | +| Gentoo | MALE | Biscoe | +| Adelie | FEMALE | Biscoe | +| Gentoo | FEMALE | Biscoe | +| Gentoo | FEMALE | Biscoe | +| Adelie | MALE | Dream | +| Adelie | FEMALE | Dream | +| Adelie | FEMALE | Dream | +| Adelie | MALE | Torgersen | +| Adelie | MALE | Torgersen | +``` + +- выбираем столбцы `species`, `island`, `sex` из таблички `little_penguins` +- сортируем все значения из `island` в возрастающем порядке (от A к Z) +- строки с одинаковыми значениями `island` дополнительно сортируем по их значениям `sex` в обратном порядке, от большего к меньшему (от Z к A) + +### Ограничение выводимых записей + +- Full dataset has 344 rows + +[src/limit.sql](https://gvwilson.github.io/sql-tutorial/src/limit.sql) + +``` +SELECT species, + sex, + island +FROM penguins +ORDER BY species, + sex, + island +LIMIT 10; +``` + +[out/limit.out](https://gvwilson.github.io/sql-tutorial/out/limit.out) + +``` +| species | sex | island | +|---------|--------|-----------| +| Adelie | | Dream | +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | FEMALE | Biscoe | +| Adelie | FEMALE | Biscoe | +| Adelie | FEMALE | Biscoe | +| Adelie | FEMALE | Biscoe | +``` + +- выбираем столбцы `species`, `sex`, `island` из таблички `penguins` +- сортируем по `species` в порядке возрастания, строки с одинаковым значением `species` сортируются по `sex`, с одинаковым `sex` дополнительно сортируются по `island` +- ну и выводим только первые 10 строк + +### Ещё некоторые параметры вывода + +[src/page.sql](https://gvwilson.github.io/sql-tutorial/src/page.sql) + +``` +SELECT species, + sex, + island +FROM penguins +ORDER BY species, + sex, + island +LIMIT 10 +OFFSET 3; +``` + +[out/page.out](https://gvwilson.github.io/sql-tutorial/out/page.out) + +``` +| species | sex | island | +|---------|--------|-----------| +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | FEMALE | Biscoe | +| Adelie | FEMALE | Biscoe | +| Adelie | FEMALE | Biscoe | +| Adelie | FEMALE | Biscoe | +| Adelie | FEMALE | Biscoe | +| Adelie | FEMALE | Biscoe | +| Adelie | FEMALE | Biscoe | +``` + +- `OFFSET` указывается после `LIMIT` и позволяет пропустить сколько-то первых строк, в данном случае пропущены 3 первых строки + +### Удаляем дубликаты + +[src/distinct.sql](https://gvwilson.github.io/sql-tutorial/src/distinct.sql) + +``` +SELECT DISTINCT species, + sex, + island +FROM penguins; +``` + +[out/distinct.out](https://gvwilson.github.io/sql-tutorial/out/distinct.out) + +``` +| species | sex | island | +|-----------|--------|-----------| +| Adelie | MALE | Torgersen | +| Adelie | FEMALE | Torgersen | +| Adelie | | Torgersen | +| Adelie | FEMALE | Biscoe | +| Adelie | MALE | Biscoe | +| Adelie | FEMALE | Dream | +| Adelie | MALE | Dream | +| Adelie | | Dream | +| Chinstrap | FEMALE | Dream | +| Chinstrap | MALE | Dream | +| Gentoo | FEMALE | Biscoe | +| Gentoo | MALE | Biscoe | +| Gentoo | | Biscoe | +``` + +- `SELECT DISTINCT` — выбираем уникальные комбинации из столбцов `species`, `sex`, `island` + +### Фильтруем результаты + +[src/filter.sql](https://gvwilson.github.io/sql-tutorial/src/filter.sql) + +``` +SELECT DISTINCT species, + sex, + island +FROM penguins +WHERE island = 'Biscoe'; +``` + +[out/filter.out](https://gvwilson.github.io/sql-tutorial/out/filter.out) + +``` +| species | sex | island | +|---------|--------|--------| +| Adelie | FEMALE | Biscoe | +| Adelie | MALE | Biscoe | +| Gentoo | FEMALE | Biscoe | +| Gentoo | MALE | Biscoe | +| Gentoo | | Biscoe | +``` + +- выбираем уникальные комбинации значений `species`, `sex`, `island` из `penguins`, где значения поля `island` равно `Biscoe` + +### Более сложные условия фильтрации + +[src/filter_and.sql](https://gvwilson.github.io/sql-tutorial/src/filter_and.sql) + +``` +SELECT DISTINCT species, + sex, + island +FROM penguins +WHERE island = 'Biscoe' + AND sex != 'MALE'; +``` + +[out/filter_and.out](https://gvwilson.github.io/sql-tutorial/out/filter_and.out) + +``` +| species | sex | island | +|---------|--------|--------| +| Adelie | FEMALE | Biscoe | +| Gentoo | FEMALE | Biscoe | +``` + +- выбираем уникальные комбинации значений `species`, `sex`, `island` из `penguins`, где значения поля `island` равно `Biscoe`, а значения поля `sex` не равно `MALE` + +### Некоторые математические действия + +[src/calculations.sql](https://gvwilson.github.io/sql-tutorial/src/calculations.sql) + +``` +SELECT flipper_length_mm / 10.0, + body_mass_g / 1000.0 +FROM penguins +LIMIT 3; +``` + +[out/calculations.out](https://gvwilson.github.io/sql-tutorial/out/calculations.out) + +``` +| flipper_length_mm / 10.0 | body_mass_g / 1000.0 | +|--------------------------|----------------------| +| 18.1 | 3.75 | +| 18.6 | 3.8 | +| 19.5 | 3.25 | +``` + +- выводим 3 первых строки значений `flipper_length_mm`, делённых на 10.0, и значений `body_mass_g`, делённых на 1000.0 + +### Переименовываем столбцы + +[src/rename_columns.sql](https://gvwilson.github.io/sql-tutorial/src/rename_columns.sql) + +``` +SELECT flipper_length_mm / 10.0 AS flipper_cm, + body_mass_g / 1000.0 AS weight_kg, + island AS where_found +FROM penguins +LIMIT 3; +``` + +[out/rename_columns.out](https://gvwilson.github.io/sql-tutorial/out/rename_columns.out) + +``` +| flipper_cm | weight_kg | where_found | +|------------|-----------|-------------| +| 18.1 | 3.75 | Torgersen | +| 18.6 | 3.8 | Torgersen | +| 19.5 | 3.25 | Torgersen | +``` + +- делим значения `flipper_length_mm` на 10.0, делим значения `body_mass_g` на 1000.0 +- переименовываем столбцы `flipper_length_mm` — в `flipper_cm`, `body_mass_g` — в `weight_kg`, `island` — в `where_found` +- выводим первые 3 строки + +**Взаимосвязь рассмотренных понятий SQL можно показать так:** + +") + +concept map: selection + +### Подсчёт с пропущенными значениями + +[src/show_missing_values.sql](https://gvwilson.github.io/sql-tutorial/src/show_missing_values.sql) + +``` +SELECT flipper_length_mm / 10.0 AS flipper_cm, + body_mass_g / 1000.0 AS weight_kg, + island AS where_found +FROM penguins +LIMIT 5; +``` + +[out/show_missing_values.out](https://gvwilson.github.io/sql-tutorial/out/show_missing_values.out) + +``` +| flipper_cm | weight_kg | where_found | +|------------|-----------|-------------| +| 18.1 | 3.75 | Torgersen | +| 18.6 | 3.8 | Torgersen | +| 19.5 | 3.25 | Torgersen | +| | | Torgersen | +| 19.3 | 3.45 | Torgersen | +``` + +- делим значения из `flipper_length_mm` на 10, затем присваиваем результаты новому столбцу `flipper_cm` +- делим значения из столбца `body_mass_g` на 1000 и затем присваивание результатов новому столбцу `weight_kg` +- переименовываем `island` в `where_found` + +### Вывод с условием при помощи WHERE + +- Repeated from above so it doesn’t count against our query limit + +[src/filter.sql](https://gvwilson.github.io/sql-tutorial/src/filter.sql) + +``` +SELECT DISTINCT species, + sex, + island +FROM penguins +WHERE island = 'Biscoe'; +``` + +[out/filter.out](https://gvwilson.github.io/sql-tutorial/out/filter.out) + +``` +| species | sex | island | +|---------|--------|--------| +| Adelie | FEMALE | Biscoe | +| Adelie | MALE | Biscoe | +| Gentoo | FEMALE | Biscoe | +| Gentoo | MALE | Biscoe | +| Gentoo | | Biscoe | +``` + +- выбираем столбцы `species`, `sex`, `island` +- выводим все записи из `penguins`, где значение `island` равно `'Biscoe'` + +[src/null_equality.sql](https://gvwilson.github.io/sql-tutorial/src/null_equality.sql) + +``` +SELECT DISTINCT species, + sex, + island +FROM penguins +WHERE island = 'Biscoe' + AND sex = 'FEMALE'; +``` + +[out/null_equality.out](https://gvwilson.github.io/sql-tutorial/out/null_equality.out) + +``` +| species | sex | island | +|---------|--------|--------| +| Adelie | FEMALE | Biscoe | +| Gentoo | FEMALE | Biscoe | +``` + +- выводим все записи из `penguins`, где значение `island` равно `'Biscoe'` и значение `sex` равно `'FEMALE'` + +### Условие с отрицанием + +- условие с оператором отрицания `!=` тоже без проблем работает + +[src/null_inequality.sql](https://gvwilson.github.io/sql-tutorial/src/null_inequality.sql) + +``` +SELECT DISTINCT species, + sex, + island +FROM penguins +WHERE island = 'Biscoe' + AND sex != 'FEMALE'; +``` + +[out/null_inequality.out](https://gvwilson.github.io/sql-tutorial/out/null_inequality.out) + +``` +| species | sex | island | +|---------|------|--------| +| Adelie | MALE | Biscoe | +| Gentoo | MALE | Biscoe | +``` + +### Выбираем NULL значения + +[src/safe_null_equality.sql](https://gvwilson.github.io/sql-tutorial/src/safe_null_equality.sql) + +``` +SELECT species, + sex, + island +FROM penguins +WHERE sex IS NULL; +``` + +[out/safe_null_equality.out](https://gvwilson.github.io/sql-tutorial/out/safe_null_equality.out) + +``` +| species | sex | island | +|---------|-----|-----------| +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | | Torgersen | +| Adelie | | Dream | +| Gentoo | | Biscoe | +| Gentoo | | Biscoe | +| Gentoo | | Biscoe | +| Gentoo | | Biscoe | +| Gentoo | | Biscoe | +``` + +- выбираем строки со значениями `species`, `sex`, `island` из таблички `penguins`, где значения `sex` нет (`NULL`) + +**Вот так можно показать связь понятий SQL, которые мы рассмотрели выше:** + +") + +concept map: null + +### Агрегирование в SQL + +[src/simple_sum.sql](https://gvwilson.github.io/sql-tutorial/src/simple_sum.sql) + +``` +SELECT sum(body_mass_g) AS total_mass +FROM penguins; +``` + +[out/simple_sum.out](https://gvwilson.github.io/sql-tutorial/out/simple_sum.out) + +``` +| total_mass | +|------------| +| 1437000 | +``` + +- суммируем все значения колонки `body_mass_g`, сохраняем в новый столбец `total_mass` + +### Распространённые агрегирующие функции в SQL + +[src/common_aggregations.sql](https://gvwilson.github.io/sql-tutorial/src/common_aggregations.sql) + +``` +SELECT MAX(bill_length_mm) AS longest_bill, + MIN(flipper_length_mm) AS shortest_flipper, + AVG(bill_length_mm) / AVG(bill_depth_mm) AS weird_ratio +FROM penguins; +``` + +[out/common_aggregations.out](https://gvwilson.github.io/sql-tutorial/out/common_aggregations.out) + +``` +| longest_bill | shortest_flipper | weird_ratio | +|--------------|------------------|------------------| +| 59.6 | 172 | 2.56087082530644 | +``` + +- находим максимальное значение из столбца `bill_length_mm`, записываем это значение как `longest_bill` +- аналогично находим минимальное из `flipper_length_mm`, находим среднее из `bill_length_mm`, среднее из `bill_depth_mm` + +### Подсчёт значений при помощи COUNT + +[src/count_behavior.sql](https://gvwilson.github.io/sql-tutorial/src/count_behavior.sql) + +``` +SELECT COUNT(*) AS count_star, + COUNT(sex) AS count_specific, + COUNT(DISTINCT sex) AS count_distinct +FROM penguins; +``` + +[out/count_behavior.out](https://gvwilson.github.io/sql-tutorial/out/count_behavior.out) + +``` +| count_star | count_specific | count_distinct | +|------------|----------------|----------------| +| 344 | 333 | 2 | +``` + +- `COUNT(*)` — считаем все значения из `count_star` +- `COUNT(sex)` — считаем все значения из столбца `sex` +- `COUNT(DISTINCT sex)` — считаем уникальные значения из `sex` (очевидно их 2: `MALE`, `FEMALE`) +- записываем эти 3 числа как `count_star`, `count_specific`, `count_distinct` соответственно + +### Группировка + +[src/simple_group.sql](https://gvwilson.github.io/sql-tutorial/src/simple_group.sql) + +``` +SELECT AVG(body_mass_g) AS average_mass_g +FROM penguins +GROUP BY sex; +``` + +[out/simple_group.out](https://gvwilson.github.io/sql-tutorial/out/simple_group.out) + +``` +| average_mass_g | +|------------------| +| 4005.55555555556 | +| 3862.27272727273 | +| 4545.68452380952 | +``` + +- из таблички `penguins` находим среднее всех значений `body_mass_g`, сохраняем как `average_mass_g` +- группируем по значениям `sex` (группы `FEMALE`, `MALE`, `NULL`) + +### Как себя ведут неагрегированные столбцы + +[src/unaggregated_columns.sql](https://gvwilson.github.io/sql-tutorial/src/unaggregated_columns.sql) + +``` +SELECT sex, + AVG(body_mass_g) AS average_mass_g +FROM penguins +GROUP BY sex; +``` + +[out/unaggregated_columns.out](https://gvwilson.github.io/sql-tutorial/out/unaggregated_columns.out) + +``` +| sex | average_mass_g | +|--------|------------------| +| | 4005.55555555556 | +| FEMALE | 3862.27272727273 | +| MALE | 4545.68452380952 | +``` + +- для того, чтобы было видно названия отдельных групп, выбираем не только среднее `AVG(body_mass_g)`, но и `sex` +- видим 3 группы: `NULL`, `FEMALE`, `MALE` + +### Выбор нужных столбцов для агрегирования + +[src/arbitrary_in_aggregation.sql](https://gvwilson.github.io/sql-tutorial/src/arbitrary_in_aggregation.sql) + +``` +SELECT sex, + body_mass_g +FROM penguins +GROUP BY sex; +``` + +[out/arbitrary_in_aggregation.out](https://gvwilson.github.io/sql-tutorial/out/arbitrary_in_aggregation.out) + +``` +| sex | body_mass_g | +|--------|-------------| +| | | +| FEMALE | 3800 | +| MALE | 3750 | +``` + +- здесь у нас популярная ошибка, мы просто выбираем `body_mass_g`, а не находим среднее, поэтому SQL выбирает любые значения из `body_mass_g`. Аккуратнее) + +### Фильтрация агрегированных значений + +[src/filter_aggregation.sql](https://gvwilson.github.io/sql-tutorial/src/filter_aggregation.sql) + +``` +SELECT sex, + AVG(body_mass_g) AS average_mass_g +FROM penguins +GROUP BY sex +HAVING average_mass_g > 4000.0; +``` + +[out/filter_aggregation.out](https://gvwilson.github.io/sql-tutorial/out/filter_aggregation.out) + +``` +| sex | average_mass_g | +|------|------------------| +| | 4005.55555555556 | +| MALE | 4545.68452380952 | +``` + +- здесь мы используем `HAVING` вместо `WHERE` (эффект тот же самый), оставляем только те значения из `average_mass_g`, которые больше 4000 + +### Читабельный вывод + +[src/readable_aggregation.sql](https://gvwilson.github.io/sql-tutorial/src/readable_aggregation.sql) + +``` +SELECT sex, + ROUND(AVG(body_mass_g), 1) AS average_mass_g +FROM penguins +GROUP BY sex +HAVING average_mass_g > 4000.0; +``` + +[out/readable_aggregation.out](https://gvwilson.github.io/sql-tutorial/out/readable_aggregation.out) + +``` +| sex | average_mass_g | +|------|----------------| +| | 4005.6 | +| MALE | 4545.7 | +``` + +- округляем среднее `AVG(body_mass_g` до 1 знака после запятой, используя `ROUND` + +### Фильтрация входных данных + +[src/filter_aggregate_inputs.sql](https://gvwilson.github.io/sql-tutorial/src/filter_aggregate_inputs.sql) + +``` +SELECT sex, + ROUND( + AVG(body_mass_g) FILTER (WHERE body_mass_g < 4000.0), + 1) + AS average_mass_g +FROM penguins +GROUP BY sex; +``` + +[out/filter_aggregate_inputs.out](https://gvwilson.github.io/sql-tutorial/out/filter_aggregate_inputs.out) + +``` +| sex | average_mass_g | +|--------|----------------| +| | 3362.5 | +| FEMALE | 3417.3 | +| MALE | 3752.5 | +``` + +- при помощи `FILTER` мы находим среднее только тех значений `body_mass_g`, которые меньше 4000 +- округляем до 1 знака после запятой, сохраняем в столбец `average_mass_g` +- группируем по `sex` + +**Вот так выглядит связь основных понятий, которые мы только что обсуждали:** + +") + +concept map: aggregation + +**Кстати, вот так выглядит создание БД в оперативной памяти:** + +[src/in_memory_db.sh](https://gvwilson.github.io/sql-tutorial/src/in_memory_db.sh) + +``` +sqlite3 :memory: +``` + +- запускаем интерактивную оболочку SQLite, создаём новую базу данных в оперативной памяти для более быстрой работы + +### Создание табличек + +[src/create_work_job.sql](https://gvwilson.github.io/sql-tutorial/src/create_work_job.sql) + +``` +CREATE TABLE job (name text NOT NULL, + billable real NOT NULL); + +CREATE TABLE work (person text NOT NULL, + job text NOT NULL); +``` + +- создаём таблицу `job` со столбцами: `name` — столбец текстовых значений, не может быть пустым (`NOT NULL`), `billable` — содержит вещественные числа, не может быть пустым +- создаём табличку `work` со столбцами: `person` — текстовый, не может быть пустым, `job` — текстовый, не может быть пустым + +### Вставляем данные + +[src/populate_work_job.sql](https://gvwilson.github.io/sql-tutorial/src/populate_work_job.sql) + +``` +INSERT INTO job +VALUES ('calibrate', 1.5), + ('clean', 0.5); + +INSERT INTO work +VALUES ('mik', 'calibrate'), + ('mik', 'clean'), + ('mik', 'complain'), + ('po', 'clean'), + ('po', 'complain'), + ('tay', 'complain'); +``` + +[out/insert_values.out](https://gvwilson.github.io/sql-tutorial/out/insert_values.out) + +``` +| name | billable | +|-----------|----------| +| calibrate | 1.5 | +| clean | 0.5 | + +| person | job | +|--------|-----------| +| mik | calibrate | +| mik | clean | +| mik | complain | +| po | clean | +| po | complain | +| tay | complain | +``` + +- ничего особенного, заполняем табличку `job` парами `name`–`billable`, и так же заполняем табличку `work` парами `person`–`job` + +### Обновляем строки + +[src/update_work_job.sql](https://gvwilson.github.io/sql-tutorial/src/update_work_job.sql) + +``` +UPDATE work +SET person = "tae" +WHERE person = "tay"; +``` + +[out/update_rows.out](https://gvwilson.github.io/sql-tutorial/out/update_rows.out) + +``` +| person | job | +|--------|-----------| +| mik | calibrate | +| mik | clean | +| mik | complain | +| po | clean | +| po | complain | +| tae | complain | +``` + +- меняем все записи `"tay"` на `"tae"` + +### Удаляем строки + +[src/delete_rows.sql](https://gvwilson.github.io/sql-tutorial/src/delete_rows.sql) + +``` +DELETE FROM work +WHERE person = "tae"; +SELECT * +FROM work; +``` + +[out/delete_rows.out](https://gvwilson.github.io/sql-tutorial/out/delete_rows.out) + +``` +| person | job | +|--------|-----------| +| mik | calibrate | +| mik | clean | +| mik | complain | +| po | clean | +| po | complain | +``` + +- удаляем все строки, где значение `person` равно `"tae"` + +### Резервное копирование + +[src/backing_up.sql](https://gvwilson.github.io/sql-tutorial/src/backing_up.sql) + +``` +CREATE TABLE backup (person text NOT NULL, + job text NOT NULL); + +INSERT INTO backup +SELECT person, + job +FROM work +WHERE person = 'tae'; + +DELETE FROM work +WHERE person = 'tae'; + +SELECT * +FROM backup; +``` + +[out/backing_up.out](https://gvwilson.github.io/sql-tutorial/out/backing_up.out) + +``` +| person | job | +|--------|----------| +| tae | complain | +``` + +- создаём табличку `backup` c текстовыми столбцами `person` и `job` +- помещаем внутрь `backup` значения столбцов `person` и `job` из таблицы `work`, где значения столбца `person` равно `'tae'` +- удаляем из `work` все записи со значением `person` равным `'tae'` +- отображаем записи таблички `backup` + +**Вот так выглядит связь основных понятий, которые мы только что обсуждали:** + +") + +concept map: data definition and modification + +### Объединение табличек при помощи JOIN + +[src/cross_join.sql](https://gvwilson.github.io/sql-tutorial/src/cross_join.sql) + +``` +SELECT * +FROM work +CROSS JOIN job; +``` + +[out/cross_join.out](https://gvwilson.github.io/sql-tutorial/out/cross_join.out) + +``` +| person | job | name | billable | +|--------|-----------|-----------|----------| +| mik | calibrate | calibrate | 1.5 | +| mik | calibrate | clean | 0.5 | +| mik | clean | calibrate | 1.5 | +| mik | clean | clean | 0.5 | +| mik | complain | calibrate | 1.5 | +| mik | complain | clean | 0.5 | +| po | clean | calibrate | 1.5 | +| po | clean | clean | 0.5 | +| po | complain | calibrate | 1.5 | +| po | complain | clean | 0.5 | +| tay | complain | calibrate | 1.5 | +| tay | complain | clean | 0.5 | +``` + +- делаем `CROSS JOIN` для 2 таблиц `work` и `job` — все возможные комбинации строк из этих таблиц (если в `work` 3 строки, а в `job` 4 строки, то результат будет иметь 4 ⋅ 3 = 12 строк) + +### INNER JOIN + +[src/inner_join.sql](https://gvwilson.github.io/sql-tutorial/src/inner_join.sql) + +``` +SELECT * +FROM work +INNER JOIN job ON work.job = job.name; +``` + +[out/inner_join.out](https://gvwilson.github.io/sql-tutorial/out/inner_join.out) + +``` +| person | job | name | billable | +|--------|-----------|-----------|----------| +| mik | calibrate | calibrate | 1.5 | +| mik | clean | clean | 0.5 | +| po | clean | clean | 0.5 | +``` + +- объединяем 2 таблицы `work` и `job` — берём те записи, где значение `job` из `work` совпадает со значением `name` из `job` + +### Агрегирование объединённых через JOIN записей + +[src/aggregate_join.sql](https://gvwilson.github.io/sql-tutorial/src/aggregate_join.sql) + +``` +SELECT work.person, + SUM(job.billable) AS pay +FROM work +INNER JOIN job ON work.job = job.name +GROUP BY work.person; +``` + +[out/aggregate_join.out](https://gvwilson.github.io/sql-tutorial/out/aggregate_join.out) + +``` +| person | pay | +|--------|-----| +| mik | 2.0 | +| po | 0.5 | +``` + +- объединяем те строки таблиц `work` и `job`, где значение `job` в таблице `work` соответствует значению `name` в `job` +- суммируем значения `billable` из таблицы `job` для каждого значения `person` из таблицы `work` +- группируем результаты по значениям `person` из `work` + +### LEFT JOIN + +[src/left_join.sql](https://gvwilson.github.io/sql-tutorial/src/left_join.sql) + +``` +SELECT * +FROM work +LEFT JOIN job ON work.job = job.name; +``` + +[out/left_join.out](https://gvwilson.github.io/sql-tutorial/out/left_join.out) + +``` +| person | job | name | billable | +|--------|-----------|-----------|----------| +| mik | calibrate | calibrate | 1.5 | +| mik | clean | clean | 0.5 | +| mik | complain | | | +| po | clean | clean | 0.5 | +| po | complain | | | +| tay | complain | | | +``` + +- склеиваем таблицы `work` и `job` по соответствующим значениям столбца `job` +- если в таблице `work` есть строки, для которых нет совпадений в таблице `job`, то они все равно будут включены в результат с пустыми (`NULL`) значениями +- использование `LEFT JOIN` гарантирует, что все строки из левой таблицы `work` будут включены в результат, независимо от наличия совпадающих строк в правой таблице `job` + +### Агрегирование данных, собранных через LEFT JOIN + +[src/aggregate_left_join.sql](https://gvwilson.github.io/sql-tutorial/src/aggregate_left_join.sql) + +``` +SELECT work.person, + sum(job.billable) AS pay +FROM work +LEFT JOIN job ON work.job = job.name +GROUP BY work.person; +``` + +[out/aggregate_left_join.out](https://gvwilson.github.io/sql-tutorial/out/aggregate_left_join.out) + +``` +| person | pay | +|--------|-----| +| mik | 2.0 | +| po | 0.5 | +| tay | | +``` + +- вычисляем сумму значений столбца `billable` из `job`, сохраняем как `pay` +- используем `LEFT JOIN`, чтобы гарантированно включить все строки из `work` в `job` +- группируем по столбцу `person` из `work` + +**Вот так выглядит связь основных понятий, которые мы только что обсуждали:** + +") + +concept map: join + +### Объединение значений + +[src/coalesce.sql](https://gvwilson.github.io/sql-tutorial/src/coalesce.sql) + +``` +SELECT work.person, + COALESCE(SUM(job.billable), 0.0) AS pay +FROM work +LEFT JOIN job ON work.job = job.name +GROUP BY work.person; +``` + +[out/coalesce.out](https://gvwilson.github.io/sql-tutorial/out/coalesce.out) + +``` +| person | pay | +|--------|-----| +| mik | 2.0 | +| po | 0.5 | +| tay | 0.0 | +``` + +- `COALESCE` используется для замены `NULL` на 0.0, если сумма `billable` для данного `person` равна `NULL` +- `LEFT JOIN` включает все записи из `work` и только соответствующие записи из `job` +- группируем по значениям столбца `person` из `work` + +### SELECT DISTINCT и условие WHERE + +[src/negate_incorrectly.sql](https://gvwilson.github.io/sql-tutorial/src/negate_incorrectly.sql) + +``` +SELECT DISTINCT person +FROM work +WHERE job != 'calibrate'; +``` + +[out/negate_incorrectly.out](https://gvwilson.github.io/sql-tutorial/out/negate_incorrectly.out) + +``` +| person | +|--------| +| mik | +| po | +| tay | +``` + +- выбираем уникальные значения из столбца `person`, где поле `job` не равно `calibrate` + +### Использование набора в условии WHERE при помощи IN + +[src/set_membership.sql](https://gvwilson.github.io/sql-tutorial/src/set_membership.sql) + +``` +SELECT * +FROM work +WHERE person NOT IN ('mik', + 'tay'); +``` + +[out/set_membership.out](https://gvwilson.github.io/sql-tutorial/out/set_membership.out) + +``` +| person | job | +|--------|----------| +| po | clean | +| po | complain | +``` + +- выбираем все строки из `work`, где `person` не равно `'mik'` и не равно `'tay'` + +### Подзапросы + +[src/subquery_set.sql](https://gvwilson.github.io/sql-tutorial/src/subquery_set.sql) + +``` +SELECT DISTINCT person +FROM work +WHERE person not in + (SELECT DISTINCT person + FROM work + WHERE job = 'calibrate'); +``` + +[out/subquery_set.out](https://gvwilson.github.io/sql-tutorial/out/subquery_set.out) + +``` +| person | +|--------| +| po | +| tay | +``` + +- внутренний подзапрос выбирает уникальные значения столбца `person` из `work`, где в поле `job` стоит `'calibrate'` +- внешний, главный запрос выбирает те уникальные значения `person`, где `person` не равно значениям из внутренного подзапроса + +### Автоикремент и PRIMARY KEY + +[src/autoincrement.sql](https://gvwilson.github.io/sql-tutorial/src/autoincrement.sql) + +``` +CREATE TABLE person (ident integer PRIMARY KEY autoincrement, + name text NOT NULL); +INSERT INTO person +VALUES (NULL, 'mik'), + (NULL, 'po'), + (NULL, 'tay'); + +SELECT * +FROM person; +INSERT INTO person +VALUES (1, "prevented"); +``` + +[out/autoincrement.out](https://gvwilson.github.io/sql-tutorial/out/autoincrement.out) + +``` +| ident | name | +|-------|------| +| 1 | mik | +| 2 | po | +| 3 | tay | +Runtime error near line 12: UNIQUE constraint failed: person.ident (19) +``` + +- создаём табличку `person` с 2 столбцами: `ident` с целочисленными значениями, `name` с текстовыми значениями; столбец `ident` устанавливаем как `PRIMARY KEY`, включаем автоматическое инкрементирование значений +- помещаем в таблицу `person` 3 пары `ident`–`name` +- при попытке добавить ещё одну пару `(1, "prevented")` возникает ошибка, поскольку уже существует строка с `indent` равным 1 + +**Внутренняя табличка:** + +[src/sequence_table.sql](https://gvwilson.github.io/sql-tutorial/src/sequence_table.sql) + +``` +SELECT * +FROM sqlite_sequence; +``` + +[out/sequence_table.out](https://gvwilson.github.io/sql-tutorial/out/sequence_table.out) + +``` +| name | seq | +|--------|-----| +| person | 3 | +``` + +- выводим все текущие значения автоинкрементных счетчиков для таблиц в БД SQLite + +### Изменение таблички при помощи ALTER + +[src/alter_tables.sql](https://gvwilson.github.io/sql-tutorial/src/alter_tables.sql) + +``` +ALTER TABLE job ADD ident integer NOT NULL DEFAULT -1; + +UPDATE job +SET ident = 1 +WHERE name = 'calibrate'; + +UPDATE job +SET ident = 2 +WHERE name = 'clean'; + +SELECT * +FROM job; +``` + +[out/alter_tables.out](https://gvwilson.github.io/sql-tutorial/out/alter_tables.out) + +``` +| name | billable | ident | +|-----------|----------|-------| +| calibrate | 1.5 | 1 | +| clean | 0.5 | 2 | +``` + +- добавляем новый столбец `ident` в табличку `job`; столбец заполняется целыми числами, не может быть пустым; ставим значение по умолчанию `-1` для этого столбца +- делаем значение столбца `ident` равным 1 там, где `name` равен `'calibrate'` +- устанавливаем значение `ident` равным 2 для строки, где `name` равен `clean` + +### Создание новой таблички на базе старой + +[src/insert_select.sql](https://gvwilson.github.io/sql-tutorial/src/insert_select.sql) + +``` +CREATE TABLE new_work (person_id integer NOT NULL, + job_id integer NOT NULL, + FOREIGN key(person_id) REFERENCES person(ident), + FOREIGN key(job_id) REFERENCES job(ident)); + +INSERT INTO new_work +SELECT person.ident AS person_id, + job.ident AS job_id +FROM (person + JOIN work + ON person.name = work.person) + +JOIN job ON job.name = work.job; + +SELECT * +FROM new_work; +``` + +[out/insert_select.out](https://gvwilson.github.io/sql-tutorial/out/insert_select.out) + +``` +| person_id | job_id | +|-----------|--------| +| 1 | 1 | +| 1 | 2 | +| 2 | 2 | +``` + +- создаём таблицу `new_work` с 2 целочисленными столбцами: `person_id` и `job_id`; оба столбца не могут быть пустыми +- 2 `FOREIGN KEY` ограничения добавляются, чтобы связать столбцы `person_id` и `job_id` новой таблицы `new_work` с соответствующими столбцами `ident` в таблицах `person` и `job` +- добавляем данные в таблицу `new_work`, используя результат запроса `SELECT` +- `FROM (person JOIN work ON person.name = work.person)` — данные будут выбраны из результатов соединения таблиц `person` и `work` по условию равенства значений столбца `name` в таблице `person` и столбца `person` в таблице `work` +- `JOIN job ON job.name = work.job` — результаты предыдущего соединения будут дополнительно соединены с таблицей `job` по условию равенства значений столбца `name` в таблице `job` и столбца `job`в `work` + +### Удаление таблички + +[src/drop_table.sql](https://gvwilson.github.io/sql-tutorial/src/drop_table.sql) + +``` +DROP TABLE work; +ALTER TABLE new_work RENAME TO work; +``` + +- удаляем `work` из БД +- изменяем имя таблички `new_work` на `work` + +[out/drop_table.out](https://gvwilson.github.io/sql-tutorial/out/drop_table.out) + +``` +CREATE TABLE job (ident integer PRIMARY KEY autoincrement, + name text NOT NULL, + billable real NOT NULL); +CREATE TABLE sqlite_sequence(name, + seq); + +CREATE TABLE person (ident integer PRIMARY KEY autoincrement, + name text NOT NULL); + +CREATE TABLE IF NOT EXISTS "work" (person_id integer NOT NULL, + job_id integer NOT NULL, + FOREIGN key(person_id) REFERENCES person(ident), + FOREIGN key(job_id) REFERENCES job(ident)); +``` + +- создаём таблицу `job` с 3 колонками: `ident` хранит целые числа, используется в качестве первичного ключа (`PRIMARY KEY`) и автоматически увеличивается (autoincrement);`name` текстовый столбец, не может быть пустым (`NOT NULL`);`billable` — столбец вещественных чисел, не может быть пустым +- создаём `sqlite_sequence` с 2 колонками: `name` и `seq` +- создаём таблицу `person` с 2 колонками: `ident` — хранит целые числа, используется в качестве первичного ключа и автоматически увеличивается (autoincrement), `name` — хранит текст, не может быть пустым +- создаём `work` с 4 колонками: `person_id` – хранит целые числа, не может быть пустым; аналогичный столбец `job_id` +- устанавливаем внешние ключи, связывающие `person_id` с `ident` в таблице `person` и `job_id` с `ident` в таблице `job` + +### Сравнение отдельных значений с агрегированными + +[src/compare_individual_aggregate.sql](https://gvwilson.github.io/sql-tutorial/src/compare_individual_aggregate.sql) + +``` +SELECT body_mass_g +FROM penguins +WHERE body_mass_g > (SELECT AVG(body_mass_g) + FROM penguins) +LIMIT 5; +``` + +[out/compare_individual_aggregate.out](https://gvwilson.github.io/sql-tutorial/out/compare_individual_aggregate.out) + +``` +| body_mass_g | +|-------------| +| 4675 | +| 4250 | +| 4400 | +| 4500 | +| 4650 | +``` + +- выбираем только те строки, где значение в столбце `body_mass_g` больше, чем среднее значение `body_mass_g` по всем строкам в таблице `penguins` +- ну и выводим только первые 5 строк + +### Сравнение отдельных значений с агрегированными внутри групп + +[src/compare_within_groups.sql](https://gvwilson.github.io/sql-tutorial/src/compare_within_groups.sql) + +``` +SELECT penguins.species, + penguins.body_mass_g, + Round(averaged.avg_mass_g, 1) AS avg_mass_g +FROM penguins + JOIN (SELECT species, + Avg(body_mass_g) AS avg_mass_g + FROM penguins + GROUP BY species) AS averaged + ON penguins.species = averaged.species +WHERE penguins.body_mass_g > averaged.avg_mass_g +LIMIT 5; +``` + +[out/compare_within_groups.out](https://gvwilson.github.io/sql-tutorial/out/compare_within_groups.out) + +``` +| species | body_mass_g | avg_mass_g | +|---------|-------------|------------| +| Adelie | 3750 | 3700.7 | +| Adelie | 3800 | 3700.7 | +| Adelie | 4675 | 3700.7 | +| Adelie | 4250 | 3700.7 | +| Adelie | 3800 | 3700.7 | +``` + +- выбираем столбцы `species` и `body_mass_g` из таблицы `penguins` +- вычисляем среднюю массу для каждого вида пингвина, округляем до 1 знака после запятой, используя подзапрос, который связывается с исходной таблицей `penguins` по полю `species` +- используя результаты подзапроса, фильтруем только те записи, где масса пингвина больше средней массы для его вида +- выводим только первые 5 записей + +### CTE — табличные выражения + +[src/common_table_expressions.sql](https://gvwilson.github.io/sql-tutorial/src/common_table_expressions.sql) + +``` +WITH grouped AS + (SELECT species, + avg(body_mass_g) AS avg_mass_g + FROM penguins + GROUP BY species) +SELECT penguins.species, + penguins.body_mass_g, + round(grouped.avg_mass_g, 1) AS avg_mass_g +FROM penguins +JOIN grouped +WHERE penguins.body_mass_g > grouped.avg_mass_g +LIMIT 5; +``` + +[out/common_table_expressions.out](https://gvwilson.github.io/sql-tutorial/out/common_table_expressions.out) + +``` +| species | body_mass_g | avg_mass_g | +|---------|-------------|------------| +| Adelie | 3750 | 3700.7 | +| Adelie | 3800 | 3700.7 | +| Adelie | 4675 | 3700.7 | +| Adelie | 4250 | 3700.7 | +| Adelie | 3800 | 3700.7 | +``` + +- создаём табличку `grouped` (с помощью `WITH`), которая содержит среднюю массу тела пингвинов (`AVG(body_mass_g)`) для каждого вида из `penguins` (`GROUP BY species`) +- из `penguins` выбираем такие столбцы: `species`, `body_mass_g`; и из из общей таблицы `grouped`выбираем `avg_mass_g`, округлённое до 1 знака +- объединяем `penguins` с общей таблицей `grouped` (через `JOIN`); для каждого пингвина будет найдена соответствующая средняя масса тела для его вида +- `WHERE` — фильтруем; оставляем только тех, у которых масса тела больше средней массы их вида +- выводим только первые 5 строк + +### Смотрим план запроса с помощью EXPLAIN + +[src/explain_query_plan.sql](https://gvwilson.github.io/sql-tutorial/src/explain_query_plan.sql) + +``` +EXPLAIN query PLAN +SELECT species, + AVG(body_mass_g) +FROM penguins +GROUP BY species; +``` + +[out/explain_query_plan.out](https://gvwilson.github.io/sql-tutorial/out/explain_query_plan.out) + +``` +QUERY PLAN +|--SCAN penguins +`--USE TEMP B-TREE FOR GROUP BY +``` + +- `EXPLAIN query PLAN` — получаем план выполнения запроса, как будет выполнен запрос в базе данных +- выбираем столбец `species`, вычисляем среднее значение столбца body_mass_g для каждого вида из `penguins` +- `GROUP BY species` — группируем результаты по столбцу `species` + +### Нумеруем строки + +- каждая таблица имеет специальный столбец `rowid` с уникальными числовыми идентификаторами + +[src/rowid.sql](https://gvwilson.github.io/sql-tutorial/src/rowid.sql) + +``` +SELECT rowid, species, island +FROM penguins +LIMIT 5; +``` + +[out/rowid.out](https://gvwilson.github.io/sql-tutorial/out/rowid.out) + +``` +| rowid | species | island | +|-------|---------|-----------| +| 1 | Adelie | Torgersen | +| 2 | Adelie | Torgersen | +| 3 | Adelie | Torgersen | +| 4 | Adelie | Torgersen | +| 5 | Adelie | Torgersen | +``` + +### Условия if-else + +[src/if_else.sql](https://gvwilson.github.io/sql-tutorial/src/if_else.sql) + +``` +WITH sized_penguins AS + (SELECT species, + iif(body_mass_g < 3500, 'small', 'large') AS size + FROM penguins) +SELECT species, + size, + count(*) AS num +FROM sized_penguins +GROUP BY species, + size +ORDER BY species, + num; +``` + +[out/if_else.out](https://gvwilson.github.io/sql-tutorial/out/if_else.out) + +``` +| species | size | num | +|-----------|-------|-----| +| Adelie | small | 54 | +| Adelie | large | 98 | +| Chinstrap | small | 17 | +| Chinstrap | large | 51 | +| Gentoo | large | 124 | +``` + +- создаём временную таблицу `sized_penguins`, которая содержит два столбца: `species` и `size` +- `size` определяется на основе условия: если `body_mass_g` меньше 3500, то он считается `'small'`, в противном случае – `'large'` +- выбираем столбцы `species` и `size` из временной таблицы `sized_penguins`, а подсчитываем количество записей для каждой комбинации `species` и `size`, используя функцию `count(*)` +- группируем данные (`GROUP BY`) по `species` и `size` + +### Выбираем с помощью SELECT и CASE + +А если нам нужны маленькие, средние и большие? + +Можно вложить if, но он быстро становится нечитаемым + +[src/case_when.sql](https://gvwilson.github.io/sql-tutorial/src/case_when.sql) + +``` +WITH sized_penguins AS + (SELECT species, + CASE + WHEN body_mass_g < 3500 THEN 'small' + WHEN body_mass_g < 5000 THEN 'medium' + ELSE 'large' + END AS SIZE + FROM penguins) +SELECT species, + SIZE, + count(*) AS num +FROM sized_penguins +GROUP BY species, + SIZE +ORDER BY species, + num; +``` + +[out/case_when.out](https://gvwilson.github.io/sql-tutorial/out/case_when.out) + +``` +| species | size | num | +|-----------|--------|-----| +| Adelie | large | 1 | +| Adelie | small | 54 | +| Adelie | medium | 97 | +| Chinstrap | small | 17 | +| Chinstrap | medium | 51 | +| Gentoo | medium | 56 | +| Gentoo | large | 68 | +``` + +- в блоке `WITH` создаём набор данных с именем `sized_penguins`, где находится `species` и `size`, определенные на `body_mass_g` +- `CASE` разделяет пингвинов на 3 категории: `'small'`, `'medium'` и `'large'` в зависимости от их массы +- в основном блоке `SELECT` выбираются вид пингвина, его размер и количество пингвинов каждого размера (`num`) из набора `sized_penguins` +- результаты группируются по виду пингвина и их размеру с помощью `GROUP BY` +- в конце запроса результаты сортируются сначала по `species` в алфавитном порядке, а затем по `num` + +### Работаем с диапазоном значений + +[src/check_range.sql](https://gvwilson.github.io/sql-tutorial/src/check_range.sql) + +``` +WITH sized_penguins AS + (SELECT species, + CASE + WHEN body_mass_g BETWEEN 3500 AND 5000 THEN 'normal' + ELSE 'abnormal' + END AS SIZE + FROM penguins) +SELECT species, + SIZE, + count(*) AS num +FROM sized_penguins +GROUP BY species, + SIZE +ORDER BY species, + num; +``` + +[out/check_range.out](https://gvwilson.github.io/sql-tutorial/out/check_range.out) + +``` +| species | size | num | +|-----------|----------|-----| +| Adelie | abnormal | 55 | +| Adelie | normal | 97 | +| Chinstrap | abnormal | 17 | +| Chinstrap | normal | 51 | +| Gentoo | abnormal | 62 | +| Gentoo | normal | 62 | +``` + +- создаём общую таблицу выражений (CTE) `sized_penguins`, она выбирает вид пингвина и определяет его размер в зависимости от массы тела; если масса в диапазоне от 3500 до 5000 г, это размер `normal`, в противном случае – `abnormal` +- затем из этой CTE извлекаем данные с указанием видов пингвинов, их размеров и количества пингвинов каждого вида и размера, используя `SELECT` с агрегирующей функцией `COUNT(*)` +- группируем по виду и размеру пингвина с помощью `GROUP BY` +- сортируем результат по виду и количеству пингвинов в порядке возрастания с помощью `ORDER BY` + +**Ещё одна БД: ** + +ER-диаграмма показывает отношения между отдельными табличками и выглядит так: + +") + +assay database table diagram + +") + +assay ER diagram + +[src/assay_staff.sql](https://gvwilson.github.io/sql-tutorial/src/assay_staff.sql) + +``` +SELECT * +FROM staff; +``` + +[out/assay_staff.out](https://gvwilson.github.io/sql-tutorial/out/assay_staff.out) + +``` +| ident | personal | family | dept | age | +|-------|----------|-----------|------|-----| +| 1 | Kartik | Gupta | | 46 | +| 2 | Divit | Dhaliwal | hist | 34 | +| 3 | Indrans | Sridhar | mb | 47 | +| 4 | Pranay | Khanna | mb | 51 | +| 5 | Riaan | Dua | | 23 | +| 6 | Vedika | Rout | hist | 45 | +| 7 | Abram | Chokshi | gen | 23 | +| 8 | Romil | Kapoor | hist | 38 | +| 9 | Ishaan | Ramaswamy | mb | 35 | +| 10 | Nitya | Lal | gen | 52 | +``` + +### Ищем по фрагменту с помощью LIKE + +[src/like_glob.sql](https://gvwilson.github.io/sql-tutorial/src/like_glob.sql) + +``` +SELECT personal, + family +FROM staff +WHERE personal LIKE '%ya%' + OR family GLOB '*De*'; +``` + +[out/like_glob.out](https://gvwilson.github.io/sql-tutorial/out/like_glob.out) + +``` +| personal | family | +|----------|--------| +| Nitya | Lal | +``` + +- `SELECT personal, family` — хотим выбрать столбцы `personal` и `family` из таблицы `staff` +- `FROM staff` — ну понятно, запрос будет выполнен в таблице `staff` +- `'%ya%'` — хотим выбрать строки, в которых значение столбца `personal` содержит подстроку `ya` (с помощью `LIKE`) или значение столбца `family` содержит `De` (с помощью `GLOB`) + +### Выбираем первую и последнюю строки + +[src/union_all.sql](https://gvwilson.github.io/sql-tutorial/src/union_all.sql) + +``` +SELECT * +FROM + (SELECT * + FROM + (SELECT * + FROM experiment + ORDER BY started ASC + LIMIT 5) + UNION ALL SELECT * + FROM + (SELECT * + FROM experiment + ORDER BY started DESC + LIMIT 5)) +ORDER BY started ASC ; +``` + +[out/union_all.out](https://gvwilson.github.io/sql-tutorial/out/union_all.out) + +``` +| ident | kind | started | ended | +|-------|-------------|------------|------------| +| 17 | trial | 2023-01-29 | 2023-01-30 | +| 35 | calibration | 2023-01-30 | 2023-01-30 | +| 36 | trial | 2023-02-02 | 2023-02-03 | +| 25 | trial | 2023-02-12 | 2023-02-14 | +| 2 | calibration | 2023-02-14 | 2023-02-14 | +| 40 | calibration | 2024-01-21 | 2024-01-21 | +| 12 | trial | 2024-01-26 | 2024-01-28 | +| 44 | trial | 2024-01-27 | 2024-01-29 | +| 34 | trial | 2024-02-01 | 2024-02-02 | +| 14 | calibration | 2024-02-03 | 2024-02-03 | +``` + +- выбираем 5 самых старых записей из таблицы `experiment`, отсортированных по возрастанию даты начала (`started ASC`) с помощью подзапроса (внутренний `SELECT`) +- выбираем 5 самых новых записей из `experiment`, отсортированных по убыванию даты начала (`started DESC`) с помощью другого подзапроса +- объединяем эти 2 подзапроса с помощью `UNION ALL`, так мы получаем временную таблицу, содержащую 10 записей (5 самых старых и 5 самых новых) +- из временной таблицы выбираем все столбцы для каждой записи (`SELECT *`) и окончательно сортируем записи по возрастанию даты начала (`started ASC`) с помощью внешнего `ORDER BY` + +### Пересечение отдельных табличек + +[src/intersect.sql](https://gvwilson.github.io/sql-tutorial/src/intersect.sql) + +``` +SELECT personal, + family, + dept, + age +FROM staff +WHERE dept = 'mb' INTERSECT + SELECT personal, + family, + dept, + age + FROM staff WHERE age < 50 ; +``` + +[out/intersect.out](https://gvwilson.github.io/sql-tutorial/out/intersect.out) + +``` +| personal | family | dept | age | +|----------|-----------|------|-----| +| Indrans | Sridhar | mb | 47 | +| Ishaan | Ramaswamy | mb | 35 | +``` + +- здесь мы используем `INTERSECT` для объединения результатов двух отдельных запросов +- вначале выбираем данные из таблицы `staff`, в которых значение поля `dept` равно `'mb'` +- потом выбираем данные из таблицы `staff`, в которых значение поля `age` меньше 50 +- с помощью `INTERSECT` объединяем результаты этих двух запросов +- в результате будут выбраны строки, которые присутствуют в обоих результатах, то есть записи из `staff`, где значение `dept` равно `'mb'` и значение `age` меньше 50 + +### Исключение + +[src/except.sql](https://gvwilson.github.io/sql-tutorial/src/except.sql) + +``` +SELECT personal, + family, + dept, + age +FROM staff +WHERE dept = 'mb' +EXCEPT +SELECT personal, + family, + dept, + age +FROM staff +WHERE age < 50 ; +``` + +[out/except.out](https://gvwilson.github.io/sql-tutorial/out/except.out) + +``` +| personal | family | dept | age | +|----------|--------|------|-----| +| Pranay | Khanna | mb | 51 | +``` + +- при помощи `SELECT` извлекаем 4 поля из `staff`: `personal`, `family`, `dept` и `age` +- затем используем `WHERE`, чтобы отфильтровать только те строки, в которых значение `dept` равно `'mb'` +- после этого при помощи `EXCEPT` удаляем из исходного результата любые строки, которые также присутствуют в результате второго запроса +- второй запрос `SELECT` также извлекает четыре поля из `staff`: `personal`, `family`, `dept` и `age` +- используем `WHERE`, чтобы отфильтровать только те строки, где значение `age` меньше 50 + +### Случайные значения в SQL + +[src/random_numbers.sql](https://gvwilson.github.io/sql-tutorial/src/random_numbers.sql) + +``` +WITH decorated AS + (SELECT random() AS rand, + personal || ' ' || family AS name + FROM staff) +SELECT rand, + abs(rand) % 10 AS selector, + name +FROM decorated +WHERE selector < 5; +``` + +[out/random_numbers.out](https://gvwilson.github.io/sql-tutorial/out/random_numbers.out) + +``` +| rand | selector | name | +|----------------------|----------|-----------------| +| 7176652035743196310 | 0 | Divit Dhaliwal | +| -2243654635505630380 | 2 | Indrans Sridhar | +| -6940074802089166303 | 5 | Pranay Khanna | +| 8882650891091088193 | 9 | Riaan Dua | +| -45079732302991538 | 5 | Vedika Rout | +| -8973877087806386134 | 2 | Abram Chokshi | +| 3360598450426870356 | 9 | Romil Kapoor | +``` + +- создаём временную таблицу `decorated` + +- в этой таблице извлекается случайное число с помощью `random()` +- конкатенируем значения `personal` и `family` под именем `name` с помощью `' '` для разделения +- таким образом создаём временную таблицу, содержащую столбцы `rand` с случайными числами и `name` со значениями из столбцов `personal` и `family` таблицы `staff` +- делаем выборку из временной таблицы `decorated`; в выборку включаем столбцы `rand`, `name` +- `abs(rand) % 10` — это мы вычисляем остаток от деления абсолютного значения `rand` на 10 +- ну и в конце оставляем только строки, где `selector` меньше 5 + +### Создание индексов + +[src/create_use_index.sql](https://gvwilson.github.io/sql-tutorial/src/create_use_index.sql) + +``` +EXPLAIN query PLAN +SELECT filename +FROM plate +WHERE filename like '%07%'; + +CREATE INDEX plate_file ON plate(filename); + +EXPLAIN query PLAN +SELECT filename +FROM plate +WHERE filename like '%07%'; +``` + +[out/create_use_index.out](https://gvwilson.github.io/sql-tutorial/out/create_use_index.out) + +``` +QUERY PLAN +`--SCAN plate USING COVERING INDEX sqlite_autoindex_plate_1 +QUERY PLAN +`--SCAN plate USING COVERING INDEX plate_file +``` + +- выбираем все значения столбца `filename` из таблицы `plate`, где значение столбца `filename` содержит подстроку `07` +- создаём индекс с именем `plate_file` для столбца `filename` в таблице `plate` +- запрашиваем план выполнения запроса (`EXPLAIN query PLAN`) + +### Генерация последовательности значений + +[src/generate_sequence.sql](https://gvwilson.github.io/sql-tutorial/src/generate_sequence.sql) + +``` +SELECT value +FROM generate_series(1, 5); +``` + +[out/generate_sequence.out](https://gvwilson.github.io/sql-tutorial/out/generate_sequence.out) + +``` +| value | +|-------| +| 1 | +| 2 | +| 3 | +| 4 | +| 5 | +``` + +- `generate_series(1, 5)` — генерируем ряд чисел от 1 до 5 +- `SELECT value` — выбираем этот столбец `value` со сгенерированными числами от 1 до 5 + +### Генерируем последовательность на основе данных + +[src/data_range_sequence.sql](https://gvwilson.github.io/sql-tutorial/src/data_range_sequence.sql) + +``` +CREATE TABLE temp (num integer NOT NULL); + +INSERT INTO temp +VALUES (1), + (5); + +SELECT value +FROM generate_series ((SELECT min(num) + FROM TEMP), + (SELECT max(num) + FROM TEMP)); +``` + +[out/data_range_sequence.out](https://gvwilson.github.io/sql-tutorial/out/data_range_sequence.out) + +``` +| value | +|-------| +| 1 | +| 2 | +| 3 | +| 4 | +| 5 | +``` + +- создаём временную таблицу `temp`, которая содержит 1 столбец с именем `num` типа integer; этот столбец не может быть пустым +- помещаем в `temp` значения 1 и 5 в столбец `num` +- используем `generate_series` для создания последовательности чисел между минимальным и максимальным значениями из столбца `num` в таблице `temp` + +### Генерация последовательностей дат + +[src/date_sequence.sql](https://gvwilson.github.io/sql-tutorial/src/date_sequence.sql) + +``` +SELECT date( + (SELECT julianday(min(started)) + FROM experiment) + value) AS some_day +FROM + (SELECT value + FROM generate_series( + (SELECT 0), + (SELECT count(*) - 1 + FROM experiment))) +LIMIT 5; +``` + +[out/date_sequence.out](https://gvwilson.github.io/sql-tutorial/out/date_sequence.out) + +``` +| some_day | +|------------| +| 2023-01-29 | +| 2023-01-30 | +| 2023-01-31 | +| 2023-02-01 | +| 2023-02-02 | +``` + +- `SELECT julianday(min(started)) FROM experiment` — находим минимальную дату в столбце `started` из `experiment`, преобразуем её в `Julian` день (числовое представление даты) и возвращаем этот `Julian` день +- внешним подзапросом вычисляем разницу между этим минимальным `Julian` днем и каждым `value` из `generate_series` +- затем складываем эти разницы с минимальным `Julian` днем, и конвертируем обратно в дату с помощью `date()` +- ну и выбираем только первые 5 результатов этого вычисления с помощью `LIMIT 5` + +### Подсчитываем количество значений за день, без пропусков + +[src/experiments_per_day.sql](https://gvwilson.github.io/sql-tutorial/src/experiments_per_day.sql) + +``` + WITH -- complete sequence of days with 0 as placeholder for number of experiments + all_days AS ( + SELECT DATE ( + ( + SELECT julianday (MIN(started)) + FROM experiment + ) + VALUE + ) AS some_day, + 0 AS zeroes + FROM ( + SELECT VALUE + FROM generate_series ( + ( + SELECT 0 + ), + ( + SELECT COUNT(*) - 1 + FROM experiment + ) + ) + ) + ), -- sequence of actual days with actual number of experiments started + actual_days AS ( + SELECT started, + COUNT(started) AS num_exp + FROM experiment + GROUP BY started + ) -- combined by joining on day and taking actual number (if available) or zero + SELECT all_days.some_day AS DAY, + COALESCE(actual_days.num_exp, all_days.zeroes) AS num_exp + FROM all_days +LEFT JOIN actual_days ON all_days.some_day = actual_days.started + LIMIT 5; +``` + +[out/experiments_per_day.out](https://gvwilson.github.io/sql-tutorial/out/experiments_per_day.out) + +``` +| day | num_exp | +|------------|---------| +| 2023-01-29 | 1 | +| 2023-01-30 | 1 | +| 2023-01-31 | 0 | +| 2023-02-01 | 0 | +| 2023-02-02 | 1 | +``` + +- создаём последовательность всех дней с нулевым значением в качестве заполнителя для количества экспериментов +- создаём последовательность фактических дней с реальным числом экспериментов +- объединяем эти последовательности, соединяя их по дням и беря реальное количество (если доступно) или ноль +- выводит результат, показывая дни (`all_days.some_day`) и соответствующее количество экспериментов (`COALESCE(actual_days.num_exp, all_days.zeroes) AS num_exp`), при этом используется функция `COALESCE`, чтобы использовать фактическое количество экспериментов, если оно доступно, или ноль, если нет + +### JOIN таблички с собой же + +[src/self_join.sql](https://gvwilson.github.io/sql-tutorial/src/self_join.sql) + +``` + WITH person AS ( + SELECT ident, + personal || ' ' || family AS name + FROM staff + ) + SELECT LEFT.name, + RIGHT.name + FROM person AS +LEFT JOIN person AS RIGHT + LIMIT 10; +``` + +[out/self_join.out](https://gvwilson.github.io/sql-tutorial/out/self_join.out) + +``` +| name | name | +|--------------|------------------| +| Kartik Gupta | Kartik Gupta | +| Kartik Gupta | Divit Dhaliwal | +| Kartik Gupta | Indrans Sridhar | +| Kartik Gupta | Pranay Khanna | +| Kartik Gupta | Riaan Dua | +| Kartik Gupta | Vedika Rout | +| Kartik Gupta | Abram Chokshi | +| Kartik Gupta | Romil Kapoor | +| Kartik Gupta | Ishaan Ramaswamy | +| Kartik Gupta | Nitya Lal | +``` + +- создаём временную общую таблицу `person` с помощью WITH +- объединяем столбцы `personal` и `family` в один столбец `name` +- при помощи `SELECT` выбираем из `person` значения столбца `name` через алиасы `left` и `right` +- после этого происходит объединение `person` с собой с помощью оператора `LEFT JOIN`, при этом таблица алиасируется как `RIGHT` + +Этот SQL-код, однако, содержит ошибку, правильный синтаксис должен быть следующим: + +``` + WITH person AS ( + SELECT ident, + personal || ' ' || family AS name + FROM staff + ) + SELECT LEFT.name, + RIGHT.name + FROM person AS LEFT +LEFT JOIN person AS RIGHT ON < условие соединения > + LIMIT 10; +``` + +В исходном примере условие соединения (`ON`) не было указано + +### Генерируем уникальные пары значений + +[src/unique_pairs.sql](https://gvwilson.github.io/sql-tutorial/src/unique_pairs.sql) + +``` + WITH person AS ( + SELECT ident, + personal || ' ' || family AS name + FROM staff + ) + SELECT LEFT.name, + RIGHT.name + FROM person AS +LEFT JOIN person AS RIGHT ON LEFT.ident < RIGHT.ident + WHERE LEFT.ident <= 4 + AND RIGHT.ident <= 4; +``` + +[out/unique_pairs.out](https://gvwilson.github.io/sql-tutorial/out/unique_pairs.out) + +``` +| name | name | +|-----------------|-----------------| +| Kartik Gupta | Divit Dhaliwal | +| Kartik Gupta | Indrans Sridhar | +| Kartik Gupta | Pranay Khanna | +| Divit Dhaliwal | Indrans Sridhar | +| Divit Dhaliwal | Pranay Khanna | +| Indrans Sridhar | Pranay Khanna | +``` + +- создаём временную таблицу `person`, которая содержит результат выбора из таблицы `staff` +- из `person` выбираем значения `left.name` и `right.name` с использованием операции слияния (`JOIN`). В этом случае происходит слияние `person` с собой, причем каждая копия `person` используется в качестве левой и правой таблиц соответственно. Слияние выполняется по условию, что идентификатор слева меньше идентификатора справа. +- затем применяем дополнительное условие с помощью `WHERE`, которое фильтрует результаты `JOIN`-операции. Это условие проверяет, что идентификаторы слева и справа меньше или равны 4 + +### Фильтрация пар + +[src/filter_pairs.sql](https://gvwilson.github.io/sql-tutorial/src/filter_pairs.sql) + +``` + WITH person AS ( + SELECT ident, + personal || ' ' || family AS name + FROM staff + ), + together AS ( + SELECT LEFT.staff AS left_staff, + RIGHT.staff AS right_staff + FROM performed AS + LEFT JOIN performed AS RIGHT ON LEFT.experiment = RIGHT.experiment + WHERE left_staff < right_staff + ) + SELECT LEFT.name AS person_1, + RIGHT.name AS person_2 + FROM person AS +LEFT JOIN person AS + RIGHT JOIN together ON LEFT.ident = left_staff + AND RIGHT.ident = right_staff; +``` + +[out/filter_pairs.out](https://gvwilson.github.io/sql-tutorial/out/filter_pairs.out) + +``` +| person_1 | person_2 | +|-----------------|------------------| +| Kartik Gupta | Vedika Rout | +| Pranay Khanna | Vedika Rout | +| Indrans Sridhar | Romil Kapoor | +| Abram Chokshi | Ishaan Ramaswamy | +| Pranay Khanna | Vedika Rout | +| Kartik Gupta | Abram Chokshi | +| Abram Chokshi | Romil Kapoor | +| Kartik Gupta | Divit Dhaliwal | +| Divit Dhaliwal | Abram Chokshi | +| Pranay Khanna | Ishaan Ramaswamy | +| Indrans Sridhar | Romil Kapoor | +| Kartik Gupta | Ishaan Ramaswamy | +| Kartik Gupta | Nitya Lal | +| Kartik Gupta | Abram Chokshi | +| Pranay Khanna | Romil Kapoor | +``` + +- во временной табличке `person` выбираем имена сотрудников из таблицы `staff`; используем `personal ' ' family AS name`, чтобы объединить значения из столбцов `personal` и `family` +- временная табличка `together` использует оператор `LEFT JOIN` для объединения таблицы `performed` с собой на основе столбца `experiment`. Затем выбираются пары сотрудников, участвовавших в одном и том же эксперименте, исключая случаи, когда идентификатор левого сотрудника (`left_staff`) больше идентификатора правого сотрудника (`right_staff`) +- затем выполняется основной `SELECT`, который использует `person` и `together` для объединения имен сотрудников на основе их идентификаторов. Он выполняет два `LEFT JOIN`, чтобы объединить `person` с самим собой и затем объединить результат с `together` на основе идентификаторов сотрудников. +- затем выбираются имена сотрудников для отображения в итоговом результате. + +### EXISTS + +[src/correlated_subquery.sql](https://gvwilson.github.io/sql-tutorial/src/correlated_subquery.sql) + +``` +SELECT name, + building +FROM department +WHERE EXISTS + (SELECT 1 + FROM staff + WHERE dept = department.ident ) +ORDER BY name; +``` + +[out/correlated_subquery.out](https://gvwilson.github.io/sql-tutorial/out/correlated_subquery.out) + +``` +| name | building | +|-------------------|------------------| +| Genetics | Chesson | +| Histology | Fashet Extension | +| Molecular Biology | Chesson | +``` + +- выбираем столбцы `name` и `building` из таблицы `department` +- `WHERE EXISTS (SELECT 1 FROM staff WHERE dept = department.ident )` — используем подзапрос, который проверяет существование хотя бы одной записи в таблице `staff`, для которой значение столбца `dept` совпадает с значением столбца `ident` из таблицы `department` +- `ORDER BY name` — устанавливаем порядок сортировки результатов по столбцу `name` в алфавитном порядке + +### NOT EXISTS в SQL + +[src/nonexistence.sql](https://gvwilson.github.io/sql-tutorial/src/nonexistence.sql) + +``` +SELECT name, + building +FROM department +WHERE NOT EXISTS + (SELECT 1 + FROM staff + WHERE dept = department.ident ) +ORDER BY name; +``` + +[out/nonexistence.out](https://gvwilson.github.io/sql-tutorial/out/nonexistence.out) + +``` +| name | building | +|---------------|----------| +| Endocrinology | TGVH | +``` + +- выбираем столбцы `name` и `building` из таблицы `department` +- `WHERE NOT EXISTS` — выбираем только те записи из `department`, для которых не существует записей в таблице `staff` +- `SELECT 1 FROM staff WHERE dept = department.ident` — проверяем, существуют ли записи в таблице `staff`, связанные с отделом из таблицы `department` +- `ORDER BY name` — сортируем результат по столбцу name + +**Избегание коррелированных подзапросов** + +[src/avoid_correlated_subqueries.sql](https://gvwilson.github.io/sql-tutorial/src/avoid_correlated_subqueries.sql) + +``` +SELECT DISTINCT department.name AS name, + department.building AS building +FROM department +JOIN staff ON department.ident = staff.dept +ORDER BY name; +``` + +[out/avoid_correlated_subqueries.out](https://gvwilson.github.io/sql-tutorial/out/avoid_correlated_subqueries.out) + +``` +| name | building | +|-------------------|------------------| +| Genetics | Chesson | +| Histology | Fashet Extension | +| Molecular Biology | Chesson | +``` + +- `SELECT DISTINCT` — выбираем уникальные значения `name` и `building` из таблицы `department` +- `JOIN staff ON department.ident = staff.dept` — объединяем таблицы `department` и `staff` на основе условия, что значение столбца `ident` из `department` равно значению `dept` из `staff` +- `ORDER BY name` — результаты выборки сортируем в алфавитном порядке по столбцу `name` + +### Опережение и отставание + +[src/lead_lag.sql](https://gvwilson.github.io/sql-tutorial/src/lead_lag.sql) + +``` + WITH ym_num AS ( + SELECT strftime ('%Y-%m', started) AS ym, + COUNT(*) AS num + FROM experiment + GROUP BY ym + ) + SELECT ym, + lag (num) OVER ( + ORDER BY ym + ) AS prev_num, + num, + lead (num) OVER ( + ORDER BY ym + ) AS next_num + FROM ym_num + ORDER BY ym; +``` + +- в этой таблице извлекается случайное число с помощью `random()` +- конкатенируем значения `personal` и `family` под именем `name` с помощью `' '` для разделения +- таким образом создаём временную таблицу, содержащую столбцы `rand` с случайными числами и `name` со значениями из столбцов `personal` и `family` таблицы `staff` +- делаем выборку из временной таблицы `decorated`; в выборку включаем столбцы `rand`, `name` +- `abs(rand) % 10` — это мы вычисляем остаток от деления абсолютного значения `rand` на 10 +- ну и в конце оставляем только строки, где `selector` меньше 5 + +### Создание индексов + +[src/create_use_index.sql](https://gvwilson.github.io/sql-tutorial/src/create_use_index.sql) + +``` +EXPLAIN query PLAN +SELECT filename +FROM plate +WHERE filename like '%07%'; + +CREATE INDEX plate_file ON plate(filename); + +EXPLAIN query PLAN +SELECT filename +FROM plate +WHERE filename like '%07%'; +``` + +[out/create_use_index.out](https://gvwilson.github.io/sql-tutorial/out/create_use_index.out) + +``` +QUERY PLAN +`--SCAN plate USING COVERING INDEX sqlite_autoindex_plate_1 +QUERY PLAN +`--SCAN plate USING COVERING INDEX plate_file +``` + +- выбираем все значения столбца `filename` из таблицы `plate`, где значение столбца `filename` содержит подстроку `07` +- создаём индекс с именем `plate_file` для столбца `filename` в таблице `plate` +- запрашиваем план выполнения запроса (`EXPLAIN query PLAN`) + +### Генерация последовательности значений + +[src/generate_sequence.sql](https://gvwilson.github.io/sql-tutorial/src/generate_sequence.sql) + +``` +SELECT value +FROM generate_series(1, 5); +``` + +[out/generate_sequence.out](https://gvwilson.github.io/sql-tutorial/out/generate_sequence.out) + +``` +| value | +|-------| +| 1 | +| 2 | +| 3 | +| 4 | +| 5 | +``` + +- `generate_series(1, 5)` — генерируем ряд чисел от 1 до 5 +- `SELECT value` — выбираем этот столбец `value` со сгенерированными числами от 1 до 5 + +### Генерируем последовательность на основе данных + +[src/data_range_sequence.sql](https://gvwilson.github.io/sql-tutorial/src/data_range_sequence.sql) + +``` +CREATE TABLE temp (num integer NOT NULL); + +INSERT INTO temp +VALUES (1), + (5); + +SELECT value +FROM generate_series ((SELECT min(num) + FROM TEMP), + (SELECT max(num) + FROM TEMP)); +``` + +[out/data_range_sequence.out](https://gvwilson.github.io/sql-tutorial/out/data_range_sequence.out) + +``` +| value | +|-------| +| 1 | +| 2 | +| 3 | +| 4 | +| 5 | +``` + +- создаём временную таблицу `temp`, которая содержит 1 столбец с именем `num` типа integer; этот столбец не может быть пустым +- помещаем в `temp` значения 1 и 5 в столбец `num` +- используем `generate_series` для создания последовательности чисел между минимальным и максимальным значениями из столбца `num` в таблице `temp` + +### Генерация последовательностей дат + +[src/date_sequence.sql](https://gvwilson.github.io/sql-tutorial/src/date_sequence.sql) + +``` +SELECT date( + (SELECT julianday(min(started)) + FROM experiment) + value) AS some_day +FROM + (SELECT value + FROM generate_series( + (SELECT 0), + (SELECT count(*) - 1 + FROM experiment))) +LIMIT 5; +``` + +[out/date_sequence.out](https://gvwilson.github.io/sql-tutorial/out/date_sequence.out) + +``` +| some_day | +|------------| +| 2023-01-29 | +| 2023-01-30 | +| 2023-01-31 | +| 2023-02-01 | +| 2023-02-02 | +``` + +- `SELECT julianday(min(started)) FROM experiment` — находим минимальную дату в столбце `started` из `experiment`, преобразуем её в `Julian` день (числовое представление даты) и возвращаем этот `Julian` день +- внешним подзапросом вычисляем разницу между этим минимальным `Julian` днем и каждым `value` из `generate_series` +- затем складываем эти разницы с минимальным `Julian` днем, и конвертируем обратно в дату с помощью `date()` +- ну и выбираем только первые 5 результатов этого вычисления с помощью `LIMIT 5` + +### Подсчитываем количество значений за день, без пропусков + +[src/experiments_per_day.sql](https://gvwilson.github.io/sql-tutorial/src/experiments_per_day.sql) + +``` + WITH -- complete sequence of days with 0 as placeholder for number of experiments + all_days AS ( + SELECT DATE ( + ( + SELECT julianday (MIN(started)) + FROM experiment + ) + VALUE + ) AS some_day, + 0 AS zeroes + FROM ( + SELECT VALUE + FROM generate_series ( + ( + SELECT 0 + ), + ( + SELECT COUNT(*) - 1 + FROM experiment + ) + ) + ) + ), -- sequence of actual days with actual number of experiments started + actual_days AS ( + SELECT started, + COUNT(started) AS num_exp + FROM experiment + GROUP BY started + ) -- combined by joining on day and taking actual number (if available) or zero + SELECT all_days.some_day AS DAY, + COALESCE(actual_days.num_exp, all_days.zeroes) AS num_exp + FROM all_days +LEFT JOIN actual_days ON all_days.some_day = actual_days.started + LIMIT 5; +``` + +[out/experiments_per_day.out](https://gvwilson.github.io/sql-tutorial/out/experiments_per_day.out) + +``` +| day | num_exp | +|------------|---------| +| 2023-01-29 | 1 | +| 2023-01-30 | 1 | +| 2023-01-31 | 0 | +| 2023-02-01 | 0 | +| 2023-02-02 | 1 | +``` + +- создаём последовательность всех дней с нулевым значением в качестве заполнителя для количества экспериментов +- создаём последовательность фактических дней с реальным числом экспериментов +- объединяем эти последовательности, соединяя их по дням и беря реальное количество (если доступно) или ноль +- выводит результат, показывая дни (`all_days.some_day`) и соответствующее количество экспериментов (`COALESCE(actual_days.num_exp, all_days.zeroes) AS num_exp`), при этом используется функция `COALESCE`, чтобы использовать фактическое количество экспериментов, если оно доступно, или ноль, если нет + +### JOIN таблички с собой же + +[src/self_join.sql](https://gvwilson.github.io/sql-tutorial/src/self_join.sql) + +``` + WITH person AS ( + SELECT ident, + personal || ' ' || family AS name + FROM staff + ) + SELECT LEFT.name, + RIGHT.name + FROM person AS +LEFT JOIN person AS RIGHT + LIMIT 10; +``` + +[out/self_join.out](https://gvwilson.github.io/sql-tutorial/out/self_join.out) + +``` +| name | name | +|--------------|------------------| +| Kartik Gupta | Kartik Gupta | +| Kartik Gupta | Divit Dhaliwal | +| Kartik Gupta | Indrans Sridhar | +| Kartik Gupta | Pranay Khanna | +| Kartik Gupta | Riaan Dua | +| Kartik Gupta | Vedika Rout | +| Kartik Gupta | Abram Chokshi | +| Kartik Gupta | Romil Kapoor | +| Kartik Gupta | Ishaan Ramaswamy | +| Kartik Gupta | Nitya Lal | +``` + +- создаём временную общую таблицу `person` с помощью WITH +- объединяем столбцы `personal` и `family` в один столбец `name` +- при помощи `SELECT` выбираем из `person` значения столбца `name` через алиасы `left` и `right` +- после этого происходит объединение `person` с собой с помощью оператора `LEFT JOIN`, при этом таблица алиасируется как `RIGHT` + +Этот SQL-код, однако, содержит ошибку, правильный синтаксис должен быть следующим: + +``` + WITH person AS ( + SELECT ident, + personal || ' ' || family AS name + FROM staff + ) + SELECT LEFT.name, + RIGHT.name + FROM person AS LEFT +LEFT JOIN person AS RIGHT ON < условие соединения > + LIMIT 10; +``` + +В исходном примере условие соединения (`ON`) не было указано + +### Генерируем уникальные пары значений + +[src/unique_pairs.sql](https://gvwilson.github.io/sql-tutorial/src/unique_pairs.sql) + +``` + WITH person AS ( + SELECT ident, + personal || ' ' || family AS name + FROM staff + ) + SELECT LEFT.name, + RIGHT.name + FROM person AS +LEFT JOIN person AS RIGHT ON LEFT.ident < RIGHT.ident + WHERE LEFT.ident <= 4 + AND RIGHT.ident <= 4; +``` + +[out/unique_pairs.out](https://gvwilson.github.io/sql-tutorial/out/unique_pairs.out) + +``` +| name | name | +|-----------------|-----------------| +| Kartik Gupta | Divit Dhaliwal | +| Kartik Gupta | Indrans Sridhar | +| Kartik Gupta | Pranay Khanna | +| Divit Dhaliwal | Indrans Sridhar | +| Divit Dhaliwal | Pranay Khanna | +| Indrans Sridhar | Pranay Khanna | +``` + +- создаём временную таблицу `person`, которая содержит результат выбора из таблицы `staff` +- из `person` выбираем значения `left.name` и `right.name` с использованием операции слияния (`JOIN`). В этом случае происходит слияние `person` с собой, причем каждая копия `person` используется в качестве левой и правой таблиц соответственно. Слияние выполняется по условию, что идентификатор слева меньше идентификатора справа. +- затем применяем дополнительное условие с помощью `WHERE`, которое фильтрует результаты `JOIN`-операции. Это условие проверяет, что идентификаторы слева и справа меньше или равны 4 + +### Фильтрация пар + +[src/filter_pairs.sql](https://gvwilson.github.io/sql-tutorial/src/filter_pairs.sql) + +``` + WITH person AS ( + SELECT ident, + personal || ' ' || family AS name + FROM staff + ), + together AS ( + SELECT LEFT.staff AS left_staff, + RIGHT.staff AS right_staff + FROM performed AS + LEFT JOIN performed AS RIGHT ON LEFT.experiment = RIGHT.experiment + WHERE left_staff < right_staff + ) + SELECT LEFT.name AS person_1, + RIGHT.name AS person_2 + FROM person AS +LEFT JOIN person AS + RIGHT JOIN together ON LEFT.ident = left_staff + AND RIGHT.ident = right_staff; +``` + +[out/filter_pairs.out](https://gvwilson.github.io/sql-tutorial/out/filter_pairs.out) + +``` +| person_1 | person_2 | +|-----------------|------------------| +| Kartik Gupta | Vedika Rout | +| Pranay Khanna | Vedika Rout | +| Indrans Sridhar | Romil Kapoor | +| Abram Chokshi | Ishaan Ramaswamy | +| Pranay Khanna | Vedika Rout | +| Kartik Gupta | Abram Chokshi | +| Abram Chokshi | Romil Kapoor | +| Kartik Gupta | Divit Dhaliwal | +| Divit Dhaliwal | Abram Chokshi | +| Pranay Khanna | Ishaan Ramaswamy | +| Indrans Sridhar | Romil Kapoor | +| Kartik Gupta | Ishaan Ramaswamy | +| Kartik Gupta | Nitya Lal | +| Kartik Gupta | Abram Chokshi | +| Pranay Khanna | Romil Kapoor | +``` + +- во временной табличке `person` выбираем имена сотрудников из таблицы `staff`; используем `personal ' ' family AS name`, чтобы объединить значения из столбцов `personal` и `family` +- временная табличка `together` использует оператор `LEFT JOIN` для объединения таблицы `performed` с собой на основе столбца `experiment`. Затем выбираются пары сотрудников, участвовавших в одном и том же эксперименте, исключая случаи, когда идентификатор левого сотрудника (`left_staff`) больше идентификатора правого сотрудника (`right_staff`) +- затем выполняется основной `SELECT`, который использует `person` и `together` для объединения имен сотрудников на основе их идентификаторов. Он выполняет два `LEFT JOIN`, чтобы объединить `person` с самим собой и затем объединить результат с `together` на основе идентификаторов сотрудников. +- затем выбираются имена сотрудников для отображения в итоговом результате. + +### EXISTS + +[src/correlated_subquery.sql](https://gvwilson.github.io/sql-tutorial/src/correlated_subquery.sql) + +``` +SELECT name, + building +FROM department +WHERE EXISTS + (SELECT 1 + FROM staff + WHERE dept = department.ident ) +ORDER BY name; +``` + +[out/correlated_subquery.out](https://gvwilson.github.io/sql-tutorial/out/correlated_subquery.out) + +``` +| name | building | +|-------------------|------------------| +| Genetics | Chesson | +| Histology | Fashet Extension | +| Molecular Biology | Chesson | +``` + +- выбираем столбцы `name` и `building` из таблицы `department` +- `WHERE EXISTS (SELECT 1 FROM staff WHERE dept = department.ident )` — используем подзапрос, который проверяет существование хотя бы одной записи в таблице `staff`, для которой значение столбца `dept` совпадает с значением столбца `ident` из таблицы `department` +- `ORDER BY name` — устанавливаем порядок сортировки результатов по столбцу `name` в алфавитном порядке + +### NOT EXISTS в SQL + +[src/nonexistence.sql](https://gvwilson.github.io/sql-tutorial/src/nonexistence.sql) + +``` +SELECT name, + building +FROM department +WHERE NOT EXISTS + (SELECT 1 + FROM staff + WHERE dept = department.ident ) +ORDER BY name; +``` + +[out/nonexistence.out](https://gvwilson.github.io/sql-tutorial/out/nonexistence.out) + +``` +| name | building | +|---------------|----------| +| Endocrinology | TGVH | +``` + +- выбираем столбцы `name` и `building` из таблицы `department` +- `WHERE NOT EXISTS` — выбираем только те записи из `department`, для которых не существует записей в таблице `staff` +- `SELECT 1 FROM staff WHERE dept = department.ident` — проверяем, существуют ли записи в таблице `staff`, связанные с отделом из таблицы `department` +- `ORDER BY name` — сортируем результат по столбцу name + +**Избегание коррелированных подзапросов** + +[src/avoid_correlated_subqueries.sql](https://gvwilson.github.io/sql-tutorial/src/avoid_correlated_subqueries.sql) + +``` +SELECT DISTINCT department.name AS name, + department.building AS building +FROM department +JOIN staff ON department.ident = staff.dept +ORDER BY name; +``` + +[out/avoid_correlated_subqueries.out](https://gvwilson.github.io/sql-tutorial/out/avoid_correlated_subqueries.out) + +``` +| name | building | +|-------------------|------------------| +| Genetics | Chesson | +| Histology | Fashet Extension | +| Molecular Biology | Chesson | +``` + +- `SELECT DISTINCT` — выбираем уникальные значения `name` и `building` из таблицы `department` +- `JOIN staff ON department.ident = staff.dept` — объединяем таблицы `department` и `staff` на основе условия, что значение столбца `ident` из `department` равно значению `dept` из `staff` +- `ORDER BY name` — результаты выборки сортируем в алфавитном порядке по столбцу `name` + +### Опережение и отставание + +[src/lead_lag.sql](https://gvwilson.github.io/sql-tutorial/src/lead_lag.sql) + +``` + WITH ym_num AS ( + SELECT strftime ('%Y-%m', started) AS ym, + COUNT(*) AS num + FROM experiment + GROUP BY ym + ) + SELECT ym, + lag (num) OVER ( + ORDER BY ym + ) AS prev_num, + num, + lead (num) OVER ( + ORDER BY ym + ) AS next_num + FROM ym_num + ORDER BY ym; +``` + +[out/lead_lag.out](https://gvwilson.github.io/sql-tutorial/out/lead_lag.out) + +``` +| ym | prev_num | num | next_num | +|---------|----------|-----|----------| +| 2023-01 | | 2 | 5 | +| 2023-02 | 2 | 5 | 5 | +| 2023-03 | 5 | 5 | 1 | +| 2023-04 | 5 | 1 | 6 | +| 2023-05 | 1 | 6 | 5 | +| 2023-06 | 6 | 5 | 3 | +| 2023-07 | 5 | 3 | 2 | +| 2023-08 | 3 | 2 | 4 | +| 2023-09 | 2 | 4 | 6 | +| 2023-10 | 4 | 6 | 4 | +| 2023-12 | 6 | 4 | 5 | +| 2024-01 | 4 | 5 | 2 | +| 2024-02 | 5 | 2 | | +``` + +- создаём временную таблицу `ym_num` из 2 столбцов: `ym` (год-месяц ‘YYYY-MM’) и `num` (количество записей в каждом месяце) +- используем SQLite `strftime` для извлечения года и месяца из `started`, агрегируем результаты с помощью `GROUP BY` +- в основном запросе выбираем данные из `ym_num`, выполняем следующие операции и получаем год-месяц `ym`, количество записей в предыдущем месяце `(lag)num`, текущее количество записей `num` и количество записей в следующем месяце `(lead)num` +- результаты упорядочиваем по столбцу `ym` (год-месяц) + +### Оконные функции + +[src/window_functions.sql](https://gvwilson.github.io/sql-tutorial/src/window_functions.sql) + +``` + WITH ym_num AS ( + SELECT strftime ('%Y-%m', started) AS ym, + COUNT(*) AS num + FROM experiment + GROUP BY ym + ) + SELECT ym, + num, + SUM(num) OVER ( + ORDER BY ym + ) AS num_done, + CUME_DIST() OVER ( + ORDER BY ym + ) AS progress + FROM ym_num + ORDER BY ym; +``` + +[out/window_functions.out](https://gvwilson.github.io/sql-tutorial/out/window_functions.out) + +``` +| ym | num | num_done | progress | +|---------|-----|----------|--------------------| +| 2023-01 | 2 | 2 | 0.0769230769230769 | +| 2023-02 | 5 | 7 | 0.153846153846154 | +| 2023-03 | 5 | 12 | 0.230769230769231 | +| 2023-04 | 1 | 13 | 0.307692307692308 | +| 2023-05 | 6 | 19 | 0.384615384615385 | +| 2023-06 | 5 | 24 | 0.461538461538462 | +| 2023-07 | 3 | 27 | 0.538461538461538 | +| 2023-08 | 2 | 29 | 0.615384615384615 | +| 2023-09 | 4 | 33 | 0.692307692307692 | +| 2023-10 | 6 | 39 | 0.769230769230769 | +| 2023-12 | 4 | 43 | 0.846153846153846 | +| 2024-01 | 5 | 48 | 0.923076923076923 | +| 2024-02 | 2 | 50 | 1.0 | +``` + +- создаём временную таблицу `ym_num`, которая содержит: `ym` — год и месяц, извлеченные из `started` в `experiment` с помощью `strftime('%Y-%m')`; `num` — количество записей в `experiment` для каждого сочетания года и месяца +- выбираем `ym` и `num` из таблицы `ym_num`, добавляем 2 дополнительных столбца: `num_done` — сумма количества экспериментов по всем предыдущим годам и месяцам (`sum(num) OVER (ORDER BY ym)`); `progress` — кумулятивное распределение количества экспериментов по всем предыдущим годам и месяцам (`cume_dist() OVER (ORDER BY ym)`) +- упорядочиваем результаты по столбцу `ym` (год и месяц) + +**Внезапно небольшое задание: объясните, что делает запрос ниже** + +[src/explain_window_function.sql](https://gvwilson.github.io/sql-tutorial/src/explain_window_function.sql) + +``` +EXPLAIN query PLAN + WITH ym_num AS ( + SELECT strftime ('%Y-%m', started) AS ym, + COUNT(*) AS num + FROM experiment + GROUP BY ym + ) + SELECT ym, + num, + SUM(num) OVER ( + ORDER BY ym + ) AS num_done, + CUME_DIST() OVER ( + ORDER BY ym + ) AS progress + FROM ym_num + ORDER BY ym; +``` + +[out/explain_window_function.out](https://gvwilson.github.io/sql-tutorial/out/explain_window_function.out) + +``` +QUERY PLAN +|--CO-ROUTINE (subquery-3) +| |--CO-ROUTINE (subquery-4) +| | |--CO-ROUTINE ym_num +| | | |--SCAN experiment +| | | `--USE TEMP B-TREE FOR GROUP BY +| | |--SCAN ym_num +| | `--USE TEMP B-TREE FOR ORDER BY +| `--SCAN (subquery-4) +`--SCAN (subquery-3) +``` + +- создаём временную табличку `ym_num` с результатами агрегирования по месяцам, где данные из `started`преобразуются в формат год-месяц (`strftime('%Y-%m', started) AS ym`) и подсчитываем количество событий (`count(*) AS num`) +- группируем результаты по полю `ym` +- выбираем поля `ym` и `num` из `ym_num` и добавляем 2 дополнительных поля: `num_done` и `progress`; `num_done` — общее количество событий/мес, сгруппированных в порядке увеличения месяца; поле `progress` — прогресс в процентном соотношении относительно общего числа записей (`cume_dist()`) +- в итоге выводим данные в порядке увеличения значения `ym` (год-месяц) + +### Используем PARTITION BY в SQL + +[src/partition_window.sql](https://gvwilson.github.io/sql-tutorial/src/partition_window.sql) + +``` +WITH y_m_num AS + (SELECT strftime('%Y', started) AS YEAR, + strftime('%m', started) AS MONTH, + count(*) AS num + FROM experiment + GROUP BY YEAR, + MONTH) +SELECT YEAR, + MONTH, + num, + sum(num) OVER (PARTITION BY YEAR + ORDER BY MONTH) AS num_done +FROM y_m_num +ORDER BY YEAR, + MONTH; +``` + +[out/partition_window.out](https://gvwilson.github.io/sql-tutorial/out/partition_window.out) + +``` +| year | month | num | num_done | +|------|-------|-----|----------| +| 2023 | 01 | 2 | 2 | +| 2023 | 02 | 5 | 7 | +| 2023 | 03 | 5 | 12 | +| 2023 | 04 | 1 | 13 | +| 2023 | 05 | 6 | 19 | +| 2023 | 06 | 5 | 24 | +| 2023 | 07 | 3 | 27 | +| 2023 | 08 | 2 | 29 | +| 2023 | 09 | 4 | 33 | +| 2023 | 10 | 6 | 39 | +| 2023 | 12 | 4 | 43 | +| 2024 | 01 | 5 | 5 | +| 2024 | 02 | 2 | 7 | +``` + +- создаём временную таблицу `y_m_num` с тремя столбцами: `YEAR,` `MONTH` и `num`. +- временную табличку заполняем записями из `experiment`. Для каждой записи определяем год и месяц даты в столбце `started` (через `strftime`), считаем количество записей (`count(*)`) для каждого года и месяца, группируем результаты по году и месяцу +- выбираем данные из `y_m_num`, добавляем столбец `num_done` — накопительное значение для `num` в пределах каждого года `sum(num) OVER (PARTITION BY YEAR ORDER BY MONTH)` — суммируем значение `num` для каждого месяца при сортировке по месяцам внутри каждого года +- в итоге сортируем результаты по году и месяцу с помощью `ORDER BY YEAR, MONTH` + +### Данные типа blob + +[src/blob.sql](https://gvwilson.github.io/sql-tutorial/src/blob.sql) + +``` +CREATE TABLE images (name text NOT NULL, + content blob); + +INSERT INTO images(name, content) +VALUES ("biohazard", readfile("img/biohazard.png")), + ("crush", readfile("img/crush.png")), + ("fire", readfile("img/fire.png")), + ("radioactive", readfile("img/radioactive.png")), + ("tripping", readfile("img/tripping.png")); + +SELECT name, + length(content) +FROM images; +``` + +[out/blob.out](https://gvwilson.github.io/sql-tutorial/out/blob.out) + +``` +| name | length(content) | +|-------------|-----------------| +| biohazard | 19629 | +| crush | 15967 | +| fire | 18699 | +| radioactive | 16661 | +| tripping | 17208 | +``` + +- создаём таблицу `images` со столбцами: `name` — текстовый столбец, не может быть пустым; `content` — столбец двоичных данных (`blob`) +- вставляем 5 пар `name`–`blob` в `images` с помощью `INSERT INTO` +- `readfile` читает содержимое файла `name` и возвращает его как двоичные данные `blob` +- выполняем выборку данных из `images` с помощью `SELECT`, получая значения `name` и вычисляя количество байт двоичных данных в `content` + +**Ещё одна БД** + +[src/lab_log_db.sh](https://gvwilson.github.io/sql-tutorial/src/lab_log_db.sh) + +``` +sqlite3 data/lab_log.db +``` + +[src/lab_log_schema.sql](https://gvwilson.github.io/sql-tutorial/src/lab_log_schema.sql) + +``` +.schema +``` + +[out/lab_log_schema.out](https://gvwilson.github.io/sql-tutorial/out/lab_log_schema.out) + +``` +CREATE TABLE sqlite_sequence(name, + seq); + +CREATE TABLE person(ident integer PRIMARY KEY autoincrement, + details text NOT NULL); + +CREATE TABLE machine(ident integer PRIMARY KEY autoincrement, + name text NOT NULL, + details text NOT NULL); + +CREATE TABLE usage(ident integer PRIMARY KEY autoincrement, + log text NOT NULL); +``` + +- создаём `sqlite_sequence` со столбцами `name` и `seq` (для значения счетчика, он используется в качестве `AUTOINCREMENT`) +- создаём `person` со столбцами `ident` (целочисленный, является первичным ключом (`PRIMARY KEY`), автоматически инкрементируется) и `details` (текстовый столбец, не может иметь значение `NULL`) +- создаём `machine` со столбцами `ident` (целочисленный, является первичным ключом (`PRIMARY KEY`), автоматически инкрементируется), `name` (текстовый, не может иметь значение `NULL`), `details`(текстовый, не может иметь значение `NULL`) +- создаём `usage` со столбцами `ident` (целочисленный, является первичным ключом (`PRIMARY KEY`), автоматически инкрементируется) и `log` (текстовый столбец, не может иметь значение `NULL`) + +### Сохранение JSON + +[src/json_in_table.sql](https://gvwilson.github.io/sql-tutorial/src/json_in_table.sql) + +``` +SELECT * +FROM machine; +``` + +[out/json_in_table.out](https://gvwilson.github.io/sql-tutorial/out/json_in_table.out) + +``` +| ident | name | details | +|-------|----------------|---------------------------------------------------------| +| 1 | WY401 | {"acquired": "2023-05-01"} | +| 2 | Inphormex | {"acquired": "2021-07-15", "refurbished": "2023-10-22"} | +| 3 | AutoPlate 9000 | {"note": "needs software update"} | +``` + +### Выбираем отдельные поля в JSON + +[src/json_field.sql](https://gvwilson.github.io/sql-tutorial/src/json_field.sql) + +``` +SELECT details->'$.acquired' AS single_arrow, + details->>'$.acquired' AS double_arrow +FROM machine; +``` + +[out/json_field.out](https://gvwilson.github.io/sql-tutorial/out/json_field.out) + +``` +| single_arrow | double_arrow | +|--------------|--------------| +| "2023-05-01" | 2023-05-01 | +| "2021-07-15" | 2021-07-15 | +| | | +``` + +- `details->'$.acquired' AS single_arrow` — с помощью `->` извлекаем значение JSON поля `acquired` из столбца `details` для каждой строки из `machine`, обозначаем его как `single_arrow` +- `details->>'$.acquired' AS double_arrow` — оператор `->>` также используется для извлечения JSON по указанному пути, но возвращает текст, в отличие от `->`, который возвращает JSON значение; здесь мы извлекаем значение JSON поля `acquired` из столбца `details` для каждой строки из `machine`, обозначаем его как `double_arrow` + +### Доступ к JSON-объекту + +[src/json_array.sql](https://gvwilson.github.io/sql-tutorial/src/json_array.sql) + +``` +SELECT ident, + json_array_length(log->'$') AS LENGTH, + log->'$[0]' AS FIRST +FROM USAGE; +``` + +[out/json_array.out](https://gvwilson.github.io/sql-tutorial/out/json_array.out) + +``` +| ident | length | first | +|-------|--------|--------------------------------------------------------------| +| 1 | 4 | {"machine":"Inphormex","person":["Gabrielle","Dub\u00e9"]} | +| 2 | 5 | {"machine":"Inphormex","person":["Marianne","Richer"]} | +| 3 | 2 | {"machine":"sterilizer","person":["Josette","Villeneuve"]} | +| 4 | 1 | {"machine":"sterilizer","person":["Maude","Goulet"]} | +| 5 | 2 | {"machine":"AutoPlate 9000","person":["Brigitte","Michaud"]} | +| 6 | 1 | {"machine":"sterilizer","person":["Marianne","Richer"]} | +| 7 | 3 | {"machine":"WY401","person":["Maude","Goulet"]} | +| 8 | 1 | {"machine":"AutoPlate 9000"} | +``` + +- `json_array_length(log->'$') AS LENGTH` — вычисляем длину массива, находящегося внутри JSON-объекта в столбце `log`; используем оператор `->`, чтобы получить массив из корневого уровня JSON-объекта, и `json_array_length` для подсчета количества элементов в этом массиве; результат помещаем в столбец с именем `LENGTH` +- `log->'$[0]' AS FIRST` — извлекаем первый элемент из массива, указанного в корневом уровне JSON-объекта в столбце `log`; используем оператор `->`, чтобы получить доступ к массиву, и указываем индекс элемента в квадратных скобках; результат сохраняем в столбец `FIRST` + +### Распаковка JSON + +[src/json_unpack.sql](https://gvwilson.github.io/sql-tutorial/src/json_unpack.sql) + +``` +SELECT ident, + json_each.key AS KEY, + json_each.value AS value +FROM USAGE, + json_each(usage.log) +LIMIT 10; +``` + +[out/json_unpack.out](https://gvwilson.github.io/sql-tutorial/out/json_unpack.out) + +``` +| ident | key | value | +|-------|-----|--------------------------------------------------------------| +| 1 | 0 | {"machine":"Inphormex","person":["Gabrielle","Dub\u00e9"]} | +| 1 | 1 | {"machine":"Inphormex","person":["Gabrielle","Dub\u00e9"]} | +| 1 | 2 | {"machine":"WY401","person":["Gabrielle","Dub\u00e9"]} | +| 1 | 3 | {"machine":"Inphormex","person":["Gabrielle","Dub\u00e9"]} | +| 2 | 0 | {"machine":"Inphormex","person":["Marianne","Richer"]} | +| 2 | 1 | {"machine":"AutoPlate 9000","person":["Marianne","Richer"]} | +| 2 | 2 | {"machine":"sterilizer","person":["Marianne","Richer"]} | +| 2 | 3 | {"machine":"AutoPlate 9000","person":["Monique","Marcotte"]} | +| 2 | 4 | {"machine":"sterilizer","person":["Marianne","Richer"]} | +| 3 | 0 | {"machine":"sterilizer","person":["Josette","Villeneuve"]} | +``` + +- `SELECT ident, json_each.key AS KEY, json_each.value AS value` — определяем, что нужно выбрать из таблицы `usage` и JSON-объектов, распарсенных с помощью функции `json_each`; из каждой строки выбираем идентификатор, а также ключ и его значение из каждого JSON-объекта в столбце `log` +- `FROM usage, json_each(usage.log)` — указываем источник данных для выборки; `usage` указывается после ключевого слова FROM, а `json_each` вызывается перед `log`, чтобы разобрать JSON-объекты из этого столбца +- `LIMIT 10` — выбираем только первые 10 строк + +### Последний элемент в массиве + +[src/json_array_last.sql](https://gvwilson.github.io/sql-tutorial/src/json_array_last.sql) + +``` +SELECT ident, + log->'$[#-1].machine' AS FINAL +FROM USAGE +LIMIT 5; +``` + +[out/json_array_last.out](https://gvwilson.github.io/sql-tutorial/out/json_array_last.out) + +``` +| ident | final | +|-------|--------------| +| 1 | "Inphormex" | +| 2 | "sterilizer" | +| 3 | "Inphormex" | +| 4 | "sterilizer" | +| 5 | "sterilizer" | +``` + +- `SELECT ident, log->'$[#-1].machine' AS FINAL` — выбираем 2 столбца из `machine`; `ident`возвращается как есть, а столбец `log` обрабатывается так: + - `log->'$[#-1].machine` — извлекаем данные из столбца `log` (`->` используется для доступа к JSON-полю в столбце `log`) + - `$[#-1]` — обращаемся к последнему элементу массива, который хранится в `log` + - `.machine` — хотим извлечь значение поля `machine` из объекта, находящегося в последнем элементе массива + +### Модифицируем JSON + +[src/json_modify.sql](https://gvwilson.github.io/sql-tutorial/src/json_modify.sql) + +``` +SELECT ident, + name, + json_set(details, '$.sold', json_quote('2024-01-25')) AS updated +FROM machine; +``` + +[out/json_modify.out](https://gvwilson.github.io/sql-tutorial/out/json_modify.out) + +``` +| ident | name | updated | +|-------|----------------|--------------------------------------------------------------| +| 1 | WY401 | {"acquired":"2023-05-01","sold":"2024-01-25"} | +| 2 | Inphormex | {"acquired":"2021-07-15","refurbished":"2023-10-22","sold":" | +| | | 2024-01-25"} | +| 3 | AutoPlate 9000 | {"note":"needs software update","sold":"2024-01-25"} | +``` + +- `SELECT ident, name, ... FROM machine;` — выбираем значения столбцов `ident` и `name` из таблицы `machine` +- `json_set(details, '$.sold', json_quote('2024-01-25')) AS updated` — при помощи `json_set`обновляем JSON-объект в столбце `details`; функция добавляет/изменяет свойство `sold` в JSON-объекте в столбце `details`, присваивая ему новое значение, полученное с помощью функции `json_quote`; результат сохраняем как `updated` + +**Обновляем табличку penguins:** + +[src/count_penguins.sql](https://gvwilson.github.io/sql-tutorial/src/count_penguins.sql) + +``` +SELECT species, + count(*) AS num +FROM penguins +GROUP BY species; +``` + +[out/count_penguins.out](https://gvwilson.github.io/sql-tutorial/out/count_penguins.out) + +``` +| species | num | +|-----------|-----| +| Adelie | 152 | +| Chinstrap | 68 | +| Gentoo | 124 | +``` + +### Immediate If в SQL + +[src/make_active.sql](https://gvwilson.github.io/sql-tutorial/src/make_active.sql) + +``` +ALTER TABLE penguins ADD active integer NOT NULL DEFAULT 1; + +UPDATE penguins +SET active = iif(species = 'Adelie', 0, 1); +``` + +- изменяем таблицу `penguins`, добавляя новый столбец `active` типа `integer`, который не может содержать значение `NULL`, и устанавливаем значение по умолчанию 1 для всех строк +- обновляем значения в столбце `active` в `penguins`; значение столбца `active` устанавливается на 0, если значение в `species` равно `'Adelie'`, иначе устанавливается на 1 +- функция `IIF` (Immediate If) используется здесь для реализации условного выражения (1 аргумент – условие, 2 – результат, если условие истинно, и 3 – результат, если условие ложно) + +[src/active_penguins.sql](https://gvwilson.github.io/sql-tutorial/src/active_penguins.sql) + +``` +SELECT species, + count(*) AS num +FROM penguins +WHERE active +GROUP BY species; +``` + +[out/active_penguins.out](https://gvwilson.github.io/sql-tutorial/out/active_penguins.out) + +``` +| species | num | +|-----------|-----| +| Chinstrap | 68 | +| Gentoo | 124 | +``` + +### Представление VIEW в SQL + +[src/views.sql](https://gvwilson.github.io/sql-tutorial/src/views.sql) + +``` +CREATE VIEW IF NOT EXISTS active_penguins (species, island, bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g, sex) AS +SELECT species, + island, + bill_length_mm, + bill_depth_mm, + flipper_length_mm, + body_mass_g, + sex +FROM penguins +WHERE active; + +SELECT species, + count(*) AS num +FROM active_penguins +GROUP BY species; +``` + +[out/views.out](https://gvwilson.github.io/sql-tutorial/out/views.out) + +``` +| species | num | +|-----------|-----| +| Chinstrap | 68 | +| Gentoo | 124 | +``` + +- создаём представление (`VIEW`) с именем `active_penguins`, если его еще не существует +- представление содержит столбцы `species`, `island`, `bill_length_mm`, `bill_depth_mm`, `flipper_length_mm`, `body_mass_g`, и `sex`; данные для представления берутся из `penguins`, при условии, что пингвины являются активными (`WHERE active`) +- выполняем выборку из представления `active_penguins`: выбираем вид пингвина (`species`) и количество таких пингвинов (`num`), удовлетворяющих условиям, заданным в представлении `active_penguins` + +**Напоминание о часах работы:** + +[src/all_jobs.sql](https://gvwilson.github.io/sql-tutorial/src/all_jobs.sql) + +``` +CREATE TABLE job (name text NOT NULL, + billable real NOT NULL); + +INSERT INTO job +VALUES ('calibrate', 1.5), + ('clean', 0.5); + +SELECT * +FROM job; +``` + +[out/all_jobs.out](https://gvwilson.github.io/sql-tutorial/out/all_jobs.out) + +``` +| name | billable | +|-----------|----------| +| calibrate | 1.5 | +| clean | 0.5 | +``` + +### Добавляем проверку CHECK + +[src/all_jobs_check.sql](https://gvwilson.github.io/sql-tutorial/src/all_jobs_check.sql) + +``` +CREATE TABLE job (name text NOT NULL, + billable real NOT NULL, CHECK (billable > 0.0)); + +INSERT INTO job +VALUES ('calibrate', 1.5); + +INSERT INTO job +VALUES ('reset', -0.5); + +SELECT * +FROM job; +``` + +[out/all_jobs_check.out](https://gvwilson.github.io/sql-tutorial/out/all_jobs_check.out) + +``` +Runtime error near line 9: CHECK constraint failed: billable > 0.0 (19) +| name | billable | +|-----------|----------| +| calibrate | 1.5 | +``` + +- создаём таблицу `job` с 2 столбцами, которые не могут быть пустыми: `name` (текстовый тип данных) и `billable` (вещественные тип данных) +- ограничение (`CHECK`) гарантирует, что значение столбца `billable` должно быть больше чем 0.0 +- добавляем новую запись в `job` с указанными значениями `'calibrate'` для столбца `name` и 1.5 для столбца `billable` — сейчас под условие `CHECK` это попадает +- пытаемся добавить еще одну запись в таблицу `job` с указанными значениями `'reset'` для столбца `name`и -0.5 для столбца `billable`. Однако, так как -0.5 меньше либо равно 0.0, то это нарушает условие `CHECK` + +**ACID** + +ACID — это акроним, который описывает набор свойств транзакций баз данных, предназначенных для обеспечения целостности данных в случае ошибок, сбоев питания и других непредвиденных ситуаций: + +1. **Атомарность (Atomicity)**: Транзакция должна быть атомарной, что означает, что она должна быть выполнена целиком или не выполнена вообще. Если одна часть транзакции не может быть выполнена, то все изменения, сделанные в рамках этой транзакции, должны быть отменены. +2. **Согласованность (Consistency)**: Транзакция должна приводить базу данных из одного согласованного состояния в другое согласованное состояние. Это означает, что все правила и ограничения, установленные на данные, должны быть соблюдены во время выполнения транзакции. +3. **Изолированность (Isolation)**: Транзакции должны быть изолированы друг от друга, чтобы предотвратить взаимное влияние. Каждая транзакция должна быть выполнена так, как если бы она была единственной выполняемой транзакцией в базе данных. Это гарантирует, что результаты одной транзакции не будут видны другим транзакциям до их завершения. +4. **Долговечность (Durability)**: Результаты выполненной транзакции должны быть постоянными и доступными даже в случае сбоя системы или перезагрузки. Это достигается путем записи изменений в постоянное хранилище, например SSD. + +### TRANSACTION в SQL + +[src/transaction.sql](https://gvwilson.github.io/sql-tutorial/src/transaction.sql) + +``` +CREATE TABLE job (name text NOT NULL, + billable real NOT NULL, CHECK (billable > 0.0)); + +INSERT INTO job +VALUES ('calibrate', 1.5); +BEGIN TRANSACTION; + +INSERT INTO job +VALUES ('clean', 0.5); +ROLLBACK; + +SELECT * +FROM job; +``` + +[out/transaction.out](https://gvwilson.github.io/sql-tutorial/out/transaction.out) + +``` +| name | billable | +|-----------|----------| +| calibrate | 1.5 | +``` + +- создаём таблицу `job` с 2 колонками, которые не могут быть пустыми: + - `name` текстового типа + - `billable` с типом данных `real` (вещественное число) и условием `CHECK (billable > 0.0)`, что гарантирует, что значение `billable` больше 0.0 +- добавляем в `job` запись: `('calibrate', 1.5)` +- начинаем новую транзакцию. +- добавляем другую запись в таблицу `job`: `('clean', 0.5)` +- откатываем последнюю транзакцию, добавляя `'clean', 0.5`, поэтому данная строка не сохраняется + +### ROLLBACK в SQL + +[src/rollback_constraint.sql](https://gvwilson.github.io/sql-tutorial/src/rollback_constraint.sql) + +``` + CREATE TABLE job ( + name text NOT NULL, + billable real NOT NULL, + CHECK (billable > 0.0) ON CONFLICT ROLLBACK + ); + + INSERT INTO job + VALUES ('calibrate', 1.5); + + INSERT INTO job + VALUES ('clean', 0.5), + ('reset', -0.5); + + SELECT * + FROM job; +``` + +[out/rollback_constraint.out](https://gvwilson.github.io/sql-tutorial/out/rollback_constraint.out) + +``` +Runtime error near line 11: CHECK constraint failed: billable > 0.0 (19) +| name | billable | +|-----------|----------| +| calibrate | 1.5 | +``` + +- создаём новую таблицу с именем `job` и 2 непустыми столбцами: текстовым `name` и вещественным `billable` +- значение в `billable` должно быть больше 0 (`CHECK (billable > 0.0)`) +- добавляем в `job` запись с именем `calibrate` со значением `billable` 1.5 +- вторая запись с именем `clean` имеет значение `billable` равное 0.5 +- третья запись с именем `reset` имеет значение `billable` равное -0.5 — тут возникает проблема с записью третьей строки, так как это нарушает ограничение `CHECK (billable > 0.0)` + +### Откат с помощью ROLLBACK + +[src/rollback_statement.sql](https://gvwilson.github.io/sql-tutorial/src/rollback_statement.sql) + +``` +CREATE TABLE job (name text NOT NULL, + billable real NOT NULL, + CHECK (billable > 0.0)); +INSERT OR ROLLBACK INTO job +VALUES ('calibrate', 1.5); + +INSERT OR ROLLBACK INTO job +VALUES ('clean', 0.5), + ('reset', -0.5); + +SELECT * +FROM job; +``` + +[out/rollback_statement.out](https://gvwilson.github.io/sql-tutorial/out/rollback_statement.out) + +``` +Runtime error near line 11: CHECK constraint failed: billable > 0.0 (19) +| name | billable | +|-----------|----------| +| calibrate | 1.5 | +``` + +- создаём таблицу `job` с 2 непустыми столбцами: + - текстовым столбцом `name` + - вещественнозначным `billable` с ограничением `CHECK (billable > 0.0)` — значение в этом столбце всегда будет больше нуля +- вставляем данные в `job` с помощью оператора `INSERT OR ROLLBACK`, а именно одну запись с названием `calibrate` и значением `billable` равным 1.5 +- вставляем ещё 2 записи в таблицу `job` с помощью оператора `INSERT OR ROLLBACK`: `clean` со значением 0.5 для `billable`, `reset` со значением -0.5 `billable` (что не подходит по условию `CHECK`) + +### Вставка значений + +[src/upsert.sql](https://gvwilson.github.io/sql-tutorial/src/upsert.sql) + +``` +CREATE TABLE jobs_done (person text UNIQUE, + num integer DEFAULT 0); + +INSERT INTO jobs_done +VALUES("zia", 1); + +.print "after first" +SELECT * +FROM jobs_done; + +.print +INSERT INTO jobs_done +VALUES("zia", 1); + +.print "after failed" +SELECT * +FROM jobs_done; + +INSERT INTO jobs_done +VALUES("zia", 1) ON conflict(person) DO +UPDATE +SET num = num + 1; + +.print "\nafter upsert" +SELECT * +FROM jobs_done; +``` + +[out/upsert.out](https://gvwilson.github.io/sql-tutorial/out/upsert.out) + +``` +after first +| person | num | +|--------|-----| +| zia | 1 | + +Runtime error near line 14: UNIQUE constraint failed: jobs_done.person (19) +after failed +| person | num | +|--------|-----| +| zia | 1 | + +after upsert +| person | num | +|--------|-----| +| zia | 2 | +``` + +- создаём `jobs_done` со столбцами `person` (текстовый тип данных с уникальными значениями) и `num`(целочисленный тип, по умолчанию равен 0) +- вставляем в `jobs_done` запись с именем `"zia"` и числом 1 +- пытаемся снова вставить строку с тем же именем `"zia"` и числом 1 и снова выводим результаты запроса `SELECT` +- вставляем строку с тем же именем `"zia"` и числом 1 но уже указываем, чтобы в случае конфликта по столбцу `person`, обновить значение столбца `num`, увеличив его на 1 + +### Создание триггера + +[src/trigger_setup.sql](https://gvwilson.github.io/sql-tutorial/src/trigger_setup.sql) + +``` +-- Track hours of lab work. +CREATE TABLE job (person text NOT NULL, + reported real NOT NULL CHECK (reported >= 0.0)); + +-- Explicitly store per-person total rather than using sum(). +CREATE TABLE total (person text UNIQUE NOT NULL, + hours real); + +-- Initialize totals. +INSERT INTO total +VALUES ("gene", 0.0), + ("august", 0.0); + +-- Define a trigger. +CREATE TRIGGER total_trigger +BEFORE +INSERT ON job BEGIN -- Check that the person exists. + +SELECT CASE + WHEN NOT EXISTS + (SELECT 1 + FROM total + WHERE person = new.person) THEN raise + (ROLLBACK, 'Unknown person ') + END; -- Update their total hours (or fail if non-negative constraint violated). + +UPDATE total +SET hours = hours + new.reported +WHERE total.person = new.person; END; +``` + +- создаём таблицу `job` со столбцами `person` и `reported` +- создаём `total` со столбцами `person` и `hours` +- устанавливаем значения `gene` и `august` в 0.0 +- создаём триггер `total_trigger`, который срабатывает перед вставкой новых записей в таблицу `job`. Этот триггер: + - проверяет, существует ли человек в таблице `total`, прежде чем разрешить вставку новых записей в таблицу `job` + - обновляет общее количество отработанных часов для соответствующего человека в таблице `total`путем добавления нового количества отработанных часов из таблицы `job` + +[src/trigger_successful.sql](https://gvwilson.github.io/sql-tutorial/src/trigger_successful.sql) + +``` +INSERT INTO job +VALUES ('gene', 1.5), + ('august', 0.5), + ('gene', 1.0); +``` + +[out/trigger_successful.out](https://gvwilson.github.io/sql-tutorial/out/trigger_successful.out) + +``` +| person | reported | +|--------|----------| +| gene | 1.5 | +| august | 0.5 | +| gene | 1.0 | + +| person | hours | +|--------|-------| +| gene | 2.5 | +| august | 0.5 | +``` + +**Срабатывание триггера** + +[src/trigger_firing.sql](https://gvwilson.github.io/sql-tutorial/src/trigger_firing.sql) + +``` +INSERT INTO job +VALUES ('gene', 1.0), + ('august', -1.0) ; +``` + +[out/trigger_firing.out](https://gvwilson.github.io/sql-tutorial/out/trigger_firing.out) + +``` +Runtime error near line 6: CHECK constraint failed: reported >= 0.0 (19) + +| person | hours | +|--------|-------| +| gene | 0.0 | +| august | 0.0 | +``` + +**Графическое представление** + +[src/lineage_setup.sql](https://gvwilson.github.io/sql-tutorial/src/lineage_setup.sql) + +``` +CREATE TABLE lineage (parent text NOT NULL, + child text NOT NULL); +INSERT INTO lineage +VALUES ('Arturo', 'Clemente'), + ('Darío', 'Clemente'), + ('Clemente', 'Homero'), + ('Clemente', 'Ivonne'), + ('Ivonne', 'Lourdes'), + ('Soledad', 'Lourdes'), + ('Lourdes', 'Santiago'); +``` + +[src/represent_graph.sql](https://gvwilson.github.io/sql-tutorial/src/represent_graph.sql) + +``` +SELECT * +FROM lineage; +``` + +[out/represent_graph.out](https://gvwilson.github.io/sql-tutorial/out/represent_graph.out) + +``` +| parent | child | +|----------|----------| +| Arturo | Clemente | +| Darío | Clemente | +| Clemente | Homero | +| Clemente | Ivonne | +| Ivonne | Lourdes | +| Soledad | Lourdes | +| Lourdes | Santiago | +``` + +") + +lineage diagram + +### Рекурсивный запрос + +[src/recursive_lineage.sql](https://gvwilson.github.io/sql-tutorial/src/recursive_lineage.sql) + +``` + WITH RECURSIVE descendent AS ( + SELECT 'Clemente' AS person, + 0 AS generations + UNION ALL + SELECT lineage.child AS person, + descendent.generations + 1 AS generations + FROM descendent + JOIN lineage ON descendent.person = lineage.parent + ) + SELECT person, + generations + FROM descendent; +``` + +[out/recursive_lineage.out](https://gvwilson.github.io/sql-tutorial/out/recursive_lineage.out) + +``` +| person | generations | +|----------|-------------| +| Clemente | 0 | +| Homero | 1 | +| Ivonne | 1 | +| Lourdes | 2 | +| Santiago | 3 | +``` + +- определяем общий термин `descendent` (потомок) как рекурсивное общее выражение. Начинаем с одной записи, где `'Clemente'` – это начальное имя, а 0 – это количество поколений. +- далее мы выполняем рекурсивное объединение с самим собой (с `descendent`) и таблицей `lineage`, чтобы найти всех потомков для каждого найденного человека. Выбираем потомка из таблицы `lineage`, увеличиваем количество поколений на 1 и продолжаем делать это для всех найденных потомков, пока они находятся +- если новых потомков больше не найдено, используем `SELECT` для выбора столбцов `person` и `generations` из `descendent` + +**База данных отслеживания контактов** + +[src/contact_person.sql](https://gvwilson.github.io/sql-tutorial/src/contact_person.sql) + +``` +SELECT * +FROM person; +``` + +[out/contact_person.out](https://gvwilson.github.io/sql-tutorial/out/contact_person.out) + +``` +| ident | name | +|-------|-----------------------| +| 1 | Juana Baeza | +| 2 | Agustín Rodríquez | +| 3 | Ariadna Caraballo | +| 4 | Micaela Laboy | +| 5 | Verónica Altamirano | +| 6 | Reina Rivero | +| 7 | Elias Merino | +| 8 | Minerva Guerrero | +| 9 | Mauro Balderas | +| 10 | Pilar Alarcón | +| 11 | Daniela Menéndez | +| 12 | Marco Antonio Barrera | +| 13 | Cristal Soliz | +| 14 | Bernardo Narváez | +| 15 | Óscar Barrios | +``` + +[src/contact_contacts.sql](https://gvwilson.github.io/sql-tutorial/src/contact_contacts.sql) + +``` +SELECT * +FROM contact; +``` + +[out/contact_contacts.out](https://gvwilson.github.io/sql-tutorial/out/contact_contacts.out) + +``` +| left | right | +|-------------------|-----------------------| +| Agustín Rodríquez | Ariadna Caraballo | +| Agustín Rodríquez | Verónica Altamirano | +| Juana Baeza | Verónica Altamirano | +| Juana Baeza | Micaela Laboy | +| Pilar Alarcón | Reina Rivero | +| Cristal Soliz | Marco Antonio Barrera | +| Cristal Soliz | Daniela Menéndez | +| Daniela Menéndez | Marco Antonio Barrera | +``` + +") + +contact diagram + +### Продолжаем работать с bi_contact + +[src/bidirectional.sql](https://gvwilson.github.io/sql-tutorial/src/bidirectional.sql) + +``` +CREATE TEMPORARY TABLE bi_contact (LEFT text, RIGHT text); + + INSERT INTO bi_contact + SELECT LEFT, + RIGHT + FROM contact +UNION ALL + SELECT RIGHT, + LEFT + FROM contact; +``` + +[out/bidirectional.out](https://gvwilson.github.io/sql-tutorial/out/bidirectional.out) + +``` +| original_count | +|----------------| +| 8 | + +| num_contact | +|-------------| +| 16 | +``` + +- создаём временную табличку `bi_contact` с 2 столбцами: `LEFT` и `RIGHT`, оба текстовые +- вставляем в `bi_contact` данные из другой таблицы при помощи `SELECT` +- используем `UNION ALL` для объединения результатов 2 операций `SELECT` в один набор данных; данные из столбца `LEFT` и `RIGHT` таблицы `contact` вставляем в таблицу `bi_contact`. Первый набор данных берёт значения из столбцов `LEFT` и `RIGHT` таблицы `contact`, а второй набор данных берёт значения из столбцов `RIGHT` и `LEFT` таблицы `contact` +- в общем, вставляем в `bi_contact` комбинацию значений из столбцов `LEFT` и `RIGHT` таблицы `contact`и их перевёрнутые комбинации + +### Обновляем идентификаторы групп + +[src/update_group_ids.sql](https://gvwilson.github.io/sql-tutorial/src/update_group_ids.sql) + +``` + SELECT left.name AS left_name, + left.ident AS left_ident, + right.name AS right_name, + right.ident AS right_ident, + min(left.ident, right.ident) AS new_ident + FROM ( + person AS +LEFT JOIN bi_contact ON left.name = bi_contact.left + ) + JOIN person AS RIGHT ON bi_contact.right = right.name; +``` + +[out/update_group_ids.out](https://gvwilson.github.io/sql-tutorial/out/update_group_ids.out) + +``` +| left_name | left_ident | right_name | right_ident | new_ident | +|-----------------------|------------|-----------------------|-------------|-----------| +| Juana Baeza | 1 | Micaela Laboy | 4 | 1 | +| Juana Baeza | 1 | Verónica Altamirano | 5 | 1 | +| Agustín Rodríquez | 2 | Ariadna Caraballo | 3 | 2 | +| Agustín Rodríquez | 2 | Verónica Altamirano | 5 | 2 | +| Ariadna Caraballo | 3 | Agustín Rodríquez | 2 | 2 | +| Micaela Laboy | 4 | Juana Baeza | 1 | 1 | +| Verónica Altamirano | 5 | Agustín Rodríquez | 2 | 2 | +| Verónica Altamirano | 5 | Juana Baeza | 1 | 1 | +| Reina Rivero | 6 | Pilar Alarcón | 10 | 6 | +| Pilar Alarcón | 10 | Reina Rivero | 6 | 6 | +| Daniela Menéndez | 11 | Cristal Soliz | 13 | 11 | +| Daniela Menéndez | 11 | Marco Antonio Barrera | 12 | 11 | +| Marco Antonio Barrera | 12 | Cristal Soliz | 13 | 12 | +| Marco Antonio Barrera | 12 | Daniela Menéndez | 11 | 11 | +| Cristal Soliz | 13 | Daniela Menéndez | 11 | 11 | +| Cristal Soliz | 13 | Marco Antonio Barrera | 12 | 12 | +``` + +### Рекурсивно устанавливаем метки + +[src/recursive_labeling.sql](https://gvwilson.github.io/sql-tutorial/src/recursive_labeling.sql) + +``` + WITH recursive labeled AS ( + SELECT person.NAME AS NAME, + person.ident AS label + FROM person + UNION -- not 'union all' + SELECT person.NAME AS NAME, + labeled.label AS label + FROM ( + person + JOIN bi_contact ON person.NAME = bi_contact.LEFT + ) + JOIN labeled ON bi_contact.RIGHT = labeled.NAME + WHERE labeled.label < person.ident + ) + SELECT NAME, + min(label) AS group_id + FROM labeled + GROUP BY NAME + ORDER BY label, + NAME; +``` + +[out/recursive_labeling.out](https://gvwilson.github.io/sql-tutorial/out/recursive_labeling.out) + +``` +| name | group_id | +|-----------------------|----------| +| Agustín Rodríquez | 1 | +| Ariadna Caraballo | 1 | +| Juana Baeza | 1 | +| Micaela Laboy | 1 | +| Verónica Altamirano | 1 | +| Pilar Alarcón | 6 | +| Reina Rivero | 6 | +| Elias Merino | 7 | +| Minerva Guerrero | 8 | +| Mauro Balderas | 9 | +| Cristal Soliz | 11 | +| Daniela Menéndez | 11 | +| Marco Antonio Barrera | 11 | +| Bernardo Narváez | 14 | +| Óscar Barrios | 15 | +``` + +- определяем рекурсивное выражение `labeled` — оно начинается с базового запроса, который выбирает имена и идентификаторы из таблицы person, + - затем используется `UNION` для объединения с другим запросом, который соединяет таблицы `person`и `bi_contact`, используя столбцы `name` и `left` в `bi_contact` и имена и метки из `person` + - затем объединяет результаты этого соединения с ранее помеченными записями из `labeled` + - `WHERE` устанавливает условие, что метка предыдущей записи должна быть меньше, чем идентификатор текущей записи `person` +- выполняем основной запрос — выбираем имена из `labeled` и вычисляем минимальную метку для каждого имени как `group_id` с помощью функции `min()` +- результат группируем по именам и сортируем сначала по метке, а затем по имени +- attention: только не используйте тут `UNION ALL`, иначе возникнет бесконечная рекурсия) + +### Работа с SQL в Python при помощи sqlite3 + +[src/basic_python_query.py](https://gvwilson.github.io/sql-tutorial/src/basic_python_query.py) + +``` +import sqlite3 + +connection = sqlite3.connect("db/penguins.db") +cursor = connection.execute("SELECT count(*) FROM penguins;") +rows = cursor.fetchall() +print(rows) +``` + +[out/basic_python_query.out](https://gvwilson.github.io/sql-tutorial/out/basic_python_query.out) + +``` +[(344,)] +``` + +- импортируем библиотечку `sqlite3` (к слову, она является одной из стандартных библиотек) для работы с SQLite +- устанавливаем соединение с БД, расположенной в файле `"db/penguins.db"`, используя метод `sqlite3.connect()`. Если этого файл не существует, то он будет создан +- создаём объект `cursor` для выполнения SQL-запросов +- `select count(*) from penguins;` — подсчитываем количество всех записей в таблице `penguins` +- `fetchall()` — получаем результат выполнения запроса, сохраняем его в переменную `rows` + +### Инкрементная выборка + +[src/incremental_fetch.py](https://gvwilson.github.io/sql-tutorial/src/incremental_fetch.py) + +``` +import sqlite3 + +connection = sqlite3.connect("db/penguins.db") +cursor = connection.cursor() +cursor = cursor.execute("SELECT species, island FROM penguins LIMIT 5;") +while row := cursor.fetchone(): + print(row) +``` + +[out/incremental_fetch.out](https://gvwilson.github.io/sql-tutorial/out/incremental_fetch.out) + +``` +('Adelie', 'Torgersen') +('Adelie', 'Torgersen') +('Adelie', 'Torgersen') +('Adelie', 'Torgersen') +('Adelie', 'Torgersen') +``` + +- коннектимся к БД с помощью `sqlite3.connect("db/penguins.db")` +- `connection.cursor()` — создаём объект `cursor`, это указатель на результат выполнения запросов +- `select species, island from penguins limit 5;` — выбираем первые 5 записей из таблицы `penguins`, возвращая значения столбцов `species` и `island` +- пока переменная `row` из `cursor.fetchone()` непустая, печатаем её (мы сразу создаём переменную `row` и тут же используем её при помощи `:=` ) + +### Простые операции CREATE, INSERT, DELETE и другие с помощью sqlite3 + +[src/insert_delete.py](https://gvwilson.github.io/sql-tutorial/src/insert_delete.py) + +``` +import sqlite3 + +connection = sqlite3.connect(":memory:") +cursor = connection.cursor() +cursor.execute("CREATE TABLE example(num integer);") + +cursor.execute("INSERT INTO example VALUES (10),(20);") +print("after insertion", cursor.execute("SELECT * FROM example;").fetchall()) + +cursor.execute("DELETE FROM example WHERE num < 15;") +print("after deletion", cursor.execute("SELECT * FROM example;").fetchall()) +``` + +[out/insert_delete.out](https://gvwilson.github.io/sql-tutorial/out/insert_delete.out) + +``` +after insertion [(10,), (20,)] +after deletion [(20,)] +``` + +- `connection = sqlite3.connect(":memory:")` — создаём подключение к БД SQLite, созданной в оперативной памяти +- `cursor = connection.cursor()` — создаём объект курсора, который используется для выполнения операций в БД +- `cursor.execute("CREATE TABLE example(num integer);")` — создаём новую таблицу с именем `example` и одним столбцом `num` для хранения целых чисел +- `cursor.execute("INSERT INTO example VALUES (10),(20);")` — вставляем 2 строки в `example` с числами 10 и 20 в столбец `num` +- `print("after insertion", cursor.execute("SELECT * FROM example;").fetchall())` — выводим содержимое таблицы `example` после вставки строк; выполняем операцию `SELECT`, чтобы выбрать все строки из таблицы, используя метод `.fetchall()` для извлечения результатов запроса +- `cursor.execute("DELETE FROM example WHERE num < 15;")` — удаляем строки из таблицы `example`, в которых значение столбца `num` меньше 15 +- ну и в конце выводим содержимое таблицы `example` после удаления строк; также выполняем `SELECT`, чтобы выбрать все строки из таблицы, используя метод `.fetchall()` для извлечения результатов запроса + +### Интерполируем значения + +[src/interpolate.py](https://gvwilson.github.io/sql-tutorial/src/interpolate.py) + +``` +import sqlite3 + +connection = sqlite3.connect(":memory:") +cursor = connection.cursor() +cursor.execute("CREATE TABLE example(num integer);") + +cursor.executemany("insert into example values (?);", [(10,), (20,)]) +print("after insertion", cursor.execute("SELECT * FROM example;").fetchall()) +``` + +[out/interpolate.out](https://gvwilson.github.io/sql-tutorial/out/interpolate.out) + +``` +after insertion [(10,), (20,)] +``` + +") + +XKCD Exploits of a Mom + +- `connection = sqlite3.connect(":memory:")` — устанавливаем соединение с БД SQLite в оперативной памяти +- `cursor = connection.cursor()` — создаём объект курсора, который используется для выполнения операций БД +- `cursor.execute("create table example(num integer);")` — создаём таблицу `example` с одним столбцом `num` типа integer +- `cursor.executemany("insert into example values (?);", [(10,), (20,)])` — вставляем значения 10 и 20 в столбец `num` таблицы `example` с использованием параметризованного запроса + +### Выполнение полноценных SQL-запросов в Python + +[src/script_execution.py](https://gvwilson.github.io/sql-tutorial/src/script_execution.py) + +``` +import sqlite3 + +SETUP = """\ +DROP TABLE IF EXISTS example; +CREATE TABLE example(num integer); +INSERT INTO example +VALUES (10), + (20); +""" + +connection = sqlite3.connect(":memory:") +cursor = connection.cursor() +cursor.executescript(SETUP) +print("after insertion", cursor.execute("SELECT * FROM example;").fetchall()) +``` + +[out/script_execution.out](https://gvwilson.github.io/sql-tutorial/out/script_execution.out) + +``` +after insertion [(10,), (20,)] +``` + +- удаляем таблицу `example`, если она существует +- создаём таблицу `example` с одним столбцом `num` типа integer +- вставляем 2 записи в таблицу `example` с числами 10 и 20 +- выполняем `SETUP` с помощью метода курсора `executescript()`, который создает новую таблицу и вставляет данные +- выводим `after insertion` для обозначения того, что последующий запрос к базе данных будет относиться к состоянию после вставки данных +- выполняем запрос к БД для выбора всех записей из таблицы `example` с помощью метода `execute()` и `fetchall()` для извлечения результатов + +### Исключения SQLite в Python + +[src/exceptions.py](https://gvwilson.github.io/sql-tutorial/src/exceptions.py) + +``` +import sqlite3 + +SETUP = """\ +CREATE TABLE example(num integer check(num > 0)); + +INSERT INTO example +VALUES (10); + +INSERT INTO example +VALUES (-1); + +INSERT INTO example +VALUES (20); +""" + +connection = sqlite3.connect(":memory:") +cursor = connection.cursor() +try: + cursor.executescript(SETUP) +except sqlite3.Error as exc: + print(f"SQLite exception: {exc}") +print("after execution", cursor.execute("SELECT * FROM example;").fetchall()) +``` + +[out/exceptions.out](https://gvwilson.github.io/sql-tutorial/out/exceptions.out) + +``` +SQLite exception: CHECK constraint failed: num > 0 +after execution [(10,)] +``` + +- устанавливаем соединение с БД в оперативной памяти с помощью `sqlite3.connect(":memory:")` +- создаём курсор для выполнения операци +- создаём таблицу `example` и вставляем в нее 3 значения с помощью `executescript()` +- в блоке `try-except` обрабатывается исключение `sqlite3.Error`, если произойдет какая-либо ошибка при выполнении запросов +- выводим содержимое таблицы `example` после выполнения запросов с помощью метода `fetchall()` + +### Python и SQLite, ещё некоторые возможности + +[src/embedded_python.py](https://gvwilson.github.io/sql-tutorial/src/embedded_python.py) + +``` +import sqlite3 + +SETUP = """\ +CREATE TABLE example(num integer); +INSERT INTO example +VALUES (-10), + (10), + (20), + (30); +""" + +def clip(value): + if value < 0: + return 0 + if value > 20: + return 20 + return value + +connection = sqlite3.connect(":memory:") +connection.create_function("clip", 1, clip) +cursor = connection.cursor() +cursor.executescript(SETUP) +for row in cursor.execute("SELECT num, clip(num) FROM example;").fetchall(): + print(row) +``` + +[out/embedded_python.out](https://gvwilson.github.io/sql-tutorial/out/embedded_python.out) + +``` +(-10, 0) +(10, 10) +(20, 20) +(30, 20) +``` + +- создаём БД SQLite в оперативной памяти, создаём табличку `example`, заполняем её таблицу значениями (-10, 10, 20, 30) +- затем определяем функцию `clip`, которая принимает один аргумент и возвращает этот аргумент, если он находится между 0 и 20, или возвращает 0, если аргумент меньше 0, или возвращает 20, если аргумент больше 20 +- выбираем значения из столбца `num` таблицы `example` и применяет функцию `clip` к каждому значению + +### Работа с датой и временем + +[src/dates_times.py](https://gvwilson.github.io/sql-tutorial/src/dates_times.py) + +``` +from datetime import date +import sqlite3 + +# Convert date to ISO-formatted string when writing to database +def _adapt_date_iso(val): + return val.isoformat() + +sqlite3.register_adapter(date, _adapt_date_iso) + +# Convert ISO-formatted string to date when reading from database +def _convert_date(val): + return date.fromisoformat(val.decode()) + +sqlite3.register_converter("date", _convert_date) + +SETUP = """\ +CREATE TABLE events(happened date NOT NULL, + description text NOT NULL); +""" + +connection = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES) +cursor = connection.cursor() +cursor.execute(SETUP) + +cursor.executemany( + "insert into events values (?, ?);", + [(date(2024, 1, 10), "started tutorial"), (date(2024, 1, 29), "finished tutorial")], +) + +for row in cursor.execute("SELECT * FROM EVENTS;").fetchall(): + print(row) +``` + +[out/dates_times.out](https://gvwilson.github.io/sql-tutorial/out/dates_times.out) + +``` +(datetime.date(2024, 1, 10), 'started tutorial') +(datetime.date(2024, 1, 29), 'finished tutorial') +``` + +- определяем функцию `_adapt_date_iso(val)` — она принимает дату и возвращает ее строковое представление в формате `ISO` +- определяем функцию `_convert_date(val)` — она принимает строку в формате `ISO` и возвращает объект типа `date` +- затем эти функции регистрируются в SQLite, чтобы обеспечить корректное преобразование данного типа данных при записи и чтении из базы данных +- после этого создается строка `SETUP`, которая содержит SQL-команду для создания таблицы `events` с двумя столбцами: `happened` типа `date` и `description` типа `text` +- с помощью `cursor.executemany` в таблицу `events` вставляются 2 записи с использованием значений типа `date` и строк +- с помощью `select * from events` и `cursor.execute` извлекаем значения всех строк из таблицы `events` + +### SQL в Jupyter Notebooks + +``` +pip install jupysql +``` + +``` +%load_ext sql +``` + +``` +%sql sqlite:///data/penguins.db +``` + +``` +Connecting to 'sqlite:///data/penguins.db' +``` + +- Подключение к БД: + - `sqlite://` — протокол с 2 слэшами в конце + - `/data/penguins.db` — 1 слэш спереди, это путь к локальной БД +- 1 знак процента `%sql` — для выполнения однострочных SQL-запросов +- 2 знака процента `%%sql` показывает, что вся ячейка будет восприниматься как один SQL-запрос + +``` +%%sql +SELECT species, + count(*) AS num +FROM penguins +GROUP BY species; +``` + +``` +Running query in 'sqlite:///data/penguins.db' +``` + +|species|num| +|---|---| +|Adelie|152| +|Chinstrap|68| +|Gentoo|124| + +### Pandas и SQL + +[src/install_pandas.sh](https://gvwilson.github.io/sql-tutorial/src/install_pandas.sh) + +``` +pip install pandas +``` + +[src/select_pandas.py](https://gvwilson.github.io/sql-tutorial/src/select_pandas.py) + +``` +import pandas as pd +import sqlite3 + +connection = sqlite3.connect("db/penguins.db") +query = "SELECT species, count(*) AS num FROM penguins GROUP BY species;" +df = pd.read_sql(query, connection) +print(df) +``` + +[out/select_pandas.out](https://gvwilson.github.io/sql-tutorial/out/select_pandas.out) + +``` +species num +0 Adelie 152 +1 Chinstrap 68 +2 Gentoo 124 +``` + +- `select species, count(*) as num from penguins group by species;` — извлекаем информацию о количестве пингвинов каждого вида из `penguins` и группируем результаты по видам +- выполняем запрос к БД с использованием метода `read_sql` библиотеки `pandas`, который читает результаты запроса и преобразует их в объект `DataFrame` (df) + +### Polars и SQL + +[src/install_polars.sh](https://gvwilson.github.io/sql-tutorial/src/install_polars.sh) + +``` +pip install polars pyarrow adbc-driver-sqlite +``` + +[src/select_polars.py](https://gvwilson.github.io/sql-tutorial/src/select_polars.py) + +``` +import polars as pl + +query = "SELECT species, count(*) AS num FROM penguins GROUP BY species;" +uri = "sqlite:///db/penguins.db" +df = pl.read_database_uri(query, uri, engine="adbc") +print(df) +``` + +[out/select_polars.out](https://gvwilson.github.io/sql-tutorial/out/select_polars.out) + +``` +shape: (3, 2) +┌───────────┬─────┐ +│ species ┆ num │ +│ --- ┆ --- │ +│ str ┆ i64 │ +╞═══════════╪═════╡ +│ Adelie ┆ 152 │ +│ Chinstrap ┆ 68 │ +│ Gentoo ┆ 124 │ +└───────────┴─────┘ +``` + +- импортирует библиотеку Polars – она похожа на pandas, но с фокусом на параллельную обработку данных +- выбираем столбец `species` и вычисляем количество записей для каждого вида пингвинов из таблицы `penguins`; результат группируем по столбцу `species` +- устанавливаем строку подключения к базе данных SQLite в переменной `uri` +- используем `pl.read_database_uri` для выполнения SQL-запроса `query` к БД, указанной в `uri`, используя движок `adbc` +- выводим результат выполнения запроса в виде таблицы данных + +### ORM + +[src/orm.py](https://gvwilson.github.io/sql-tutorial/src/orm.py) + +``` +from sqlmodel import Field, Session, SQLModel, create_engine, select + +class Department(SQLModel, table=True): + ident: str = Field(default=None, primary_key=True) + name: str + building: str + +engine = create_engine("sqlite:///db/assays.db") +with Session(engine) as session: + statement = select(Department) + for result in session.exec(statement).all(): + print(result) +``` + +[out/orm.out](https://gvwilson.github.io/sql-tutorial/out/orm.out) + +``` +building='Chesson' name='Genetics' ident='gen' +building='Fashet Extension' name='Histology' ident='hist' +building='Chesson' name='Molecular Biology' ident='mb' +building='TGVH' name='Endocrinology' ident='end' +``` + +- создаём класс `Department`, который представляет модель данных для отделов; каждый атрибут класса соответствует столбцу в таблице БД +- создаём объект `engine`, который представляет собой подключение к SQLite БД, где `assays.db` – это имя файла БД +- создаём `Session` для взаимодействия с базой данных через созданный `engine` +- формируем SQL-запрос с помощью `select(Department)`, который выбирает все данные из таблицы, представленной моделью `Department` +- выполняем запрос к БД через `session.exec(statement).all()`, который возвращает все строки, удовлетворяющие условию запроса + +### Продолжаем работать с ORM + +[src/orm_relation.py](https://gvwilson.github.io/sql-tutorial/src/orm_relation.py) + +``` +class Staff(SQLModel, table=True): + ident: str = Field(default=None, primary_key=True) + personal: str + family: str + dept: Optional[str] = Field(default=None, foreign_key="department.ident") + age: int + +engine = create_engine("sqlite:///db/assays.db") +SQLModel.metadata.create_all(engine) +with Session(engine) as session: + statement = select(Department, Staff).where(Staff.dept == Department.ident) + for dept, staff in session.exec(statement): + print(f"{dept.name}: {staff.personal} {staff.family}") +``` + +[out/orm_relation.out](https://gvwilson.github.io/sql-tutorial/out/orm_relation.out) + +``` +Histology: Divit Dhaliwal +Molecular Biology: Indrans Sridhar +Molecular Biology: Pranay Khanna +Histology: Vedika Rout +Genetics: Abram Chokshi +Histology: Romil Kapoor +Molecular Biology: Ishaan Ramaswamy +Genetics: Nitya Lal +``` + +- объявляем класс `Staff`; он использует SQLModel, что позволяет использовать этот класс как схему для создания таблицы в БД. Указание `table=True` в качестве аргумента класса говорит SQLModel о том, что данный класс должен отображаться в базу данных как таблица. У `Staff` есть несколько атрибутов : + - `ident` – строковое поле, которое будет использоваться в качестве первичного ключа в базе данных. Оно имеет значение по умолчанию `None` и задается как первичный ключ (`primary_key=True`) + - `personal` – строковое поле + - `family` – строковое поле + - `dept` – опциональное строковое поле; имеет значение по умолчанию `None` и устанавливается как внешний ключ (`foreign_key="department.ident"`) + - `age` – целочисленное поле +- после определения `Staff`, создается экземпляр движка для работы с БД SQLite с помощью вызова функции `create_engine` из библиотеки SQLAlchemy +- затем вызываем метод `create_all` у метаданных SQLModel, что приводит к созданию всех таблиц, определенных в виде классов с помощью SQLModel, на основе ранее созданного движка базы данных +- далее устанавливаем сессия БД с использованием созданного ранее движка +- формируется SQL-запрос, который выбирает данные из таблиц `Department` и `Staff`, объединяя их по условию, что поле `Staff.dept` равно полю `Department.ident` +- выполняем этот запрос в сессии БД, и для каждой строки результата выводится название отдела и персональные данные сотрудника
\ No newline at end of file @@ -0,0 +1,479 @@ +### Перемещение по экрану ### + +`H ( gg )` - в начало экрана +`M` - в середину экрана +`L ( G )` - в конец экрана +`(` - к предыдущему предложению +`)` - к следующему предложению +`{` - к предыдущему параграфу +`}` - к следующему параграфу + +### Перемещение по словам в строк ### + +В командах `b, e, B, E` - переход зависит от положения курсора в слове. + +`w` - в начало следующего слова +`W` - в начало следующего слова после пробела +`b` - в начало текущего/предыдущего слова (beginning) +`B` - в начало текущего/предыдущего слова перед пробелом +`e` - в конец текущего/следующего слова (end) +`E` - в конец текущего/следующего слова после пробела + +### Перемещение по строкe ### + +`0`, `Home` - к началу строки +`$`, `End` - к концу строки +`_`, `^` - к первому не пустому символу в строке +`g_` - к последнему не пустому символу в строке +`-` - к первому не пустому символу в строке сверху +`+` - к первому не пустому символу в строке снизу +`gg` - к первой строке +`G` - к последней строке +`ngg` - к строке под номером n (например: 45gg) +`nG` - к строке под номером n (аналог ngg) + +### Перемещение, по символам в строке ### + +`%` - к парной скобке вперед по тексту строки `()` `{}` `[]` +`f` и `X` - к следующему символу `X` в строке (`X` - любой символ) +`F` и `X` - к следующему символу `X` в строке (обратное направление) +`t` и `X` - перед символом `X` в строке (`till`) +`T` и `X` - перед символом `X` в строке (обратное направление) (`till`) +`;` - повторить переход в прямом направлении +`,` - повторить переход в обратном направлении + +### Поиск в документе ### + +- `/Выражение` - поиск текста вперед по документу от курсора +- `?Выражение` - поиск текста назад по документу от курсора +`\c` перед `Выражением` - совершит поиск без учета регистра + +В режиме поиска искомое слово будет подсвечено. +Отключить подсветку можно через косоль: +``` +:nohlsearch +``` +или поискать несуществующую ерунду ( `/asdfasd`) +`,` и `пробел` - Отключить подсветку поискового слова + +## Ввод текста ### +`insert mode` - режим редактирования + +`a` - добавить текст после курсора +`i` - добавить текст до курсора +`A` - добавить текст в конце строки +`I` - добавить текст в начале строки +`O` - добавить строку сверху курсора +`o` - добавить строку снизу курсора + +Все эти команды переключают vim в режим редактирования. + +`Esc` - _выход_ из режима редактирования + +## Выделение ### + +`visual mode` - режим выделения текста + +`v` - посимвольное выделение текста (visual) +`V` - построчное выделение текста `v + hjkl` - выделение текста `SHIFT + v` - выделить строку `CTR + v` - выделение прямоугольника `Esc` - _выход_ из режима выделения + +### Продвинутое выделение ### + +`vip` - выделить параграф +`viw` - выделить слово +`Shift+v` или `0v$` - выделить строку +`^v$` - выделить строку, начиная с первого непробельного символа +`vi(` - выделить всё между ближайшими круглыми скобками (аналогично `vi\[` и `vi\{` для квадратных и фигурных скобок) +`va(` - выделить всё между ближайшими круглыми скобками, включая сами скобки +`v2j` - выделить на две строки вниз +`dip` - вырезать параграф + +### Копирование и вставка ### +`y`[направление] - копировать символ под курсором по направлению (h, j, k, l, $, 0 и т.п.) +`yy` - копировать строку +`"*y` - копировать в системный буфер обмена для вставки в другое приложение (например браузер) +`p` - вставить после курсора (paste) +`P` - вставить до курсора +`gp` - вставить после курсора и переместить курсор на один символ вправо +`gP` - вставить до курсора и переместить курсор на один символ влево + +### Удаление (вырезка) текста ### +`x`, `DEL` - удалить (вырезать) символ под курсором +`X` - удалить (вырезать) символ до курсора (аналог backspace) +`d`[направление] - удалить (вырезать) символы по направлению (h, j, k, l, $, 0 и т.п.) +`dd` - удалить (вырезать) строку +`D` - удалить (вырезать) символы от курсора до конца строки + +### Изменение и замена текста ### + +`J` - объединить строку с нижней (join) +`r`[символ] - заменить символ под курсором (replace) +`R` - начать ввод с заменой текста (аналог кнопки insert) +`s` - заменить символ и начать ввод (substitute) +`S` - заменить строку и начать ввод +`c`[направление] - изменить символ по направлению (h, l, e, b и т.п.) +`C` - изменить строку от курсора до конца (change) +`~` - сменить регистр символа под курсором +`gu` - преобразовать в нижний регистр (для изменения - сместите курсор) +`gU` - преобразовать в верхний регистр (для изменения -сместите курсор) + +### Отмена действий ### + +`u` - отменить действие (undo) `:u\[ndo\]` +`U` - отменить все изменения в строке +`Ctrl+R` - вернуть отменённое действие `:red\[o\]` +`.` - повторить последнее изменение + +### Нумерация строк ### +`:set number` включить нумерацию строк +`:set nonumber` отключить нумерацию строк + +### Радактирование нескольких файлов ### + +Я считаю, что наиболее удобный способ редактирования нескольких файлов - использование вкладок. +Вы можете открыть несколько файлов на отдельных вкладках через командную строку, например, так: + +``` +vim -p file1.txt file2.txt +``` + +Или, если у вас уже открыт vim, вы можете открыть новый файл на новой вкладке, например: + +``` +:tabe file2.txt +``` + +Открыв вкладки, используйте `gt` для просмотра следующей вкладки и `gT` просмотра предыдущей вкладки. + +Вы также можете перейти к первой вкладке с помощью `1gt`, второй вкладке с помощью `2gt` и т. д. + +Вы можете закрыть вкладки, используя `:tabc` + +Наконец, вы можете переместить текущую вкладку в ту `n`-позицию, `:ntabm` где `n` любое число больше или равно 0. + +### Файл настройки: ~/.vimrc ### +```sh +# Можно редактировать файлы через сеть, например +:e <scp|ftp|ftps>://user@host/path/to/the/file.txt +:Ex или :e ./ - файловый менеджер + +#== Основы == +hjkl перемещение в разные стороны +i режим вставки +I добавление в начало строки +a режим добавления +A добавление в конец строки +o добавить строку сразу за текущей +O добавить строку перед текущей +R писать поверх имеющегося текста +u, :u[ndo] отмена предыдущего действия (undo) +CTR-R, :red[o] отмена отмены предыдущего действия (redo) +dd вырезать (удалить) строку +cc удалить и начать редактирование +yy копировать строку +p вставить из буфера обмена +<n>d удалить n+1 строку +<n>y скопировать n+1 строку +ESC перейти в режим просмотра +DEL удалить следующий символ +:<n> перейти на строку #n +% перейти к парной скобке +:e **/filename.c редактировать файл (с поиском по имени) +:w [fname] записать изменения +:wa сохранить изменения во всех файлах +:q выйти из редактора +:q! выйти из редактора, не сохраняя изменения +:color <name> выбор цветовой схемы. цветвые схемы: + /usr/local/share/vim/vim72/colors/*.vim +:pwd текущий каталог +:cd [path] перейти в другой каталог +:!команда выполнить команду - man, git, и так далее + стрелочками веерх и вниз можно автодополнять + команды и искать по истории +CTR+p или CTR+n автоматическое дополнение текста + (в режиме редактирования) +CTR+r,=,<expr> вставить выражение, например 5*2 - 3 + (в режиме редактирования) +CTR+u, CTR+d Page Up / Page Down +CTR+y, CTR+e Перемотка вверх/вниз без движения курсора + +#== Подсветка синтаксиса == +:syntax on включить подсветку +:syntax off выключить подсветку (по умолчанию) + +#== Перенос строк == +:set wrap разрешить word wrap (по умолчанию) +:set nowrap запретить word wrap + +#== Печать == +:ha[rdcopy] распечатать документ +:set printoptions=duplex:off отключить двустороннюю печать + +#== Сворачивание == +zc свернуть блок +zo развернуть блок +zM закрыть все блоки +zR открыть все блоки +za инвертирование +zf см :set foldmethod=manual +:set foldenable включить свoрачивание +:set foldmethod=syntax сворачивание на основе синтаксиса +:set foldmethod=indent сворачивание на основе отступов +:set foldmethod=manual выделяем участок с помощью v и говорим zf +:set foldmethod=marker сворачивание на основе маркеров в тексте +:set foldmarker=bigin,end задаем маркеры начала и конца блока + +#== Маркеры == +ma установить локальный маркер a +mB установить глобальный маркер B +`c перейти к локальному маркеру c +`0 вернуться на позицию, на которой закончили + работу при закрытии vim +:marks просмотр маркеров +set viminfo='1000,f1' маркеры пишутся в ~/.viminfo, восстанавливаясь + при следующем запуске vim. маркер хранит + последнюю позицию курсора в файле +#== Сессии == +mksession file.session сохранить текущую сессию +source file.session восстановить ранее сохраненную сессию + +#== Макросы == +qa записать макрос с именем a +q в режиме записи макроса: закончить запись +@a выполнить макрос с именем a +@@ повторить последний макрос + +#== Регистры == +ayy скопировать строку в регистр a +bdd вырезать строку и поместить в регистр b +С2d вырезать три строки и дописать в конец + регистра C +:reg [name1][name2][...] просмотреть содержимое регистров + +#== Выделение == +v + hjkl выделение текста +SHIFT + v выделить строку +CTR + v выделение прямоугольника +p вставить +y копировать +d удалить +gu к нижнему регистру +gU к верхнему регистру + +#== Отступы == +[#]> сдвинуть выделенное вправо +[#]< сдвинуть выделенное влево +[#]>> сдвинуть строку вправо +[#]<< сдвинуть строку влево +set tabstop=# для табуляции используется # пробелов +set shiftwidth=# в командах отступа используется # пробелов +set [no]expandtab заменять ли табуляцию на соответствующее + число пробелов + +#== Поиск и замена в файле == +/Выражение поиск выражения в файле +\cВыражение поиск без учета регистра +n следующее совпадение +N предыдущее совпадение +:%s/foo/bar/gi замена строк, см http://eax.me/regular-expr/ + +#== Поиск по всему проекту == +:vimgrep /EXPR/ **/*.c поиск по регулярному выражению +:copen показать все найденные места +:close скрыть все найденные места +:cn переход к следующему результату +:cp переход к предыдущему результату + +#== Нумерация строк == +:set number включить нумерацию строк +:set nonumber отключить нумерацию строк + +#== Работа с вкладками (a.k.a табами) == +:tabnew [fname] создать таб +:tabs вывести список табов +:tabn следующий таб +:tabp предыдущий таб +<n>gt перейти на таб #n +gt следующий таб +gT предыдущий таб +:tabm +1 переместить таб вперед на одну позицию +:tabm -1 переместить таб назад на одну позицию +:tabm 2 переместить таб на заданную позицию + (нумерация начинается с нуля) + +#== Работа с окнами == +:split горизонтальное разбиение +:vsplit вертикальное разбиение +Ctr+W, затем + с закрыть окно + +- изменение высоты текущего окна + <> изменение ширины текущего окна + = установить равный размер окон + hjkl или стрелочки перемещение между окнами + +#== Проверка орфографии == + mkdir -p ~/.vim/spell + cd ~/.vim/spell + wget http://ftp.vim.org/vim/runtime/spell/ru.koi8-r.sug + wget http://ftp.vim.org/vim/runtime/spell/ru.koi8-r.spl + wget http://ftp.vim.org/vim/runtime/spell/en.ascii.sug + wget http://ftp.vim.org/vim/runtime/spell/en.ascii.spl + +:set spell spelllang=ru,en включить проверку орфографии +:set nospell выключить проверку орфографии +]s следующее слово с ошибкой +[s предыдущее слово с ошибкой +z= замена слова на альтернативу из списка +zg good word +zw wrong word +zG ignore word + +#== Работа с кодировкой == +e ++enc=<имя кодировки> Редактирование файла в ??? кодировке +w ++enc=<имя кодировки> Сохранить файл в новой кодировке +set fileencodings=utf-8,koi8-r Список автоматически определяемых + кодировок в порядке убывания + приоритета + +#== Другое == +:set [no]wildmenu При авто-дополнении в командной строке над + ней выводятся возможные варианты +:set list Отображать табуляцию и переводы строк +q: История команд +. Повторение последней команды +#======================================================= + +a - insert после курсора +I - insert в начало текущей строки +o - создает строку ниже и insert +O - создает строку выше и insert + +gg - перейти в начало файла +1G - в начало +42G - на 42 строку +G - перейти на последний абзац + +0 - первый символ предложения +$ - последний символ предложения + +w - перейти в начало следующего слова +W - +b - перейти в начало предыдущего слова +B - +e - перейти в конец текущего слова +E - перейти в конец текущего слова (пробел) +( - перейти к началу текущего предложения +) - перейти к началу следующего предложения +{ - перейти к началу текущего абзаца +} - перейти к началу следующего абзаца +\[\[ - перейти к началу текущего раздела +\]\] - перейти к началу следующего раздела + +### Поиск: +/шаблонEnter - поиск вперед +?шаблонEnter - поиск назад + +### Замена текста: +cw - до конца слова +c2b - на 2 слова назад +c$ - в конец строки +c0 - в начало строки + +### Строки: +cc - полная замена строки +C - замена от текущего положения до конца строки +J - объединить текущую строку и следующую + (. - можно повторить) + +### Символы: +r - замена символа и переход в командный режим +R - замена символов от курсора вперед +~ - смена регистра +x - удалить символ под курсором +X - удалить символ перед курсором +50i*ESC - вставка 50 \* +25A\*=ESC - вставка 50 \*= + +### Замена текста: +s - замена символа +4s - замена 4 символов +S - замена всей строки +|| замена на && - курсор на первый символ -> 2r& + +### Удаление текста: +dw - удалить слово и пробел с курсора и до конца слова +dd - удалить строку целиком +D - удалить строку от курсора до конца строки +de - удаляет только слово с курсора и до конца слова +d$ - удаляет от курсора до конца строки +d0 - удаляет от курсора до начала строки +xp - поменять местами буквы + +### Команды: +u - отмена последней операции +ctrl+R - отмена отмены +U - восстановить строку к первоначальному виду +p - вставить после курсора +P - вставить до курсора +y - копировать (yw, y$, y0) +yy - копировать строку целиком (4yy - 4 строки) +. - повтор + +### Прокрутка экрана: +^F - вперед +^B - назад +^D - вперед на половину +^U - назад на половину +^E - вперед на строку +^Y - назад на строку + +### Положение экрана: +z Enter и z+Enter - прокрутить чтобы текущая строка оказалась в верхней части экрана +z. - прокрутить чтобы текущая строка оказалась в центре экрана +z- - прокрутить чтобы текущая строка оказалась в нижней части экрана + +^L - перерисовка экрана +H - первый символ начала верхней строки экрана +M - первый символ начала средней строки экрана +L - первый символ начала нижней строки экрана +nH - +nL - + + +f - поиск символа +r - замена символа под курсором +R - замена до нажатия ESC +/ - поиск слова вниз документа от текущего положения курсора +? - поиск слова вверх документа от текущего положения курсора +n - следующий в поиске +control + g - имя файла и в какой строке курсор +shift + g - перейти в конец файла +11 + shift + G - вернет к строке когда нажали control + g + +m x - закладка на строку с именем x +x - вернуться на строку с закладкой х + +v - режим выделения + стрелки +V - выделение всей строки +d - удалить выделение +dw - удалить слово +d$ - удалить до конца строки +D - удалить текст от курсора до конца строки +x - вырезать в буфер +yy - копировать в буфер ? +p - вставить из буфера справа от курсора +P - вставить из буфера слева от курсора +dw - удалить слово +dd - удалить строку +cc - удалить и начать редактирование +u - отменить действие (назад) +U - отменить во всей строке +2dd - удалить 2 строки + +cw - заменить слово +Cw - заменить текст до конца строки + +q1 - запись макроса (закончить запись = q) +@1 - выполнить макрос 1 +``` + diff --git a/VSCode.md b/VSCode.md new file mode 100644 index 0000000..e856eec --- /dev/null +++ b/VSCode.md @@ -0,0 +1,151 @@ +==_Extensions:_== + +**autoDocstring +autopep8 +C/C++ +C/C++ Extension Pack +C/C++ Themes +Code Runner +Material Icon Theme +PlatformiO IDE +Pylance +Python +Python Debugger +Russian Language Pack for Visual Studio +Vim** + +================================ + +## Table of Contents + +- [VSCode Keyboard Shortcuts for Developers](#vscode-keyboard-shortcuts-for-developers) + - [General Shortcuts](#general-shortcuts) + - [Basic Editing](#basic-editing) + - [Rich Languages Editing](#rich-languages-editing) + - [Multi-cursor and Selection](#multi-cursor-and-selection) + - [Display](#display) + - [Search and Replace](#search-and-replace) + - [Navigation](#navigation) + - [Editor Management](#editor-management) + - [File Management](#file-management) + - [Debug](#debug) + - [Integrated Terminal](#integrated-terminal) + +# VSCode Keyboard Shortcuts for Developers + +## General Shortcuts + +| Command | Description | +| ------- | ----------- | +| `Ctrl + Shift + P` | Show Command Palette | +| `Ctrl + Shift + X` | Show Extensions | + +## Basic Editing + +| Command | Description | +| ------- | ----------- | +| `Ctrl + A` | Select All | +| `Ctrl + C` | Copy line | +| `Ctrl + D` | Duplicate line | +| `Ctrl + L` | Select current line | +| `Ctrl + X` | Cut line | +| `Ctrl + U` | Undo last cursor operation | +| `Ctrl + V` | Paste | +| `Ctrl + Y` | Redo Changes | +| `Ctrl + Z` | Undo Changes | +| `Alt + Down` | Move Line Down | +| `Alt + Up` | Move Line Up| + +## Rich Languages Editing + +| Command | Description | +| ------- | ----------- | +| `F2` | Rename Symbol | +| `Shift + F12` | Go to References | +| `F12` | Go to Definition | +| `Ctrl + Space` | Trigger Suggest | +| `Ctrl + .` | Quick Fix | + +## Multi-cursor and Selection + +| Command | Description | +| ------- | ----------- | +| `Alt + Click` | Insert cursor | +| `Shift + Alt + UpArrow` | Copy line up | +| `Shift + Alt + DownArrow` | Copy line down | +| `Shift + Alt + Click` | Select text in blocks | +| `Ctrl + Alt + UpArrow` | Add cursor above | +| `Ctrl + Alt + DownArrow` | Add cursor below | +| `Ctrl + Shift + K` | Delete line | +| `Ctrl + Shift + L` | Select all occurrences of current selection | + +## Display + +| Command | Description | +| ------- | ----------- | +| `Ctrl + Shift + F` | Show Search | + +## Search and Replace + +| Command | Description | +| ------- | ----------- | +| `Ctrl + F` | Find | +| `Ctrl + H` | Replace | +| `Ctrl + Shift + F` | Find in Files | +| `Ctrl + Shift + H` | Replace in Files | +| `Ctrl + Shift + Enter` | Replace All | + +## Navigation + +| Command | Description | +| ------- | ----------- | +| `Ctrl + P` | Go to File | +| `Ctrl + J` | Toggle panel (Opens/closes terminal) | +| `Ctrl + B` | Toggle primary sidebar | +| `Ctrl + Shift + E` | Go to explorer | +| `Ctrl + Shift + X` | Go to extensions | +| `Ctrl + Shift + D` | Go to Run and Debug | +| `Ctrl + Shift + G` | Go to Source Control | +| `Ctrl + Shift + H` | Go to Search | +| `Ctrl + Shift + J` | Go to Problems | +| `` Ctrl + ↑/↓ ``| Scroll up/down | +| `` Ctrl+ Home/End ``| Scroll top/bottom | + +## Editor Management + +| Command | Description | +| ------- | ----------- | +| `Ctrl + W` | Close Editor | +| `Ctrl + \` | Split Editor | +| `Ctrl + Shift + PgUp` | Move Editor Left | +| `Ctrl + Shift + PgDn` | Move Editor Right | + +## File Management + +| Command | Description | +| ------- | ----------- | +| `Ctrl + N` | New Text File | +| `Ctrl + O` | Open | +| `Ctrl + S` | Save | +| `Ctrl + Shift + S` | Save As | +| `Ctrl + W` | Close | +| `Ctrl + Shift + T` | Open Recently Closed Editor | + +## Debug + +| Command | Description | +| ------- | ----------- | +| `F9` | Toggle Breakpoint | +| `F5` | Start | +| `Ctrl + F5` | Start (without debugging) | +| `F11` | Step Into | + +## Integrated Terminal + +| Command | Description | +| ------- | ----------- | +| `` Ctrl + ` `` | Show integrated terminal | +| `` Ctrl + Shift + ` `` | Create new integrated terminal | + + + diff --git a/YANDEX_DISK.md b/YANDEX_DISK.md new file mode 100644 index 0000000..869591a --- /dev/null +++ b/YANDEX_DISK.md @@ -0,0 +1,65 @@ +### Подключение через webdav : + +```sh +sudo su +apt install -y davfs2 +mkdir /home/user/name_dir + +vim /etc/davfs2/secrets +# вставляем в конце: +https://webdav.yandex.ru YOUR_username YOUR_password +# YOUR_password - генерим на Яндексе в аккаунте -> безопасности -> пароли приложений. + +vim /etc/fstab + +https://webdav.yandex.ru /home/vlapa/300Gb/yaDisk davfs gid=vlapa,uid=vlapa,auto 0 0 + +# перегружаемся. + +df -h /home/vlapa/300Gb/ + +#================================ +# Сохраните перед внесением изменений +/etc/apt/sources.list.d/yandex-disk.list + +wget -O YANDEX-DISK-KEY.GPG http://repo.yandex.ru/yandex-disk/YANDEX-DISK-KEY.GPG +apt-key add YANDEX-DISK-KEY.GPG +echo "deb http://repo.yandex.ru/yandex-disk/deb/ stable main" >> /etc/apt/sources.list.d/yandex-disk.list + +apt-get update +apt-get install yandex-disk + +yandex-disk setup + +# Введите название каталога для хранения локальной копии Диска. Если вы оставите название пустым, в домашнем каталоге будет создана папка **Yandex.Disk** + +# 1. Введите название каталога для хранения локальной копии Диска. Если вы оставите название пустым, в домашнем каталоге будет создана папка Yandex.Disk. +# 2. Укажите, использовать ли прокси-сервер (y/n). +# 3. Укажите, запускать ли клиент при старте системы (y/n) + +# После того как мастер завершит работу, в каталоге ~/.config/yandex-disk будет создан файл конфигурации config.cfg. + +# Путь к файлу с данными авторизации + +auth="/home/user/.config/yandex-disk/passwd" + +# Каталог для хранения локальной копии Диска. + +dir="/home/user/myDisk" + +Не синхронизировать указанные каталоги. + +# exclude-dirs="exclude/dir1,exclude/dir2,path/to/another/exclude/dir" + +# Указать прокси-сервер. Примеры: + + +#proxy=https,127.0.0.1,80 +#proxy=https,127.0.0.1,80,login,password +#proxy=https,127.0.0.1,443 +#proxy=https,127.0.0.1,443 +#proxy=socks4,my.proxy.local,1080,login,password +#proxy=socks5,my.another.proxy.local,1081 +#proxy=auto +#proxy=no +``` diff --git a/_PROGRAMs/C++/C UART.md b/_PROGRAMs/C++/C UART.md new file mode 100644 index 0000000..f58dd7c --- /dev/null +++ b/_PROGRAMs/C++/C UART.md @@ -0,0 +1,165 @@ +# Работа с UART (Си) + +Рассмотрим передачу и приём сообщения по UART подробнее (на Си). + +Сконфигурировать UART на передачу и приём сообщения на Си также просто как и на ассемблере мультиклеточного процессора. +Кроме того на Си для работы с UART доступны полезные функции и макросы, такие как вывод значения типа int, вывод строки, +вывод заданного массива памяти данных. + +В приведённых ниже примерах кода используется библиотека для работы с UART, которую необходимо подключить: + +#include <uart.h> + +## а) Передача по UART + +### 1. Настраиваем UART на передачу + +Первым шагом задаём структуру (все элементы структуры можно посмотреть в файле uart.h). + +UART_InitTypeDef UART_InitStructure; + +Затем инициализируем нужные параметры (незаданные параметры по умолчанию инициализируются нулём). + + UART_InitStructure.BaudRate = 38400; //скорость обмена данными + UART_InitStructure.TypeParity = 0x00000000; //тип контроля четности + UART_InitStructure.Parity = 0x00000000; //разрешение контроля четности + UART_InitStructure.FlowControl = 0x00000000; //разрешение аппаратного контроля + UART_InitStructure.Mode = 0x00000002; //разрешение передачи + + +Инициализация для UART0: + +uart_init(UART0, &UART_InitStructure); + + +Для UART3: + +uart_init(UART3, &UART_InitStructure); + +### 2. Разрешаем альтернативные функции порта + +Для UART0: + +GPIOB->BPS = 0x00000F00; + +Для UART3: + +GPIOD->BPS = 0x00000F00; + +### 3. Проверка буфера передатчика на полноту + +Для UART0: + +while(UART_FIFO_TX_FULL(UART0) == 1); + +Для UART3: + +while(UART_FIFO_TX_FULL(UART3) == 1); + +### 4.Передача байта по UART + +Для UART0: + +UART_SEND_BYTE(0xAB, UART0); + +Для UART3: + +UART_SEND_BYTE(0xAB, UART3); + +В результате можно получить простой пример по отправке байта по UART со скоростью 38400 Кбит/с: + +Для UART0: + +#include <HDL51001_ccf.h> +#include <uart.h> + +void main() +{ + UART_InitTypeDef UART_InitStructure; + + UART_InitStructure.BaudRate = 38400; //скорость обмена данными + UART_InitStructure.TypeParity = 0x00000000; //тип контроля четности + UART_InitStructure.Parity = 0x00000000; //разрешение контроля четности + UART_InitStructure.FlowControl = 0x00000000; //разрешение аппаратного контроля + UART_InitStructure.Mode = 0x00000002; //разрешение передачи + + uart_init(UART0, &UART_InitStructure); + + GPIOB->BPS = 0x00000F00; + + while(UART_FIFO_TX_FULL(UART0) == 1); + UART_SEND_BYTE(0xAB, UART0); +} + +Для UART3 пример можно составить аналогично, только лишь заменив порт B на D и UART0 на UART3. + +Переданный байт данных можно получить с помощью программы - монитора COM-порта настроенного на данные с 1 стоп-битом, +скоростью передачи 38400 кбит/с, размером данных - 8 бит и отключённым контролем чётности. + +## б) Приём по UART + +Для приёма по UART необходимо: + +### 1. Выполнить пункты 1,2 под а) + +### 2. Проверить буфер приемника на наличие новых данных + +Для UART0: + +while(UART_NEW_DATA(UART0) == 0); + +Для UART3: + +while(UART_NEW_DATA(UART3) == 0); + +### 3. Прочитать данные + +Для UART0: + +rbyte = UART_GET_BYTE(UART0); + +Для UART3: + +rbyte = UART_GET_BYTE(UART3); + +Пример приёма байта данных составить довольно просто по аналогии с передачей байта. Следует заметить, что помимо анализа буфера приемника на +наличие новых данных, необходимо проверять буфер приёма на полноту. + +## API функции UART: + +Кроме полезных макросов имеются функции для вывода данных по UART. +Разберём подробнее каждую функцию, которая объявлена в uart.h + +`void uart_init(UART_TypeDef *UART, UART_InitTypeDef* UART_InitStruct) + +Функция _uart_init()_ выполняет инициализацию заданного номера UART. +UART - может принимать значения: UART0, UART1, UART2, UART3 +UART_InitStruct - структура, содержащая элементы конфигурации UART + +`void DM2UART(UART_TypeDef *UART, int start_address, int size_block) + +Функция _DM2UART ()_ выводит область памяти данных по UART младшим байтом вперёд. +UART - может принимать значения: UART0, UART1, UART2, UART3 +start_address - начальный адрес памяти данных +size_block - размер области данных + +`void uart_send_str(char *str, UART_TypeDef *UART) + +Функция _uart_send_str()_ выводит строку по UART. +UART - может принимать значения: UART0, UART1, UART2, UART3 +str - строка символов + +`void uart_send_int(int str, UART_TypeDef *UART) + +Функция _uart_send_int()_ выводит переменную типа int по UART. +UART - может принимать значения: UART0, UART1, UART2, UART3 +str - переменная типа int + +### Замечание: + +В версиях библиотеки до 01.04.13 необходимо перед вызовом функций DM2UART (), uart_send_int() +применять конструкцию: + +`while(UART_FIFO_TX_EMPTY(UART0) == 0); + +В версиях библиотеки после 01.04.13 функции могут использоваться самостоятельно.
\ No newline at end of file diff --git a/_PROGRAMs/C++/MQTT.md b/_PROGRAMs/C++/MQTT.md new file mode 100644 index 0000000..c24332e --- /dev/null +++ b/_PROGRAMs/C++/MQTT.md @@ -0,0 +1,29 @@ + +```C++ +bool reconnect() { + Serial.print("MQTT connect : "); + Serial.println(mqtt_server); + uint8_t countMQTT = 20; // кол-во попыток соединения + uint16_t countPause = 500; // пауза между попытками + while (!(client.connect(mqtt_client, mqtt_user, mqtt_pass)) && countMQTT--) { + Serial.print(countMQTT); + Serial.print('>'); + delay(countPause); + } + if (client.connected()) { + Serial.println("MQTT connected - OK !"); + String topic = "/"; + topic += mqtt_client2; + topic += inTopicTempRoom; + mqtt_subscribe(client, topic); + topic = "/"; + topic += mqtt_client2; + topic += inTopicReboot; + mqtt_subscribe(client, topic); + return true; + } else { + return false; + } +} +``` + diff --git a/_PROGRAMs/C++/TIME.md b/_PROGRAMs/C++/TIME.md new file mode 100644 index 0000000..9643b06 --- /dev/null +++ b/_PROGRAMs/C++/TIME.md @@ -0,0 +1,26 @@ + +```C++ +String timeCalc(uint32_t t) { + uint32_t timer = t / 1000; + uint32_t timeSec = (timer % 3600) % 60; + uint32_t timeMin = (timer % 3600) / 60; + uint32_t timeHour = timer / 3600 % 24; + uint32_t timeDay = timer / 60 / 60 / 24; + String s = ""; + s += timeDay; + s += ":"; + if (timeHour < 10) + s += "0"; + s += timeHour; + s += ":"; + if (timeMin < 10) + s += "0"; + s += timeMin; + s += ":"; + if (timeSec < 10) + s += "0"; + s += timeSec; + return s; +} +``` + diff --git a/_PROGRAMs/C++/WiFi.md b/_PROGRAMs/C++/WiFi.md new file mode 100644 index 0000000..fc47f88 --- /dev/null +++ b/_PROGRAMs/C++/WiFi.md @@ -0,0 +1,40 @@ + +```C++ +const char *ssid = "ssid"; +const char *pass = "passwd"; + +bool setupWiFi(const char *wifi_ssid, const char *wifi_pass) { + digitalWrite(LED_BUILTIN, LOW); // включаем светодиод + Serial.print("\n\nSetup WiFi: "); + Serial.println(ssid); + WiFi.mode(WIFI_STA); + uint8_t countWiFi = 20; // кол-во попыток соединения + uint16_t countPause = 500; // пауза между попытками + WiFi.begin(wifi_ssid, wifi_pass); + while ((WiFi.status() != WL_CONNECTED) && countWiFi--) { + Serial.print(countWiFi); + Serial.print('>'); + delay(countPause); + } + digitalWrite(LED_BUILTIN, HIGH); // выключаем светодиод + if (WiFi.status() == WL_CONNECTED) { + // индикация IP + Serial.print("\nWiFi connected - OK !\n"); + Serial.println(WiFi.localIP()); + // индикация силы сигнала + int16_t dBm = WiFi.RSSI(); + Serial.print("RSSI dBm = "); + Serial.println(dBm); + quality_RSSI = 2 * (dBm + 100); + if (quality_RSSI >= 100) + quality_RSSI = 100; + Serial.print("RSSI % = "); + Serial.println(quality_RSSI); + Serial.println(); + return true; + } else { + return false; + } +} +``` + diff --git a/_PROGRAMs/C++/snprintf.md b/_PROGRAMs/C++/snprintf.md new file mode 100644 index 0000000..376e26b --- /dev/null +++ b/_PROGRAMs/C++/snprintf.md @@ -0,0 +1,48 @@ +# snprintf в C/C++: разбираемся с примерами + +👋 Привет! В этой статье мы поговорим о функции `snprintf`. В отличие от функции [`sprintf`](https://codelessons.dev/ru/sprintf-in-c-cplusplus/), она принимает дополнительный аргумент: максимальная длинна C-строки, которая будет записана по адресу. Сначала мы посмотрим на сигнатуру этой функции, а потом напишем простую программу, чтобы продемонстрировать работу этой функции. В [конце статьи](https://codelessons.dev/ru/snprintf-in-c-cplusplus/#%D1%83%D0%BF%D1%80%D0%B0%D0%B6%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F) вы найдете упражнения для закрепления материала. + + + +## Как отформатировать строку используя snprintf + +Функция `snprintf` объявлена в заголовочном файле `<stdio.h>`. В C++ вы также можете использовать `<cstdio>`. + +`snprintf` форматирует строку как [`printf`](https://codelessons.dev/ru/printf-in-c-cplusplus/), но записывает результат работы по адресу, вместо того, чтобы вывести строку в стандартный вывод. Также эта функция принимает максимальное количество байтов, которые нужно записать по адресу. Это делает ее более безопасной альтернативой функции `sprintf`. + +Прототип этой функции выглядит следующим образом: + +```cpp +int snprintf(char* s, size_t n, const char* format, ...); +``` + +- Первым аргументом функция принимает ссылку на начало строки, куда будет записан результат ее работы. В конец строки будет обязательно записан символ ее окончания - `\0`. +- Вторым аргументом мы передаем максимальное количество байтов, которые будут записаны по адресу (первый аргумент). В максимальное количество байтов включается `\0`. +- Третьим аргументом передается строка форматирования. +- Далее передаются дополнительные аргументы. Их количество зависит от используемых [спецификаторов формата](https://codelessons.dev/ru/printf-in-c-cplusplus/#%D1%81%D0%BF%D0%B5%D1%86%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D1%80%D1%8B-%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B0) в строке форматирования (третий аргумент). +- Возвращает функция длину отформатированной строки (как и `printf`). На это число не влияет максимальное число записанных байтов `n` (второй аргумент). + +Теперь давайте посмотрим на простой пример использования этой функции: + +```c +#include <stdio.h> + +int main() { + int a = 1, b = 2; + char s[6]; + + int n = snprintf(s, 6, "%d + %d = %d", a, b, a + b); + + printf("s = %s, n = %d", s, n); + + return 0; +} +``` + +Вывод программы: + +```console +s = 1 + 2, n = 9 +``` + +Как вы можете видеть, функция обрезала строку. Только первые пять символов попали в `s` (только пять, потому что последний символ занимает `\0`).
\ No newline at end of file diff --git a/_PROGRAMs/C++/УСРЕДНЕНИЕ.md b/_PROGRAMs/C++/УСРЕДНЕНИЕ.md new file mode 100644 index 0000000..3204281 --- /dev/null +++ b/_PROGRAMs/C++/УСРЕДНЕНИЕ.md @@ -0,0 +1,67 @@ + +```C++ +################################################ +// бегущее среднее +float k = 0.1; // коэффициент фильтрации, 0.0-1.0 +float expRunningAverage(float newVal) { + static float filVal = 0; + filVal += (newVal - filVal) * k; + return filVal; +} + +################################################ +// медиана на 3 значения со своим буфером +float medianRoom(float newValRoom) { + static float bufRoom[3] = {0, 0, 0}; + if (!(bufRoom[0] + bufRoom[1] + bufRoom[2])) + bufRoom[0] = bufRoom[1] = bufRoom[2] = newValRoom; + static byte countRoom = 0; + bufRoom[countRoom] = newValRoom; + if (++countRoom >= 3) + countRoom = 0; + float dataRoom = (max(bufRoom[0], bufRoom[1]) == max(bufRoom[1], bufRoom[2])) + ? max(bufRoom[0], bufRoom[2]) + : max(bufRoom[1], min(bufRoom[0], bufRoom[2])); + // return expRunningAverage(data); + return dataRoom; +} + +################################################ + +# это не совсем средние это апериодичное звено или простейший фильтр НЧ , и он имеет зону не чувствительности, скользящие средние делается через массив, код для ПЫКов , ну думаю разберетесь. + +unsigned int ReadADC_AN0(void) +{ static unsigned char s_ucCount; + static unsigned int m_TempResalt[32]; + static unsigned int s_uiSymm; + + s_ucCount++; + s_ucCount&=0b00011111; + s_uiSymm -= m_TempResalt[s_ucCount]; + __delay_us(20); + ADCON0bits.GO_DONE=1; + while(ADCON0bits.GO_DONE) continue; + __delay_us(5); + m_TempResalt[s_ucCount] = ADRESH; + m_TempResalt[s_ucCount]<<=8; + m_TempResalt[s_ucCount]+= ADRESL; + s_uiSymm += m_TempResalt[s_ucCount]; + return s_uiSymm/32; +} + +################################################ +// AVR: Усредняем показания: +// revOld = старое значение +uint16_t filtr(uint16_t rev) +{ + static uint8_t a = pow(2, KOEFF) - 1; + (revOld > rev / KOEFF) ? revOld = (a * revOld + rev) >> KOEFF : revOld = rev; + return revOld; +} + +################################################ + + +################################################ +``` + diff --git a/_PROGRAMs/ESP8266-ESP32/EEPROM.md b/_PROGRAMs/ESP8266-ESP32/EEPROM.md new file mode 100644 index 0000000..a32093c --- /dev/null +++ b/_PROGRAMs/ESP8266-ESP32/EEPROM.md @@ -0,0 +1,33 @@ +## Список всех функций библиотеки EEPROM для ESP32, ESP8266 с подробными описаниями: + +**1. EEPROM.begin(size):** + +Инициализирует эмулированную EEPROM с указанным размером `size` (в байтах). Обычно вызывается в функции `setup()`. + +**2. EEPROM.end():** + +Завершает работу с эмулированной EEPROM и освобождает зарезервированные ресурсы. Необходимо вызывать, когда вы закончили работу с EEPROM. + +**3. EEPROM.write(address, value):** + +Записывает значение `value` (байт) в указанный адрес `address` в эмулированной EEPROM. + +**4. EEPROM.read(address):** + +Считывает байт данных из указанного адреса `address` в эмулированной EEPROM и возвращает это значение. + +**5. EEPROM.update(address, value):** + +Обновляет значение в указанном адресе `address` в эмулированной EEPROM. Если новое значение отличается от текущего, то происходит запись нового значения. + +**6. EEPROM.get(address, data):** + +Считывает данные из указанного адреса `address` в эмулированной EEPROM и сохраняет их в переменную `data`. Может использоваться для считывания структур или пользовательских типов данных. + +**7. EEPROM.put(address, data):** + +Записывает данные из переменной `data` в указанный адрес `address` в эмулированной EEPROM. Может использоваться для записи структур или пользовательских типов данных. + +**8. EEPROM.commit():** + +Сохраняет все изменения, внесенные в эмулированную EEPROM, во флэш-памяти. Это необходимо вызвать после операций записи, чтобы изменения вступили в силу.
\ No newline at end of file diff --git a/_PROGRAMs/ESP8266-ESP32/RTC память.md b/_PROGRAMs/ESP8266-ESP32/RTC память.md new file mode 100644 index 0000000..1ea035e --- /dev/null +++ b/_PROGRAMs/ESP8266-ESP32/RTC память.md @@ -0,0 +1,15 @@ +### Сохраняем данные в RTC-память + +При работе с платой ESP32 данные можно сохранять в RTC память (точнее, в ее RTC-модуль), она оснащена 8 Кб памяти SRAM, которую называют **«быстрой RTC-памятью»**. +Хранящиеся в ней данные не стираются во время глубокого сна, но стираются при нажатии на кнопку сброса под названием **«EN»**. + +Чтобы сохранить данные в RTC-память, перед объявлением переменной нужно добавить конструкцию: +```C++ +RTC_DATA_ATTR + +# В результате код сохранит переменную **«bootCount»** в RTC-память: + +RTC_DATA_ATTR int bootCount = 0; +``` + + diff --git a/__github учебник.md b/__github учебник.md new file mode 100644 index 0000000..ba36c33 --- /dev/null +++ b/__github учебник.md @@ -0,0 +1 @@ +https://github.com/amaargiru/pycore
\ No newline at end of file diff --git a/files/15d72c288938426db50c249effd75b07.png b/files/15d72c288938426db50c249effd75b07.png Binary files differnew file mode 100644 index 0000000..9b2ed6d --- /dev/null +++ b/files/15d72c288938426db50c249effd75b07.png diff --git a/files/1Gb_cab.png b/files/1Gb_cab.png Binary files differnew file mode 100644 index 0000000..ab1a340 --- /dev/null +++ b/files/1Gb_cab.png diff --git a/files/1fd1edd152259cb558abab7409faa120.png b/files/1fd1edd152259cb558abab7409faa120.png Binary files differnew file mode 100644 index 0000000..ed85155 --- /dev/null +++ b/files/1fd1edd152259cb558abab7409faa120.png diff --git a/files/2016-07-21 21Aygo-глушитель.jpg b/files/2016-07-21 21Aygo-глушитель.jpg Binary files differnew file mode 100644 index 0000000..a4cadae --- /dev/null +++ b/files/2016-07-21 21Aygo-глушитель.jpg diff --git a/files/2025-02-20 04.18.59.jpg b/files/2025-02-20 04.18.59.jpg Binary files differnew file mode 100644 index 0000000..049429e --- /dev/null +++ b/files/2025-02-20 04.18.59.jpg diff --git a/files/2025-06-20_22-34-47.png b/files/2025-06-20_22-34-47.png Binary files differnew file mode 100644 index 0000000..44bdade --- /dev/null +++ b/files/2025-06-20_22-34-47.png diff --git a/files/264fc3d5aab6c1a767f7659c207e1618.png b/files/264fc3d5aab6c1a767f7659c207e1618.png Binary files differnew file mode 100644 index 0000000..33b08db --- /dev/null +++ b/files/264fc3d5aab6c1a767f7659c207e1618.png diff --git a/files/2e66d41ed2450e40ffd3fa20ba1036d7.png b/files/2e66d41ed2450e40ffd3fa20ba1036d7.png Binary files differnew file mode 100644 index 0000000..dcd03c8 --- /dev/null +++ b/files/2e66d41ed2450e40ffd3fa20ba1036d7.png diff --git a/files/32de918111f7fed3e8183b4b9148286b.png b/files/32de918111f7fed3e8183b4b9148286b.png Binary files differnew file mode 100644 index 0000000..db48dbc --- /dev/null +++ b/files/32de918111f7fed3e8183b4b9148286b.png diff --git a/files/370b55d2c9ca15ef5153581f2af2b9b0.png b/files/370b55d2c9ca15ef5153581f2af2b9b0.png Binary files differnew file mode 100644 index 0000000..abfc078 --- /dev/null +++ b/files/370b55d2c9ca15ef5153581f2af2b9b0.png diff --git a/files/4a2b1a7f1e1bee9a26d7885b1d5e0f8c.png b/files/4a2b1a7f1e1bee9a26d7885b1d5e0f8c.png Binary files differnew file mode 100644 index 0000000..503f0fa --- /dev/null +++ b/files/4a2b1a7f1e1bee9a26d7885b1d5e0f8c.png diff --git a/files/6cda81091bcc9f99ad224bd5c5426f8e.png b/files/6cda81091bcc9f99ad224bd5c5426f8e.png Binary files differnew file mode 100644 index 0000000..6f4745e --- /dev/null +++ b/files/6cda81091bcc9f99ad224bd5c5426f8e.png diff --git a/files/73ce6d03c95dd19ac4263224cc1acac4.png b/files/73ce6d03c95dd19ac4263224cc1acac4.png Binary files differnew file mode 100644 index 0000000..b871de0 --- /dev/null +++ b/files/73ce6d03c95dd19ac4263224cc1acac4.png diff --git a/files/7c6829decd5925af5d5e0dcc27128438.png b/files/7c6829decd5925af5d5e0dcc27128438.png Binary files differnew file mode 100644 index 0000000..61e52e0 --- /dev/null +++ b/files/7c6829decd5925af5d5e0dcc27128438.png diff --git a/files/9132596e2802a7ecd3a47842f6475f60.png b/files/9132596e2802a7ecd3a47842f6475f60.png Binary files differnew file mode 100644 index 0000000..672295d --- /dev/null +++ b/files/9132596e2802a7ecd3a47842f6475f60.png diff --git a/files/Aygo-зад_полы_и_панель.jpg b/files/Aygo-зад_полы_и_панель.jpg Binary files differnew file mode 100644 index 0000000..c1a3289 --- /dev/null +++ b/files/Aygo-зад_полы_и_панель.jpg diff --git a/files/Aygo-задний_бампер.jpg b/files/Aygo-задний_бампер.jpg Binary files differnew file mode 100644 index 0000000..31434e5 --- /dev/null +++ b/files/Aygo-задний_бампер.jpg diff --git a/files/Aygo-задняя_подвеска.jpg b/files/Aygo-задняя_подвеска.jpg Binary files differnew file mode 100644 index 0000000..65b4bcd --- /dev/null +++ b/files/Aygo-задняя_подвеска.jpg diff --git a/files/Aygo-передн_подвеска.jpg b/files/Aygo-передн_подвеска.jpg Binary files differnew file mode 100644 index 0000000..cb78d6d --- /dev/null +++ b/files/Aygo-передн_подвеска.jpg diff --git a/files/Aygo-передний_бампер.jpg b/files/Aygo-передний_бампер.jpg Binary files differnew file mode 100644 index 0000000..41dc699 --- /dev/null +++ b/files/Aygo-передний_бампер.jpg diff --git a/files/Aygo-топл_бак_и_горловина.jpg b/files/Aygo-топл_бак_и_горловина.jpg Binary files differnew file mode 100644 index 0000000..b2ebd27 --- /dev/null +++ b/files/Aygo-топл_бак_и_горловина.jpg diff --git a/files/DB_metall.ods b/files/DB_metall.ods Binary files differnew file mode 100644 index 0000000..bc6621b --- /dev/null +++ b/files/DB_metall.ods diff --git a/files/Pasted image 20240312221215.png b/files/Pasted image 20240312221215.png Binary files differnew file mode 100644 index 0000000..714f40b --- /dev/null +++ b/files/Pasted image 20240312221215.png diff --git a/files/Pasted image 20250214130256.png b/files/Pasted image 20250214130256.png Binary files differnew file mode 100644 index 0000000..9447a00 --- /dev/null +++ b/files/Pasted image 20250214130256.png diff --git a/files/Pasted image 20250214130512.png b/files/Pasted image 20250214130512.png Binary files differnew file mode 100644 index 0000000..dbef1d9 --- /dev/null +++ b/files/Pasted image 20250214130512.png diff --git a/files/Pasted image 20250217114433.png b/files/Pasted image 20250217114433.png Binary files differnew file mode 100644 index 0000000..33df2a7 --- /dev/null +++ b/files/Pasted image 20250217114433.png diff --git a/files/Pasted image 20250501030617.png b/files/Pasted image 20250501030617.png Binary files differnew file mode 100644 index 0000000..d8472a8 --- /dev/null +++ b/files/Pasted image 20250501030617.png diff --git a/files/Pasted image 20250501031129.png b/files/Pasted image 20250501031129.png Binary files differnew file mode 100644 index 0000000..c85bfb7 --- /dev/null +++ b/files/Pasted image 20250501031129.png diff --git a/files/Pasted image 20250501031423.png b/files/Pasted image 20250501031423.png Binary files differnew file mode 100644 index 0000000..ba87255 --- /dev/null +++ b/files/Pasted image 20250501031423.png diff --git a/files/Pasted image 20250501031545.png b/files/Pasted image 20250501031545.png Binary files differnew file mode 100644 index 0000000..f93e5fa --- /dev/null +++ b/files/Pasted image 20250501031545.png diff --git a/files/Pasted image 20250501031558.png b/files/Pasted image 20250501031558.png Binary files differnew file mode 100644 index 0000000..6afb1c6 --- /dev/null +++ b/files/Pasted image 20250501031558.png diff --git a/files/Pasted image 20250501031753.png b/files/Pasted image 20250501031753.png Binary files differnew file mode 100644 index 0000000..0f1ca56 --- /dev/null +++ b/files/Pasted image 20250501031753.png diff --git a/files/Pasted image 20250621003213.png b/files/Pasted image 20250621003213.png Binary files differnew file mode 100644 index 0000000..aaf7cec --- /dev/null +++ b/files/Pasted image 20250621003213.png diff --git a/files/Pasted image 20250621003225.png b/files/Pasted image 20250621003225.png Binary files differnew file mode 100644 index 0000000..3cdb5f4 --- /dev/null +++ b/files/Pasted image 20250621003225.png diff --git a/files/Python_шпаргалка.png b/files/Python_шпаргалка.png Binary files differnew file mode 100644 index 0000000..9fa1e78 --- /dev/null +++ b/files/Python_шпаргалка.png diff --git a/files/TG_bot_VPN-main.zip b/files/TG_bot_VPN-main.zip Binary files differnew file mode 100644 index 0000000..5323159 --- /dev/null +++ b/files/TG_bot_VPN-main.zip diff --git a/files/TG_bot_VPN-main/.env.template b/files/TG_bot_VPN-main/.env.template new file mode 100644 index 0000000..3bb0162 --- /dev/null +++ b/files/TG_bot_VPN-main/.env.template @@ -0,0 +1,5 @@ +BOT_TOKEN="Токен бота" +ADMIN_ID="Telegram id админа" +CHANNEL_ID="ID телеграмм канала (формата @channel)" +DEFAULT_SERVER_USER="имя пользователя серверов по умолчанию" +DEFAULT_SERVER_PASSWORD="Пароль пользователя серверов" diff --git a/files/TG_bot_VPN-main/.gitignore b/files/TG_bot_VPN-main/.gitignore new file mode 100644 index 0000000..245e6b6 --- /dev/null +++ b/files/TG_bot_VPN-main/.gitignore @@ -0,0 +1,172 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +database/database.db +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +# PyPI configuration file +.pypirc +qr_codes
\ No newline at end of file diff --git a/files/TG_bot_VPN-main/LICENSE b/files/TG_bot_VPN-main/LICENSE new file mode 100644 index 0000000..4fd52d7 --- /dev/null +++ b/files/TG_bot_VPN-main/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Semen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/files/TG_bot_VPN-main/README.md b/files/TG_bot_VPN-main/README.md new file mode 100644 index 0000000..703861c --- /dev/null +++ b/files/TG_bot_VPN-main/README.md @@ -0,0 +1,27 @@ +# Telegram Bot VPN + +**Подключение должно происходить через стороннее приложение Hiddify** + +У пользователя должно быть следующие меню: +* /start + * Приветствие, "ссылка на канал" + * Бот должен делать проверку подписки на канал + * Ниже кнопка "Я подписался" + * Если пользователь не подписался — сообщение: "_для того что бы пользоваться ботом вам необходимо подписаться на канал_" + * Если успешно подписался: "_Спасибо, что выбрали наш сервис, приятного пользования._" +* 3 кнопки: Локация, Инструкции, Поддержка + * При клике на кнопку Локацию (/location): + * Выбор серверов (страна подключения) и кнопка смена локации. По нажатию на выбранную локацию, должен появляться QR-код, Текст инструкция, защищенный ключ для подключения. + * При клике на кнопку Поддержка (/help): + * Должно быть описание, ниже кнопка Support + +Для каждого пользователя должен создаваться уникальный ключ для подключения + +Ограничения по устройствам: **на один уникальный ключ не более 3 устройств** + +Бот должен сохранять в БД всех пользователей + +**Админ панель:** +* Настройка серверов +* Рассылки пользователям +* Редактирование текста в постах и название кнопок diff --git a/files/TG_bot_VPN-main/config_data/__init__.py b/files/TG_bot_VPN-main/config_data/__init__.py new file mode 100644 index 0000000..d63bc18 --- /dev/null +++ b/files/TG_bot_VPN-main/config_data/__init__.py @@ -0,0 +1 @@ +from . import config diff --git a/files/TG_bot_VPN-main/config_data/config.py b/files/TG_bot_VPN-main/config_data/config.py new file mode 100644 index 0000000..c04ba3e --- /dev/null +++ b/files/TG_bot_VPN-main/config_data/config.py @@ -0,0 +1,48 @@ +import os +from dotenv import load_dotenv, find_dotenv + +if not find_dotenv(): + exit('Переменные окружения не загружены, т.к отсутствует файл .env') +else: + load_dotenv() + +BOT_TOKEN = os.getenv('BOT_TOKEN') +DEFAULT_COMMANDS = ( + ('start', "Запустить бота"), + ('help', "Вывести справку"), + ('location', "Выбрать сервера для подключения"), + ('instruction', "Запросить инструкцию для подключения VPN"), +) +ADMIN_COMMANDS = ( + ("admin_panel", "Админка"), + ("message_sending", "Рассылка сообщений"), + ("add_vpn_key", "Вручную добавить VPN ключ") +) + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PATH_TO_PYTHON = os.path.normpath(os.path.join(BASE_DIR, ".venv/bin/python.exe")) +ADMIN_ID = os.getenv('ADMIN_ID') +ALLOWED_USERS = [int(ADMIN_ID), + 418333240, # владелец канала + 610640621, # Второй админ + ] + +CHANNEL_ID = os.getenv('CHANNEL_ID') + +DEFAULT_SERVER_USER = os.getenv("DEFAULT_SERVER_USER") +DEFAULT_SERVER_PASSWORD = os.getenv("DEFAULT_SERVER_PASSWORD") + +XRAY_CONFIG_PATH = "/usr/local/etc/xray/config.json" +QR_CODE_DIR = os.path.join(BASE_DIR, "qr_codes") # Папка для хранения сгенерированных QR-кодов + +# Убедимся, что папка для QR-кодов существует +if not os.path.exists(QR_CODE_DIR): + os.makedirs(QR_CODE_DIR) + +XRAY_REALITY_FINGERPRINT = "chrome" + +DOMAINS_LIST = [ + "www.google.com", + "speed.cloudflare.com", + "www.microsoft.com", +]
\ No newline at end of file diff --git a/files/TG_bot_VPN-main/database/__init__.py b/files/TG_bot_VPN-main/database/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/files/TG_bot_VPN-main/database/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/files/TG_bot_VPN-main/database/models.py b/files/TG_bot_VPN-main/database/models.py new file mode 100644 index 0000000..ded6bae --- /dev/null +++ b/files/TG_bot_VPN-main/database/models.py @@ -0,0 +1,69 @@ +import datetime +import os.path +import re + +from config_data.config import BASE_DIR +import peewee + +db = peewee.SqliteDatabase(os.path.join(BASE_DIR, "database/database.db")) + + +class BaseModel(peewee.Model): + """ Базовая модель """ + class Meta: + database = db + + +class Server(BaseModel): + """ Модель сервера для подключения VPN """ + username = peewee.CharField() + password = peewee.CharField() + location = peewee.CharField() + ip_address = peewee.CharField(unique=True) + public_key = peewee.CharField(null=True, unique=True) + + +class VPNKey(BaseModel): + """ Модель для VPN ключа. Привязан к серверу. Имеет qr код (картинка) для подключения """ + server = peewee.ForeignKeyField(Server, related_name="keys", + on_delete="cascade", + on_update="cascade") + name = peewee.CharField() + key = peewee.CharField(unique=True) + qr_code = peewee.CharField() + is_valid = peewee.BooleanField(default=True) + created_at = peewee.DateTimeField(default=datetime.datetime.now()) + updated_at = peewee.DateTimeField(default=datetime.datetime.now()) + + def extract_uuid(self) -> str | None: + """ + Извлекает UUID из VLESS-ссылки ключа. + """ + match = re.match(r'vless://([a-fA-F0-9-]{36})@', self.key) + return match.group(1) if match else None + + +class User(BaseModel): + """ Модель пользователя """ + user_id = peewee.CharField(unique=True) + full_name = peewee.CharField() + username = peewee.CharField() + is_premium = peewee.BooleanField(null=True) + is_subscribed = peewee.BooleanField(default=False) + vpn_key = peewee.ForeignKeyField(VPNKey, related_name="users", null=True, on_update="set null", + on_delete="set null") + + +class Group(BaseModel): + """ Класс группы """ + group_id = peewee.CharField(unique=True) + title = peewee.CharField() + description = peewee.TextField(null=True) + bio = peewee.TextField(null=True) + invite_link = peewee.CharField(null=True) + location = peewee.CharField(null=True) + username = peewee.CharField(null=True) + + +def create_models(): + db.create_tables(BaseModel.__subclasses__()) diff --git a/files/TG_bot_VPN-main/handlers/__init__.py b/files/TG_bot_VPN-main/handlers/__init__.py new file mode 100644 index 0000000..3fb88a6 --- /dev/null +++ b/files/TG_bot_VPN-main/handlers/__init__.py @@ -0,0 +1,2 @@ +from . import custom_heandlers +from . import default_heandlers diff --git a/files/TG_bot_VPN-main/handlers/custom_heandlers/__init__.py b/files/TG_bot_VPN-main/handlers/custom_heandlers/__init__.py new file mode 100644 index 0000000..f34731f --- /dev/null +++ b/files/TG_bot_VPN-main/handlers/custom_heandlers/__init__.py @@ -0,0 +1,3 @@ +from . import admin_handlers +from. import location_handlers +from. import instruction_handlers diff --git a/files/TG_bot_VPN-main/handlers/custom_heandlers/admin_handlers.py b/files/TG_bot_VPN-main/handlers/custom_heandlers/admin_handlers.py new file mode 100644 index 0000000..51fe601 --- /dev/null +++ b/files/TG_bot_VPN-main/handlers/custom_heandlers/admin_handlers.py @@ -0,0 +1,405 @@ +import os + +import peewee +import qrcode +from telebot.types import Message +from config_data.config import ALLOWED_USERS, DEFAULT_COMMANDS, ADMIN_COMMANDS, QR_CODE_DIR +from database.models import User, Server, VPNKey +from keyboards.inline.admin_buttons import ( + users_markup, + admin_markup, + get_vpn_markup, + get_servers_markup, + delete_vpn_markup, + key_actions_markup +) +from loader import bot, app_logger +from states.states import AdminPanel +from utils.functions import valid_ip, convert_amnezia_xray_json_to_vless_str, get_all_commands_bot +from utils.generate_vpn_keys import setup_server, generate_key +from utils.work_vpn_keys import suspend_key, resume_key, revoke_key, cleanup_server + + +@bot.message_handler(commands=["admin_panel"]) +def admin_panel(message: Message): + """ Хендлер для админ панели """ + if message.from_user.id in ALLOWED_USERS: + app_logger.info(f"Администратор {message.from_user.full_name} зашел в админ панель.") + bot.send_message(message.from_user.id, "🔧 Добро пожаловать в админ панель!\n\nВыберите нужную опцию ниже 👇", + reply_markup=admin_markup()) + bot.set_state(message.from_user.id, AdminPanel.get_option) + else: + bot.send_message(message.from_user.id, "❌ У вас недостаточно прав") + app_logger.warning(f"Пользователь {message.from_user.full_name} пытался войти в админ панель") + + +@bot.callback_query_handler(func=None, state=AdminPanel.get_option) +def admin_panel_handler(call): + """ Callback handler для выбора раздела админ панели """ + bot.answer_callback_query(callback_query_id=call.id) + + if call.data == "Exit": + bot.send_message(call.message.chat.id, "🚪 Вы успешно вышли из админ панели. До встречи!") + bot.set_state(call.message.chat.id, None) + app_logger.info(f"Администратор {call.from_user.full_name} вышел из админ панели.") + elif call.data == "users": + app_logger.info(f"Администратор {call.from_user.full_name} зашел в юзер панель.") + bot.send_message(call.message.chat.id, "👥 Список всех пользователей базы данных:", reply_markup=users_markup()) + bot.set_state(call.message.chat.id, AdminPanel.get_users) + elif call.data == "servers": + app_logger.info(f"Администратор {call.from_user.full_name} зашел в панель управления серверами.") + bot.send_message(call.message.chat.id, "🖥 Панель управления серверами:", reply_markup=get_servers_markup()) + bot.set_state(call.message.chat.id, AdminPanel.get_servers) + + +@bot.callback_query_handler(func=None, state=AdminPanel.get_users) +def get_user(call): + """ Хендлер для работы с юзерами из админ панели """ + bot.answer_callback_query(callback_query_id=call.id) + if call.data == "Exit": + + bot.send_message(call.message.chat.id, "Выберите опцию", reply_markup=admin_markup()) + bot.set_state(call.message.chat.id, AdminPanel.get_option) + app_logger.info(f"Администратор {call.from_user.full_name} вернулся обратно к выбору опций.") + else: + user_obj: User = User.get_by_id(call.data) + try: + vpn_key: VPNKey = user_obj.vpn_key + vpn_key = vpn_key.name + except Exception: + vpn_key = "отсутствует" + app_logger.info(f"Администратор {call.from_user.full_name} запросил " + f"информацию о пользователе {user_obj.full_name}") + bot.send_message(call.message.chat.id, + f"👤 Имя: {user_obj.full_name}\n" + f"📱 Телеграм: @{user_obj.username}\n" + f"📢 Подписан на канал: {user_obj.is_subscribed}\n" + f"🔑 VPN ключ: {vpn_key}" + ) + + +@bot.callback_query_handler(func=None, state=AdminPanel.get_servers) +def server_panel_handler(call): + """ Хендлер для управления серверами """ + bot.answer_callback_query(callback_query_id=call.id) + + if call.data == "Add": + bot.send_message(call.message.chat.id, + "📄 Введите данные сервера в следующем формате:\n" + "🏙 Location (например, США)\n" + "👤 Username (например, root)\n" + "🔒 Password (пароль от root)\n" + "🌐 IP address" + ) + bot.set_state(call.message.chat.id, AdminPanel.add_server) + return + + server_id = call.data + server_obj: Server = Server.get_by_id(server_id) + + app_logger.info(f"Администратор {call.from_user.full_name} запросил информацию о сервере {server_obj.location}") + + # Проверка настроен ли сервер + status = "✅ Настроен" if server_obj.public_key else "❌ Требует настройки" + + bot.send_message(call.message.chat.id, + f"🖥 Сервер: {server_obj.location}\n" + f"🔰 Статус: {status}\n" + f"🌐 IP адрес: {server_obj.ip_address}\n" + f"🔑 Количество ключей: {server_obj.keys.count()}", + reply_markup=get_vpn_markup(server_id)) + bot.set_state(call.message.chat.id, AdminPanel.get_vpn_keys) + + +@bot.message_handler(state=AdminPanel.add_server) +def add_server(message: Message): + """ Добавление нового сервера """ + try: + server_data = [item.strip() for item in message.text.split("\n")] + if len(server_data) != 4: # Убрали порт X-UI + raise ValueError("❌ Неверное количество полей!") + elif valid_ip(server_data[3]) is False: + raise ValueError("❌ Неверный формат IP адреса!") + + # Создаем сервер и настраиваем + server = Server.create( + location=server_data[0], + username=server_data[1], + password=server_data[2], + ip_address=server_data[3] + ) + app_logger.info(f"Администратор {message.from_user.full_name} начинает настройку сервера {server.location}") + bot.send_message(message.from_user.id, "Начинаю настройку сервера, подождите...") + # Автоматическая настройка сервера + if setup_server(server): + bot.send_message(message.from_user.id, "✅ Сервер успешно настроен!") + else: + bot.send_message(message.from_user.id, "❌ Ошибка настройки сервера!") + server.delete_instance() + bot.set_state(message.from_user.id, None) + return + + bot.set_state(message.from_user.id, None) + app_logger.info(f"Администратор {message.from_user.full_name} добавил сервер {server.location}") + + except Exception as ex: + bot.send_message(message.from_user.id, f"❌ Некорректные данные!\n{ex}") + app_logger.error(f"Ошибка при добавлении сервера {ex}") + bot.set_state(message.from_user.id, None) + + +@bot.callback_query_handler(func=None, state=AdminPanel.get_vpn_keys) +def vpn_panel_handler(call): + """ Хендлер для управления всеми привязанными к серверу VPN ключами """ + bot.answer_callback_query(callback_query_id=call.id) + + if "Generate" in call.data: + # Генерация нового ключа + server_id = call.data.split()[1] + server = Server.get_by_id(server_id) + app_logger.info(f"Администратор {call.from_user.full_name} запросил генерацию" + f" VPN ключа для сервера {server.location}") + bot.send_message(call.message.chat.id, f"⌛ Пожалуйста, подождите... Идет генерация нового VPN ключа...") + key = generate_key(server) + if key: + bot.send_message(call.message.chat.id, f"✅ Отлично! VPN ключ «{key.name}» успешно создан!") + app_logger.info(f"Администратор {call.from_user.full_name} успешно создал " + f"VPN ключ {key.name} для сервера {server.location}") + else: + bot.send_message(call.message.chat.id, "❌ Ошибка генерации ключа!") + + bot.set_state(call.message.chat.id, AdminPanel.get_vpn_keys) + return + + if "Delete" in call.data: + server_id = call.data.split()[1] + server_obj: Server = Server.get_by_id(server_id) + bot.send_message(call.message.chat.id, f"🗑 Начинается полная очистка сервера. Пожалуйста, подождите...") + if cleanup_server(server_obj): + app_logger.info(f"Администратор {call.from_user.full_name} удалил сервер {server_obj.location}") + bot.send_message(call.message.chat.id, f"✅ Сервер {server_obj.location} успешно удалён вместе " + f"с привязанными VPN ключами!") + bot.set_state(call.message.chat.id, AdminPanel.get_servers) + else: + bot.send_message(call.message.chat.id, "❌ Ошибка удаления сервера!") + bot.set_state(call.message.chat.id, AdminPanel.get_servers) + return + + if "VPN - " in call.data: + # Выдача всей информации по VPN ключу + vpn_obj: VPNKey = VPNKey.get_by_id(call.data.split("VPN - ")[1]) + app_logger.info(f"Администратор {call.from_user.full_name} запросил информацию о VPN ключе {vpn_obj.name}") + status = "✅ Активен" if vpn_obj.is_valid else "⏸ Приостановлен / Занят" + users = ", ".join([user.full_name for user in vpn_obj.users]) if vpn_obj.users else "Нет пользователей" + text = ( + f"🔑 Ключ: {vpn_obj.name}\n" + f"📍 Сервер: {vpn_obj.server.location}\n" + f"📡 Статус: {status}\n" + f"👤 Пользователи: {users}\n" + f"🕒 Создан: {vpn_obj.created_at.strftime('%d.%m.%Y %H:%M')}" + ) + + # Отправляем QR-код если есть + if os.path.exists(vpn_obj.qr_code): + with open(vpn_obj.qr_code, 'rb') as qr_file: + bot.send_photo(call.message.chat.id, qr_file, caption=text, + reply_markup=key_actions_markup(vpn_obj.id)) + else: + bot.send_message(call.message.chat.id, text, + reply_markup=key_actions_markup(vpn_obj.id)) + bot.set_state(call.message.chat.id, AdminPanel.delete_vpn) + elif "Cancel" in call.data: + # Возврат в меню серверов + bot.send_message(call.message.chat.id, "Вы вернулись в меню серверов.", + reply_markup=get_servers_markup()) + app_logger.info(f"Администратор {call.from_user.full_name} вернулся в меню серверов") + bot.set_state(call.message.chat.id, AdminPanel.get_servers) + else: + bot.set_state(call.message.chat.id, AdminPanel.get_vpn_keys) + +@bot.callback_query_handler(func=None, state=AdminPanel.delete_vpn) +def vpn_panel_handler(call): + """ Хендлер для удаления VPN ключа """ + bot.answer_callback_query(callback_query_id=call.id) + + if "action_" in call.data: + action, key_id = call.data.split("_")[1], call.data.split("_")[2] + vpn_key = VPNKey.get_by_id(key_id) + + if action == "suspend": + app_logger.info(f"Администратор {call.message.from_user.full_name} запросил остановку " + f"VPN ключа {vpn_key.name}") + if suspend_key(vpn_key): + bot.send_message(call.message.chat.id, f"⏸ Ключ {vpn_key.name} приостановлен") + else: + bot.send_message(call.message.chat.id, "❌ Ошибка приостановки ключа!") + + elif action == "resume": + app_logger.info(f"Администратор {call.message.from_user.full_name} запросил возобновление работы " + f"VPN ключа {vpn_key.name}") + if resume_key(vpn_key): + bot.send_message(call.message.chat.id, f"▶️ Ключ {vpn_key.name} возобновлен") + else: + bot.send_message(call.message.chat.id, "❌ Ошибка возобновления ключа!") + + elif action == "revoke": + app_logger.info(f"Администратор {call.message.from_user.full_name} запросил отзыв " + f"VPN ключа {vpn_key.name}") + if revoke_key(vpn_key): + for user in User.select().where(User.vpn_key == vpn_key): + user.vpn_key = None + user.save() + bot.send_message(call.message.chat.id, f"🗑 Ключ {vpn_key.name} отозван") + else: + bot.send_message(call.message.chat.id, "❌ Ошибка отзыва ключа!") + + bot.set_state(call.message.chat.id, AdminPanel.get_servers) + return + + if "Cancel" in call.data: + bot.send_message(call.message.chat.id, "Вы вернулись в админку.", + reply_markup=admin_markup()) + app_logger.info(f"Администратор {call.from_user.full_name} вернулся в админку") + bot.set_state(call.message.chat.id, AdminPanel.get_option) + return + else: + # Скорее всего, пользователь выбрал другой VPN ключ + bot.set_state(call.message.chat.id, AdminPanel.get_vpn_keys) + vpn_panel_handler(call) + + +@bot.message_handler(commands=["message_sending"]) +def message_sending_handler(message: Message): + """ Хендлер рассылки сообщений юзерам """ + if message.from_user.id in ALLOWED_USERS: + app_logger.info(f"Администратор {message.from_user.full_name} вызвал команду /message_sending.") + bot.send_message(message.from_user.id, "✉️ Введите текст сообщения для рассылки пользователям:") + bot.set_state(message.from_user.id, AdminPanel.send_message) + else: + bot.send_message(message.from_user.id, "❌ У вас недостаточно прав для выполнения этой команды!") + app_logger.warning(f"Попытка доступа к /message_sending без прав администратора {message.from_user.full_name}") + + +@bot.message_handler(state=AdminPanel.send_message) +def send_message_to_users_handler(message: Message): + """ Отправка сообщений пользователям """ + # Проверка, что сообщение для рассылки - не одна из команд бота + + if message.text in get_all_commands_bot(): + bot.send_message(message.from_user.id, "Это одна из команд бота") + bot.set_state(message.from_user.id, None) + return + + # Проверка, что сообщение не пустое + if not message.text: + bot.send_message(message.from_user.id, "Сообщение не может быть пустым.") + return + app_logger.info(f"Администратор {message.from_user.full_name} начал рассылку сообщений: {message.text}") + + bot.send_message(message.chat.id, "Начинаю рассылку...") + bot.send_chat_action(message.chat.id, "typing") + for user_obj in User.select(): + if int(user_obj.user_id) not in ALLOWED_USERS: + bot.send_message(user_obj.user_id, message.text) + bot.send_message(message.chat.id, "✅ Рассылка сообщений завершена!") + bot.set_state(message.from_user.id, None) + + +@bot.message_handler(commands=["add_vpn_key"]) +def add_vpn_key_handler(message: Message): + """ Хендлер для ручного добавления VPN ключа """ + if message.from_user.id in ALLOWED_USERS: + app_logger.info(f"Администратор {message.from_user.full_name} вызвал команду /add_vpn_key.") + bot.send_message(message.from_user.id, "🔑 Введите название нового VPN ключа:") + bot.set_state(message.from_user.id, AdminPanel.add_vpn_key_name) + else: + bot.send_message(message.from_user.id, "У вас недостаточно прав!") + + +@bot.message_handler(state=AdminPanel.add_vpn_key_name) +def add_vpn_key_name_handler(message: Message): + """ Обработка ввода названия VPN ключа """ + app_logger.info(f"Администратор {message.from_user.full_name} ввел название VPN ключа: {message.text}") + with bot.retrieve_data(message.from_user.id, message.chat.id) as data: + data["vpn_key_name"] = message.text + bot.send_message(message.from_user.id, "🔑 Введите VPN KEY в формате JSON или " + "VLESS-ссылку (например, vless://...) :") + bot.set_state(message.from_user.id, AdminPanel.add_vpn_key_key) + + +@bot.message_handler(state=AdminPanel.add_vpn_key_key) +def add_vpn_key_key_handler(message: Message): + """ Обработка ввода VPN ключа """ + + if message.text in get_all_commands_bot(): + bot.send_message(message.from_user.id, "Это одна из команд бота") + bot.set_state(message.from_user.id, None) + return + + app_logger.info(f"Администратор {message.from_user.full_name} ввел VPN ключ") + if "vless://" not in message.text: + vless_str = convert_amnezia_xray_json_to_vless_str(message.text) + if vless_str is None: + bot.send_message(message.from_user.id, "Не удалось преобразовать JSON в VLESS строку. " + "Проверьте правильность ввода.") + app_logger.warning("Не удалось преобразовать json конфиг в vless строку!") + return + else: + vless_str = message.text + with bot.retrieve_data(message.from_user.id, message.chat.id) as data: + data["vpn_key_key"] = vless_str + bot.send_message(message.from_user.id, "Выберите сервер, к которому принадлежит ключ", + reply_markup=get_servers_markup()) + bot.set_state(message.from_user.id, AdminPanel.save_vpn_key) + + +@bot.callback_query_handler(func=None, state=AdminPanel.save_vpn_key) +def save_vpn_handler(call): + """ Хендлер для сохранения в БД VPN ключа """ + bot.answer_callback_query(callback_query_id=call.id) + + if call.data == "Add": + bot.send_message(call.message.chat.id, + "📄 Введите данные сервера в следующем формате:\n" + "🏙 Location (например, США)\n" + "👤 Username (например, root)\n" + "🔒 Password (пароль от root)\n" + "🌐 IP address" + ) + bot.set_state(call.message.chat.id, AdminPanel.add_server) + return + + server_id = call.data + server_obj: Server = Server.get_by_id(server_id) + with bot.retrieve_data(call.from_user.id, call.from_user.id) as data: + try: + key_number = len(server_obj.keys) + 1 if hasattr(server_obj, "keys") else 1 + qr_code_filename = f"vpn_key_{server_obj.id}_{key_number}.png" + qr_code_path = os.path.join(QR_CODE_DIR, qr_code_filename) + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_H, + box_size=10, + border=4 + ) + qr.add_data(data["vpn_key_key"]) + qr.make(fit=True) + img = qr.make_image(fill_color="black", back_color="white") + img.save(qr_code_path) + app_logger.info(f"QR-код сгенерирован и сохранён по пути: {qr_code_path}") + vpn_key = VPNKey.create( + name=data["vpn_key_name"], + key=data["vpn_key_key"], + qr_code=qr_code_path, + server=server_obj, + ) + except peewee.IntegrityError: + bot.send_message(call.message.chat.id, "Такой ключ уже существует!") + bot.send_message(call.message.chat.id, "Вы вышли в главное меню") + app_logger.warning(f"Попытка создания дубликата VPN ключа {data['vpn_key_name']}!") + + else: + app_logger.info(f"Администратор {call.message.from_user.full_name} добавил VPN ключ {vpn_key.name} " + f"к серверу {server_obj.location}") + bot.send_message(call.message.chat.id, f"VPN ключ {vpn_key.name} добавлен к серверу {server_obj.location}.") + bot.set_state(call.message.chat.id, None) diff --git a/files/TG_bot_VPN-main/handlers/custom_heandlers/instruction_handlers.py b/files/TG_bot_VPN-main/handlers/custom_heandlers/instruction_handlers.py new file mode 100644 index 0000000..4702212 --- /dev/null +++ b/files/TG_bot_VPN-main/handlers/custom_heandlers/instruction_handlers.py @@ -0,0 +1,26 @@ +from telebot.types import Message + +from config_data.config import CHANNEL_ID +from database.models import User +from loader import bot, app_logger +from keyboards.inline.app_buttons import get_apps_murkup + + +@bot.message_handler(commands=["instruction"]) +def instruction_handler(message: Message): + """ Хендлер для выбора сервера подключения """ + app_logger.info(f"Пользователь {message.from_user.full_name} вызвал команду /location") + cur_user = User.get(User.user_id == message.from_user.id) + + if cur_user.is_subscribed: + instruction_text = ( + "📖 Подробная инструкция по подключению доступна по [ссылке]" + "(https://telegra.ph/Kak-ispolzovat-VPN-servis-Guard-Tunnel-01-16).\n\n" + "💡 Для использования VPN, пожалуйста, скачайте приложение **Hiddify**.\n\n" + "Желаем вам безопасного и комфортного подключения! 🚀" + ) + bot.send_message(message.chat.id, instruction_text, parse_mode="Markdown", reply_markup=get_apps_murkup()) + else: + bot.send_message(message.chat.id, f"🚫 Вы не подписаны на [наш канал](https://t.me/{CHANNEL_ID[1:]})!\n" + f"Подпишитесь, чтобы получить доступ ко всему функционалу.", + parse_mode="Markdown") diff --git a/files/TG_bot_VPN-main/handlers/custom_heandlers/location_handlers.py b/files/TG_bot_VPN-main/handlers/custom_heandlers/location_handlers.py new file mode 100644 index 0000000..45490e4 --- /dev/null +++ b/files/TG_bot_VPN-main/handlers/custom_heandlers/location_handlers.py @@ -0,0 +1,135 @@ +from telebot.types import Message + +from config_data.config import CHANNEL_ID +from database.models import User, Server, VPNKey +from loader import bot, app_logger, scheduler +from keyboards.inline.servers import get_locations_markup, get_instruction_markup +from states.states import GetVPNKey +from utils.functions import is_subscribed +from utils.generate_vpn_keys import generate_key +from utils.tasks import cancel_key_revocation +from utils.work_vpn_keys import revoke_key + + +@bot.message_handler(commands=["location"]) +def location_handler(message: Message): + """ Хендлер для выбора сервера подключения """ + app_logger.info(f"Пользователь {message.from_user.full_name} вызвал команду /location") + cur_user = User.get(User.user_id == message.from_user.id) + + if is_subscribed(CHANNEL_ID, message.from_user.id): + cur_user.is_subscribed = True + bot.send_message(message.chat.id, "🌍 Пожалуйста, выберите сервер для подключения:", reply_markup=get_locations_markup()) + bot.set_state(message.chat.id, GetVPNKey.get_server) + else: + bot.send_message(message.chat.id, f"🚫 Вы не подписаны на [наш канал](https://t.me/{CHANNEL_ID[1:]})!\n" + f"Подпишитесь, чтобы получить доступ ко всему функционалу.", + parse_mode="Markdown") + cur_user.is_subscribed = False + cur_user.save() + + +@bot.callback_query_handler(func=None, state=GetVPNKey.get_server) +def get_server_handler(call): + """ Хендлер для получения сервера подключения """ + bot.answer_callback_query(callback_query_id=call.id) + + server_id = call.data + + cur_user: User = User.get(User.user_id == call.from_user.id) + cur_server: Server = Server.get_by_id(server_id) + + # Проверка на то, что текущий VPN ключ пользователя удален. + try: + VPNKey.get_by_id(cur_user.vpn_key) + except Exception: + app_logger.warning(f"Внимание! У пользователя {cur_user.full_name} не нашлось ключа VPN!") + cur_user.vpn_key = None + cur_user.save() + + app_logger.info(f"Пользователь {cur_user.full_name} выбрал сервер {cur_server.location}") + + # Получение всех vpn ключей данного сервера и выбор одного + for vpn_key_obj in cur_server.keys: + if vpn_key_obj.is_valid: + if cur_user.vpn_key is not None: + users_vpn = VPNKey.get_by_id(cur_user.vpn_key) + users_vpn.is_valid = True + users_vpn.save() + app_logger.info(f"VPN ключ {users_vpn.name} теперь свободен.") + cur_user.vpn_key = vpn_key_obj + cur_user.save() + vpn_key_obj.is_valid = False + vpn_key_obj.save() + app_logger.info(f"Пользователь {cur_user.full_name} зарезервировал ключ {vpn_key_obj.name}") + with open(vpn_key_obj.qr_code, "rb") as qr_code: + bot.send_photo( + call.message.chat.id, + qr_code, + caption=( + "🔒 Мы не храним информацию о ваших подключениях!\n\n" + f"🔑 Имя ключа: *{vpn_key_obj.name}*\n" + f"🌍 Сервер: *{cur_server.location}*\n" + f"🔗 URL для подключения:\n\n`{vpn_key_obj.key}`" + ), + parse_mode="Markdown", + reply_markup=get_instruction_markup() + ) + bot.set_state(call.message.chat.id, None) + return + + # Если нет свободных ключей, генерируем новый + app_logger.warning(f"Внимание! Для сервера {cur_server.location} не нашлось свободных VPN ключей! " + f"Генерирую новый...") + bot.send_message(call.message.chat.id, "⌛ Пожалуйста, подождите... Идет генерация нового VPN ключа...") + + new_key: VPNKey = generate_key(cur_server) + if new_key is None: + app_logger.error("Внимание! Не удалось сгенерировать новый ключ!") + bot.send_message(call.message.chat.id, "❌ Ведутся технические работы на стороне сервера, поэтому " + "генерация нового ключа пока невозможна!\n" + "Мы приносим свои извинения, попробуйте позже 😊") + bot.set_state(call.message.chat.id, None) + return + app_logger.info(f"Сгенерирован новый ключ {new_key.name}!") + + if cur_user.vpn_key is not None: + users_vpn = VPNKey.get_by_id(cur_user.vpn_key) + revoke_key(users_vpn) + + cur_user.vpn_key = new_key + cur_user.save() + + new_key.is_valid = False + new_key.save() + app_logger.info(f"Пользователь {cur_user.full_name} зарезервировал новый ключ {new_key.name}") + with open(new_key.qr_code, "rb") as qr_code: + bot.send_photo( + call.message.chat.id, + qr_code, + caption=( + "🔒 Мы не храним информацию о ваших подключениях!\n\n" + f"🔑 Имя ключа: *{new_key.name}*\n" + f"🌍 Сервер: *{cur_server.location}*\n" + f"🔗 URL для подключения:\n\n`{new_key.key}`" + ), + parse_mode="Markdown", + reply_markup=get_instruction_markup() + ) + bot.set_state(call.message.chat.id, None) + + +@bot.callback_query_handler(func=lambda call: call.data.startswith("renew_")) +def renew_key_handler(call): + bot.answer_callback_query(callback_query_id=call.id) + try: + vpn_key_id = int(call.data.split("_")[1]) + vpn_key = VPNKey.get_by_id(vpn_key_id) + # Отменяем запланированную задачу отзыва + cancel_key_revocation(vpn_key, scheduler) + bot.send_message(call.message.chat.id, + f"✅ Ваш VPN ключ «{vpn_key.name}» успешно продлён!") + app_logger.info(f"VPN ключ {vpn_key.name} продлён пользователем {call.from_user.full_name}.") + except Exception as e: + app_logger.error(f"Ошибка в обработчике продления ключа: {e}") + bot.send_message(call.message.chat.id, "❌ Произошла ошибка при продлении ключа.") diff --git a/files/TG_bot_VPN-main/handlers/default_heandlers/__init__.py b/files/TG_bot_VPN-main/handlers/default_heandlers/__init__.py new file mode 100644 index 0000000..4e4b225 --- /dev/null +++ b/files/TG_bot_VPN-main/handlers/default_heandlers/__init__.py @@ -0,0 +1,6 @@ +from . import start +from . import help +from . import echo + + +__all__ = ['start', 'help', 'echo'] diff --git a/files/TG_bot_VPN-main/handlers/default_heandlers/echo.py b/files/TG_bot_VPN-main/handlers/default_heandlers/echo.py new file mode 100644 index 0000000..f8f5904 --- /dev/null +++ b/files/TG_bot_VPN-main/handlers/default_heandlers/echo.py @@ -0,0 +1,21 @@ +from telebot.types import Message +from loader import bot +from handlers.custom_heandlers.location_handlers import location_handler +from handlers.default_heandlers.help import bot_help +from handlers.custom_heandlers.instruction_handlers import instruction_handler + + +# Эхо хендлер, куда летят текстовые сообщения без указанного состояния + +@bot.message_handler(state=None) +def bot_echo(message: Message): + if message.text == "🌍 Серверы": + location_handler(message) + elif message.text == "❓ Справка": + bot_help(message) + elif message.text == "📖 Инструкция": + instruction_handler(message) + else: + bot.reply_to(message, f"Введите любую команду из меню, чтобы я начал работать\n" + f"Либо выберите одну из кнопок, которые я вам прислал 👇") + diff --git a/files/TG_bot_VPN-main/handlers/default_heandlers/help.py b/files/TG_bot_VPN-main/handlers/default_heandlers/help.py new file mode 100644 index 0000000..fc77dd4 --- /dev/null +++ b/files/TG_bot_VPN-main/handlers/default_heandlers/help.py @@ -0,0 +1,13 @@ +from telebot.types import Message + +from config_data.config import DEFAULT_COMMANDS, ADMIN_COMMANDS, ALLOWED_USERS +from loader import bot + + +@bot.message_handler(commands=['help']) +def bot_help(message: Message): + commands = [f"/{command} - {description}" for command, description in DEFAULT_COMMANDS] + if message.from_user.id in ALLOWED_USERS: + commands.extend([f"/{command} - {description}" for command, description in ADMIN_COMMANDS]) + bot.reply_to(message, "📋 Доступные команды:\n" + "\n".join(commands)) + bot.send_message(message.chat.id, "🤝 Для поддержки обращайтесь: @guardtunnel_support") diff --git a/files/TG_bot_VPN-main/handlers/default_heandlers/start.py b/files/TG_bot_VPN-main/handlers/default_heandlers/start.py new file mode 100644 index 0000000..32beca8 --- /dev/null +++ b/files/TG_bot_VPN-main/handlers/default_heandlers/start.py @@ -0,0 +1,99 @@ +from telebot.types import Message +from loader import bot, app_logger +from config_data.config import DEFAULT_COMMANDS, ADMIN_COMMANDS, ALLOWED_USERS, CHANNEL_ID +from database.models import User, Group +from utils.functions import is_subscribed +from keyboards.inline.subscribed import is_subscribed_markup +from keyboards.reply.handlers_reply import handlers_reply +from states.states import SubscribedState + + +start_text = """*Вы не подписались на канал!* + +☺️ Для доступа к полному функционалу бота вам необходимо подписаться на наш новостной канал [Guard Tunnel VPN](https://t.me/{channel_id}) + +🎁 В подарок вы получите *бесплатный доступ* на использование нашего сервиса + +*С Уважением, команда Guard Tunnel VPN* + +_После подписки на наш канал нажмите ниже на кнопку: ✅ Я подписался_""" + + +@bot.message_handler(commands=['start']) +def bot_start(message: Message): + if message.chat.type == "private": + is_sub = True if message.from_user.id in ALLOWED_USERS else False + if User.get_or_none(user_id=message.from_user.id) is None: + app_logger.info(f"Внимание! Новый юзер: {message.from_user.full_name} - {message.from_user.username}") + User.create(user_id=message.from_user.id, + full_name=message.from_user.full_name, + username=message.from_user.username if message.from_user.username is not None else "None", + is_premium=message.from_user.is_premium, + is_subscribed=is_sub) + commands = [f"/{command} - {description}" for command, description in DEFAULT_COMMANDS] + if message.from_user.id in ALLOWED_USERS: + commands.extend([f"/{command} - {description}" for command, description in ADMIN_COMMANDS]) + bot.send_message( + message.from_user.id, + f"Здравствуйте, {message.from_user.full_name}! 👋\n" + f"Вы вошли как администратор. Доступны следующие команды:\n{'\n'.join(commands)}", + reply_markup=handlers_reply() + ) + else: + if is_subscribed(CHANNEL_ID, message.from_user.id): + # Если пользователь подписан на канал, тогда ему можно пользоваться ботом. + bot.send_message(message.from_user.id, f"Приветствуем, {message.from_user.full_name}.\n" + f"Рады приветствовать вас на нашем сервисе!\n" + f"Что бы использовать наш VPN сервис, " + f"следуйте инструкциям ниже 👇\n" + f"{'\n'.join(commands)}", + reply_markup=handlers_reply()) + cur_user = User.get(User.user_id == message.from_user.id) + cur_user.is_subscribed = True + cur_user.save() + else: + bot.send_message(message.from_user.id, start_text.format(channel_id=CHANNEL_ID[1:]), + reply_markup=is_subscribed_markup(), parse_mode='Markdown', + disable_web_page_preview=True) + bot.set_state(message.from_user.id, SubscribedState.subscribe) + + else: + bot.send_message(message.chat.id, "Здравствуйте! Я - телеграм бот, модератор каналов и групп. " + "Чтобы получить больше информации, " + "обратитесь к администратору, или напишите мне в личку)") + if Group.get_or_none(group_id=message.chat.id) is None: + Group.create(group_id=message.chat.id, + title=message.chat.title, + description=message.chat.description, + bio=message.chat.bio, + invite_link=message.chat.invite_link, + location=message.chat.location, + username=message.chat.username) + app_logger.info(f"Внимание! Новая группа: {message.chat.title} - {message.chat.invite_link}") + if User.get_or_none(user_id=message.from_user.id) is None: + User.create(user_id=message.from_user.id, + full_name=message.from_user.full_name, + username=message.from_user.username, + is_premium=message.from_user.is_premium) + app_logger.info(f"Внимание! Новый юзер: {message.from_user.full_name} - {message.from_user.username}") + +@bot.callback_query_handler(func=None, state=SubscribedState.subscribe) +def is_subscribed_handler(call): + if is_subscribed(CHANNEL_ID, call.from_user.id): + commands = [f"/{command} - {description}" for command, description in DEFAULT_COMMANDS] + app_logger.info(f"Пользователь {call.from_user.full_name} подписался на канал!") + + cur_user = User.get(User.user_id == call.from_user.id) + cur_user.is_subscribed = True + cur_user.save() + + bot.answer_callback_query(callback_query_id=call.id) + bot.send_message(call.message.chat.id, "Спасибо, что выбрали наш сервис, приятного использования!\n" + f"Вам доступны следующие команды:\n" + f"{'\n'.join(commands)}") + bot.set_state(call.message.chat.id, None) + else: + bot.answer_callback_query(callback_query_id=call.id) + bot.send_message(call.message.chat.id, start_text.format(channel_id=CHANNEL_ID[1:]), + reply_markup=is_subscribed_markup(), parse_mode='Markdown', + disable_web_page_preview=True) diff --git a/files/TG_bot_VPN-main/keyboards/__init__.py b/files/TG_bot_VPN-main/keyboards/__init__.py new file mode 100644 index 0000000..e1e9fef --- /dev/null +++ b/files/TG_bot_VPN-main/keyboards/__init__.py @@ -0,0 +1,2 @@ +from . import inline +from . import reply diff --git a/files/TG_bot_VPN-main/keyboards/inline/__init__.py b/files/TG_bot_VPN-main/keyboards/inline/__init__.py new file mode 100644 index 0000000..48c5cc6 --- /dev/null +++ b/files/TG_bot_VPN-main/keyboards/inline/__init__.py @@ -0,0 +1,3 @@ +from . import admin_buttons +from. import servers +from. import subscribed diff --git a/files/TG_bot_VPN-main/keyboards/inline/admin_buttons.py b/files/TG_bot_VPN-main/keyboards/inline/admin_buttons.py new file mode 100644 index 0000000..08fd456 --- /dev/null +++ b/files/TG_bot_VPN-main/keyboards/inline/admin_buttons.py @@ -0,0 +1,71 @@ +from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton + +from config_data.config import ALLOWED_USERS +from database.models import User, Server + + +def admin_markup(): + """ Inline buttons для выбора меню админа """ + actions = InlineKeyboardMarkup(row_width=2) + actions.add(InlineKeyboardButton(text=f"🖥 Управление серверами", callback_data="servers")) + actions.add(InlineKeyboardButton(text=f"👥 Управление пользователями", callback_data="users")) + actions.add(InlineKeyboardButton(text=f"🚪 Выйти", callback_data="Exit")) + return actions + + +def users_markup(): + """ Inline buttons для выбора юзера """ + users_obj = User.select() + actions = InlineKeyboardMarkup(row_width=2) + + for user in users_obj: + if int(user.user_id) not in ALLOWED_USERS: + actions.add(InlineKeyboardButton(text=f"👤 {user.full_name}", callback_data=user.id)) + actions.add(InlineKeyboardButton(text=f"🔙 Назад", callback_data="Exit")) + return actions + + +def get_servers_markup(): + """ Inline buttons для выдачи всех локаций серверов """ + actions = InlineKeyboardMarkup(row_width=1) + servers_obj = Server.select() + for server in servers_obj: + actions.add(InlineKeyboardButton(text=f"🌍 {server.location}", callback_data=str(server.id))) + actions.add(InlineKeyboardButton(text=f"➕ Добавить сервер", callback_data="Add")) + return actions + +def get_vpn_markup(server_id): + """ Inline buttons для выдачи vpn ключей сервера """ + cur_server: Server = Server.get_by_id(server_id) + actions = InlineKeyboardMarkup(row_width=2) + for vpn_key_obj in cur_server.keys: + actions.add(InlineKeyboardButton(text=f"🔑 {vpn_key_obj.name}", callback_data=f"VPN - {str(vpn_key_obj.id)}")) + actions.add(InlineKeyboardButton(text=f"🔄 Сгенерировать новый ключ", callback_data=f"Generate {cur_server.id}")) + actions.add(InlineKeyboardButton(text=f"🗑 Удалить сервер", callback_data=f"Delete {cur_server.id}"), + InlineKeyboardButton(text=f"🔙 Назад", callback_data="Cancel")) # Возврат в меню серверов + return actions + +def delete_vpn_markup(vpn_obj_id: int): + """ Inline buttons для удаления vpn ключа """ + actions = InlineKeyboardMarkup(row_width=2) + actions.add(InlineKeyboardButton(text=f"🗑 Удалить", callback_data=f"Del - {vpn_obj_id}"), + InlineKeyboardButton(text=f"🚪 Выйти", callback_data="Cancel")) + return actions + + +def key_actions_markup(key_id: int) -> InlineKeyboardMarkup: + """ + Клавиатура для управления ключом: + - Приостановить/возобновить + - Отозвать + - Назад + """ + markup = InlineKeyboardMarkup(row_width=2) + + markup.add( + InlineKeyboardButton("⏸ Приостановить", callback_data=f"action_suspend_{key_id}"), + InlineKeyboardButton("▶️ Возобновить", callback_data=f"action_resume_{key_id}"), + InlineKeyboardButton("🗑 Отозвать", callback_data=f"action_revoke_{key_id}"), + InlineKeyboardButton("🔙 Назад", callback_data="Cancel") + ) + return markup
\ No newline at end of file diff --git a/files/TG_bot_VPN-main/keyboards/inline/app_buttons.py b/files/TG_bot_VPN-main/keyboards/inline/app_buttons.py new file mode 100644 index 0000000..b62b0eb --- /dev/null +++ b/files/TG_bot_VPN-main/keyboards/inline/app_buttons.py @@ -0,0 +1,22 @@ +from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton + + +def get_apps_murkup(): + """ Inline buttons для выдачи ссылок на скачивание приложений """ + actions = InlineKeyboardMarkup(row_width=3) + + actions.add(InlineKeyboardButton(text="💻 Windows", url="https://github.com/hiddify/hiddify-next/releases/" + "download/v2.0.5/Hiddify-Windows-Setup-x64.exe"), + InlineKeyboardButton(text="🍏 MacOS", url="https://apps.apple.com/ru/app/" + "hiddify-proxy-vpn/id6596777532"), + InlineKeyboardButton(text="🐧 Linux", url="https://github.com/hiddify/hiddify-next/releases/" + "download/v2.0.5/Hiddify-Debian-x64.deb") + ) + actions.add(InlineKeyboardButton(text="📱 iOS", url="https://apps.apple.com/ru/app/" + "hiddify-proxy-vpn/id6596777532"), + InlineKeyboardButton(text="🤖 Android", url="https://play.google.com/store/" + "apps/details?id=app.hiddify.com") + ) + actions.add(InlineKeyboardButton(text="🔀 Другой вариант", url="https://github.com/hiddify/" + "hiddify-app/releases/tag/v2.0.5")) + return actions diff --git a/files/TG_bot_VPN-main/keyboards/inline/servers.py b/files/TG_bot_VPN-main/keyboards/inline/servers.py new file mode 100644 index 0000000..1b74b56 --- /dev/null +++ b/files/TG_bot_VPN-main/keyboards/inline/servers.py @@ -0,0 +1,19 @@ +from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton +from database.models import Server + + +def get_locations_markup(): + """ Inline buttons для выдачи всех локаций серверов """ + actions = InlineKeyboardMarkup(row_width=1) + servers_obj = Server.select() + for server in servers_obj: + actions.add(InlineKeyboardButton(text=f"🌍 {server.location}", callback_data=str(server.id))) + return actions + +def get_instruction_markup(): + """ Inline buttons для выдачи ссылки на инструкцию """ + actions = InlineKeyboardMarkup(row_width=1) + actions.add(InlineKeyboardButton(text="📖 Инструкция для подключения", url="https://telegra.ph/" + "Kak-ispolzovat-VPN-servis-" + "Guard-Tunnel-01-16")) + return actions diff --git a/files/TG_bot_VPN-main/keyboards/inline/subscribed.py b/files/TG_bot_VPN-main/keyboards/inline/subscribed.py new file mode 100644 index 0000000..e627494 --- /dev/null +++ b/files/TG_bot_VPN-main/keyboards/inline/subscribed.py @@ -0,0 +1,17 @@ +from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton +from config_data.config import CHANNEL_ID + + +def is_subscribed_markup(): + """ Inline buttons для проверки подписки """ + actions = InlineKeyboardMarkup(row_width=2) + actions.add(InlineKeyboardButton(text=f"Guard Tunnel VPN", url=f"https://t.me/{CHANNEL_ID[1:]}", + callback_data="1"), + InlineKeyboardButton(text=f" ✅ Я подписался", callback_data="2")) + return actions + +def get_renew_markup(vpn_key_id: int): + """Возвращает inline-клавиатуру с кнопкой «Продлить ключ».""" + markup = InlineKeyboardMarkup() + markup.add(InlineKeyboardButton(text="🔄 Продлить ключ", callback_data=f"renew_{vpn_key_id}")) + return markup diff --git a/files/TG_bot_VPN-main/keyboards/reply/__init__.py b/files/TG_bot_VPN-main/keyboards/reply/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/files/TG_bot_VPN-main/keyboards/reply/__init__.py diff --git a/files/TG_bot_VPN-main/keyboards/reply/handlers_reply.py b/files/TG_bot_VPN-main/keyboards/reply/handlers_reply.py new file mode 100644 index 0000000..a6149b0 --- /dev/null +++ b/files/TG_bot_VPN-main/keyboards/reply/handlers_reply.py @@ -0,0 +1,9 @@ +from telebot.types import ReplyKeyboardMarkup, KeyboardButton + + +def handlers_reply() -> ReplyKeyboardMarkup: + keyboard = ReplyKeyboardMarkup(True, False, row_width=2, + input_field_placeholder="Нажмите на нужную кнопку либо введите команду...") + keyboard.add(KeyboardButton('🌍 Серверы'), KeyboardButton('❓ Справка')) + keyboard.add(KeyboardButton('📖 Инструкция')) + return keyboard diff --git a/files/TG_bot_VPN-main/loader.py b/files/TG_bot_VPN-main/loader.py new file mode 100644 index 0000000..9576017 --- /dev/null +++ b/files/TG_bot_VPN-main/loader.py @@ -0,0 +1,30 @@ +import os.path + +from apscheduler.schedulers.background import BackgroundScheduler +from telebot import TeleBot +from telebot.storage import StateMemoryStorage +from config_data import config +from logging.handlers import RotatingFileHandler +import logging + +from config_data.config import BASE_DIR + +storage = StateMemoryStorage() +bot = TeleBot(token=config.BOT_TOKEN, state_storage=storage) +scheduler = BackgroundScheduler() + + +log_formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(name)s - %(message)s') +my_handler = RotatingFileHandler(os.path.join(BASE_DIR, "bot.log"), mode='a', maxBytes=5 * 1024 * 1024, + backupCount=1, encoding="utf8", delay=0) +my_handler.setFormatter(log_formatter) +my_handler.setLevel(logging.DEBUG) + +stream_handler = logging.StreamHandler() +stream_handler.setFormatter(log_formatter) +stream_handler.setLevel(logging.INFO) + +app_logger = logging.getLogger("app_logger") +app_logger.setLevel(logging.DEBUG) +app_logger.addHandler(my_handler) +app_logger.addHandler(stream_handler) diff --git a/files/TG_bot_VPN-main/main.py b/files/TG_bot_VPN-main/main.py new file mode 100644 index 0000000..b6ec0b1 --- /dev/null +++ b/files/TG_bot_VPN-main/main.py @@ -0,0 +1,31 @@ +from datetime import datetime + +from loader import bot, app_logger, scheduler +import handlers # noqa +from telebot.custom_filters import StateFilter +from utils.set_bot_commands import set_default_commands +from database.models import create_models +from config_data.config import ADMIN_ID +from utils.tasks import check_and_revoke_keys, send_renewal_notifications + +if __name__ == '__main__': + create_models() + app_logger.debug("Подключение к базе данных...") + bot.add_custom_filter(StateFilter(bot)) + set_default_commands(bot) + app_logger.info("Загрузка базовых команд...") + # Очистка всех задач + scheduler.remove_all_jobs() + + # Запуск проверки каждые 5 минут + scheduler.add_job(check_and_revoke_keys, 'interval', minutes=5, next_run_time=datetime.now()) + scheduler.start() + app_logger.info("Запущен планировщик для проверки подписки пользователей и отзыва ключей.") + + # Каждые 8 часов отправляем уведомления о продлении ключей + # scheduler.add_job(send_renewal_notifications, 'interval', hours=8, args=[scheduler], next_run_time=datetime.now()) + # app_logger.info("Запущен планировщик для продления ключей.") + + app_logger.info(f"Бот @{bot.get_me().username} запущен.") + bot.send_message(ADMIN_ID, "Бот запущен.") + bot.infinity_polling() diff --git a/files/TG_bot_VPN-main/requirements.txt b/files/TG_bot_VPN-main/requirements.txt new file mode 100644 index 0000000..af3951b --- /dev/null +++ b/files/TG_bot_VPN-main/requirements.txt @@ -0,0 +1,9 @@ +peewee +pyTelegramBotAPI +python-dotenv +requests +paramiko +pexpect +cryptography +qrcode[pil] +APScheduler diff --git a/files/TG_bot_VPN-main/states/__init__.py b/files/TG_bot_VPN-main/states/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/files/TG_bot_VPN-main/states/__init__.py @@ -0,0 +1 @@ + diff --git a/files/TG_bot_VPN-main/states/states.py b/files/TG_bot_VPN-main/states/states.py new file mode 100644 index 0000000..8ee0f11 --- /dev/null +++ b/files/TG_bot_VPN-main/states/states.py @@ -0,0 +1,24 @@ +from telebot.handler_backends import State, StatesGroup + + +class AdminPanel(StatesGroup): + get_option = State() + get_users = State() + get_servers = State() + add_server = State() + get_vpn_keys = State() + delete_server = State() + delete_vpn = State() + send_message = State() + + add_vpn_key_name = State() + add_vpn_key_key = State() + save_vpn_key = State() + + +class SubscribedState(StatesGroup): + subscribe = State() + +class GetVPNKey(StatesGroup): + get_server = State() + get_key = State() diff --git a/files/TG_bot_VPN-main/utils/__init__.py b/files/TG_bot_VPN-main/utils/__init__.py new file mode 100644 index 0000000..ab665e1 --- /dev/null +++ b/files/TG_bot_VPN-main/utils/__init__.py @@ -0,0 +1 @@ +from . import misc diff --git a/files/TG_bot_VPN-main/utils/functions.py b/files/TG_bot_VPN-main/utils/functions.py new file mode 100644 index 0000000..44e9433 --- /dev/null +++ b/files/TG_bot_VPN-main/utils/functions.py @@ -0,0 +1,65 @@ +from config_data.config import DEFAULT_COMMANDS, ADMIN_COMMANDS +from loader import bot, app_logger +import json + + + +def is_subscribed(chat_id, user_id): + """ + Функция для проверки, подписан ли пользователь на канал. + :param chat_id: id канала + :param user_id: id пользователя + :return: bool + """ + result = bot.get_chat_member(chat_id, user_id) + if result.status in ("creator", "administrator", "member", "restricted"): + return True + return False + + +def valid_ip(address): + try: + host_bytes = address.split('.') + valid = [int(b) for b in host_bytes] + valid = [b for b in valid if 0 <= b <= 255] + return len(host_bytes) == 4 and len(valid) == 4 + except (TypeError, ValueError, IndexError): + return False + +def convert_amnezia_xray_json_to_vless_str(amnezia_str: str) -> str | None: + """ + Функция для конвертации AMnezia Xray JSON в VLESS строчку + :param amnezia_str: JSON-строка с настройками Amnezia Xray + :return: VLESS-строка либо None объект + """ + try: + json_object = json.loads(amnezia_str) + except Exception: + app_logger.error("Не удалось преобразовать JSON в объект!") + return None + try: + outbounds = json_object["outbounds"][0]["settings"]["vnext"][0] + stream_settings = json_object["outbounds"][0]["streamSettings"] + + address_and_port = f"{outbounds['address']}:{outbounds['port']}" + flow = outbounds["users"][0]["flow"] + user_id = outbounds["users"][0]["id"] + type_of_net = stream_settings["network"] + security = stream_settings["security"] + fp = stream_settings["realitySettings"]["fingerprint"] + pbk = stream_settings["realitySettings"]["publicKey"] + sni = stream_settings["realitySettings"]["serverName"] + sid = stream_settings["realitySettings"]["shortId"] + + url = (f"vless://{user_id}@{address_and_port}?flow={flow}&type={type_of_net}&" + f"security={security}&fp={fp}&sni={sni}&pbk={pbk}&sid={sid}") + except Exception as ex: + app_logger.error(f"Не получилось конвертировать конфиг Amnezia!\n{ex}") + return None + return url + +def get_all_commands_bot(): + total_commands = [f"/{elem[0]}" for elem in DEFAULT_COMMANDS] + total_commands.extend([f"/{elem[0]}" for elem in ADMIN_COMMANDS]) + total_commands.extend(["🌍 Серверы", "❓ Справка", "📖 Инструкция"]) + return total_commands diff --git a/files/TG_bot_VPN-main/utils/generate_vpn_keys.py b/files/TG_bot_VPN-main/utils/generate_vpn_keys.py new file mode 100644 index 0000000..b9981c7 --- /dev/null +++ b/files/TG_bot_VPN-main/utils/generate_vpn_keys.py @@ -0,0 +1,458 @@ +import base64 +import os +import json +import uuid +import tempfile +import paramiko +import qrcode +import secrets +import random +from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.hazmat.primitives import serialization +from loader import app_logger +from database.models import VPNKey, Server +import copy +from config_data.config import ( + DEFAULT_SERVER_USER, + DEFAULT_SERVER_PASSWORD, + XRAY_CONFIG_PATH, + QR_CODE_DIR, + XRAY_REALITY_FINGERPRINT, + DOMAINS_LIST, +) + +def generate_x25519_keys_base64() -> dict: + """ + Генерирует пару ключей X25519 и возвращает их в base64-формате, + что соответствует требованиям Xray Reality. + """ + private_key = x25519.X25519PrivateKey.generate() + public_key = private_key.public_key() + private_bytes = private_key.private_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PrivateFormat.Raw, + encryption_algorithm=serialization.NoEncryption() + ) + public_bytes = public_key.public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw + ) + private_b64 = base64.b64encode(private_bytes).decode('utf-8') + public_b64 = base64.b64encode(public_bytes).decode('utf-8') + return {"private": private_b64, "public": public_b64} + + +def remote_generate_xray_keys(server_obj: Server) -> dict: + cmd = "xray x25519" + output = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=cmd, + timeout=30 + ) + keys = {} + for line in output.splitlines(): + if line.lower().startswith("private"): + keys["private"] = line.split(":", 1)[1].strip() + elif line.lower().startswith("public"): + keys["public"] = line.split(":", 1)[1].strip() + if "private" in keys and "public" in keys: + return keys + else: + app_logger.error("Не удалось сгенерировать ключи через 'xray x25519' на сервере.") + return None + + +def ensure_reality_params(config_template: dict, server_obj: Server) -> tuple: + # Выбираем случайный домен из DOMAINS_LIST + domain = random.choice(DOMAINS_LIST) + # Генерируем ключи (приватный и публичный) на сервере + keys = remote_generate_xray_keys(server_obj) + if not keys: + raise Exception("Удаленная генерация ключей не удалась") + # Генерируем список shortIds (например, 6 значений) + short_ids = [secrets.token_hex(4) for _ in range(6)] + + # Создаем глубокую копию шаблона, чтобы не изменять оригинал + config = copy.deepcopy(config_template) + + # Обновляем параметры в секции realitySettings + reality = config["inbounds"][0]["streamSettings"]["realitySettings"] + reality["dest"] = f"{domain}:443" + reality["serverNames"] = [domain] + reality["privateKey"] = keys["private"] + reality["publicKey"] = keys["public"] # <-- Добавляем поле publicKey + reality["shortIds"] = short_ids + + # Обновляем домены в routing.rules (если указаны) + for rule in config.get("routing", {}).get("rules", []): + if "domain" in rule: + rule["domain"] = [domain] + + # Возвращаем обновленный конфиг и публичный ключ для формирования VLESS-ссылки + return config, keys["public"] + + +SECURE_XRAY_CONFIG = { + "log": { + "loglevel": "debug" + }, + "inbounds": [ + { + "listen": "0.0.0.0", + "port": 443, + "protocol": "vless", + "settings": { + "clients": [], + "decryption": "none" + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "dest": "", + "serverNames": [], + "privateKey": "", + "shortIds": [], + "publicKey": "" + } + }, + "sniffing": { + "enabled": True, + "destOverride": ["http", "tls", "quic"] + } + } + ], + "outbounds": [ + { + "protocol": "freedom", + "tag": "direct" + }, + { + "protocol": "blackhole", + "tag": "block" + } + ] +} + + +def execute_ssh_command(ip: str, username: str, password: str, command: str, timeout: int = 60) -> str: + client = paramiko.SSHClient() + try: + client.load_system_host_keys() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect( + hostname=ip, + username=username, + password=password, + timeout=10, + allow_agent=False, + look_for_keys=False + ) + stdin, stdout, stderr = client.exec_command(command, timeout=timeout, get_pty=True) + output = stdout.read().decode('utf-8').strip() + err = stderr.read().decode('utf-8').strip() + if err: + app_logger.error(f"Ошибка выполнения команды на {ip}: {err}") + return output + except Exception as ex: + app_logger.error(f"Ошибка при подключении к {ip}: {ex}") + return "" + finally: + client.close() + + + + +def setup_server(server_obj: Server) -> bool: + """ + Настраивает сервер для работы VPN. + Если возникают какие-то ошибки при установке пакетов - + https://timeweb.cloud/docs/unix-guides/troubleshooting-unix/ustranenie-oshibki-could-not-get-lock-var-lib-dpkg-lock + + Алгоритм: + 1. Подключение по SSH с правами администратора. + 2. Проверка наличия DEFAULT_SERVER_USER. Если отсутствует, создаём пользователя и устанавливаем пароль. + Для установки пароля используется команда chpasswd, что исключает необходимость интерактивного ввода. + 3. С использованием SFTP загружается обновлённый и безопасный конфиг Xray. + 4. Перезапуск службы Xray. + + :param server_obj: объект Server с данными для подключения. + :return: True, если настройка прошла успешно, иначе False. + """ + try: + # Подключаемся к серверу под администратором + with paramiko.SSHClient() as ssh_admin: + ssh_admin.load_system_host_keys() + ssh_admin.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh_admin.connect( + hostname=server_obj.ip_address, + username=server_obj.username, + password=server_obj.password, + timeout=10, + allow_agent=False, + look_for_keys=False + ) + # Проверяем наличие пользователя DEFAULT_SERVER_USER + check_cmd = f"id {DEFAULT_SERVER_USER}" + stdin, stdout, stderr = ssh_admin.exec_command(check_cmd, timeout=30) + error_output = stderr.read().decode('utf-8').lower() + if "no such user" in error_output: + app_logger.info( + f"Пользователь {DEFAULT_SERVER_USER} не найден на сервере {server_obj.location}. Создаем его.") + # Создаем пользователя + ssh_admin.exec_command(f"useradd -m -s /bin/bash -G sudo {DEFAULT_SERVER_USER}", timeout=20) + # Устанавливаем пароль с помощью chpasswd (без интерактивного ввода) + passwd_cmd = f'echo "{DEFAULT_SERVER_USER}:{DEFAULT_SERVER_PASSWORD}" | chpasswd' + ssh_admin.exec_command(passwd_cmd, timeout=20) + app_logger.info(f"Пользователь {DEFAULT_SERVER_USER} создан и пароль установлен.") + else: + app_logger.info(f"Пользователь {DEFAULT_SERVER_USER} уже существует") + update_jq_cmd = ( + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S apt update && " + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S apt upgrade -y && " + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S apt install -y jq" + ) + execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=update_jq_cmd, + timeout=300 + ) + app_logger.info(f"Системные компоненты обновлены, зависимости установлены.") + xray_check_cmd = "which xray" + xray_installed = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=xray_check_cmd, + timeout=30 + ) + if not xray_installed: + app_logger.info("Xray не найден, начинаю установку с использованием официального скрипта.") + install_xray_cmd = ( + "curl -O https://raw.githubusercontent.com/XTLS/Xray-install/main/install-release.sh && " + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S bash install-release.sh" + ) + output_install_xray = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=install_xray_cmd, + timeout=300 + ) + app_logger.info(f"Установка Xray выполнена успешно. Вывод: {output_install_xray}") + else: + app_logger.info("Xray уже установлен. Сервер настроен") + return True + + # Разрешаем 443 порт + allow_443_port_command = ( + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S ufw allow 443/tcp && " + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S netstat -tulpn | grep ':443'" + ) + port_output = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=allow_443_port_command, + timeout=300 + ) + app_logger.info(f"Порт 443 разрешен. Проверка: {port_output}") + # Подключаемся для загрузки конфигурации Xray + ssh = paramiko.SSHClient() + ssh.load_system_host_keys() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + hostname=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + timeout=10, + allow_agent=False, + look_for_keys=False + ) + sftp = ssh.open_sftp() + + # заполняем конфигурацию данными + final_config, public_key = ensure_reality_params(SECURE_XRAY_CONFIG, server_obj) + + # Сохраняем public key в модель сервера + server_obj.public_key = public_key + server_obj.save() + + # Сохраняем локально безопасный конфиг Xray + local_config_path = os.path.join(tempfile.gettempdir(), f"secure_xray_config_{server_obj.id}.json") + with open(local_config_path, "w") as f: + json.dump(final_config, f, indent=2) + # Загружаем конфиг на сервер + + temp_remote_path = f"/tmp/secure_xray_config_{server_obj.id}.json" + sftp.put(local_config_path, temp_remote_path) + app_logger.info(f"Конфигурационный файл загружен во временную директорию: {temp_remote_path}") + + # Перемещаем файл с помощью sudo, чтобы обойти ограничение прав доступа. + move_cmd = f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S mv {temp_remote_path} {XRAY_CONFIG_PATH}' + move_output = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=move_cmd, + timeout=30 + ) + app_logger.info(f"Файл конфигурации перемещён в {XRAY_CONFIG_PATH}. Вывод: {move_output}") + sftp.close() + ssh.close() + + # Проверяем конфиг файл на валидность + test_config = f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S xray run -test -confdir /usr/local/etc/xray/' + test_output = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=test_config, + timeout=30 + ) + app_logger.info(f"Конфиг проверен. Вывод: {test_output}") + + # Синхронизируем время на сервере + time_syn_cmd = f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S timedatectl set-ntp true' + execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=time_syn_cmd, + timeout=30 + ) + app_logger.info(f"Время на сервере синхронизировано.") + + # Перезапускаем службу Xray для применения конфигурации + restart_cmd = f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S systemctl restart xray' + restart_output = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=restart_cmd, + timeout=30 + ) + app_logger.info(f"Служба Xray перезапущена. Вывод: {restart_output}") + return True + except Exception as ex: + app_logger.error(f"Ошибка при настройке сервера {server_obj.location}: {ex}") + return False + + +def generate_key(server_obj: Server) -> VPNKey | None: + """ + Генерирует новый VPN ключ для сервера. + + Алгоритм: + 1. Генерация уникального UUID для клиента. + 2. Обновление конфигурации Xray на сервере с помощью утилиты jq (добавление нового клиента в список). + 3. Перезапуск службы Xray. + 4. Формирование VLESS-ссылки с параметрами Xray Reality. + 5. Генерация QR-кода для VLESS-ссылки. + 6. Создание записи VPNKey в базе данных. + + :param server_obj: объект Server. + :return: объект VPNKey с рабочей VLESS‑ссылкой и путем к QR‑коду, либо None при ошибке. + """ + try: + # Шаг 1. Генерация UUID для нового клиента + client_uuid = str(uuid.uuid4()) + app_logger.info(f"Сгенерирован новый UUID для клиента: {client_uuid}") + + # Шаг 2. Обновление конфигурации Xray через jq + update_cmd = ( + f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S sh -c ' + f'"jq \'.inbounds[0].settings.clients += [{{\\"id\\": \\"{client_uuid}\\", ' + f'\\"flow\\": \\"xtls-rprx-vision\\"}}]\' {XRAY_CONFIG_PATH} > {XRAY_CONFIG_PATH}.tmp && ' + f'mv {XRAY_CONFIG_PATH}.tmp {XRAY_CONFIG_PATH}"' + ) + update_output = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=update_cmd, + timeout=30 + ) + app_logger.info(f"Конфигурация Xray обновлена. Вывод: {update_output}") + + # Шаг 3. Перезапуск службы Xray + restart_cmd = f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S systemctl restart xray' + restart_output = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=restart_cmd, + timeout=30 + ) + app_logger.info(f"Служба Xray перезапущена. Вывод: {restart_output}") + + # Шаг 4. Формирование VLESS-ссылки + # Получаем данные из конфиг файла + config_content = execute_ssh_command( + ip=server_obj.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=f"cat {XRAY_CONFIG_PATH}", + timeout=30 + ) + try: + config_json = json.loads(config_content) + # Извлекаем данные из секции realitySettings во втором inbound (индекс 1) + reality_settings = config_json["inbounds"][0]["streamSettings"]["realitySettings"] + # Предполагаем, что в конфиге теперь есть оба параметра: publicKey и serverNames + server_name = reality_settings.get("serverNames", [None])[0] + public_key = server_obj.public_key + short_id = random.choice(reality_settings["shortIds"]) + if not server_name or not public_key or not short_id: + raise ValueError("Недостаточно параметров Xray Reality в конфиге.") + except Exception as e: + app_logger.error(f"Ошибка при парсинге конфигурационного файла: {e}") + return None + + vless_link = ( + f"vless://{client_uuid}@{server_obj.ip_address}:443?" + f"security=reality&" + f"encryption=none&" + f"flow=xtls-rprx-vision&" + f"type=tcp&" + f"fp={XRAY_REALITY_FINGERPRINT}&" + f"sni={server_name}&" + f"pbk={public_key}&" + f"sid={short_id}#GuardVPN" + ) + app_logger.info(f"Сформирована VLESS ссылка: {vless_link}") + + + # Шаг 5. Генерация QR-кода + key_number = len(server_obj.keys) + 1 if hasattr(server_obj, "keys") else 1 + qr_code_filename = f"vpn_key_{server_obj.id}_{key_number}.png" + qr_code_path = os.path.join(QR_CODE_DIR, qr_code_filename) + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_H, + box_size=10, + border=4 + ) + qr.add_data(vless_link) + qr.make(fit=True) + img = qr.make_image(fill_color="black", back_color="white") + img.save(qr_code_path) + app_logger.info(f"QR-код сгенерирован и сохранён по пути: {qr_code_path}") + + # Шаг 6. Создание записи VPNKey в БД с использованием peewee. + vpn_key = VPNKey.create( + server=server_obj, + name=f"VPN Key {server_obj.location} #{key_number}", + key=vless_link, + qr_code=qr_code_path, + is_valid=True + ) + app_logger.info(f"VPN ключ успешно создан: {vpn_key.key}") + return vpn_key + except Exception as ex: + app_logger.error(f"Ошибка при генерации VPN ключа для сервера {server_obj.location}: {ex}") + return None diff --git a/files/TG_bot_VPN-main/utils/misc/__init__.py b/files/TG_bot_VPN-main/utils/misc/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/files/TG_bot_VPN-main/utils/misc/__init__.py @@ -0,0 +1 @@ + diff --git a/files/TG_bot_VPN-main/utils/set_bot_commands.py b/files/TG_bot_VPN-main/utils/set_bot_commands.py new file mode 100644 index 0000000..6494889 --- /dev/null +++ b/files/TG_bot_VPN-main/utils/set_bot_commands.py @@ -0,0 +1,8 @@ +from telebot.types import BotCommand +from config_data.config import DEFAULT_COMMANDS + + +def set_default_commands(bot): + bot.set_my_commands( + [BotCommand(*i) for i in DEFAULT_COMMANDS] + ) diff --git a/files/TG_bot_VPN-main/utils/tasks.py b/files/TG_bot_VPN-main/utils/tasks.py new file mode 100644 index 0000000..be6e7c8 --- /dev/null +++ b/files/TG_bot_VPN-main/utils/tasks.py @@ -0,0 +1,112 @@ +import datetime + +from apscheduler.schedulers.background import BackgroundScheduler + +from keyboards.inline.subscribed import get_renew_markup +from loader import bot, app_logger +from database.models import VPNKey, User +from config_data.config import CHANNEL_ID, ALLOWED_USERS +from utils.functions import is_subscribed +from utils.work_vpn_keys import revoke_key + +# Глобальный словарь для хранения job_id запланированных задач по отзыву ключей +pending_revocation_jobs = {} + + +def check_and_revoke_keys(): + """ + Проверяет, подписаны ли пользователи, которым выданы VPN ключи. + Если обнаруживается, что хотя бы один пользователь, привязанный к ключу, отписался, + отправляет ему уведомление и отзывает ключ. + """ + # Получаем активные ключи + app_logger.info("Проверка пользователей...") + + active_keys = VPNKey.select().where(VPNKey.is_valid == False) + for vpn_key in active_keys: + revoke_this = False + for user in vpn_key.users: + # Если пользователь не подписан, отправляем уведомление + if not is_subscribed(CHANNEL_ID, user.user_id): + user.is_subscribed = False + user.vpn_key = None + user.save() + try: + bot.send_message(user.user_id, + "Ваш VPN ключ отозван, так как вы отписались от канала.") + app_logger.info(f"Пользователь {user.full_name} отписался от канала, " + f"поэтому его ключ {vpn_key.name} был отозван!") + # for admin_id in ALLOWED_USERS: + # bot.send_message(admin_id, + # f"Пользователь {user.full_name} отписался от канала, " + # f"поэтому его ключ {vpn_key.name} был отозван!") + except Exception as e: + app_logger.error(f"Не удалось отправить уведомление пользователю {user.user_id}: {e}") + revoke_this = True + if revoke_this: + if revoke_key(vpn_key): + app_logger.info(f"VPN ключ {vpn_key.id} отозван из-за отписки пользователя(ей).") + else: + app_logger.error(f"Ошибка при отзыве ключа {vpn_key.id}.") + + +def schedule_key_revocation(vpn_key: VPNKey, user_obj: User, scheduler: BackgroundScheduler): + """ + Планирует задачу отзыва ключа через 1 час. + Сохраняет идентификатор задачи в глобальном словаре по vpn_key.id. + """ + def revoke_job(): + # Если задача не была отменена (то есть пользователь не продлил ключ) + if vpn_key.id in pending_revocation_jobs: + try: + bot.send_message( + user_obj.user_id, + f"⌛ Время продления VPN ключа «{vpn_key.name}» истекло! Ключ был аннулирован.\n" + f"Вы можете создать новый ключ, нажав на кнопку Серверы 👇" + ) + except Exception as e: + app_logger.error(f"Ошибка отправки уведомления пользователю {user_obj.full_name}: {e}") + revoke_key(vpn_key) + user_obj.vpn_key = None + user_obj.save() + app_logger.info(f"VPN ключ {vpn_key.name} аннулирован по истечении времени продления!") + # Убираем задачу из pending_revocation_jobs + pending_revocation_jobs.pop(vpn_key.id, None) + + # Планируем выполнение через 1 час + job = scheduler.add_job(revoke_job, 'date', run_date=datetime.datetime.now() + datetime.timedelta(hours=1)) + pending_revocation_jobs[vpn_key.id] = job.id + app_logger.info(f"Запланирована задача отзыва для ключа {vpn_key.name} через 1 час.") + +def cancel_key_revocation(vpn_key: VPNKey, scheduler: BackgroundScheduler): + """ + Отменяет запланированную задачу отзыва ключа, если она существует. + """ + job_id = pending_revocation_jobs.pop(vpn_key.id, None) + if job_id: + scheduler.remove_job(job_id) + app_logger.info(f"Запланированная задача отзыва для ключа {vpn_key.name} отменена.") + + +def send_renewal_notifications(scheduler: BackgroundScheduler): + """ + Каждые 8 часов рассылает пользователям уведомления о продлении VPN ключей. + Для каждого активного (зарезервированного) ключа отправляется сообщение с inline-кнопкой. + При отправке планируется задача, которая через 1 час отзовет ключ, если пользователь не нажмёт кнопку. + """ + # Получаем список пользователей, у которых назначен VPN ключ (предполагается, что у пользователя есть vpn_key) + app_logger.info("Отправка уведомлений о продлении VPN ключей...") + + for user in User.select().where(User.vpn_key.is_null(False)): + vpn_key = user.vpn_key + try: + bot.send_message( + user.user_id, + f"⏰ Напоминаем, что срок действия вашего VPN ключа «{vpn_key.name}» скоро истечёт.\n" + "Нажмите кнопку ниже, чтобы продлить его действие.", + reply_markup=get_renew_markup(vpn_key.id) + ) + # Планируем задачу отзыва ключа через 1 час + schedule_key_revocation(vpn_key, user, scheduler) + except Exception as e: + app_logger.error(f"Ошибка отправки уведомления пользователю {user.user_id}: {e}") diff --git a/files/TG_bot_VPN-main/utils/work_vpn_keys.py b/files/TG_bot_VPN-main/utils/work_vpn_keys.py new file mode 100644 index 0000000..f030ec3 --- /dev/null +++ b/files/TG_bot_VPN-main/utils/work_vpn_keys.py @@ -0,0 +1,183 @@ +import os + +from config_data.config import DEFAULT_SERVER_PASSWORD, XRAY_CONFIG_PATH, DEFAULT_SERVER_USER +from database.models import VPNKey, Server +from loader import app_logger +from utils.generate_vpn_keys import execute_ssh_command + + +def suspend_key(vpn_key: VPNKey) -> bool: + """ + Приостанавливает действие ключа, удаляя его из конфигурации Xray на сервере. + Обновляет статус is_valid в базе данных. + """ + client_uuid = vpn_key.extract_uuid() + if not client_uuid: + app_logger.error(f"Не удалось извлечь UUID из ключа {vpn_key.id}") + return False + + server = vpn_key.server + try: + # Удаление клиента из конфигурации с помощью jq + remove_cmd = ( + f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S sh -c ' + f'"jq \'del(.inbounds[0].settings.clients[] | select(.id == \\"{client_uuid}\\"))\' ' + f'{XRAY_CONFIG_PATH} > {XRAY_CONFIG_PATH}.tmp && ' + f'mv {XRAY_CONFIG_PATH}.tmp {XRAY_CONFIG_PATH}"' + ) + output = execute_ssh_command( + ip=server.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=remove_cmd, + timeout=30 + ) + app_logger.info(f"Клиент {client_uuid} удалён из конфига. Вывод: {output}") + + # Перезапуск Xray + restart_output = execute_ssh_command( + ip=server.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S systemctl restart xray', + timeout=30 + ) + app_logger.info(f"Xray перезапущен: {restart_output}") + + # Обновление статуса ключа + vpn_key.is_valid = False + vpn_key.save() + return True + except Exception as ex: + app_logger.error(f"Ошибка при приостановке ключа {vpn_key.id}: {ex}") + return False + +def resume_key(vpn_key: VPNKey) -> bool: + """ + Возобновляет действие ключа, добавляя его обратно в конфигурацию Xray. + Обновляет статус is_valid в базе данных. + """ + client_uuid = vpn_key.extract_uuid() + if not client_uuid: + app_logger.error(f"Не удалось извлечь UUID из ключа {vpn_key.id}") + return False + + server = vpn_key.server + try: + # Добавление клиента в конфигурацию + add_cmd = ( + f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S sh -c ' + f'"jq \'.inbounds[0].settings.clients += [{{"id": "{client_uuid}", "flow": "xtls-rprx-vision"}}]\' ' + f'{XRAY_CONFIG_PATH} > {XRAY_CONFIG_PATH}.tmp && ' + f'mv {XRAY_CONFIG_PATH}.tmp {XRAY_CONFIG_PATH}"' + ) + output = execute_ssh_command( + ip=server.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=add_cmd, + timeout=30 + ) + app_logger.info(f"Клиент {client_uuid} добавлен в конфиг. Вывод: {output}") + + # Перезапуск Xray + restart_output = execute_ssh_command( + ip=server.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=f'echo {DEFAULT_SERVER_PASSWORD} | sudo -S systemctl restart xray', + timeout=30 + ) + app_logger.info(f"Xray перезапущен: {restart_output}") + + # Обновление статуса ключа + vpn_key.is_valid = True + vpn_key.save() + return True + except Exception as ex: + app_logger.error(f"Ошибка при возобновлении ключа {vpn_key.id}: {ex}") + return False + +def revoke_key(vpn_key: VPNKey) -> bool: + """ + Полностью отзывает ключ: удаляет из конфигурации Xray и из базы данных. + """ + try: + # Удаление из конфигурации + if not suspend_key(vpn_key): + return False + + # Удаление QR-кода, если он существует + if os.path.exists(vpn_key.qr_code): + os.remove(vpn_key.qr_code) + + # Удаление из базы данных + vpn_key.delete_instance() + + app_logger.info(f"Ключ {vpn_key.id} полностью отозван.") + return True + except Exception as ex: + app_logger.error(f"Ошибка при отзыве ключа {vpn_key.id}: {ex}") + return False + +def get_active_keys(server: Server) -> list[VPNKey]: + """ + Возвращает список активных ключей для указанного сервера. + """ + return list(server.keys.where(VPNKey.is_valid == True)) + +def get_inactive_keys(server: Server) -> list[VPNKey]: + """ + Возвращает список неактивных ключей для указанного сервера. + """ + return list(server.keys.where(VPNKey.is_valid == False)) + + +def cleanup_server(server: Server) -> bool: + """Полная очистка сервера от Xray и конфигов, с подробным логированием.""" + app_logger.info(f"Администратор запросил полное удаление севрера {server.location}. Начинаю очистку...") + try: + cmds = [ + # Остановка и отключение службы Xray + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S systemctl stop xray", + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S systemctl disable xray", + # Удаление Xray с помощью официального скрипта + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S bash -c 'curl -sL https://raw.githubusercontent.com/XTLS/Xray-install/main/install-release.sh | bash -s -- remove'", + # Очистка пакетов и конфигов + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S apt-get purge xray -y", + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S rm -rf /usr/local/etc/xray/", + # Восстановление фаервола: удаление правила для 443/tcp + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S ufw delete allow 443/tcp", + # Удаление созданного пользователя + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S pkill -9 -u {DEFAULT_SERVER_USER} || true", + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S userdel -rf {DEFAULT_SERVER_USER} || true", + # Очистка cron задач + f"echo {DEFAULT_SERVER_PASSWORD} | sudo -S crontab -u {DEFAULT_SERVER_USER} -r || true" + ] + full_output = "" + for cmd in cmds: + output = execute_ssh_command( + ip=server.ip_address, + username=DEFAULT_SERVER_USER, + password=DEFAULT_SERVER_PASSWORD, + command=cmd, + timeout=60 + ) + full_output += f"\nКоманда: {cmd}\nВывод: {output}" + + # Удаляем запись о сервере из БД + server.delete_instance() + + # Удаление связанных VPN ключей + for vpn_key in VPNKey.select().where(VPNKey.server == server): + for user in vpn_key.users: + user.vpn_key = None + user.save() + app_logger.info(f"VPN ключ {vpn_key.name} удален!") + vpn_key.delete_instance() + + app_logger.info(f"Cleanup завершён. Общий вывод: {full_output}") + return True + except Exception as ex: + app_logger.error(f"Server cleanup failed: {ex}") + return False
\ No newline at end of file diff --git a/files/ae81365154ba5166845a7a9171d2248b.png b/files/ae81365154ba5166845a7a9171d2248b.png Binary files differnew file mode 100644 index 0000000..f63e4dd --- /dev/null +++ b/files/ae81365154ba5166845a7a9171d2248b.png diff --git a/files/b74e1c10b7c49ab7cc235e746af07128.png b/files/b74e1c10b7c49ab7cc235e746af07128.png Binary files differnew file mode 100644 index 0000000..4a94e09 --- /dev/null +++ b/files/b74e1c10b7c49ab7cc235e746af07128.png diff --git a/files/dbb44ce3043db021cfbb195941e52a5a.png b/files/dbb44ce3043db021cfbb195941e52a5a.png Binary files differnew file mode 100644 index 0000000..3e73252 --- /dev/null +++ b/files/dbb44ce3043db021cfbb195941e52a5a.png diff --git a/files/eafc9c46561ec70fb5744ea1d85eb1c0.png b/files/eafc9c46561ec70fb5744ea1d85eb1c0.png Binary files differnew file mode 100644 index 0000000..9293df5 --- /dev/null +++ b/files/eafc9c46561ec70fb5744ea1d85eb1c0.png diff --git a/files/telegram-cloud-photo-size-2-5220111584251211570-y.jpg b/files/telegram-cloud-photo-size-2-5220111584251211570-y.jpg Binary files differnew file mode 100644 index 0000000..fdcdbbd --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5220111584251211570-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5220111584251211572-y.jpg b/files/telegram-cloud-photo-size-2-5220111584251211572-y.jpg Binary files differnew file mode 100644 index 0000000..df18fec --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5220111584251211572-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5220111584251211573-y.jpg b/files/telegram-cloud-photo-size-2-5220111584251211573-y.jpg Binary files differnew file mode 100644 index 0000000..81b657f --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5220111584251211573-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5230966980947866123-y.jpg b/files/telegram-cloud-photo-size-2-5230966980947866123-y.jpg Binary files differnew file mode 100644 index 0000000..a2b08e7 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5230966980947866123-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5235693785730632253-y.jpg b/files/telegram-cloud-photo-size-2-5235693785730632253-y.jpg Binary files differnew file mode 100644 index 0000000..b8f7168 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5235693785730632253-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5249336126576118394-y.jpg b/files/telegram-cloud-photo-size-2-5249336126576118394-y.jpg Binary files differnew file mode 100644 index 0000000..39d0d9c --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5249336126576118394-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5255812146984502723-y.jpg b/files/telegram-cloud-photo-size-2-5255812146984502723-y.jpg Binary files differnew file mode 100644 index 0000000..7d1b522 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5255812146984502723-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5264765212471446942-y.jpg b/files/telegram-cloud-photo-size-2-5264765212471446942-y.jpg Binary files differnew file mode 100644 index 0000000..4a30a0d --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5264765212471446942-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5269638479933982570-y.jpg b/files/telegram-cloud-photo-size-2-5269638479933982570-y.jpg Binary files differnew file mode 100644 index 0000000..56fe121 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5269638479933982570-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5280728042542581828-y.jpg b/files/telegram-cloud-photo-size-2-5280728042542581828-y.jpg Binary files differnew file mode 100644 index 0000000..f2fe665 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5280728042542581828-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5310086154055444585-y.jpg b/files/telegram-cloud-photo-size-2-5310086154055444585-y.jpg Binary files differnew file mode 100644 index 0000000..d373755 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5310086154055444585-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5316919099131030050-y.jpg b/files/telegram-cloud-photo-size-2-5316919099131030050-y.jpg Binary files differnew file mode 100644 index 0000000..f3335d1 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5316919099131030050-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5337133182321027005-y.jpg b/files/telegram-cloud-photo-size-2-5337133182321027005-y.jpg Binary files differnew file mode 100644 index 0000000..d0478ac --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5337133182321027005-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5350524228924991162-y.jpg b/files/telegram-cloud-photo-size-2-5350524228924991162-y.jpg Binary files differnew file mode 100644 index 0000000..74d5997 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5350524228924991162-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5382031937209757544-y.jpg b/files/telegram-cloud-photo-size-2-5382031937209757544-y.jpg Binary files differnew file mode 100644 index 0000000..357c2cd --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5382031937209757544-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5382031937209757546-y.jpg b/files/telegram-cloud-photo-size-2-5382031937209757546-y.jpg Binary files differnew file mode 100644 index 0000000..d15ecbf --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5382031937209757546-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5386535536837126461-y.jpg b/files/telegram-cloud-photo-size-2-5386535536837126461-y.jpg Binary files differnew file mode 100644 index 0000000..05383d0 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5386535536837126461-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5399847483028401010-y.jpg b/files/telegram-cloud-photo-size-2-5399847483028401010-y.jpg Binary files differnew file mode 100644 index 0000000..3737103 --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5399847483028401010-y.jpg diff --git a/files/telegram-cloud-photo-size-2-5406976059292904279-x.jpg b/files/telegram-cloud-photo-size-2-5406976059292904279-x.jpg Binary files differnew file mode 100644 index 0000000..d0759af --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5406976059292904279-x.jpg diff --git a/files/telegram-cloud-photo-size-2-5413616564063822987-y.jpg b/files/telegram-cloud-photo-size-2-5413616564063822987-y.jpg Binary files differnew file mode 100644 index 0000000..485989a --- /dev/null +++ b/files/telegram-cloud-photo-size-2-5413616564063822987-y.jpg diff --git a/files/telegram-cloud-photo-size-4-5776315316576566340-y.jpg b/files/telegram-cloud-photo-size-4-5776315316576566340-y.jpg Binary files differnew file mode 100644 index 0000000..a370e5a --- /dev/null +++ b/files/telegram-cloud-photo-size-4-5776315316576566340-y.jpg diff --git a/files/telegram-cloud-photo-size-4-5850234924966199387-w.jpg b/files/telegram-cloud-photo-size-4-5850234924966199387-w.jpg Binary files differnew file mode 100644 index 0000000..3038b28 --- /dev/null +++ b/files/telegram-cloud-photo-size-4-5850234924966199387-w.jpg diff --git a/files/telegram-cloud-photo-size-4-5850234924966199390-w.jpg b/files/telegram-cloud-photo-size-4-5850234924966199390-w.jpg Binary files differnew file mode 100644 index 0000000..896a8e2 --- /dev/null +++ b/files/telegram-cloud-photo-size-4-5850234924966199390-w.jpg diff --git a/files/telegram-cloud-photo-size-4-5850234924966199391-w.jpg b/files/telegram-cloud-photo-size-4-5850234924966199391-w.jpg Binary files differnew file mode 100644 index 0000000..9a0ea44 --- /dev/null +++ b/files/telegram-cloud-photo-size-4-5850234924966199391-w.jpg diff --git a/files/telegram-cloud-photo-size-4-5863854399405801203-w.jpg b/files/telegram-cloud-photo-size-4-5863854399405801203-w.jpg Binary files differnew file mode 100644 index 0000000..9b1dc32 --- /dev/null +++ b/files/telegram-cloud-photo-size-4-5863854399405801203-w.jpg diff --git a/files/telegram-cloud-photo-size-4-6016931255788092228-y.jpg b/files/telegram-cloud-photo-size-4-6016931255788092228-y.jpg Binary files differnew file mode 100644 index 0000000..2a593ae --- /dev/null +++ b/files/telegram-cloud-photo-size-4-6016931255788092228-y.jpg diff --git a/files/Регулятор_оборотов_кулера.png b/files/Регулятор_оборотов_кулера.png Binary files differnew file mode 100644 index 0000000..82be635 --- /dev/null +++ b/files/Регулятор_оборотов_кулера.png diff --git a/files/СкриптыДляАдмина.jpg b/files/СкриптыДляАдмина.jpg Binary files differnew file mode 100644 index 0000000..b5f08a9 --- /dev/null +++ b/files/СкриптыДляАдмина.jpg diff --git a/files/Снимок экрана 2023-08-06 в 22.19.19.png b/files/Снимок экрана 2023-08-06 в 22.19.19.png Binary files differnew file mode 100644 index 0000000..2095b5e --- /dev/null +++ b/files/Снимок экрана 2023-08-06 в 22.19.19.png diff --git a/files/Снимок экрана 2023-10-06 в 10.40.44.png b/files/Снимок экрана 2023-10-06 в 10.40.44.png Binary files differnew file mode 100644 index 0000000..9f88ee3 --- /dev/null +++ b/files/Снимок экрана 2023-10-06 в 10.40.44.png diff --git a/files/Снимок экрана 2023-10-19 в 09.53.24.png b/files/Снимок экрана 2023-10-19 в 09.53.24.png Binary files differnew file mode 100644 index 0000000..9073ab5 --- /dev/null +++ b/files/Снимок экрана 2023-10-19 в 09.53.24.png diff --git a/files/Снимок экрана 2023-10-19 в 09.53.43.png b/files/Снимок экрана 2023-10-19 в 09.53.43.png Binary files differnew file mode 100644 index 0000000..e1067f5 --- /dev/null +++ b/files/Снимок экрана 2023-10-19 в 09.53.43.png diff --git a/files/Снимок экрана 2023-10-29 в 14.23.34.png b/files/Снимок экрана 2023-10-29 в 14.23.34.png Binary files differnew file mode 100644 index 0000000..501d850 --- /dev/null +++ b/files/Снимок экрана 2023-10-29 в 14.23.34.png diff --git a/files/Снимок экрана 2023-12-17 в 20.47.18.png b/files/Снимок экрана 2023-12-17 в 20.47.18.png Binary files differnew file mode 100644 index 0000000..a7a0f16 --- /dev/null +++ b/files/Снимок экрана 2023-12-17 в 20.47.18.png diff --git a/files/Снимок экрана 2023-12-17 в 20.47.46.png b/files/Снимок экрана 2023-12-17 в 20.47.46.png Binary files differnew file mode 100644 index 0000000..df5df20 --- /dev/null +++ b/files/Снимок экрана 2023-12-17 в 20.47.46.png diff --git a/files/Снимок экрана 2023-12-17 в 20.53.26.png b/files/Снимок экрана 2023-12-17 в 20.53.26.png Binary files differnew file mode 100644 index 0000000..0da19c5 --- /dev/null +++ b/files/Снимок экрана 2023-12-17 в 20.53.26.png diff --git a/files/Снимок экрана 2023-12-17 в 20.55.53.png b/files/Снимок экрана 2023-12-17 в 20.55.53.png Binary files differnew file mode 100644 index 0000000..9f7dc79 --- /dev/null +++ b/files/Снимок экрана 2023-12-17 в 20.55.53.png diff --git a/files/Снимок экрана 2024-03-10 в 20.55.05.png b/files/Снимок экрана 2024-03-10 в 20.55.05.png Binary files differnew file mode 100644 index 0000000..362c4e3 --- /dev/null +++ b/files/Снимок экрана 2024-03-10 в 20.55.05.png diff --git a/files/Снимок экрана 2024-03-14 в 13.21.49.png b/files/Снимок экрана 2024-03-14 в 13.21.49.png Binary files differnew file mode 100644 index 0000000..8fe9ab2 --- /dev/null +++ b/files/Снимок экрана 2024-03-14 в 13.21.49.png diff --git a/files/Снимок экрана 2024-03-14 в 13.22.17.png b/files/Снимок экрана 2024-03-14 в 13.22.17.png Binary files differnew file mode 100644 index 0000000..1d11b2d --- /dev/null +++ b/files/Снимок экрана 2024-03-14 в 13.22.17.png diff --git a/files/Снимок экрана 2024-03-14 в 13.25.59.png b/files/Снимок экрана 2024-03-14 в 13.25.59.png Binary files differnew file mode 100644 index 0000000..f514466 --- /dev/null +++ b/files/Снимок экрана 2024-03-14 в 13.25.59.png diff --git a/files/Снимок экрана 2024-03-14 в 13.27.58.png b/files/Снимок экрана 2024-03-14 в 13.27.58.png Binary files differnew file mode 100644 index 0000000..7c63518 --- /dev/null +++ b/files/Снимок экрана 2024-03-14 в 13.27.58.png diff --git a/files/Снимок экрана 2024-03-14 в 13.29.48.png b/files/Снимок экрана 2024-03-14 в 13.29.48.png Binary files differnew file mode 100644 index 0000000..bf572ae --- /dev/null +++ b/files/Снимок экрана 2024-03-14 в 13.29.48.png diff --git a/files/Снимок экрана 2024-03-14 в 13.32.01.png b/files/Снимок экрана 2024-03-14 в 13.32.01.png Binary files differnew file mode 100644 index 0000000..e40eb19 --- /dev/null +++ b/files/Снимок экрана 2024-03-14 в 13.32.01.png diff --git a/files/Снимок экрана 2024-03-25 в 22.49.58.png b/files/Снимок экрана 2024-03-25 в 22.49.58.png Binary files differnew file mode 100644 index 0000000..7ca4a5f --- /dev/null +++ b/files/Снимок экрана 2024-03-25 в 22.49.58.png diff --git a/files/Снимок экрана 2024-09-15 в 18.55.25.png b/files/Снимок экрана 2024-09-15 в 18.55.25.png Binary files differnew file mode 100644 index 0000000..a8fca78 --- /dev/null +++ b/files/Снимок экрана 2024-09-15 в 18.55.25.png diff --git a/files/Снимок экрана 2024-09-15 в 19.02.02.png b/files/Снимок экрана 2024-09-15 в 19.02.02.png Binary files differnew file mode 100644 index 0000000..a84ef47 --- /dev/null +++ b/files/Снимок экрана 2024-09-15 в 19.02.02.png diff --git a/files/Снимок экрана 2024-09-15 в 19.10.29.png b/files/Снимок экрана 2024-09-15 в 19.10.29.png Binary files differnew file mode 100644 index 0000000..b42a047 --- /dev/null +++ b/files/Снимок экрана 2024-09-15 в 19.10.29.png diff --git a/files/Снимок экрана 2024-09-16 в 21.31.06.png b/files/Снимок экрана 2024-09-16 в 21.31.06.png Binary files differnew file mode 100644 index 0000000..0010f2b --- /dev/null +++ b/files/Снимок экрана 2024-09-16 в 21.31.06.png diff --git a/files/Снимок экрана 2024-09-16 в 21.36.18.png b/files/Снимок экрана 2024-09-16 в 21.36.18.png Binary files differnew file mode 100644 index 0000000..421a0d5 --- /dev/null +++ b/files/Снимок экрана 2024-09-16 в 21.36.18.png diff --git a/files/Снимок экрана 2024-09-16 в 21.39.34.png b/files/Снимок экрана 2024-09-16 в 21.39.34.png Binary files differnew file mode 100644 index 0000000..a884d26 --- /dev/null +++ b/files/Снимок экрана 2024-09-16 в 21.39.34.png diff --git a/files/Снимок экрана 2024-09-16 в 21.45.31.png b/files/Снимок экрана 2024-09-16 в 21.45.31.png Binary files differnew file mode 100644 index 0000000..85e2116 --- /dev/null +++ b/files/Снимок экрана 2024-09-16 в 21.45.31.png diff --git a/files/Снимок экрана 2024-09-16 в 21.45.54.png b/files/Снимок экрана 2024-09-16 в 21.45.54.png Binary files differnew file mode 100644 index 0000000..98a2112 --- /dev/null +++ b/files/Снимок экрана 2024-09-16 в 21.45.54.png diff --git a/files/Снимок экрана 2024-11-01 в 01.39.59.png b/files/Снимок экрана 2024-11-01 в 01.39.59.png Binary files differnew file mode 100644 index 0000000..c8f630a --- /dev/null +++ b/files/Снимок экрана 2024-11-01 в 01.39.59.png diff --git a/files/Снимок экрана 2025-02-02 в 18.05.19.png b/files/Снимок экрана 2025-02-02 в 18.05.19.png Binary files differnew file mode 100644 index 0000000..204dd03 --- /dev/null +++ b/files/Снимок экрана 2025-02-02 в 18.05.19.png diff --git a/files/Снимок экрана 2025-02-02 в 18.06.09.png b/files/Снимок экрана 2025-02-02 в 18.06.09.png Binary files differnew file mode 100644 index 0000000..7aadd31 --- /dev/null +++ b/files/Снимок экрана 2025-02-02 в 18.06.09.png diff --git a/files/Снимок экрана 2025-02-02 в 18.13.01.png b/files/Снимок экрана 2025-02-02 в 18.13.01.png Binary files differnew file mode 100644 index 0000000..d5428a2 --- /dev/null +++ b/files/Снимок экрана 2025-02-02 в 18.13.01.png diff --git a/files/Снимок экрана 2025-02-02 в 18.29.43.png b/files/Снимок экрана 2025-02-02 в 18.29.43.png Binary files differnew file mode 100644 index 0000000..488777c --- /dev/null +++ b/files/Снимок экрана 2025-02-02 в 18.29.43.png diff --git a/files/Снимок экрана 2025-02-02 в 18.30.02.png b/files/Снимок экрана 2025-02-02 в 18.30.02.png Binary files differnew file mode 100644 index 0000000..325061e --- /dev/null +++ b/files/Снимок экрана 2025-02-02 в 18.30.02.png diff --git a/files/Снимок экрана 2025-02-11 в 01.15.10.png b/files/Снимок экрана 2025-02-11 в 01.15.10.png Binary files differnew file mode 100644 index 0000000..b9fd663 --- /dev/null +++ b/files/Снимок экрана 2025-02-11 в 01.15.10.png diff --git a/files/Снимок экрана 2025-02-11 в 01.17.25.png b/files/Снимок экрана 2025-02-11 в 01.17.25.png Binary files differnew file mode 100644 index 0000000..b9d05c4 --- /dev/null +++ b/files/Снимок экрана 2025-02-11 в 01.17.25.png diff --git a/files/Снимок экрана 2025-05-14 в 00.35.18.png b/files/Снимок экрана 2025-05-14 в 00.35.18.png Binary files differnew file mode 100644 index 0000000..c841e3d --- /dev/null +++ b/files/Снимок экрана 2025-05-14 в 00.35.18.png diff --git a/files/Снимок экрана 2025-05-14 в 00.38.05.png b/files/Снимок экрана 2025-05-14 в 00.38.05.png Binary files differnew file mode 100644 index 0000000..e7c051a --- /dev/null +++ b/files/Снимок экрана 2025-05-14 в 00.38.05.png @@ -0,0 +1,24 @@ +#!/bin/bash + +# Скрипт для PUSH commit + +set -e + +echo "============================" +stat=$(git status | grep "modified") +if [[ -n $stat ]] +then +echo "GIT synhronization." +read -p "Input COMMIT: " text +echo "============================" +git add . +git commit -m "$text" +echo "============================" +git push +echo "============================" +git log --oneline -5 +echo "============================" +else +echo "Изменений нет !" +echo "============================" +fi
\ No newline at end of file diff --git a/telegram-cloud-photo-size-2-5253592580900375225-x.jpg.md b/telegram-cloud-photo-size-2-5253592580900375225-x.jpg.md new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/telegram-cloud-photo-size-2-5253592580900375225-x.jpg.md diff --git a/Англо-русский СЛОВАРЬ !!!.md b/Англо-русский СЛОВАРЬ !!!.md new file mode 100644 index 0000000..6a68949 --- /dev/null +++ b/Англо-русский СЛОВАРЬ !!!.md @@ -0,0 +1,268 @@ +## Общее + +| английский | русский | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| software | программное обеспечение (ПО) | +| hardware | аппаратное обеспечение, оборудование | +| data | данные, информация | +| freeware | бесплатное ПО | +| open source | ПО с открытым исходным кодом | +| code | код (в смысле исходный код программы или в смысле код/номер ошибки или статуса, например, HTTP code — код HTTP статуса) | +| source, source code | исходный код программы | +| coding, programming | программирование, разработка ПО | +| develop | разрабатывать | +| create | создавать | +| remove, delete, drop | удалять | +| compile | компилировать | +| compress | сжимать | +| archive | архивировать | +| debug | отлаживать (программу) | +| issue | проблема, вопрос, требующий решения | +| error, bug | ошибка (например, HTTP 404 Error, запрашиваемый файл не найден на сервере; или ошибка деления на ноль, или ошибка в синтаксисе программы) | +| exception | исключение (нестандартное поведение системы, которое мы обрабатываем отдельным кодом) | +| encrypt | зашифровать | +| decrypt | расшифровать | +| verify | проверить, верифицировать | +| prepare | подготовить (например, данные на отправку) | +| send | отправить (например, send request — отправить запрос) | +| get | получить (например, get name — получить имя) | +| disable | деактивировать, отключить | +| enable | активировать, включить | +| change | изменить (например, _change timezone_ — изменить временную зону) | +| utility | утилита | +| tool | инструмент | +| from scratch | с нуля (например, создавать проект с нуля) | +| above | выше (выше в тексте, например) | +| below | ниже (ниже в тексте, например) | +| under | под (например, under the hood — под капотом) | +| after | после | +| before | до | +| already | уже | +| yet | пока что | +| capabilities | возможности | +| authenticate | аутентификация, проверка подлинности (например, проверка подлинности пользователя путём проверки корректности введённого им логина и пароля) | +| authorize | авторизация, разрешить (например, проверка наличия прав на выполнение действия в системе) | +| example | пример (_for example_ — например) | +| built-in | встроенный | +| first | первый | +| last | последний | +| middle | середина, средний | +| search | поиск, искать, найти | +| find | искать, найти | +| run, execute | запустить, выполнить | +| date | дата | +| time | время | +| current | текущий (например, _current date_ — текущая дата) | +| parent | родитель, родительский | +| child | ребёнок, дочерний | +| use | использовать | +| fast | быстрый | +| slow | медленный | +| report | отчёт | +| documentation | документация | +| useful | полезный | +| work | работа, работать | +| deprecated | устаревший | +| automatic | автоматический | +| default | по умолчанию | +| dynamic | динамический | +| invalid | недействительный, недопустимый | +| hot key | горячая клавиша | + +## Сеть, Интернет + +| английский | русский | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| network | сеть | +| hostname | имя хоста (компьютера/сервера) в сети | +| domain | домен (например, yandex.ru) | +| download | загружать что-то из сети/Интернет | +| upload | выгружать что-то (например, на сервер) | +| server | сервер в сети (компьютер, отвечающий на запросы по сети, или конкретная программа, запущенная на нём, обслуживающая сетевые запросы). От слова _serve_, обслуживать | +| port | порт, чаще всего на сервере, порт 80 — стандартный порт протокола HTTP | +| request | запрос | +| connect | соединяться, подключаться (например, к серверу) | +| disconnect | отсоединиться, отключиться (например, от сервера) | +| connection | соединение | +| login | вход, войти, пройти аутентификацию (например, на сервере) | +| logout | выход, выйти (например, с сервера) | +| gateway | шлюз | +| hyperlink | гиперссылка (например, ссылка на веб-странице) | +| router | роутер, маршрутизатор (от слова _route_, маршут) | +| redirect | перенаправиперенаправить | + +## Операционные системы (ОС) + +| английский | русский | +| ----------------------------- | ------------------------------------------------------------------------------ | +| boot | загрузка, загрузить (например, загрузить ОС Linux) | +| bootloop | цикличная перезагрузка, когда устройство не может даже загрузиться | +| reboot | перезагрузить | +| shutdown | выключить | +| install, setup | установить (например, приложение) | +| uninstall | удалить установленное | +| update | обновить (например, приложение) | +| environment variable | переменная среды | +| GUI, Graphical User Interface | графический пользовательский интерфейс | +| terminal | командный терминал | +| command | команда | +| shell | командная оболочка | +| partition | раздел диска | +| root | корень (обычно имеют в виду корневого пользователя ОС с максимальными правами) | + +## Оборудование + +| английский | русский | +| ---------------------------- | ------------------ | +| laptop | ноутбук | +| keyboard | клавиатура | +| mouse | мышь | +| monitor, screen | монитор, экран | +| device | устройство | +| CPU, Central Processing Unit | процессор | +| RAM, Random Access Memory | оперативная память | +| disk | диск | + +## Пользователи, права + +| английский | русский | +| ---------- | ---------------------------------------------------- | +| user | пользователь | +| account | аккаунт, учётная запись | +| group | группа (например, пользователей) | +| access | доступ | +| allow | разрешить (например, доступ пользователю или группе) | +| deny | запретить (например, доступ пользователю или группе) | + +## Файлы, текст, интерфейс + +| английский | русский | +| ----------------- | ------------------------------------- | +| copy | копировать (например, текст или файл) | +| cut | вырезать (например, текст или файл) | +| paste | вставлять (например, текст или файл) | +| save | сохранить | +| folder, directory | папка, директория | +| text | текст | +| list | список, перечень, перечисление | +| bold | жирный | +| italic | курсив | +| underline | подчеркивание | +| image, picture | изображение | +| figure | рисунок | +| path | путь (до файла, например) | +| show | показать | +| hide | скрыть | + +## Базы данных (БД) + +| английский | русский | +| -------------------- | ------------------------------------------------------------------------------ | +| database | база данных, БД | +| query | запрос | +| storage | хранилище | +| table | таблица (например, в базе данных) | +| row | строка | +| column | колонка | +| select | выбрать (например, данные из БД) | +| insert | вставить (например, данные в БД) | +| drop, delete, remove | удалить, убрать | +| join | объединить (например, список в строку или объединить данные нескольких таблиц) | +| split | разделить (например, строку по символу запятой) | +| index | индекс (например, поисковый индекс БД) | +| limit | ограничение | + +## Из языков программирования + +| английский | русский | +| --------------- | --------------------------------------------------------------- | +| data type | тип данных | +| expression | выражение | +| quotes | кавычки, символы кавычек | +| variable | переменная | +| constant | константа | +| string | строка | +| char, symbol | символ, знак | +| number | число | +| append | добавить, присоединить (например, элемент в конец списка) | +| open | открыть (например, файл) | +| read | прочесть (например, из файла или из сети) | +| write | записать (например, в файл или в сеть) | +| array | массив | +| list | список | +| dictionary | словарь ключ-значение | +| key, keyword | ключ | +| value | значение | +| set | множество | +| queue | очередь | +| tuple | кортеж | +| object | объект | +| class | класс | +| public | публичный (например, публичный метод) | +| private | приватный (например, приватный метод) | +| protected | защищённый (например, защищённый метод) | +| static | статичный (например, статичный метод) | +| function | функция | +| function call | вызов функции | +| parameter | параметр | +| argument | аргумент | +| method | метод | +| module | модуль | +| package | пакет | +| import | импорт (например, кода из библиотеки) | +| return | возврат, вернуть (например, возврат значения из функции) | +| exit | выход, выйти (например, выйти из программы) | +| initialize | инициализировать | +| configure | конфигурировать | +| settings | настройки | +| implement | реализовать | +| abstract | абстрактный (например, абстрактный класс) | +| interface | интерфейс | +| extend | расширить | +| length | длина (например, строки) | +| loop | цикл | +| iteration | итерация (например, итерация цикла) | +| nested | вложенный (вложенный цикл, например | +| count | количество (например, элементов с массиве) | +| sort | сортировать | +| true / false | правда / ложь | +| boolean | булево, булевский тип данных (имеющий значение правда или ложь) | +| definition | определение (например, функции) | +| scope | область видимости | +| formatting | форматирование | +| placeholder | заполнитель, место для размещения чего-то | +| process | процесс | +| thread | поток | +| multiprocessing | многопроцессорная обработка | +| asynchronous | асинхронный | +| concurrency | конкурентность | +| parallelism | параллелизм | +| security | безопасность | +| sign | подписать | + +## Из разработки + +| английский | русский | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| design | проектировать, создавать, проектирование, разработка, дизайн | +| deploy | развертывание приложения на сервере tion | +| template | шаблон | +| convention | соглашение | +| rule | правило | +| integrate | интегрировать | +| architecture | архитектура | +| object-oriented programming | ООП, объектно-ориентированное программирование | +| backend | серверное ПО | +| frontend | клиентское ПО, работающее на устройстве пользователя | +| dependency | зависимость | +| dependency injection | инъекция зависимости | +| maintain | поддерживать | +| library | библиотека | +| framework | фреймворк / каркас или набор библиотек для разработки приложений | +| MVC | архитектура модель (Model), отображение (View), контроллер (Controller) | +| clean architecture | чистая архитектура | +| DRY | Don't Repeat Yourself, принцип разработки «не повторяйся» | +| KISS | Keep It Simple Stupid, принцип разработки «не переусложняй» | +| YAGNI | You Aren't Gonna Need It, принцип разработки «вам это не понадобится» | +| SOLID | набор принципов разработки: SRP (Single Responsibility Principle, принцип единой ответственности), OCP (Open-Close Principle, принцип открытости-закрытости), LSP (Liskov Substitution Principle, принцип подстановки Лисков), ISP (Interface Segregation Principle, принцип разделения интерфейсов), DIP (Dependency Inversion Principle, принцип инверсии зависимостей) | + |
