Пингвитон
215 members
4 photos
10 links
Я @lauvenhelz и иногда пишу тут какие-то полезные штуки про Linux и Python
Download Telegram
to view and join the conversation
У вас заканчивается место на сервере и непонятно, кто его отжал. Вы посмотрели логи и очевидные места, куда пишут приложения, но везде всё законно и умеренно. Где мои 20 ГБ места? На помощь приходит это:

du -xmt 100M / | sort -n

Не выйдет за пределы файловой системы ( -x ), просмотрит от корня ( / ), покажет все файлы больше 100 МБ ( -t 100M ), покажет размер в МБ ( -m ), отстортирует в порядке возрастания ( | sort -n ).
Channel name was changed to «Пингвитон»
Примерно в 2012 году мне попался в интернетах кроссворд с регэкспами. Я его распечатала, решала на бумажке с карандашом несколько дней и была в восторге. После пыталась найти ещё, но успешна не была. А сейчас спустя годы попалось вот это: https://regexcrossword.com -- и это офигенно. Там есть туториал для начинающих, который состоит из простейших конструкций; кроссворды начинаются с простых и маленьких, а потом усложняются. А в конце вас ждёт тот самый монстр, что я решала в 2012. Отличная штука, чтобы разобраться в регэкспах, запомнить их и получить удовольствие.
Иногда нужно вывести весь файл, кроме первой строки. Например, в файле есть несколько колонок, которые вы хотите как-то проанализировать и обработать, а названия колонок мешаются. Вывести содержимое файла без первой строки можно так:

tail -n+2 /tmp/servers.txt

Эта запись значит "вывести все строки файла, начиная со второй". Подставить можно любое число в зависимости от того, сколько первых строк файла вы хотите исключить из вывода.

А вот такая запись (без `+`) приведёт к выводу последних двух строк файла:

tail -n2 /tmp/servers.txt
Часто нужно скопировать из терминала выхлоп какой-то команды. Например, я регулярно использую realpath, чтобы вывести оригинальный путь вместо симлинка или абсолютный путь вместо относительного, а затем мне нужно этот полученный путь скопировать и куда-то вставить -- в другой терминал, редактор, мессенджер. Раньше я медленно и печально выделяла, а потом нажимала ctrl+shift+c. Иногда не с первого раза попадаешь в выделение, иногда в Gnome почему-то не с первого раза работает копирование. Печаль и раздражение. Теперь использую xsel -- это быстро и очень удобно.

realpath tmp/ | xsel -b

-- скопирует результаты команды realpath tmp/ в буфер обмена. Могу нажать ctrl+v и вставить полученный результат прямо в это сообщение: /home/lauvenhelz/tmp
Найти и удалить все .pyc файлы в директории и поддиректориях:

find . -name "*.pyc" -type f -delete

Однако, сначала лучше запустить эту же команду без -delete, чтобы убедиться, что в списке только то, что вы действительно хотите удалить:

find . -name "*.pyc" -type f

