BashTex | Linux


Kanal geosi va tili: Rossiya, Ruscha


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

Связанные каналы

Kanal geosi va tili
Rossiya, Ruscha
Statistika
Postlar filtri


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


nullglob: когда *.log внезапно превращается в строку

Bash умеет раскрывать glob-шаблоны:

*.log

Но если файлов с таким расширением нет, по умолчанию шаблон может остаться обычной строкой.
Например:

for file in /var/log/*.log; do
echo "$file"
done

Если .log файлов нет, цикл может получить буквально:

/var/log/*.log

Это легко превращается в ошибку в автоматизации.

▪️Включаем nullglob

shopt -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 📱 #bash #linux


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, которая меняет это поведение.

▪️Включаем lastpipe

shopt -s lastpipe

Теперь последняя команда pipeline может выполняться в текущем shell.

count=0

printf '%s\n' a b c |
while read -r line; do
((count++))
done

echo "$count"

При подходящих условиях получим: 3

▪️Есть важное условие

lastpipe работает только когда job control отключён:

set +m
shopt -s lastpipe

Именно поэтому поведение может отличаться между интерактивным Bash и обычным скриптом.

▪️Проверить настройку

shopt lastpipe

Получим:

lastpipe off

или:

lastpipe on

▪️Почему это не универсальная замена

Вместо изменения поведения shell часто проще использовать редирект:

while read -r line; do
((count++))
done <


$? после &&, || и 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 не создаёт обычный 1

if 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 📱 #bash #linux


return против exit: как не завершить весь скрипт внутри функции

В Bash функция может вернуть управление вызывающему коду или полностью завершить текущий shell. Эти два случая часто путают.

▪️exit завершает весь скрипт

check_config() {
if [ ! -f config.conf ]; then
echo "Config not found"
exit 1
fi
}

check_config

echo "Продолжаем работу"

Если config.conf нет, выполнение закончится прямо внутри функции. Строка Продолжаем работу никогда не выполнится.

▪️return завершает только функцию

check_config() {
if [ ! -f config.conf ]; then
echo "Config not found"
return 1
fi
}

check_config

echo "Продолжаем работу"

Теперь функция завершилась с кодом 1, но сам скрипт продолжил выполнение.

Именно поэтому return обычно используют внутри функций, а exit - на уровне самого скрипта.

▪️Код возврата можно обработать

if check_config; then
echo "Config OK"
else
echo "Config error"
fi

Или сохранить результат:

check_config
status=$?

if [ "$status" -ne 0 ]; then
echo "Ошибка: $status"
fi

▪️Важный нюанс

return можно использовать только внутри функции или в контексте, где Bash допускает возврат из текущего sourced-файла.

Например:

return 1

в обычном запускаемом скрипте верхнего уровня приведёт к ошибке:

return: can only `return' from a function

▪️Типичная ошибка

deploy() {
check_config || exit 1
start_service
}

Если deploy вызывается из другого скрипта как функция, exit завершит уже весь shell.

Чаще правильнее:

deploy() {
check_config || return 1
start_service
}

А решение о завершении оставить вызывающему коду:

deploy || exit 1

Так функция сообщает об ошибке, а не решает за весь скрипт, что делать дальше.

BashTex 📱 #bash #linux


/proc/PID/cwd: когда процесс работает не из того каталога

У каждого Linux-процесса есть текущий рабочий каталог. Обычно мы видим его через shell, но для уже запущенного процесса можно посмотреть напрямую через /proc.

readlink /proc/1234/cwd

Например:

/var/lib/app

Это каталог, относительно которого процесс разрешает относительные пути.

▪️Найти процессы с неожиданным cwd

for p in /proc/[0-9]*; do
pid=${p##*/}
cwd=$(readlink "$p/cwd" 2>/dev/null) || continue
printf '%-8s %s\n' "$pid" "$cwd"
done

Теперь можно быстро увидеть, какие процессы находятся в /tmp, /dev/shm, домашних каталогах или других необычных местах.

▪️Почему это может быть важно

Приложение может запускаться из:

/opt/myapp

но после работы оказаться в другом каталоге из-за chdir().

Особенно интересны процессы с:

/tmp
/dev/shm
/home/user

если для конкретного сервиса такое поведение не ожидается.

▪️Связь с systemd

Для unit можно задать:

[Service]
WorkingDirectory=/var/lib/myapp

А фактическое значение уже запущенного процесса проверить через /proc.

