$? после &&, || и if: почему код возврата Bash бывает неочевиднымВ Bash
$? содержит код завершения последней выполненной команды. Но конструкции &&, || и if меняют то, какие команды вообще выполняются.false && echo "OK"
echo $?
echo после && не запускается, потому что false вернула 1.Итоговый
$? - 1.А здесь:
true && echo "OK"
echo $?
последней командой становится
echo "OK", поэтому $? уже равен 0.|| ситуация аналогичнаяfalse || echo "fallback"
echo $?
false запускается, затем выполняется echo, потому что первая команда завершилась ошибкой.В итоге
$? будет 0 - код echo, а не false.Если нужен исходный результат:
false
status=$?
if [ "$status" -ne 0 ]; then
echo "Command failed: $status"
fi
if не создаёт обычный 1if false; then
echo "success"
fi
echo $?
Здесь
if возвращает статус последней выполненной команды в выбранной ветке. Если ни одна ветка не выполнялась, результатом становится код проверки условия.command || echo "failed"
status=$?
status здесь содержит результат echo, а не command.Если важно сохранить код ошибки:
if ! command; then
status=$?
fi
Но здесь есть ещё один нюанс:
! инвертирует статус, поэтому для получения исходного кода лучше сохранять его непосредственно после команды.Например:
command
status=$?
if [ "$status" -ne 0 ]; then
echo "failed: $status"
fi
В сложных Bash-скриптах
$? легко прочитать слишком поздно. Любая следующая команда - даже echo, printf или test - может заменить предыдущий код возврата.Поэтому если результат команды важен, сохраняйте его сразу.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
lastpipe: как Bash меняет поведение последней команды в pipelineОдна из особенностей Bash - команды в pipeline обычно выполняются в отдельных subshell. Из-за этого переменные, изменённые внутри
while, после цикла могут не сохраниться.Например:
count=0
printf '%s\n' a b c |
while read -r line; do
((count++))
done
echo "$count"
В некоторых случаях результатом будет
0.Но у Bash есть настройка
lastpipe, которая меняет это поведение.lastpipeshopt -s lastpipe
Теперь последняя команда pipeline может выполняться в текущем shell.
count=0
printf '%s\n' a b c |
while read -r line; do
((count++))
done
echo "$count"
При подходящих условиях получим:
3lastpipe работает только когда job control отключён:set +m
shopt -s lastpipe
Именно поэтому поведение может отличаться между интерактивным Bash и обычным скриптом.
shopt lastpipe
Получим:
lastpipe off
или:
lastpipe on
Вместо изменения поведения shell часто проще использовать редирект:
while read -r line; do
((count++))
done < <(printf '%s\n' a b c)
Так код не зависит от настройки
lastpipe.lastpipe действительно интересенЭто полезно знать при разборе чужих Bash-скриптов: одна и та же конструкция с pipeline может вести себя по-разному в зависимости от настроек shell и режима job control.
lastpipe - хороший пример того, как небольшая опция Bash меняет фундаментальное поведение pipeline и область видимости изменений.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
nullglob: когда *.log внезапно превращается в строкуBash умеет раскрывать glob-шаблоны:
*.log
Но если файлов с таким расширением нет, по умолчанию шаблон может остаться обычной строкой.
Например:
for file in /var/log/*.log; do
echo "$file"
done
Если
.log файлов нет, цикл может получить буквально:/var/log/*.log
Это легко превращается в ошибку в автоматизации.
nullglobshopt -s nullglob
Теперь шаблон без совпадений исчезает:
files=(/var/log/*.log)
echo "${#files[@]}"
Если файлов нет:
0А не
1 с буквальным *.log.Без
nullglob:files=(/tmp/*.tmp)
может создать массив из одного элемента:
/tmp/*.tmp
После:
shopt -s nullglob
получаем действительно пустой массив.
nullglob влияет только на текущий shellЕго можно включить локально внутри функции:
check_logs() {
local old_nullglob
shopt -q nullglob
old_nullglob=$?
shopt -s nullglob
files=(/var/log/*.log)
((${#files[@]})) && printf '%s\n' "${files[@]}"
((old_nullglob == 0)) || shopt -u nullglob
}Но для простых скриптов обычно достаточно включить настройку в начале.
Тогда существует противоположная настройка:
shopt -s failglob
При отсутствии совпадений Bash выдаст ошибку вместо передачи буквального шаблона дальше.
Glob кажется простой частью Bash, пока скрипт не запускается на сервере, где ожидаемых файлов просто нет.
nullglob позволяет отличить настоящий список файлов от строки с нераскрытым шаблоном - особенно полезно при работе с массивами, логами и автоматической обработкой каталогов.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2
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 -Cgenerate_config > /etc/myapp/config.conf
Если файл уже существует, операция остановится вместо того, чтобы молча уничтожить текущую конфигурацию.
noclobber не является полноценной защитой от всех способов изменения файла. Это именно настройка shell для оператора >.Её задача проще: превратить потенциально опасную случайную перезапись в явную ошибку.
В Bash
> выглядит безобидно, но фактически означает «открыть файл на запись и обнулить его». noclobber позволяет добавить дополнительный барьер там, где потеря существующего содержимого недопустима.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
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"
donemapfile - встроенный механизм Bash для преобразования потоковых данных в массив. Он делает код короче и избавляет от целого класса проблем, связанных с while read, пайпами и сохранением переменных.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
BASH_SOURCE и FUNCNAME: как понять, откуда реально вызвали функциюВ больших Bash-скриптах функция может вызываться из другой функции, которая пришла из отдельного файла. Обычный
$0 в такой ситуации мало что объясняет.Для этого у Bash есть специальные массивы:
BASH_SOURCE
FUNCNAME
foo() {
echo "file: ${BASH_SOURCE[0]}"
echo "caller: ${BASH_SOURCE[1]}"
echo "function: ${FUNCNAME[0]}"
}
fooBASH_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[@]}"покажет цепочку файлов, через которые прошёл вызов.
$0echo "$0"
обычно показывает имя запущенного скрипта или shell.
А:
echo "${BASH_SOURCE[0]}"показывает конкретный Bash-файл, где находится текущий код.
Это особенно заметно при использовании:
source lib.sh
При отладке больших shell-проектов сообщение вроде:
ERROR: failedпочти бесполезно.
А:
ERROR: deploy() in lib/deploy.shуже позволяет быстро найти источник проблемы.
BASH_SOURCE и FUNCNAME превращают Bash-функции из непрозрачного набора вызовов в трассируемый стек.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
/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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
tee + process substitution: один поток и несколько получателей
Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution.
▪️ Что делает tee. tee дублирует поток:
command | tee file.log
вывод остается в терминале и одновременно пишется в
▪️ Несколько получателей. tee может писать сразу в несколько файлов:
command | tee out1.log out2.log
Но иногда нужно не просто файл, а другую команду.
▪️ Process substitution. Bash позволяет подставить вывод команды как файл:
>(command)
Это называется process substitution.
▪️ Комбинируем
command | tee >(grep ERROR > errors.log)
Одна копия идет в терминал, а другая в 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
Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution.
command | tee file.log
вывод остается в терминале и одновременно пишется в
file.logcommand | tee out1.log out2.log
Но иногда нужно не просто файл, а другую команду.
>(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
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍1
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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
failglob - почему Bash должен падать при нераскрывшемся glob
Обычно Bash спокойно оставляет glob как есть, если совпадений нет:
Если
Но в автоматизации это может быть проблемой: скрипт продолжит выполнение, хотя вы рассчитывали, что glob что-то нашёл.
▪️ failglob меняет поведение
Если совпадений нет, Bash сам выдаст ошибку:
То есть проблема обнаруживается на этапе раскрытия glob, ещё до запуска команды.
▪️ Особенно полезно в скриптах:
Без
С
▪️ Но есть нюанс
Тогда при отсутствии совпадений массив просто будет пустым.
Итого получается:
Для критичных автоматизаций
BashTex📱 #bash #linux
Обычно Bash спокойно оставляет glob как есть, если совпадений нет:
rm /var/log/app/*.old
Если
.old-файлов нет, команда фактически получит:rm: cannot remove '/var/log/app/*.old': No such file or directory
Но в автоматизации это может быть проблемой: скрипт продолжит выполнение, хотя вы рассчитывали, что glob что-то нашёл.
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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
PSI: /proc/pressure/* - как понять, чего реально не хватает системе
Для этого в Linux есть PSI - Pressure Stall Information:
Например:
▪️
▪️
Например:
Это уже гораздо интереснее, чем просто увидеть высокий RAM usage.
Если
Если начинает расти
То же самое для I/O:
Если I/O PSI высокий, а CPU почти не испытывает pressure, проблема может быть не в загрузке процессора, а в storage latency.
А CPU PSI позволяет увидеть ситуацию, когда CPU формально загружен не на 100%, но runnable-задачи всё равно регулярно ждут свою очередь.
Проверить всё сразу:
PSI полезен именно как метрика задержки из-за конкуренции за ресурсы, а не просто их потребления.
Поэтому вместо вопроса «CPU/RAM/Disk загружены?» иногда правильнее спросить:
«Какой ресурс заставляет процессы ждать?»
BashTex📱 #perfomance #linux
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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
systemd-delta: как найти, чем на самом деле переопределён unit
Иногда вы открываете unit-файл и видите одну конфигурацию, а systemd ведёт себя иначе.
Причина часто в drop-in override, созданном через
Например, основной unit:
а дополнительная настройка лежит здесь:
▪️ Посмотреть переопределения
Команда показывает отличия между vendor-конфигурацией и локальными изменениями.
Можно проверить конкретный unit:
Если сервис был переопределён, вы увидите соответствующий drop-in.
▪️ Почему это важно
Допустим, в основном unit указано:
Открыв только
systemd при запуске учитывает оба слоя.
▪️ Посмотреть итоговую конфигурацию
Для проверки того, что systemd реально использует:
Например:
А список связанных drop-in можно увидеть так:
Здесь systemd покажет основной unit и применённые drop-in-файлы.
▪️ Практический сценарий
Сервис неожиданно перезапускается:
В основном unit
Вместо того чтобы сразу менять конфигурацию, проверяем:
И обнаруживаем старый override:
Именно он мог изменить поведение сервиса.
▪️ Важный нюанс
Пакет может заменить vendor unit в
Поэтому при странном поведении unit полезно проверить не только сам файл, но и что поверх него было добавлено или изменено.
BashTex📱 #bash #systemd
Иногда вы открываете 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8🔥2🗿1
file vs расширение - как Linux определяет тип файла на самом деле
В Linux расширение файла само по себе ничего не определяет.
Для системы:
это просто имена файлов. Можно спокойно сделать:
и содержимое файла от этого никак не изменится.
▪️ Откуда тогда
Утилита
Например:
Даже если файл называется:
file всё равно может определить его как PNG.
▪️ Главный источник - magic bytes
А file сопоставляет такие сигнатуры с базой magic:
и использует правила из базы magic.
▪️ Можно обмануть расширение
Результат всё равно будет примерно таким:
Потому что имя изменилось, а байты остались прежними.
▪️ Но не всё определяется только первыми байтами
Для ELF:
может показать архитектуру, разрядность и тип исполняемого файла.
Для текстовых файлов утилита анализирует содержимое и может определить кодировку, тип текста и другие признаки.
▪️ Практический сценарий
Скачали файл с подозрительным расширением:
Не стоит сразу доверять имени.
Сначала:
А затем, если это бинарник:
или:
Так можно понять, что реально лежит внутри файла.
▪️ Важный нюанс
Если формат не имеет узнаваемой сигнатуры или несколько форматов используют похожую структуру, результат может быть приблизительным.
И ещё важнее: file определяет тип по содержимому, но не говорит, что файл безопасен.
Файл может называться
В Linux расширение - часть имени.
А реальный формат гораздо чаще определяется тем, какие байты находятся внутри файла.
BashTex📱 #bash #systemd
В 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.
Многие форматы имеют характерную сигнатуру в начале файла.
Например, 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
du vs df: почему Linux показывает разный размер диска
Иногда ситуация выглядит странно:
показывает, что занято 80 GB.
А:
находит только 55 GB.
Кажется, что Linux «потерял» 25 GB. На самом деле
▪️ Что считает
Он показывает, сколько места файловая система считает занятым и свободным.
▪️ Что считает
▪️ Одна из главных причин расхождения
Процесс может держать открытым файл, который уже удалили.
Например:
Имя файла исчезло из каталога, но процесс всё ещё держит его открытым.
Для
А файловая система продолжает считать его блоки занятыми.
Проверить:
Можно увидеть что-то вроде:
Пока процесс не закроет этот дескриптор, место может не освободиться.
▪️ Есть и другие причины
Например,
Или часть пространства файловой системы зарезервирована и не доступна обычным пользователям.
Посмотреть файловую систему подробнее:
▪️ Практический сценарий
На сервере внезапно заканчивается место:
Показывает:
Но:
показывает только 70G.
Первое, что стоит проверить:
Если там огромный
▪️ Важный нюанс
«Сколько блоков файловой системы сейчас занято?»
А
«Сколько места занимают доступные мне файлы в этом дереве?»
Поэтому при расхождении
Сначала нужно понять, куда именно делось пространство.
BashTex📱 #bash #systemd
Иногда ситуация выглядит странно:
df -h /
показывает, что занято 80 GB.
А:
du -sh /
находит только 55 GB.
Кажется, что Linux «потерял» 25 GB. На самом деле
du и df измеряют разные вещи.dfdf смотрит на файловую систему и её блоки:df -h /
Он показывает, сколько места файловая система считает занятым и свободным.
dudu обходит дерево каталогов и суммирует пространство, связанное с найденными файлами: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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Sparse files: почему файл на 100 ГБ может занимать несколько мегабайт
Размер файла и реально занятое место на диске не всегда совпадают.
Можно создать файл, который логически имеет размер 100 ГБ, но физически занимает всего несколько мегабайт.
Для этого используются sparse files.
▪️ Создаём sparse-файл
Теперь:
покажет:
Но:
может показать всего несколько килобайт.
▪️ Что произошло
Файл содержит большой диапазон нулей, для которого файловая система не обязана физически выделять блоки.
Условно:
Второй вариант показывает размер так, будто sparse-областей нет.
Например:
▪️ Практический сценарий
Есть сервер виртуализации, и администратор видит:
Образы занимают:
Но:
показывает:
Это не обязательно ошибка подсчёта.
Часть образов может быть sparse.
▪️ Важный нюанс
Копирование sparse-файла не всегда сохраняет holes.
Например, некоторые способы копирования могут превратить разреженный файл в обычный и реально записать нулевые блоки.
Проверить результат можно снова через:
Поэтому при работе с большими VM-образами важно различать логический размер файла и реально выделенное дисковое пространство.
BashTex📱 #bash #systemd
Размер файла и реально занятое место на диске не всегда совпадают.
Можно создать файл, который логически имеет размер 100 ГБ, но физически занимает всего несколько мегабайт.
Для этого используются sparse files.
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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7