И да, очевидно, расширение можно подставить любое (:

Кроме того, можно использовать не только для файлов ( -type f ), но и для имён директорий ( -type d ) или симлинков ( -type l ), больше возможных типов см. в man find (на самом деле, тип в команде можно вообще не использовать, но, чтобы случайно не удалить лишнее, лучше указывать).
Пингвитон
У меня есть таймстемп, например, 1549886527, и я хочу быстро сконвертировать его во что-то человекочитаемое. Использую date: ~$ date -u -d @1549886527 Mon Feb 11 12:02:07 UTC 2019 На всякий случай: таймстемп -- это seconds since the epoch, а epoch -- это…
Тут ^^^ рассказывала, как сконвертировать таймстемп в человекочитаемое время из консоли. А вот так можно его в консоли сгенерировать:

~$ date +%s
1591195563

Раньше для этого запускала Python, импортировала time и выполняла time.time(), теперь можно проще и быстрее.

Однако, консольный date выводит только секунды. Если нужна большая точность, то всё равно придётся обратиться к Python:

>>> import time
>>> time.time()
1591195700.0724027
Читатель подсказывает, что date тоже умеет:

~$ date +%s.%N
1591202943.155504934

Я, правда, пока не решила, что мне проще-удобнее -- вводить это или всё же сходить в питонячью консоль и заимпортить time (:
У меня есть большой файл, который я хочу передать другому человеку. Если я нахожусь с ним в одной сети, мне не понадобится медленно и печально выкладывать его на dropbox, google drive, отправлять в Телеграм или по почте. Я могу с помощью Python в одну команду поднять HTTP сервер, сделав доступной директорию, где лежит файл, и другой человек скачает его напрямую.

Вот моя директория, где лежит файл, который я хочу сделать доступным для скачивания:

~/tmp/dir-to-share$ ls -la 
total 188088
drwxr-xr-x 2 lauvenhelz lauvenhelz 4096 Jun 9 21:06 .
drwxr-xr-x 6 lauvenhelz lauvenhelz 4096 Jun 9 21:05 ..
-rw-r--r-- 1 lauvenhelz lauvenhelz 192587776 Jun 9 21:06 wetches.db


В этой директории запускаю следующее:

~/tmp/dir-to-share$ python -m http.server 
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...


(это для python3; для python2 замените http.server на SimpleHTTPServer )

Всё. Сервер поднят. По дефолту поднимается на 8000 порту. Если дефолтное поведение забылось, то порт любезно пишется в первой же строке после запуска сервиса (:

Осталось узнать свой ip внутри сети и отдать ссылку другому человеку. Запускаем ip r, смотрим src для маршрута по умолчанию:

~$ ip r | head -1
default via 192.168.1.1 dev wlp2s0 proto dhcp src 192.168.1.11 metric 302


Моя ссылка, чтобы расшарить файл: http://192.168.1.11:8000/ -- человек из одной со мной сети может скачать по ней файл. Не забудьте остановить сервис (нажать ctl+c), когда файл будет скачан.
Полагаю, все однажды видели или использовали вот эту конструкцию:

try:
<какой-то код>
except <какой-то тип эксепшена>:
pass


Это обычный способ реализации логики вида "нужно поймать эксепшен конкретного типа, проигнорировать его и продолжить работу". В стандартной библиотеке есть более лаконичный способ:

from contextlib import suppress

with suppress(<какой-то тип эксепшена>):
<какой-то код>


Это контекстный менеджер suppress, который импортируется из модуля contextlib. Ему передаётся тип эксепшена (или типы, можно несколько, да), который необходимо "проглотить", дальше в теле with пишем код, который эти эксепшены может выкинуть. Если эксепшен возникнет, он будет пойман и проигнорирован, а управление перейдёт к первой строчке после блока with.

Документация стандартной библиотеки напоминает, что "как и с любым другим механизмом, полностью подавляющим эксепшены, этот контекстный менеджер следует использоваться только в конкретных специфических случаях, когда молчаливое продолжение работы программы после возникновения эксепшена точно является правильным поведением".

В общем, не глотайте эксепшены без повода, процесс дебага может стать очень болезненным (:

Хороший пример, когда этот контекстный менеджер удобно использовать, -- удаление файла. Я хочу почистить старые файлы перед запуском программы, но может быть так, что это первый запуск и старых файлов ещё не накопилось. Делаю так:

with suppress(FileNotFoundError):
os.remove('my-file.txt')


Этот контекстный менеджер доступен, начиная с Python 3.4.

Если вы используете более старые версии Питона, то вы можете добавить в проект собственную реализацию, которая будет выглядеть так:

from contextlib import contextmanager

@contextmanager
def suppress(*exceptions):
try:
yield
except exceptions:
pass
Я тут писала письмо пользователям, где доносила мысль, что для влючения некоторого функционала нужно добавить переменную в конфиг. И для облегчения пользовательской жизни (чтобы им не нужно было открывать файл, вставлять туда данные, сохранять и закрывать файл), решила использовать echo.

Команда сначала выглядела как-то так:

echo 'BOO = True' >> /my/project/local.py


Предполагается, что команда должна добавить в конец файла новую строчку с переменной. Так вот.

Не надо так.

Что может пойти не так? В local.py уже могут быть данные. Но мы же используем >> , это как раз про добавление в конец файла, разве нет? Да, но кто сказал, что добавление данных в конец файла == добавление данных на новую строку? Чтобы новые данные встали на новую строку, предыдущая строка должна заканчиваться \n. Где гарантия, что при предыдущих изменениях файла этот \n там появился? А вдруг файл выглядит вот так?

~$ cat /tmp/local.py 
FOO = False~$


Если для такого файла я воспользуюсь командой из начала, то в итоге получу:

~$ echo 'BOO = True' >> /tmp/local.py
~$ cat /tmp/local.py
FOO = FalseBOO = True


И конфиг, очевидно, не будет работать. Что делать? Вставлять \n самостоятельно перед своими данными. Однако, если просто добавить \n в команду, снова получится не то:

~$ echo '\nBOO = True' >> /tmp/local.py
~$ cat /tmp/local.py
FOO = False\nBOO = True


Чтобы всё это заработало, echo нужно передать опцию -e (enable interpretation of backslash escapes):

~$ echo -e '\nBOO = True' >> /tmp/local.py
~$ cat /tmp/local.py
FOO = False
BOO = True


Теперь конфиг в безопасности.
Несколько минут назад недоумённо смотрела на это:

# python3
>>> round(7.5)
8
>>> round(10.5)
10


Вы видите то, что вижу я? 7.5 округляется вверх, а 10.5 округляется вниз. Это в третьем Питоне.

А вот во втором в обоих случаях округляется вверх:

# python2
>>> round(7.5)
8.0
>>> round(10.5)
11.0


Оказалось, что в Python 3 используется "округление до ближайшего чётного", то есть 7,5 -> 8, 10,5 -> 10. Это даже написано в документации к round(): rounding is done toward the even choice. Википедия объясняет такой подход тем, что он minimizes the expected error when summing over rounded figures. То есть если вы складываете много округлённых чисел, то итоговая погрешность будет намного меньше при таком подходе. Однако, в случае, когда вам нужно посчитать, сколько двухместных столов вам нужно на группы из 20 и 21 человека -- у вас могут быть проблемы. По логике третьего Питона на любую из этих групп понадобится по 10 столов:

>>> round(20 / 2)
10
>>> round(21 / 2)
10


Бедный неприкаянный последний участник, которому не досталось место за столом из-за округления ):
Короче, будьте бдительны с round().

В случаях, когда вам точно всегда нужно округление вверх, вместо round() можно использовать:

1) либо math.ceil() -- округление вверх из модуля math (универсальный способ для любых делителей):

>>> import math
>>> math.ceil(20 / 2)
10
>>> math.ceil(21 / 2)
11


2) либо вот так (когда делим на 2):

>>> n = 20 
>>> n // 2 + n % 2
10
>>> n = 21
>>> n // 2 + n % 2
11


3) либо вот так (тоже когда делим на 2):