Это полезнее, чем тупо смотреть конфигурацию: /proc/PID/cwd показывает состояние конкретного процесса прямо сейчас.

BashTex 📱 #bash #proc


setsid: как действительно отделить процесс от SSH-сессии

nohup часто используют, чтобы процесс пережил закрытие терминала:
nohup ./backup.sh &

Но nohup в основном защищает от SIGHUP. Сам процесс всё ещё может оставаться связанным с текущей session.
Для полного создания новой сессии существует setsid.

▪️Что происходит обычно

После подключения по SSH можно посмотреть:

ps -o pid,ppid,sid,tty,cmd

У процессов будут общие SID и управляющий терминал.

При закрытии SSH это может иметь значение для поведения процессов.

▪️Запуск через setsid

setsid ./backup.sh

Процесс становится лидером новой session и получает новый SID.

Проверить:

ps -o pid,ppid,sid,tty,cmd -C backup.sh

Можно увидеть, что SID процесса больше не связан с вашей SSH-сессией.

▪️Почему одного & недостаточно

./backup.sh &

Фоновый процесс всё равно является частью текущей shell-сессии.
& лишь не блокирует терминал ожиданием процесса.

▪️А nohup?

nohup ./backup.sh &

Это уже лучше для сценария с закрытием терминала, но nohup и setsid решают разные задачи.

nohup изменяет обработку SIGHUP и обычно перенаправляет стандартный вывод.
setsid создаёт новую session и отделяет процесс от текущей session.

▪️Комбинация

Для простого запуска полностью отдельно:

setsid ./backup.sh >/tmp/backup.log 2>&1 < /dev/null &

Теперь процесс не зависит от стандартного ввода текущего терминала и находится в отдельной session.

▪️Где это полезно

• запуск долгих задач из SSH;
• анализ поведения daemon-like процессов;
• shell-обвязки для сервисов;
• понимание PID, PPID, SID и TTY.

▪️Почему это важно

&, nohup и setsid часто воспринимают как взаимозаменяемые способы “запустить в фоне”. На самом деле они меняют разные свойства процесса.

Если нужно понять, почему программа продолжает зависеть от SSH или терминала, смотреть стоит не только на PID, но и на SID, PPID и управляющий TTY.

BashTex 📱 #bash #SSH


/proc/PID/environ: какие переменные реально получил процесс

env показывает окружение текущего shell. Но для диагностики сервиса этого недостаточно: после запуска процесс мог получить совсем другой набор переменных.
Linux хранит окружение каждого процесса здесь:

cat /proc/1234/environ

Значения разделены не переносами строк, а нулевыми байтами, поэтому удобнее:

tr '\0' '\n' < /proc/1234/environ

▪️Найти конкретную переменную

tr '\0' '\n' < /proc/1234/environ | grep '^PATH='

Или проверить настройки прокси:

tr '\0' '\n' < /proc/1234/environ |
grep -Ei 'proxy|http_proxy|https_proxy'

▪️Сравнить окружение двух процессов

Например, приложение запущено вручную и через systemd:

tr '\0' '\n' < /proc/1234/environ | sort > /tmp/app.env
tr '\0' '\n' < /proc/5678/environ | sort > /tmp/service.env

diff -u /tmp/app.env /tmp/service.env

Так можно быстро найти переменную, из-за которой одна версия работает, а другая нет.

▪️Почему env может вводить в заблуждение

Вы выполняете:

echo "$PATH"

и видите одно значение.
Но процесс, запущенный несколько часов назад, мог получить старый PATH. Изменение конфигурации shell не меняет окружение уже работающего процесса.

То же касается:

HTTP_PROXY
LD_LIBRARY_PATH
LANG
HOME
JAVA_HOME

▪️Важный момент

Чтение /proc/PID/environ требует соответствующих прав доступа. Для чужих процессов доступ может быть ограничен настройками безопасности системы.
И главное: вывод нельзя бездумно отправлять в логи. В окружении могут находиться токены, пароли и другие секреты.

▪️Почему это важно

Когда сервис работает “вручную”, но ломается под systemd, cron или другим менеджером процессов, часто проверяют конфигурационные файлы, но забывают про окружение.

/proc/PID/environ показывает именно то, что получил уже запущенный процесс - а не то, что, как кажется, должно было быть передано ему.

BashTex 📱 #bash #linux


/proc/PID/fd: как узнать, что на самом деле держит процесс

ps показывает процесс, lsof умеет искать открытые файлы. Но иногда быстрее посмотреть непосредственно в /proc.

У каждого процесса есть каталог:

