LEARN X · ЗА 15 МИН
Bash
Bash за 15 минут: переменные, аргументы, условия, циклы, case, функции, массивы, редиректы, строки и trap — весь язык скриптов в комментированном коде.
Bash — язык скриптов командной оболочки Unix. Здесь весь язык на одной странице: синтаксис в плотно закомментированном коде, почти без прозы. Команды (grep, sed, find) живут в отдельной шпаргалке — тут только сам ЯЗЫК.
1. Шебанг и запуск
#!/bin/bash
# Первая строка — шебанг: указывает интерпретатор скрипта.
# Запуск:
# chmod +x script.sh # сделать файл исполняемым
# ./script.sh # запустить
# bash script.sh # или явно через bash (chmod не нужен)
# Это однострочный комментарий — начинается с #.
echo "Привет, мир" # вывод строки + перевод строки -> Привет, мир
echo -n "без переноса" # -n: не добавлять \n в конце
echo -e "таб:\tстрока" # -e: включить escape-последовательности (\t \n)
printf "%s = %d\n" "x" 42 # printf: форматированный вывод -> x = 42
2. Переменные
name="Аня" # присваивание БЕЗ пробелов вокруг = (name = ... — ошибка!)
count=10 # числа хранятся как строки
echo "$name" # обращение через $ -> Аня
echo "${name}!" # ${} — явные границы имени -> Аня!
echo "${name}_id" # без скобок $name_id искал бы переменную name_id
# Кавычки решают всё:
echo "$name стоит 5$" # двойные: подстановка работает -> Аня стоит 5$
echo '$name дословно' # одинарные: НЕТ подстановки -> $name дословно
readonly PI=3.14 # константа, переприсвоить нельзя
unset count # удалить переменную
export PATH_EXTRA=/opt # export — передать переменную дочерним процессам
# Значения по умолчанию:
echo "${city:-Москва}" # если city пуста/не задана -> Москва
echo "${city:=Питер}" # то же, но ещё и присвоит city=Питер
echo "${must:?нужно!}" # если must пуста — выйти с ошибкой "нужно!"
3. Аргументы и спецпеременные
#!/bin/bash
# Запуск: ./script.sh apple banana
echo "$0" # имя самого скрипта -> ./script.sh
echo "$1" # первый аргумент -> apple
echo "$2" # второй аргумент -> banana
echo "$#" # КОЛИЧЕСТВО аргументов -> 2
echo "$@" # ВСЕ аргументы списком -> apple banana
echo "$*" # все аргументы одной строкой
echo "$$" # PID текущего процесса
false # команда, завершившаяся с ошибкой
echo "$?" # код возврата ПОСЛЕДНЕЙ команды -> 1 (0 = успех, иначе ошибка)
shift # сдвиг: $2 становится $1, $3 -> $2 и т.д.
# Перебор всех аргументов:
for arg in "$@"; do
echo "аргумент: $arg"
done
4. Подстановки
# Подстановка команды — вставить вывод команды в строку:
now=$(date +%H:%M) # современный синтаксис $( )
files=`ls` # устаревший синтаксис с обратными кавычками
echo "Сейчас $now"
# Арифметика — внутри $(( )):
echo $(( 2 + 3 * 4 )) # -> 14
echo $(( 10 / 3 )) # целочисленное деление -> 3
echo $(( 10 % 3 )) # остаток -> 1
echo $(( 2 ** 8 )) # степень -> 256
n=5; (( n++ )) # (( )) — арифметический контекст, n стало 6
echo $n
# Расширение скобок (brace expansion):
echo {1..5} # -> 1 2 3 4 5
echo {a..e} # -> a b c d e
echo {0..10..2} # шаг 2 -> 0 2 4 6 8 10
echo file_{jpg,png,gif} # -> file_jpg file_png file_gif
echo img{01..03}.png # -> img01.png img02.png img03.png
5. Условия
Между [ и операндами ОБЯЗАТЕЛЬНЫ пробелы. [[ ]] — расширенная версия Bash: безопаснее и умеет &&, ||, шаблоны.
x=10
if [ "$x" -gt 5 ]; then
echo "больше пяти"
elif [ "$x" -eq 5 ]; then
echo "ровно пять"
else
echo "меньше пяти"
fi
# Числовые сравнения (внутри [ ] и test):
# -eq равно -ne не равно -gt > -lt < -ge >= -le <=
# Строковые сравнения:
if [ "$name" = "Аня" ]; then echo "совпало"; fi # = равно, != не равно
if [ -z "$s" ]; then echo "строка пустая"; fi # -z пустая
if [ -n "$s" ]; then echo "строка непустая"; fi # -n непустая
# Проверки файлов:
# -e существует -f файл -d каталог -r читаемый -w записываемый
if [ -f "/etc/hosts" ]; then echo "файл есть"; fi
# [[ ]] — расширенный тест Bash:
if [[ "$x" -gt 5 && "$name" == А* ]]; then # == с шаблоном, логика внутри
echo "оба условия верны"
fi
if [[ "$file" =~ \.txt$ ]]; then echo "это .txt (regex)"; fi # =~ regex
6. Циклы
# for по списку:
for fruit in apple banana cherry; do
echo "$fruit"
done
# for по диапазону:
for i in {1..3}; do echo "шаг $i"; done # шаг 1, шаг 2, шаг 3
# for в C-стиле:
for (( i = 0; i < 3; i++ )); do echo "i=$i"; done
# while — пока условие истинно:
n=1
while [ "$n" -le 3 ]; do
echo "n=$n"
(( n++ ))
done
# until — пока условие ЛОЖНО (зеркало while):
m=1
until [ "$m" -gt 3 ]; do
echo "m=$m"
(( m++ ))
done
# Управление циклом:
for i in {1..5}; do
(( i == 2 )) && continue # continue: пропустить текущую итерацию
(( i == 4 )) && break # break: прервать весь цикл
echo "$i" # выведет 1 3
done
7. Case
read -p "Команда: " cmd
case "$cmd" in
start)
echo "запуск"
;; # ;; завершает ветку
stop|halt) # | — несколько шаблонов в одной ветке
echo "остановка"
;;
re*) # шаблон: всё, что начинается с re
echo "перезапуск?"
;;
*) # * — значение по умолчанию (любое другое)
echo "неизвестно: $cmd"
;;
esac # case читается задом наперёд: esac
8. Функции
# Два способа объявить:
greet() { # классический, скобки пустые
echo "Привет, $1" # аргументы — как у скрипта: $1 $2 $@ $#
}
function bye { # с ключевым словом function
echo "Пока, $1"
}
greet "Аня" # вызов БЕЗ скобок -> Привет, Аня
bye "Аня" # -> Пока, Аня
# return возвращает только КОД (0..255), не данные:
is_even() {
(( $1 % 2 == 0 )) && return 0 || return 1
}
if is_even 4; then echo "чётное"; fi
# Чтобы "вернуть" значение — печатают его и ловят через $( ):
square() { echo $(( $1 * $1 )); }
result=$(square 5) # -> 25
echo "$result"
# local — локальная переменная (видна только внутри функции):
counter() {
local n=0 # без local n утёк бы в глобальную область
(( n++ ))
echo "$n"
}
9. Массивы
# Индексируемый массив:
fruits=(apple banana cherry) # объявление через скобки
fruits[3]="date" # добавить/изменить элемент
echo "${fruits[0]}" # доступ по индексу -> apple
echo "${fruits[@]}" # все элементы -> apple banana cherry date
echo "${#fruits[@]}" # количество элементов -> 4
echo "${!fruits[@]}" # все индексы -> 0 1 2 3
# Перебор:
for f in "${fruits[@]}"; do
echo "$f"
done
fruits+=("elderberry") # добавить в конец
unset 'fruits[1]' # удалить элемент по индексу
# Ассоциативный массив (словарь), Bash 4+:
declare -A ages # declare -A — обязательно для словаря
ages["Аня"]=30
ages["Боб"]=25
echo "${ages[Аня]}" # -> 30
for key in "${!ages[@]}"; do # ${!arr[@]} — перебор ключей
echo "$key: ${ages[$key]}"
done
10. Ввод-вывод и редиректы
# Чтение со стандартного ввода:
read -p "Имя: " user # -p печатает приглашение
read -s -p "Пароль: " pass # -s скрывает ввод (для паролей)
read -a arr # -a прочитать слова в массив
# Редиректы вывода:
echo "строка" > out.txt # > перезаписать файл
echo "ещё" >> out.txt # >> дописать в конец
wc -l < out.txt # < взять ввод из файла
# Потоки: 1 = stdout (вывод), 2 = stderr (ошибки):
ls /нет 2> err.txt # 2> перенаправить только ошибки
ls /нет > all.txt 2>&1 # 2>&1 слить stderr в stdout (порядок важен!)
command > /dev/null 2>&1 # выбросить весь вывод в никуда
# Конвейер (pipe) — вывод одной команды на ввод другой:
cat out.txt | sort | uniq # | связывает stdout -> stdin
# Here-document — многострочный текст на вход:
cat <<EOF
Строка 1, переменные работают: $user
Строка 2
EOF
cat <<'EOF' # кавычки у метки — без подстановки $
Дословно $user
EOF
# Here-string — короткая строка на вход:
grep "a" <<< "banana"
11. Строки
s="Hello, World"
echo "${#s}" # длина строки -> 12
echo "${s:7}" # подстрока с 7-го символа -> World
echo "${s:0:5}" # 5 символов с начала -> Hello
# Замена:
echo "${s/o/0}" # заменить ПЕРВОЕ вхождение -> Hell0, World
echo "${s//o/0}" # заменить ВСЕ вхождения -> Hell0, W0rld
# Обрезка по шаблону (удаление префикса/суффикса):
path="/home/user/file.txt"
echo "${path##*/}" # ## — самый длинный префикс по */ -> file.txt
echo "${path%/*}" # % — самый короткий суффикс /* -> /home/user
echo "${path%.txt}" # убрать суффикс .txt -> /home/user/file
# Регистр (Bash 4+):
echo "${s^^}" # ВЕРХНИЙ регистр -> HELLO, WORLD
echo "${s,,}" # нижний регистр -> hello, world
# Проверка подстроки:
if [[ "$s" == *World* ]]; then echo "содержит World"; fi
12. Полезное
#!/bin/bash
set -e # выйти при первой же ошибке (ненулевой код любой команды)
set -u # ошибка при использовании необъявленной переменной
set -o pipefail # код ошибки конвейера = код упавшего звена, а не последнего
set -euo pipefail # всё сразу — типичная "строгая" преамбула скрипта
# Логические операторы по коду возврата:
mkdir -p /tmp/x && echo "ок" # && — следующая команда, если ПРЕДЫДУЩАЯ успешна
cat нет.txt || echo "не нашёл" # || — следующая, если предыдущая УПАЛА
test -d /tmp && echo "есть" || echo "нет" # тернарная идиома
# trap — перехват сигналов и событий (очистка ресурсов):
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT # выполнить при выходе из скрипта (любом)
trap 'echo прервано; exit 1' INT # перехват Ctrl+C (сигнал INT)
# Коды выхода:
exit 0 # 0 — успех
exit 1 # 1..255 — ошибка (1 общая, 2 неверное использование, ...)
# Полезные идиомы:
: "${VAR:?переменная VAR обязательна}" # упасть, если VAR не задана
command -v git >/dev/null || { echo "нет git"; exit 1; } # проверка наличия