>>> n = 20 
>>> (n + 1) // 2
10
>>> n = 21
>>> (n + 1) // 2
11


4) либо вот так (для любого делителя):

>>> a = 2
>>> n = 20
>>> (n + a - 1) // a
10
>>> n = 21
>>> (n + a - 1) // a
11
Если вы не знаете, куда на сервере положить какие-то файлы (например, относящиеся к вашему сервису -- исполняемые, кэши, статику, логи, вот это всё), то почитать о том, "как правильно" и для чего вообще на машинах существуют всякие /usr/share, /usr/lib, /var/lib, /lib, /etc и пр., можно тут:

man hier

hier от hierarchy. Содержит в себе "description of the filesystem hierarchy". Там мало, но полезно.
Пересмотрела старый видос Раймонда Хеттингера (core-разработчик Python) про "красивый, идиотматичный Питон" и вслед за Раймондом хочу напомнить, что вот это:

twitter_search('@lauvenhelz', retweets=False, numtweets=20, popular=True)

-- лучше вот этого:

twitter_search('@lauvenhelz', False, 20, True)

Clarify function calls with keyword arguments. Если передаваемые аргументы не говорят сами за себя, кем они являются, указывайте имя параметра. Не стоит заставлять себя и других людей ходить в объявление функции, чтобы понять, с чем именно она тут вызывается.

Вот хорошее на эту же тему:
Увидел у Мэтта Годболта в докладе хорошо сформулированное, и с вами поделюсь.
А у нас снова рубрика #ненадотак

Я регулярно сравниваю какие-то хеши. И регулярно я делаю это глазами, сравнивая несколько первых и последних символов. Если они совпадают, всё ок, хеши одинаковые, едем дальше.

Так вот. Не надо так (: Следите:

~$ echo -n "subtitle illusive planes" | md5sum
4188d4cdcf2be92a112bdb8ce4500243 -
~$ echo -n "wantings premises forego" | md5sum
4188d209a75e1a9b90c6fe3efe300243 -


Будьте бдительны, сравнивайте хеши полностью, а не только начало-конец.

Для наглядности картинкой:
Пример из книги The Joy of Cryptography by Mike Rosulek.