ls -l /proc/1234/fd

Внутри находятся символические ссылки на все открытые файловые дескрипторы процесса.

▪️Что можно увидеть

Например:

0 -> /dev/null
1 -> /var/log/app.log
2 -> /var/log/app.err
5 -> /tmp/data.db

0, 1 и 2 — стандартные stdin, stdout и stderr. Остальные дескрипторы приложение открыло самостоятельно.

▪️Найти удалённые файлы

Особенно полезен такой поиск:

ls -l /proc/1234/fd | grep deleted

Можно обнаружить:

7 -> /var/log/app.log (deleted)

Файл уже отсутствует в каталоге, но процесс всё ещё держит его открытым.

▪️Посмотреть тип дескриптора

readlink /proc/1234/fd/7

Результат может быть:

socket:[48192]

или:

pipe:[72831]

или:

/run/app.sock

То есть через один каталог можно увидеть не только обычные файлы, но и сокеты, pipes и другие объекты ядра.

▪️Найти все открытые дескрипторы процесса

for fd in /proc/1234/fd/*; do
printf '%s -> %s\n' "${fd##*/}" "$(readlink "$fd")"
done

Получается компактная карта того, с какими объектами взаимодействует процесс.

▪️Почему это важно

Если сервис ведёт себя странно, полезно посмотреть не только его аргументы и память, но и то, что он реально держит открытым.

/proc/PID/fd помогает быстро найти удалённые логи, неожиданные файлы, UNIX-сокеты и каналы IPC - причём без отдельного инструмента анализа.

BashTex 📱 #bash #utils


PIPESTATUS: как узнать, какая команда в пайпе реально упала

В Bash есть неприятная особенность: после пайпа $? показывает статус только последней команды.

Например:

false | grep test

echo $?

Здесь grep успешно отработал и вернул 0, поэтому ошибка false потерялась.

▪️На помощь приходит PIPESTATUS

Сразу после выполнения пайпа:

false | grep test

echo "${PIPESTATUS[@]}"

Получим:

1 1

Каждое значение соответствует своей команде.

false → 1
grep test → 1

▪️Более наглядный пример

cat /missing/file | grep ERROR | sort

echo "${PIPESTATUS[@]}"

Можно получить:

1 1 0

То есть cat завершился с ошибкой, grep ничего не получил, а sort технически выполнился успешно.

▪️Важный нюанс

PIPESTATUS очень легко потерять:

false | true

echo "${PIPESTATUS[@]}"

работает.

Но если между пайпом и проверкой выполнить другую команду:

false | true

echo "check"
echo "${PIPESTATUS[@]}"

теперь PIPESTATUS относится уже к echo "check".

Поэтому сохраняйте его сразу:

false | true

status=("${PIPESTATUS[@]}")

echo "${status[@]}"

▪️А если нужен общий статус пайпа?

Есть:

set -o pipefail

Теперь:

false | true

echo $?

вернёт ненулевой код.

Но pipefail отвечает только на вопрос «пайп завершился успешно?», а PIPESTATUS позволяет понять «какая именно команда вернула ошибку?».

▪️Почему это важно

В сложных Bash-конвейерах последняя команда может успешно завершиться даже тогда, когда предыдущая уже упала. PIPESTATUS позволяет не терять эти ошибки и точно определить проблемный этап.

BashTex 📱 #bash #utils


Проверка времени отклика сервисов

Когда сервис работает, но пользователи жалуются на медлительность, нужно мерить не аптайм, а отклик. Это легко сделать обычным curl.

▪️ Самый простой замер


time curl -s https://bashtex.com > /dev/null

Показывает общее время выполнения запроса и быстро понять, тормозит или нет.

▪️ Точнее: только сетевое время


curl -s -o /dev/null -w "time_total: %{time_total}\n" https://bashtex.com

Полезные метрики:

time_namelookup
time_connect
time_starttransfer
time_total

Пример:


curl -w "DNS:%{time_namelookup} CONNECT:%{time_connect} TTFB:%{time_starttransfer} TOTAL:%{time_total}\n" \
-o /dev/null -s https://bashtex.com

▪️ Проверка нескольких сервисов


for url in https://a.ru https://b.ru; do
curl -o /dev/null -s -w "$url %{time_total}\n" "$url"
done

▪️ Таймаут обязателен


curl --connect-timeout 3 --max-time 5 https://bashtex.com

Без таймаута любой мониторинг бесполезен.

BashTex 📱 #bash #utils


eval: почему опасен и когда реально нужен

