uz
Feedback
BashTex | Linux

BashTex | Linux

Kanalga Telegram’da o‘tish

Авторский канал для тех, кто хочет глубже погрузиться в мир Linux. Подойдет для разработчиков, системных администраторов и DevOps Реклама: @dad_admin

Ko'proq ko'rsatish
2 514
Obunachilar
Ma'lumot yo'q24 soatlar
-37 kun
+1630 kun

Ma'lumot yuklanmoqda...

O'xshash kanallar
Ma'lumot yo'q
Muammo bormi? Iltimos, sahifani yangilang yoki bizning qo'llab-quvvatlash boshqaruvchimizga murojaat qiling>.
Kirish va chiqish esdaliklari
---
---
---
---
---
---
Obunachilarni jalb qilish
Sentabr '26
Sentabr '26
+3
0 kanalda
Avgust '26
+54
11 kanalda
Get PRO
Iyul '26
+16
1 kanalda
Get PRO
Iyun '26
+38
5 kanalda
Get PRO
May '26
+8
0 kanalda
Get PRO
Aprel '26
+14
0 kanalda
Get PRO
Mart '26
+22
0 kanalda
Get PRO
Fevral '26
+18
0 kanalda
Get PRO
Yanvar '26
+85
5 kanalda
Get PRO
Dekabr '25
+122
19 kanalda
Get PRO
Noyabr '25
+93
2 kanalda
Get PRO
Oktabr '25
+107
7 kanalda
Get PRO
Sentabr '25
+197
20 kanalda
Get PRO
Avgust '25
+16
1 kanalda
Get PRO
Iyul '25
+130
13 kanalda
Get PRO
Iyun '25
+57
4 kanalda
Get PRO
May '25
+70
1 kanalda
Get PRO
Aprel '25
+728
23 kanalda
Get PRO
Mart '25
+129
6 kanalda
Get PRO
Fevral '25
+235
9 kanalda
Get PRO
Yanvar '25
+497
19 kanalda
Get PRO
Dekabr '24
+134
8 kanalda
Get PRO
Noyabr '24
+819
17 kanalda
Sana
Obunachilarni jalb qilish
Esdaliklar
Kanallar
16 Sentabr0
15 Sentabr0
14 Sentabr0
13 Sentabr+1
12 Sentabr0
11 Sentabr0
10 Sentabr+1
09 Sentabr0
08 Sentabr0
07 Sentabr0
06 Sentabr0
05 Sentabr+1
04 Sentabr0
03 Sentabr0
02 Sentabr0
01 Sentabr0
Kanal postlari
Sparse files: почему файл на 100 ГБ может занимать несколько мегабайт Размер файла и реально занятое место на диске не всегда совпадают. Можно создать файл, который логически имеет размер 100 ГБ, но физически занимает всего несколько мегабайт. Для этого используются sparse files. ▪️Создаём sparse-файл
truncate -s 100G test.img
Теперь:
ls -lh test.img
покажет:
-rw-r--r-- 1 user user 100G test.img
Но:
du -h test.img
может показать всего несколько килобайт. ls смотрит на логический размер, а du - на реально выделенные filesystem blocks. ▪️Что произошло Файл содержит большой диапазон нулей, для которого файловая система не обязана физически выделять блоки. Условно:
логический файл:

[данные][     огромная область нулей     ][данные]
   ↓                 ↓                       ↓
 блоки            hole                    блоки

Эта незаполненная область называется hole.

▪️Можно посмотреть оба размера

stat test.img

Обратите внимание на:

Size:
Blocks:

Size — логический размер файла.
Blocks — сколько filesystem blocks реально выделено.

▪️Почему это важно
Sparse files часто используются для образов виртуальных машин и контейнеров:

VM disk image → 100 GB
actual allocated space → 12 GB

Пока виртуальная машина не записала данные в свободные области, физическое место не требуется.

Но если эти области постепенно заполняются, реальное потребление диска начинает расти.

▪️Как проверить sparse-файл

