BashTex | Linux
Открыть в Telegram
Авторский канал для тех, кто хочет глубже погрузиться в мир Linux. Подойдет для разработчиков, системных администраторов и DevOps Реклама: @dad_admin
Больше2 514
Подписчики
Нет данных24 часа
-37 дней
+1630 дней
Загрузка данных...
Похожие каналы
Нет данных
Возникли проблемы? Пожалуйста, обновите страницу или обратитесь к нашему support-менеджеру .
Облако тегов
Входящие и исходящие упоминания
---
---
---
---
---
---
Привлечение подписчиков
сентябрь '26
сентябрь '26
+3
в 0 каналах
август '26
+54
в 11 каналах
Get PRO
июль '26
+16
в 1 каналах
Get PRO
июнь '26
+38
в 5 каналах
Get PRO
май '26
+8
в 0 каналах
Get PRO
апрель '26
+14
в 0 каналах
Get PRO
март '26
+22
в 0 каналах
Get PRO
февраль '26
+18
в 0 каналах
Get PRO
январь '26
+85
в 5 каналах
Get PRO
декабрь '25
+122
в 19 каналах
Get PRO
ноябрь '25
+93
в 2 каналах
Get PRO
октябрь '25
+107
в 7 каналах
Get PRO
сентябрь '25
+197
в 20 каналах
Get PRO
август '25
+16
в 1 каналах
Get PRO
июль '25
+130
в 13 каналах
Get PRO
июнь '25
+57
в 4 каналах
Get PRO
май '25
+70
в 1 каналах
Get PRO
апрель '25
+728
в 23 каналах
Get PRO
март '25
+129
в 6 каналах
Get PRO
февраль '25
+235
в 9 каналах
Get PRO
январь '25
+497
в 19 каналах
Get PRO
декабрь '24
+134
в 8 каналах
Get PRO
ноябрь '24
+819
в 17 каналах
| Дата | Привлечение подписчиков | Упоминания | Каналы | |
| 16 сентября | 0 | |||
| 15 сентября | 0 | |||
| 14 сентября | 0 | |||
| 13 сентября | +1 | |||
| 12 сентября | 0 | |||
| 11 сентября | 0 | |||
| 10 сентября | +1 | |||
| 09 сентября | 0 | |||
| 08 сентября | 0 | |||
| 07 сентября | 0 | |||
| 06 сентября | 0 | |||
| 05 сентября | +1 | |||
| 04 сентября | 0 | |||
| 03 сентября | 0 | |||
| 02 сентября | 0 | |||
| 01 сентября | 0 |
Посты канала
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 📱 #мем | 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 📱 #мем | 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 📱 #мем | 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 |