В Bash почти любую строку можно превратить в команду через eval. Он берёт переданный текст и запускает его как новый Bash-код.

Именно поэтому это один из самых спорных инструментов в shell-скриптах.

▪️Как работает eval

Простой пример:

cmd="ls -la"

eval "$cmd"

Bash сначала получает строку:

ls -la

а затем выполняет её как обычную команду.

▪️Где возникает проблема

Если данные приходят от пользователя:
read input

eval "$input"

Теперь введённый текст становится кодом.
Например, вместо ожидаемого аргумента можно передать дополнительные команды, которые Bash выполнит при обработке строки.

Главная проблема eval - повторный разбор данных как команд.

▪️Почему кавычки не всегда спасают

Например:

file="test.txt"

eval "cat $file"

Пока значение простое - всё работает.

Но если внутри появятся специальные символы:
file="file name.txt"
или другие управляющие конструкции, поведение может отличаться от ожидаемого.

▪️Частая замена eval

Вместо хранения команды строкой:

cmd="ls -la /tmp"
eval "$cmd"

лучше использовать массив:

cmd=(ls -la /tmp)

"${cmd[@]}"

Теперь Bash хранит команду и аргументы отдельно.

▪️Когда eval действительно нужен

Иногда без него сложно обойтись. Например, при работе с динамическими именами переменных:
name="USER"

eval "echo \$$name"

Но даже здесь часто есть более безопасные варианты:

declare -n ref="$name"
echo "$ref"

▪️Проверка перед использованием

Если всё же нужен eval, данные должны быть полностью контролируемыми:

case "$action" in
start|stop|restart)
eval "service_$action"
;;
esac

Здесь значение ограничено заранее известным списком.

▪️Почему это важно

eval не является “плохой” командой сам по себе. Проблема появляется, когда данные превращаются в код.
В большинстве случаев массивы, case, функции или declare решают задачу безопаснее. eval стоит оставлять только для ситуаций, где действительно нужен второй проход парсинга Bash.

BashTex 📱 #eval


Проверка доступности локальных Unix-сервисов: nc -U и curl --unix-socket

Не все сервисы в Linux слушают TCP-порты. Многие приложения работают только через UNIX-сокеты - локальные каналы связи между процессами.

Например:

/run/docker.sock
/run/php/php-fpm.sock
/run/containerd/containerd.sock

Снаружи такие сервисы не видны через обычный nmap, но внутри системы они могут иметь важное значение.

▪️Найти активные UNIX-сокеты

ss -xl

Пример:

u_str LISTEN 0 128 /run/docker.sock

Теперь можно проверить, отвечает ли сервис.

▪️Проверка через nc

Netcat умеет подключаться к UNIX-сокетам:

nc -U /run/docker.sock

Если соединение установлено - сокет доступен.

Для HTTP-сервисов можно отправить запрос вручную:

printf "GET / HTTP/1.0\r\n\r\n" | nc -U /run/service.sock

▪️Проверка HTTP API через curl

Многие локальные API работают через UNIX-сокеты:

curl --unix-socket /run/docker.sock http://localhost/version

Здесь localhost не используется как сетевой адрес - он нужен только для формирования HTTP-запроса.

▪️Пример с Docker

Вместо:

docker version

можно напрямую обратиться к API:

curl --unix-socket /var/run/docker.sock \
http://localhost/_ping

Ответ:

OK

означает, что Docker daemon доступен.

▪️Проверка прав доступа

Даже если сервис работает, доступ может быть ограничен:

ls -l /run/docker.sock

Например:

srw-rw---- root docker docker.sock

Только пользователи группы docker смогут подключиться.

▪️Почему это важно

UNIX-сокеты часто остаются вне стандартного мониторинга. Администратор проверяет открытые порты, но забывает про локальные API и IPC-каналы.

Проверка через ss, nc -U и curl --unix-socket помогает быстро понять:
жив ли сервис;
кто имеет к нему доступ;
отвечает ли локальное API;
нет ли неожиданного открытого канала между процессами.

BashTex 📱 #sort


Почему sort | uniq не всегда работает так, как ожидается

Когда нужно убрать дубликаты, многие сразу пишут:

sort file.txt | uniq

Работает. Но только если понимать, что именно делает каждая команда.

▪️Как работает uniq

Распространённое заблуждение - uniq ищет все повторяющиеся строки.

На самом деле он удаляет только соседние дубликаты.

Например:

apple
banana
apple
orange

Если выполнить:

uniq file.txt