du -h test.img
du --apparent-size -h test.img
Второй вариант показывает размер так, будто sparse-областей нет. Например:
8.0K    test.img
100G    test.img
▪️Практический сценарий Есть сервер виртуализации, и администратор видит:
ls -lh /var/lib/images/*
Образы занимают: 500G Но: du -sh /var/lib/images показывает: 180G Это не обязательно ошибка подсчёта. Часть образов может быть sparse. ▪️Важный нюанс Копирование sparse-файла не всегда сохраняет holes. Например, некоторые способы копирования могут превратить разреженный файл в обычный и реально записать нулевые блоки. Проверить результат можно снова через:
du -h file
du --apparent-size -h file
Поэтому при работе с большими VM-образами важно различать логический размер файла и реально выделенное дисковое пространство. BashTex 📱 #bash #systemd

2
du vs df: почему Linux показывает разный размер диска Иногда ситуация выглядит странно: df -h / показывает, что занято 80 GB. А: du -sh / находит только 55 GB. Кажется, что Linux «потерял» 25 GB. На самом деле du и df измеряют разные вещи. ▪️Что считает df df смотрит на файловую систему и её блоки: df -h / Он показывает, сколько места файловая система считает занятым и свободным. ▪️Что считает du du обходит дерево каталогов и суммирует пространство, связанное с найденными файлами: du -xhd1 / 2>/dev/null -x здесь важен: он не позволяет уйти на другие файловые системы. ▪️Одна из главных причин расхождения Процесс может держать открытым файл, который уже удалили. Например: rm /var/log/app.log Имя файла исчезло из каталога, но процесс всё ещё держит его открытым. Для du такого файла уже не существует. А файловая система продолжает считать его блоки занятыми. Проверить: lsof +L1 Можно увидеть что-то вроде: app 1234 ... /var/log/app.log (deleted) Пока процесс не закроет этот дескриптор, место может не освободиться. ▪️Есть и другие причины Например, du без -x может пересекать mount points и давать неожиданный результат. Или часть пространства файловой системы зарезервирована и не доступна обычным пользователям. Посмотреть файловую систему подробнее: findmnt / df -h / df -i / ▪️Практический сценарий На сервере внезапно заканчивается место: df -h / Показывает: /dev/sda2 100G 95G 5G 95% / Но: du -xsh / показывает только 70G. Первое, что стоит проверить: lsof +L1 Если там огромный (deleted) лог, причина найдена. ▪️Важный нюанс df отвечает примерно на вопрос: «Сколько блоков файловой системы сейчас занято?» А du: «Сколько места занимают доступные мне файлы в этом дереве?» Поэтому при расхождении df и du не стоит сразу запускать rm -rf по большим каталогам. Сначала нужно понять, куда именно делось пространство. BashTex 📱 #bash #systemd
207
3
file vs расширение - как Linux определяет тип файла на самом деле В Linux расширение файла само по себе ничего не определяет. Для системы: backup.tar.gz backup.jpg backup.txt это просто имена файлов. Можно спокойно сделать: mv image.jpg image.txt и содержимое файла от этого никак не изменится. ▪️Откуда тогда file знает тип Утилита file смотрит не на расширение, а на содержимое файла. file image.txt file archive.bin file unknown Например: unknown: PNG image data, 1920 x 1080, 8-bit/color RGB Даже если файл называется: photo.txt file всё равно может определить его как PNG. ▪️Главный источник - magic bytes Многие форматы имеют характерную сигнатуру в начале файла. Например, PNG начинается с: 89 50 4e 47 0d 0a 1a 0a Проверить первые байты можно: xxd -l 16 photo.png А file сопоставляет такие сигнатуры с базой magic: file --version и использует правила из базы magic. ▪️Можно обмануть расширение cp image.png document.txt file document.txt Результат всё равно будет примерно таким: document.txt: PNG image data Потому что имя изменилось, а байты остались прежними. ▪️Но не всё определяется только первыми байтами file может анализировать не только сигнатуру. Для ELF: file /bin/ls может показать архитектуру, разрядность и тип исполняемого файла. Для текстовых файлов утилита анализирует содержимое и может определить кодировку, тип текста и другие признаки. ▪️Практический сценарий Скачали файл с подозрительным расширением: download.exe Не стоит сразу доверять имени. Сначала: file download.exe А затем, если это бинарник: readelf -h download.exe или: xxd -l 32 download.exe Так можно понять, что реально лежит внутри файла. ▪️Важный нюанс file не является универсальным детектором содержимого. Если формат не имеет узнаваемой сигнатуры или несколько форматов используют похожую структуру, результат может быть приблизительным. И ещё важнее: file определяет тип по содержимому, но не говорит, что файл безопасен. Файл может называться document.pdf, определяться как PDF и при этом содержать вредоносную нагрузку. В Linux расширение - часть имени. А реальный формат гораздо чаще определяется тем, какие байты находятся внутри файла. BashTex 📱 #bash #systemd
257
4
BashTex 📱 #мем
BashTex 📱 #мем
500
5
systemd-delta: как найти, чем на самом деле переопределён unit Иногда вы открываете unit-файл и видите одну конфигурацию, а systemd ведёт себя иначе. Причина часто в drop-in override, созданном через systemctl edit. Например, основной unit: /usr/lib/systemd/system/nginx.service а дополнительная настройка лежит здесь: /etc/systemd/system/nginx.service.d/override.conf ▪️Посмотреть переопределения systemd-delta Команда показывает отличия между vendor-конфигурацией и локальными изменениями. Можно проверить конкретный unit: systemd-delta nginx.service Если сервис был переопределён, вы увидите соответствующий drop-in. ▪️Почему это важно Допустим, в основном unit указано: [Service] Restart=no А где-то в /etc/systemd/system/nginx.service.d/override.conf: [Service] Restart=always Открыв только /usr/lib/systemd/system/nginx.service, вы будете смотреть на неполную картину. systemd при запуске учитывает оба слоя. ▪️Посмотреть итоговую конфигурацию Для проверки того, что systemd реально использует: systemctl show nginx.service Например: systemctl show nginx.service -p Restart -p RestartUSec А список связанных drop-in можно увидеть так: systemctl cat nginx.service Здесь systemd покажет основной unit и применённые drop-in-файлы. ▪️Практический сценарий Сервис неожиданно перезапускается: systemctl status nginx В основном unit Restart= не настроен. Вместо того чтобы сразу менять конфигурацию, проверяем: systemd-delta nginx.service И обнаруживаем старый override: /etc/systemd/system/nginx.service.d/override.conf Именно он мог изменить поведение сервиса. ▪️Важный нюанс systemd-delta особенно полезен после обновлений дистрибутива. Пакет может заменить vendor unit в /usr/lib/systemd/system/, но локальный override в /etc/systemd/system/ продолжит действовать. Поэтому при странном поведении unit полезно проверить не только сам файл, но и что поверх него было добавлено или изменено. BashTex 📱 #bash #systemd
412
6
PSI: /proc/pressure/* - как понять, чего реально не хватает системе load average показывает, что в системе есть очередь на выполнение или I/O. Но он плохо отвечает на другой вопрос: насколько пользователи и процессы реально страдают от этого дефицита? Для этого в Linux есть PSI - Pressure Stall Information: cat /proc/pressure/cpu cat /proc/pressure/memory cat /proc/pressure/io Например: full avg10=0.00 avg60=0.01 avg300=0.00 total=... ▪️some - хотя бы часть задач была вынуждена ждать ресурс. ▪️full - все задачи соответствующей группы одновременно стояли из-за этого ресурса. avg10, avg60, avg300 - процент времени за последние 10, 60 и 300 секунд, когда происходило такое ожидание. Например: some avg10=18.5 full avg10=7.2 Это уже гораздо интереснее, чем просто увидеть высокий RAM usage. Если memory some растёт - процессы регулярно упираются в нехватку памяти. Если начинает расти memory full - система уже попадает в периоды, когда все учитываемые задачи одновременно не могут нормально продвигаться из-за memory pressure. То же самое для I/O: cat /proc/pressure/io Если I/O PSI высокий, а CPU почти не испытывает pressure, проблема может быть не в загрузке процессора, а в storage latency. А CPU PSI позволяет увидеть ситуацию, когда CPU формально загружен не на 100%, но runnable-задачи всё равно регулярно ждут свою очередь. Проверить всё сразу: for f in /proc/pressure/*; do echo "=== $f ===" cat "$f" done PSI полезен именно как метрика задержки из-за конкуренции за ресурсы, а не просто их потребления. Поэтому вместо вопроса «CPU/RAM/Disk загружены?» иногда правильнее спросить: «Какой ресурс заставляет процессы ждать?» BashTex 📱 #perfomance #linux
419
7
Хотите выйти на зарубежный рынок, но непонятно, с чего начать? Если вы: — фрилансер и хотите работать с иностранными заказчик
Хотите выйти на зарубежный рынок, но непонятно, с чего начать? Если вы: — фрилансер и хотите работать с иностранными заказчиками; — IT-специалист или развиваете IT-компанию; — владелец digital- или маркетингового агентства; — дизайнер или руководитель дизайн-студии; — эксперт и хотите продавать свои услуги за пределами локального рынка; — предприниматель, который планирует развивать бизнес за рубежом Меня зовут Альберт Сабиров. Владелец международного digital агентства. Приглашаю вас на свой канал, где я на практике работаю с зарубежными рынками и в канале делюсь опытом, который помогает разобраться: — где искать первых клиентов за рубежом; — как выйти на иностранных заказчиков; — как адаптировать и упаковать свои услуги под зарубежный рынок; — как принимать оплату из других стран; — как открывать зарубежные счета и работать с ними; — как зарегистрировать компанию за границей; — что учитывать в договорах, налогах и законодательстве; — как выстроить маркетинг и привлечение клиентов на новом рынке; — какие ошибки могут стоить денег и времени при выходе за рубеж. Если хотите перестать зависеть только от локального рынка и понять, как системно начать работать за рубежом — присоединяйтесь. Альберт Сабиров | Зарубежный рынок
261
8
— где искать первых клиентов за рубежом; — как выйти на иностранных заказчиков; — как адаптировать и упаковать свои услуги под зарубежный рынок; — как принимать оплату из других стран; — как открывать зарубежные счета и работать с ними; — как зарегистрировать компанию за границей; — что учитывать в договорах, налогах и законодательстве; — как выстроить маркетинг и привлечение клиентов на новом рынке; — какие ошибки могут стоить денег и времени при выходе за рубеж. Если хотите перестать зависеть только от локального рынка и понять, как системно начать работать за рубежом — присоединяйтесь. Альберт Сабиров | Зарубежный рынок
47
9
failglob - почему Bash должен падать при нераскрывшемся glob Обычно Bash спокойно оставляет glob как есть, если совпадений нет: rm /var/log/app/*.old Если .old-файлов нет, команда фактически получит: rm: cannot remove '/var/log/app/*.old': No such file or directory Но в автоматизации это может быть проблемой: скрипт продолжит выполнение, хотя вы рассчитывали, что glob что-то нашёл. ▪️failglob меняет поведение shopt -s failglob files=(/var/log/app/*.old) Если совпадений нет, Bash сам выдаст ошибку: bash: no match: /var/log/app/*.old То есть проблема обнаруживается на этапе раскрытия glob, ещё до запуска команды. ▪️Особенно полезно в скриптах: #!/usr/bin/env bash set -e shopt -s failglob for file in /backup/*.tar.gz; do process "$file" done Без failglob цикл может получить буквально строку /backup/*.tar.gz. С failglob скрипт сразу сигнализирует, что ожидаемых файлов нет. ▪️Но есть нюанс failglob не всегда нужен. Если отсутствие файлов - нормальная ситуация, лучше использовать nullglob: shopt -s nullglob files=(/backup/*.tar.gz) for file in "${files[@]}"; do process "$file" done Тогда при отсутствии совпадений массив просто будет пустым. Итого получается: failglob → отсутствие совпадения считается ошибкой. nullglob → отсутствие совпадения превращается в пустой список. Для критичных автоматизаций failglob помогает не пропустить логическую ошибку, замаскированную обычным поведением Bash. BashTex 📱 #bash #linux
367
10
KillMode=: почему systemctl stop иногда оставляет процессы Остановить сервис через: systemctl stop myapp.service не всегда означает, что исчезнет только один процесс. У systemd есть отдельная настройка, определяющая, какие процессы будут остановлены вместе с unit. Это KillMode=. ▪️Например: [Service] ExecStart=/opt/myapp/start.sh KillMode=control-group По умолчанию systemd работает с control group сервиса. При остановке он может завершить все процессы внутри cgroup, а не только основной PID. Проверить: systemctl show myapp.service -p KillMode ▪️Самый интересный вариант - process [Service] KillMode=process В этом режиме сигнал отправляется только основному процессу сервиса. Если start.sh породил: myapp ├─ worker1 └─ worker2 дочерние процессы могут продолжить работу после остановки unit. ▪️control-group [Service] KillMode=control-group systemd отслеживает cgroup целиком: myapp.service ├─ PID 1200 ├─ PID 1201 └─ PID 1202 При остановке unit процессы внутри группы также попадут под завершение. ▪️Проверить, кто реально принадлежит сервису systemctl status myapp.service или: systemctl show myapp.service -p ControlGroup Затем: systemd-cgls Можно увидеть дерево процессов по cgroup. ▪️Ещё один режим KillMode=mixed При остановке SIGTERM получает основной процесс, а при последующем принудительном завершении systemd применяет его ко всей control group. Это позволяет дать главному процессу шанс корректно завершиться, не оставляя дочерние процессы навсегда. ▪️Почему это важно Особенно легко получить «призраков» при запуске приложения через shell-обёртку: ExecStart=/bin/bash /opt/start.sh Если скрипт запускает фоновые процессы, нужно понимать, в какой cgroup они окажутся и как KillMode повлияет на их завершение. systemctl stop - это не просто отправка SIGTERM одному PID. В systemd процесс существует внутри cgroup, и политика KillMode определяет масштаб остановки. BashTex 📱 #bash #linux
353
11
BashTex 📱 #мем
BashTex 📱 #мем
527
12
tee + process substitution: один поток и несколько получателей Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution. ▪️ Что делает tee. tee дублирует поток: command | tee file.log вывод остается в терминале и одновременно пишется в file.log ▪️ Несколько получателей. tee может писать сразу в несколько файлов: command | tee out1.log out2.log Но иногда нужно не просто файл, а другую команду. ▪️ Process substitution. Bash позволяет подставить вывод команды как файл: >(command) Это называется process substitution. ▪️ Комбинируем command | tee >(grep ERROR > errors.log) command генерирует поток tee дублирует его Одна копия идет в терминал, а другая в grep. Если найден ERROR, запись попадет в errors.log. ▪️ Более реальный пример. Допустим, идет сбор логов: journalctl -f | tee >(grep ERROR >> errors.log) Теперь: полный поток остается в терминале, ошибки автоматически сохраняются ▪️ Можно делать несколько обработчиков journalctl -f | tee \ >(grep ERROR >> errors.log) \ >(grep WARN >> warn.log) Один поток и сразу несколько фильтров. BashTex 📱 #bash #utils
563
13
RuntimeMaxSec: как systemd убивает сервис, который работает слишком долго Иногда сервис не падает и не зависает в классическом смысле - он просто продолжает работать часами, хотя по логике приложения должен завершиться. В systemd для этого есть RuntimeMaxSec=. ▪️Ограничиваем время жизни сервиса В unit-файле: [Service] ExecStart=/usr/local/bin/job.sh RuntimeMaxSec=30min После 30 минут с момента запуска systemd остановит сервис. Для более точного ограничения: RuntimeMaxSec=1h 30min ▪️Что происходит после истечения времени Это не просто kill -9. По умолчанию systemd инициирует обычную остановку сервиса. Если процесс не завершится в течение TimeoutStopSec=, systemd применит настроенное принудительное завершение. Например: [Service] RuntimeMaxSec=30min TimeoutStopSec=20s Получается цепочка: 30 мин → остановка ↓ 20 сек на graceful shutdown ↓ принудительное завершение ▪️Проверить фактические параметры После запуска: systemctl show myjob.service \ -p RuntimeMaxUSec \ -p TimeoutStopUSec А текущий статус: systemctl status myjob.service ▪️Что будет при следующем запуске RuntimeMaxSec не запрещает сервису запускаться снова. Если unit настроен так: [Service] Restart=on-failure RuntimeMaxSec=30min важен результат завершения и дополнительные параметры restart policy. Сервис может быть запущен systemd снова после остановки. Поэтому RuntimeMaxSec стоит рассматривать вместе с Restart=, а не как самостоятельный механизм watchdog. ▪️Практический сценарий Допустим, есть batch-задача: [Service] Type=oneshot ExecStart=/opt/scripts/import.sh RuntimeMaxSec=2h Если импорт по какой-то причине зависнет на бесконечной операции, systemd не оставит процесс работать сутки. ▪️Важный нюанс RuntimeMaxSec - это лимит общего времени работы, а не проверка того, отвечает ли приложение. Если сервис должен завершаться за 5 минут, но иногда работает 10 минут по штатной причине, такой лимит будет ошибкой конфигурации. Для контроля «жив ли сервис и отвечает ли он» нужны другие механизмы. BashTex 📱 #bash #linux
484
14
local с подстановкой команды: почему local result=$(cmd) скрывает ошибку В Bash есть тонкая ловушка внутри функций. На первый взгляд эти два варианта выглядят одинаково: result=$(false) echo "$?" Здесь $? будет 1. Но если написать: local result=$(false) echo "$?" получим: 0 ▪️Почему так происходит В конструкции: local result=$(cmd) выполняются сразу две вещи: 1. Bash выполняет $(cmd) и получает результат. 2. local объявляет переменную. Проблема в том, что код возврата всей команды определяется local, а не самой command substitution. local успешно создал переменную - поэтому функция видит 0. ▪️Особенно неприятно с обработкой ошибок Например: get_data() { local result=$(curl -fsS https://example.com/api) echo "status=$?" } Если curl завершится с ошибкой, проверка $? после local может показать успешное выполнение. Ошибка потеряна. ▪️Безопасный вариант Разделите объявление и присваивание: get_data() { local result result=$(curl -fsS https://example.com/api) local status=$? echo "status=$status" } Теперь $? относится непосредственно к curl. ▪️Ещё лучше - проверять сразу get_data() { local result if result=$(curl -fsS https://example.com/api); then printf '%s\n' "$result" else echo "command failed" return 1 fi } Здесь условие if проверяет именно exit code command substitution. ▪️Та же ловушка есть не только с local Похожая ситуация возникает с некоторыми встроенными командами, когда результат присваивания объединяется с другой операцией. Поэтому правило простое: local result result=$(cmd) а не: local result=$(cmd) ▪️Почему это важно В обычном скрипте такая мелочь может остаться незаметной. В функции, которая используется в цепочке: deploy || exit 1 потерянный exit code уже превращается в реальную логическую ошибку: функция может сообщить об успехе, хотя команда внутри неё завершилась с ошибкой. BashTex 📱 #bash #linux
368
15
read -d: как читать Bash-поток не по строкам read обычно ждёт перевод строки: while IFS= read -r line; do printf '%s\n' "$line" done < file.txt Но у read можно изменить разделитель. Это особенно полезно для данных, где обычный \n не является надёжной границей записи. ▪️Читать по нулевому байту Например, find умеет выдавать имена файлов через \0: find /tmp -type f -print0 | while IFS= read -r -d '' file; do printf '%s\n' "$file" done -d '' означает: читать до NUL-символа. Это позволяет корректно обработать имя вроде: report 2026.txt где внутри имени действительно может быть перевод строки. ▪️Почему обычный read здесь опасен Такой вариант: find /tmp -type f -print0 | while read -r file; do ... done не соответствует формату данных: find разделяет записи \0, а read по умолчанию ищет \n. ▪️Можно использовать другой разделитель Например, прочитать значения через :: while IFS= read -r -d ':' value; do printf '<%s>\n' "$value" done <<< 'one:two:three:' Получим: <one> <two> <three> ▪️read умеет ещё и ограничивать количество символов IFS= read -r -n 1 char Теперь Bash прочитает только один символ. А: IFS= read -r -N 8 chunk будет ждать ровно 8 символов, не считая разделитель строки. ▪️Где это полезно read -d особенно хорошо сочетается с командами, поддерживающими NUL-разделители: find ... -print0 git ... -z sort -z xargs -0 Получается безопасная цепочка, в которой пробелы, кавычки и даже переводы строк внутри имён файлов не ломают обработку. ▪️Почему это важно Разделитель данных должен совпадать с тем, как эти данные реально генерируются. read - это не только «прочитать строку»: с -d, -n и -N он может работать с потоками гораздо точнее. BashTex 📱 #bash #linux
399
16
BashTex 📱 #мем
BashTex 📱 #мем
625
17
/proc/PID/wchan: где именно завис процесс Если процесс показывает высокий D в ps, сам статус ещё не говорит, на чём именно он остановился. Для этого Linux предоставляет /proc/<PID>/wchan. ▪️Сначала найдём процесс ps -eo pid,stat,wchan:30,cmd | grep '[D]' Например: 2418 D io_schedule backup Здесь wchan показывает kernel wait channel - функцию ядра, в которой процесс ожидает продолжения. ▪️Посмотреть напрямую cat /proc/2418/wchan Можно получить: io_schedule Или: nfs_wait_bit_killable Во втором случае уже появляется конкретная зацепка: процесс может ждать операции, связанной с NFS. ▪️Сравнить несколько процессов for p in /proc/[0-9]*; do pid=${p##*/} state=$(awk '{print $3}' "$p/stat" 2>/dev/null) [ "$state" = D ] || continue printf '%-8s %s\n' \ "$pid" \ "$(cat "$p/wchan" 2>/dev/null)" done Получится примерно: 2418 io_schedule 2421 io_schedule 2517 nfs_wait_bit_killable Если десятки процессов одновременно находятся в одном wchan, это уже может указывать на общий узкий участок. ▪️Посмотреть стек ядра Для более глубокого разбора: cat /proc/2418/stack Можно увидеть цепочку вызовов ядра, в которой находится поток. Например: io_schedule schedule ... Доступ к /proc/PID/stack зависит от прав и настроек безопасности. ▪️Почему kill -9 иногда не помогает Процесс в состоянии D находится в uninterruptible sleep. Если он ждёт завершения определённой операции ядра, SIGKILL не может мгновенно вывести его из этого состояния. Поэтому вместо бесконечного: kill -9 2418 полезнее сначала посмотреть: ps -o pid,stat,wchan,cmd -p 2418 cat /proc/2418/wchan cat /proc/2418/stack Так можно понять, почему процесс не реагирует. ▪️Почему это важно ps показывает симптом - D. wchan помогает увидеть причину ожидания на уровне ядра. Для проблем с дисками, NFS, storage и зависшими I/O-операциями это один из самых быстрых способов перейти от «процесс завис» к пониманию, где именно он застрял. BashTex 📱 #bash #linux
588
18
BASH_SOURCE и FUNCNAME: как понять, откуда реально вызвали функцию В больших Bash-скриптах функция может вызываться из другой функции, которая пришла из отдельного файла. Обычный $0 в такой ситуации мало что объясняет. Для этого у Bash есть специальные массивы: BASH_SOURCE FUNCNAME ▪️Простой пример foo() { echo "file: ${BASH_SOURCE[0]}" echo "caller: ${BASH_SOURCE[1]}" echo "function: ${FUNCNAME[0]}" } foo BASH_SOURCE[0] показывает файл, в котором выполняется текущая функция, а BASH_SOURCE[1] - файл, откуда она была вызвана. ▪️Посмотреть весь стек вызовов foo() { printf '%s\n' "${FUNCNAME[@]}" } bar() { foo } bar Можно получить примерно: foo bar main То есть Bash хранит стек функций. ▪️Особенно полезно для логирования Например: log() { printf '[%s] %s: %s\n' \ "$(date '+%F %T')" \ "${FUNCNAME[1]}" \ "$*" } Теперь: deploy() { log "starting deployment" } deploy может вывести: [2026-08-24 12:30:01] deploy: starting deployment Лог сразу показывает, какая функция создала сообщение. ▪️А если скрипт состоит из нескольких файлов Допустим: main.sh lib.sh utils.sh и функции вызывают друг друга через source. Тогда: printf '%s\n' "${BASH_SOURCE[@]}" покажет цепочку файлов, через которые прошёл вызов. ▪️Чем это отличается от $0 echo "$0" обычно показывает имя запущенного скрипта или shell. А: echo "${BASH_SOURCE[0]}" показывает конкретный Bash-файл, где находится текущий код. Это особенно заметно при использовании: source lib.sh ▪️Почему это важно При отладке больших shell-проектов сообщение вроде: ERROR: failed почти бесполезно. А: ERROR: deploy() in lib/deploy.sh уже позволяет быстро найти источник проблемы. BASH_SOURCE и FUNCNAME превращают Bash-функции из непрозрачного набора вызовов в трассируемый стек. BashTex 📱 #bash #linux
498
19
mapfile: как загружать вывод команды в массив без while read Если результат команды нужно сохранить построчно, часто используют: items=() while IFS= read -r line; do items+=("$line") done < <(some_command) В Bash для этого есть встроенный mapfile. ▪️Простой вариант mapfile -t files < <(find /tmp -type f) Теперь каждая строка - отдельный элемент массива: printf '%s\n' "${files[@]}" -t убирает символ переноса строки из каждого элемента. ▪️Зачем это лучше Нет отдельного while, нет проблемы с subshell и не нужно вручную добавлять элементы: mapfile -t users < <(cut -d: -f1 /etc/passwd) echo "Users: ${#users[@]}" Можно обращаться к конкретному элементу: echo "${users[0]}" ▪️Можно читать файл напрямую mapfile -t lines < config.txt А затем: for line in "${lines[@]}"; do printf 'CONFIG: %s\n' "$line" done ▪️Читать только часть вывода Например: mapfile -t -n 10 lines < access.log В массив попадут только первые 10 строк. ▪️Есть важный нюанс mapfile ориентирован именно на разделённые строки данные. Если содержимое может содержать \n внутри логического элемента, обычный построчный массив уже не подходит. Для других разделителей можно использовать -d: mapfile -d ',' -t items < data.txt Теперь разделителем считается запятая. ▪️Где это удобно Например, получить список systemd-сервисов: mapfile -t services < <( systemctl list-units --type=service --state=running --no-legend | awk '{print $1}' ) После этого массив можно безопасно передавать дальше: for service in "${services[@]}"; do systemctl is-active "$service" done ▪️Почему это важно mapfile - встроенный механизм Bash для преобразования потоковых данных в массив. Он делает код короче и избавляет от целого класса проблем, связанных с while read, пайпами и сохранением переменных. BashTex 📱 #bash #linux
413
20
noclobber: как запретить Bash случайно перезаписать файл Обычный оператор: echo "new config" > config.txt без предупреждения удалит старое содержимое config.txt. Для скриптов, где случайная перезапись критичного файла недопустима, в Bash есть режим noclobber. ▪️Включаем его set -o noclobber Теперь: echo "new config" > config.txt если файл уже существует, завершится ошибкой: bash: config.txt: cannot overwrite existing file ▪️Проверить состояние set -o | grep noclobber Или короткая форма: set -C Выключить обратно: set +C ▪️Но >> продолжает работать noclobber защищает именно от перезаписи через >: echo "new line" >> config.txt добавит данные в конец файла. ▪️А как принудительно перезаписать? Есть специальная конструкция: echo "new config" >| config.txt Оператор >| игнорирует noclobber и разрешает перезапись. Это удобно, когда защита включена глобально, но конкретный участок скрипта действительно должен заменить файл. ▪️Где это полезно Например, скрипт генерирует конфигурацию: set -C generate_config > /etc/myapp/config.conf Если файл уже существует, операция остановится вместо того, чтобы молча уничтожить текущую конфигурацию. ▪️Важный нюанс noclobber не является полноценной защитой от всех способов изменения файла. Это именно настройка shell для оператора >. Её задача проще: превратить потенциально опасную случайную перезапись в явную ошибку. ▪️Почему это важно В Bash > выглядит безобидно, но фактически означает «открыть файл на запись и обнулить его». noclobber позволяет добавить дополнительный барьер там, где потеря существующего содержимого недопустима. BashTex 📱 #bash #linux
474