Каждый рабочий день администратора проходит в bash, и скорость работы в нем напрямую зависит от того, понимаете ли вы, что именно происходит между нажатием Enter и запуском программы. В этом уроке разберем, как оболочка хранит и наследует переменные, как находит исполняемый файл, как раскрывает то, что вы написали, и как пользоваться историей и справкой так, чтобы не печатать одно и то же по двадцать раз. Это фундамент: на нем потом стоят скрипты, systemd-юниты и автоматизация.

Как это работает
У оболочки есть два сорта переменных. Переменные оболочки видны только текущему процессу bash. Переменные окружения помечены флагом экспорта и копируются в каждый дочерний процесс. Когда вы запускаете команду, bash делает fork, дочерний процесс наследует копию окружения родителя - и ничего из того, что дочерний процесс там поменяет, обратно к вам не вернется. Поэтому изменить окружение текущей сессии скрипт не может, только подсказать вам это сделать.
Команда export переводит обычную переменную в разряд экспортируемых. set без аргументов печатает вообще все переменные и функции оболочки, env - только экспортированное окружение. unset удаляет переменную. Запомните разницу: переменная без export существует, но дочерние процессы ее не увидят.
PATH - это список каталогов через двоеточие, по которым bash ищет исполняемый файл, когда вы пишете голое имя команды. Поиск идет слева направо, берется первое совпадение. Чтобы не сканировать диск каждый раз, bash кеширует найденные пути в хеш-таблице. Поэтому после установки программы в уже просмотренный каталог иногда нужно сбросить кеш через hash -r.
Прежде чем искать в PATH, bash раскрывает написанную строку в несколько проходов: тильда (~ в домашний каталог), переменные ($VAR), подстановка команд ($(...)), арифметика ($((...))), и в конце - глоб-шаблоны (* ? [ ]). Глоб раскрывает сама оболочка, а не программа: ls *.txt получает уже готовый список файлов. Если совпадений нет, шаблон по умолчанию остается как есть - частый источник сюрпризов.
Кавычки управляют тем, какие проходы отработают. Одинарные кавычки выключают все раскрытия - внутри буквально каждый символ. Двойные кавычки оставляют живыми $, обратные кавычки и обратный слеш, но гасят разбиение на слова и глоб. Обратный слеш экранирует один следующий символ. Правило простое: переменные, в которых могут быть пробелы или спецсимволы, всегда берите в двойные кавычки.
Команды и примеры
Переменные и окружение:
Код: Выделить всё
GREETING="привет" # только в этой оболочке
export EDITOR=vim # уйдет во все дочерние процессы
export PATH="$HOME/bin:$PATH" # добавили свой каталог в начало
env | grep EDITOR # видно в окружении
set | grep GREETING # видно среди переменных оболочки
unset GREETING # удалили
Код: Выделить всё
type -a ls # ЧЕМ является имя: alias, builtin, функция, файл - и ВСЕ варианты
which -a python3 # внешняя утилита, ищет только файлы в PATH
command -v cp # переносимый способ узнать, как разрешится команда
Сброс кеша путей и обход алиаса:
Код: Выделить всё
hash -r # очистить кеш найденных команд
\ls # обратный слеш гасит алиас, вызовет настоящий ls
command ls # то же самое без алиаса
Код: Выделить всё
echo "Сегодня $(date +%F), ядро $(uname -r)"
echo "2+2 = $(( 2 + 2 ))"
name="мир"; echo "Двойные: $name" # Двойные: мир
echo 'Одинарные: $name' # Одинарные: $name
touch "файл с пробелом.txt" # без кавычек создаст ТРИ файла
Код: Выделить всё
history 10 # последние 10 команд
!! # повторить предыдущую команду (удобно с sudo !!)
!ssh # последняя команда, начинавшаяся на ssh
!$ # последний аргумент прошлой команды
Справка. Разделы man - это не прихоть, а способ разрешить конфликты имен:
Код: Выделить всё
man 1 printf # 1 - пользовательские команды
man 5 passwd # 5 - форматы файлов (файл /etc/passwd, не команда!)
man 8 mount # 8 - администрирование, обычно root
man -k network # поиск по короткому описанию (он же apropos)
man -f passwd # к какому разделу относится имя (он же whatis)
Частые грабли
- Забыли export - переменная видна в текущей оболочке, но скрипт или дочерний процесс ее не получает.
- Изменили PATH или любую переменную в скрипте, запущенном как ./script.sh - после выхода ничего не изменилось, потому что это был отдельный процесс. Нужен source script.sh или точка: . script.sh.
- Поставили новую программу, а bash запускает старую или говорит command not found - сработал кеш путей, лечится hash -r.
- Переменная в двойных кавычках с пробелами разъехалась на несколько аргументов - забыли кавычки вокруг "$var".
- which ls говорит /bin/ls, а на деле выполняется алиас с цветом - which не видит алиасы, проверяйте через type.
- man passwd открыл команду смены пароля, а нужен был формат файла - указывайте раздел: man 5 passwd.
- Дописали PATH в начало без $PATH в конце (PATH="$HOME/bin") - потеряли доступ ко всем системным командам в этой сессии.
- Создайте каталог ~/bin, положите туда скрипт hello (с строкой #!/bin/bash и echo привет), сделайте chmod +x.
- Запустите hello - убедитесь, что command not found, затем добавьте export PATH="$HOME/bin:$PATH" и запустите снова.
- Выполните type hello и which hello, сравните вывод; затем создайте алиас alias hello='echo алиас' и снова сравните type и which.
- Задайте COLOR=red без export, запустите bash (вложенная оболочка), проверьте echo $COLOR - пусто; выйдите, сделайте export и повторите.
- Через Ctrl+R найдите в истории команду с chmod и выполните повторно, не печатая заново.
- Откройте man 5 crontab и man 1 crontab, найдите разницу в содержимом; затем man -k passwd посмотрите все разделы с этим именем.
- Чем отличается переменная оболочки от переменной окружения и какая команда переводит одну в другую?
- Почему изменения PATH внутри ./script.sh не сохраняются, а внутри source script.sh - сохраняются?
- В каком порядке bash раскрывает тильду, переменные, подстановку команд и глоб-шаблоны?
- Что покажет type ls и что покажет which ls, если на ls есть алиас, и почему они расходятся?
- Зачем нужна команда hash -r и в какой ситуации без нее bash запустит не тот файл?
- Команда и файл называются одинаково (passwd) - как открыть man именно для формата файла, а не для команды?