получим тот же самый список - одинаковые строки не стоят рядом.

▪️Зачем нужен sort

После сортировки одинаковые строки оказываются соседними:

sort file.txt | uniq

Результат:

apple
banana
orange

Именно поэтому эти команды почти всегда используются вместе.

▪️Когда сортировка не подходит

Если порядок строк важен, sort изменит его.
Например, при анализе логов или событий по времени это может полностью исказить картину.

В таких случаях лучше использовать:

awk '!seen[$0]++'

Команда удаляет дубликаты, но сохраняет исходный порядок строк.

▪️Найти только повторяющиеся строки

Иногда нужны не уникальные записи, а наоборот - только дубликаты:

sort file.txt | uniq -d

Или вывести количество повторений:

sort file.txt | uniq -c

Получим:

15 nginx
8 sshd
3 cron

▪️Почему это важно

uniq кажется простой утилитой, но ошибка в понимании её работы часто приводит к неверным результатам. Перед использованием всегда стоит помнить: она сравнивает только соседние строки, а не весь файл целиком.

BashTex 📱 #sort


mkfifo: обмен данными между процессами через именованные каналы

Обычный pipe:

command1 | command2

работает только пока запущены обе команды. Но иногда нужен постоянный канал, к которому разные процессы могут подключаться позже.

Для этого в Linux есть FIFO (named pipe).

▪️Создание именованного канала

mkfifo /tmp/my_pipe

Теперь /tmp/my_pipe выглядит как файл:

ls -l /tmp/my_pipe

Вывод:

prw-r--r-- 1 user user 0 Aug 4 12:00 my_pipe

Буква p означает pipe.

▪️Передача данных между двумя процессами

Терминал 1:

cat /tmp/my_pipe

Терминал 2:

echo "hello from process" > /tmp/my_pipe

Первый процесс сразу получит сообщение.

▪️Пример с логированием

Можно отправлять события из одного скрипта в другой:

mkfifo /tmp/events.pipe

tail -f /var/log/app.log > /tmp/events.pipe &

А отдельный обработчик:

while read line; do
echo "EVENT: $line"
done < /tmp/events.pipe

Теперь обработка логов отделена от источника данных.

▪️Важный момент
FIFO блокирует процесс:

echo "data" > /tmp/my_pipe

будет ждать, пока кто-то откроет канал на чтение.

Поэтому в автоматизации часто используют фоновые процессы:

cat /tmp/my_pipe &
echo "start" > /tmp/my_pipe

▪️Чем отличается от обычного файла

Данные в FIFO не сохраняются на диске.
Если никто не читает:
процесс → FIFO → процесс
данные не складываются в очередь, а ждут получателя.

▪️Где полезно

• связь между Bash-скриптами;
• простые IPC-механизмы;
• обработка потоков данных;
• разделение генератора и обработчика.

▪️Почему это важно

mkfifo часто заменяет временные файлы и сложные конструкции с перенаправлениями. Это простой способ построить связь между независимыми процессами прямо средствами Linux без дополнительных сервисов.

BashTex 📱 #mfifo


Когда переменные пропадают

Одна из частых ловушек в bash - переменная изменилась… но снаружи осталась прежней. Причина почти всегда одна - subshell.

▪️ Что такое subshell. Subshell - это дочерний процесс bash. Он получает копию переменных, но изменения не возвращаются назад. Создается, например, здесь:


( command )

или в пайпах:


command | while read ...

▪️ Классическая проблема


count=0

echo -e "a\nb\nc" | while read -r line; do
((count++))
done

echo "$count"

Результат: 0

Почему? Цикл while выполняется в subshell.

▪️ Правильный вариант


count=0

while read -r line; do
((count++))
done <


localhost dan repost
Video oldindan ko‘rish uchun mavjud emas
Telegram'da ko‘rish
С ДНЕМ СИСАДМИНА! 👍

По традиции в этот великий день мы смотрим классику 🎬

😎 localhost › IT-юмор


env -i: запуск программы в полностью “чистом” окружении

Большинство программ наследуют десятки переменных окружения: PATH, HOME, LANG, LD_LIBRARY_PATH, прокси, токены и многое другое.
Иногда именно они становятся причиной странного поведения приложения.

▪️Посмотреть текущее окружение

env
или:
printenv

Вы увидите все переменные, которые будут унаследованы дочерними процессами.

▪️Запуск без окружения

Команда:

env -i bash

запустит новый Bash практически без переменных окружения.
Если выполнить:
env
вывод окажется пустым или почти пустым.

▪️Передать только нужные переменные

Например:

env -i PATH=/usr/bin:/bin HOME=/tmp bash

Теперь программа получит только PATH и HOME.
Это особенно удобно при поиске ошибок.

▪️Практический пример

Есть скрипт:

./deploy.sh

У одного пользователя он работает, у другого - нет.

Проверяем:

env -i PATH=/usr/bin:/bin ./deploy.sh

Если проблема исчезла или, наоборот, воспроизвелась, причина, скорее всего, в одной из переменных окружения, а не в самом скрипте.

▪️Где ещё используется

• отладка CI/CD;
• проверка зависимостей от окружения;
• запуск сервисов в предсказуемых условиях;
• поиск проблем с PATH, LANG, HOME и прокси.

▪️Почему это важно

Ошибки, связанные с окружением, часто трудно воспроизвести: скрипт работает у одного администратора и ломается у другого. env -i позволяет полностью исключить влияние наследуемых переменных и быстро понять, зависит ли программа от окружения больше, чем кажется.

BashTex 📱 #bash #linux


find -exec против -exec {} +: почему одна команда может быть в десятки раз быстрее

Многие используют find так:

find /var/log -name "*.log" -exec gzip {} \;

Команда работает, но есть нюанс: для каждого найденного файла запускается отдельный процесс gzip.

▪️Что происходит

Если найдено 5000 файлов, Bash выполнит примерно 5000 запусков gzip.

Это лишние создания процессов, переключения контекста и заметная потеря производительности.

▪️Более эффективный вариант

find /var/log -name "*.log" -exec gzip {} +

Теперь find собирает несколько файлов и передаёт их одной команде:

gzip file1.log file2.log file3.log ...

Количество запусков сокращается в десятки или даже сотни раз.

▪️В чём разница

Вариант с \;:

gzip file1
gzip file2
gzip file3

Вариант с +:

gzip file1 file2 file3

Логика обработки не меняется, но накладные расходы становятся значительно меньше.

▪️Когда + использовать нельзя

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

find . -type f -exec mv {} {}.bak \;

или внутри используется сложная оболочка:

find . -type f -exec sh -c '...' \;

В таких случаях объединение аргументов может изменить поведение команды.

▪️Как выбрать

Используйте -exec {} +, когда команда умеет принимать сразу несколько файлов:

rm
chmod
chown
gzip
cp
mv (при копировании в каталог)
ls

Если же обработка должна происходить строго по одному файлу — остаётся -exec {} \;.

▪️Почему это важно

Во многих скриптах find -exec {} \; используется по привычке. Замена всего одного символа — \; на + - позволяет значительно сократить число создаваемых процессов и ускорить обработку больших каталогов без изменения логики скрипта.

BashTex 📱 #bash #linux


exec: замена процесса без создания дочернего

Обычно при запуске команды Bash создаёт новый процесс:

sleep 60

После завершения sleep управление возвращается обратно в shell.

Но exec работает иначе - он заменяет текущий процесс новым. Старый процесс исчезает, а его PID сохраняется.

▪️Простой пример

echo $$
exec sleep 60

После выполнения вы больше не вернётесь в текущий shell. Процесс Bash будет полностью заменён на sleep.

▪️Почему PID не меняется

Запустим:

echo $$
exec bash

И снова:

echo $$

PID останется тем же. Новый экземпляр Bash не был создан - текущий процесс просто заменился.

▪️Где это действительно полезно

Представьте простой launcher:

#!/bin/bash

echo "Starting application..."
exec /usr/local/bin/myapp

После запуска в системе не останется лишнего процесса Bash - только myapp.

Это особенно важно для:
контейнеров Docker;
systemd-сервисов;
entrypoint-скриптов.

▪️Почему это лучше обычного запуска

Без exec:

bash
└── myapp

С exec:

myapp

Нет промежуточного процесса, сигналы (SIGTERM, SIGINT) сразу получает приложение, а не оболочка.

▪️Когда использовать нельзя

После exec текущий скрипт прекращает существование:

echo "Before"
exec sleep 5
echo "After"

Строка After никогда не выполнится.

▪️Почему это важно

Многие воспринимают exec как ещё один способ запуска команды. На самом деле это механизм замены процесса. Он позволяет убрать лишний уровень в дереве процессов, избежать проблем с обработкой сигналов и сделать запуск сервисов более предсказуемым.

BashTex 📱 #bash #linux

20 ta oxirgi post ko‘rsatilgan.