Skip to main content

Глава 6. Bash и автоматизация работы в командной строке

Bash и автоматизация работы в командной строке

Цель занятия — освоить историю команд, раскрытие шаблонов, переменные, условия и циклы, затем написать небольшой сценарий копирования. Материал рассчитан на Ubuntu Server 26.04 LTS. Основная учётная запись — student, учебный пароль — 123456. Все файлы занятия создаются в домашнем каталоге.

1. Оболочка и терминал

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

getent passwd student
printf '%s\n' "$SHELL"
ps -p $$ -o pid,comm,args
type cd printf ls

Последнее поле записи пользователя показывает назначенную оболочку. $SHELL обычно содержит оболочку входа, но не гарантирует, что именно она выполняет текущую команду. $$ — PID текущего Bash. cd меняет каталог самой оболочки, поэтому является встроенной командой.

Вход в служебную учётную запись может быть ограничен оболочкой nologin; это не мешает запускать службу от её имени. Назначать всем служебным пользователям Bash не требуется.

Запомнить: оболочка

Командная оболочка — интерпретатор, который преобразует текст команд в операции. Сценарий — файл с командами для интерпретатора. Сценарий выполняется с правами запустившего его пользователя.

2. История и редактирование команд

Стрелки вверх и вниз позволяют выбрать ранее введённые команды. Ctrl+R начинает обратный поиск по истории; набирайте часть команды, повторяйте Ctrl+R для предыдущих совпадений. Перед запуском проверьте найденную строку.

history 10

Ctrl+A перемещает курсор к началу строки, Ctrl+E — к концу, Ctrl+U удаляет участок от курсора до начала, Ctrl+K — от курсора до конца. Tab дополняет команды и пути; при нескольких вариантах повторное нажатие обычно показывает список. Ctrl+C отменяет ввод или посылает сигнал программе переднего плана.

История обычно сохраняется в ~/.bash_history, но её состав зависит от настроек и завершения сеанса. Она не является полным журналом аудита. Записи !! и !номер могут раскрыться в другую команду; в начале обучения выбирайте строку стрелками и перечитывайте её. Пароли и закрытые ключи не передавайте прямо в командной строке.

Приёмы перемещения и редактирования

Команда или сочетание Действие
Ctrl+L Перерисовать экран, переместив текущую строку ввода наверх
Ctrl+W Удалить слово слева от курсора при стандартных настройках Bash
cd - Перейти в предыдущий рабочий каталог и вывести его путь
cd ~
cd /etc
cd -
pwd

Ожидаемый результат последних двух команд — домашний каталог /home/student. cd - возвращает в предыдущий каталог; cd .. переходит в родительский. Это разные операции. Пример выполняйте в Bash со стандартным режимом редактирования строки; назначение клавиш можно изменить настройками.

Настройка истории и псевдонимы команд

history -d НОМЕР удаляет выбранную запись из истории текущей оболочки; номер сначала находят через history. HISTCONTROL=ignoreboth объединяет ignorespace и ignoredups: не добавляет строки с начальным пробелом и последовательные повторы. Настройка не очищает уже записанное, не действует на все оболочки и не превращает историю в защищённое хранилище секретов.

mkdir -p ~/chapter06
alias ll='ls -alF'
alias cpu10='ps -eo pid,comm,pcpu --sort=-pcpu | head -n 11'
type ll
ll ~/chapter06
cpu10
unalias cpu10

Alias — подстановка для имени команды в интерактивной оболочке. Он не является отдельной программой и обычно не раскрывается в сценариях. Не перекрывайте настоящую команду install псевдонимом установки пакетов: такая программа существует и выполняет другую работу.

Для постоянного ll откройте nano ~/.bash_aliases, добавьте строку alias ll='ls -alF' и сохраните прежний текст. Стандартный ~/.bashrc Ubuntu обычно подключает этот файл; проверьте это перед добавлением собственного подключения. source ~/.bashrc применяет настройки к текущему интерактивному Bash. Подключайте только собственный проверенный файл: source исполняет его команды в текущей оболочке.

Запомнить: псевдоним команды

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

3. Шаблоны и кавычки

Сначала подготовьте примеры:

mkdir -p ~/chapter06/source ~/chapter06/copy
cd ~/chapter06
printf 'Первый файл\n' > source/one.txt
printf 'Второй файл\n' > source/two.txt
printf 'Имя с пробелом\n' > 'source/lecture notes.txt'
ls source/*.txt
cat 'source/lecture notes.txt'

* в шаблоне имени соответствует произвольной последовательности символов, ? — одному символу, [12] — одному из перечисленных символов. Оболочка раскрывает шаблон до запуска программы. Скрытые имена обычно не входят в *. Если совпадений нет, поведение зависит от настроек Bash; нельзя считать, что шаблон всегда превращается в пустой список.

Одинарные кавычки сохраняют текст буквально. В двойных кавычках работают подстановка переменных и команд, но пробелы внутри результата не разделяют аргумент. Поэтому пути из переменных почти всегда заключают в двойные кавычки.

Выучить: кавычки

'текст' — буквальная строка. "$переменная" — один аргумент со значением переменной. Незаключённое в кавычки значение может разделиться на слова и раскрыться как шаблон имени.

В подготовленном каталоге source находятся три текстовых файла. Команда ls source/*.txt передаёт ls три пути; кавычки в cat 'source/lecture notes.txt' сохраняют путь с пробелами как один аргумент.

note_path="$HOME/chapter06/source/lecture notes.txt"
printf 'Один аргумент: <%s>\n' "$note_path"
printf 'Без кавычек: <%s>\n' $note_path

Первая команда выводит одну строку с полным путём. Вторая в данном примере выводит две строки: пробел разделяет значение переменной на два аргумента. Это учебная демонстрация ошибки; при работе с путями используйте "$note_path".

Записать: аргумент команды

Аргумент — отдельное значение, переданное команде после её имени. Кавычки помогают оболочке определить границы аргументов и сами обычно не передаются программе.

4. Переменные и коды завершения

course='Ubuntu Server'
printf 'Курс: %s\n' "$course"
today=$(date +%F)
printf 'Дата: %s\n' "$today"
count=3
printf 'Следующее число: %s\n' "$((count + 1))"

При присваивании вокруг = нет пробелов. $(команда) подставляет стандартный вывод команды без завершающих переводов строки. $((выражение)) выполняет целочисленную арифметику. Локальная переменная оболочки не передаётся внешним программам автоматически; export имя делает её переменной окружения дочерних процессов. Не переопределяйте HOME, PATH и другие системные переменные для хранения учебных данных.

Переменная оболочки и переменная окружения

lecture_topic='Командная строка'
printf 'Текущий Bash: %s\n' "$lecture_topic"
bash -c 'printf "Дочерний Bash до export: <%s>\n" "${lecture_topic-}"'
export lecture_topic
bash -c 'printf "Дочерний Bash после export: <%s>\n" "$lecture_topic"'
unset lecture_topic

До export дочерний Bash выводит пустое значение между угловыми скобками. После export он получает строку Командная строка. Запись ${lecture_topic-} подставляет пустую строку, если переменная не установлена. Одинарные кавычки вокруг команд для bash -c откладывают раскрытие переменной до запуска дочерней оболочки. unset удаляет учебную переменную из текущей оболочки.

Запомнить: переменная и окружение

Переменная оболочки хранит значение в текущей оболочке. Переменная окружения передаётся запускаемым дочерним процессам. Изменение переменной в дочернем процессе не изменяет переменную родительской оболочки.

5. Коды завершения и условия

test -f source/one.txt
result=$?
printf 'Код проверки: %s\n' "$result"

Код 0 обычно означает успех, ненулевой — иной результат или ошибку. Он относится к последней команде и меняется после следующей. Последовательность A && B запускает B после успеха A, A || B — после неуспеха A. В конвейере по умолчанию учитывается код последней команды; set -o pipefail меняет правило, позволяя обнаружить неуспех предыдущих звеньев.

Последовательность команд

Запись Когда запускается B
A; B После завершения A, независимо от её кода
A && B Если A завершилась с кодом 0
`A
false; printf 'После false с точкой с запятой\n'
false && printf 'Эта строка не появится\n'
false || printf 'Обработан ненулевой код\n'
true && printf 'Обработан успешный код\n'

true возвращает код 0, false — код 1. При выполнении всех четырёх строк появятся три сообщения; второе сообщение не появится. Ненулевой код не всегда означает поломку программы: например, проверка существования отсутствующего файла сообщает таким способом ложное условие.

Проверки файлов, строк и чисел

Условия в Bash опираются на код завершения проверочной команды. Команда test и запись [ ... ] выполняют проверки. В [ ... ] закрывающая скобка должна быть отдельным аргументом; пробелы обязательны.

Проверка Условие истинно, если
[ -f "$path" ] Путь обозначает обычный файл
[ -d "$path" ] Путь обозначает каталог
[ -z "$text" ] Строка пустая
[ -n "$text" ] Строка непустая
[ "$number" -eq 3 ] Целое число равно 3
[ "$number" -lt 3 ] Целое число меньше 3
[ "$number" -le 3 ] Целое число меньше либо равно 3

Проверки -f и -d обычно следуют по символической ссылке к её цели. Числовые операции требуют целых чисел; произвольную введённую строку нельзя сразу считать числом.

note_path="$HOME/chapter06/source/one.txt"
if [ -f "$note_path" ]; then
    printf 'Файл найден: %s\n' "$note_path"
else
    printf 'Файл отсутствует: %s\n' "$note_path"
fi

После подготовки данных появится сообщение Файл найден. Ключевое слово then начинает действия для истинного условия, else — для ложного, fi завершает конструкцию. Если действия при ложном условии не нужны, ветвь else можно опустить.

Конструкция [[ ... ]] — средство проверки условий самого Bash; это не другое написание команды [. В ней можно соединять проверки через && и ||, а ! обращает результат проверки:

if [[ -f "$HOME/chapter06/source/one.txt" && -d "$HOME/chapter06/source" ]]; then
    printf 'Найдены и файл, и каталог\n'
fi

Здесь && находится внутри условной конструкции. В примерах выше A && B соединяло целые команды. Эти применения нужно различать. Сценарии этой главы используют возможности Bash; запускайте их через Bash, а не через sh.

Выучить: код завершения и условие

Код завершения — числовой результат команды. Код 0 означает успех или истинность проверки. if выбирает ветвь по этому коду. Значение $? нужно сохранить сразу после интересующей команды.

6. Ввод данных и циклы

read -r -p 'Введите тему занятия: ' lecture_topic
printf 'Тема: %s\n' "$lecture_topic"
counter=1
while [ "$counter" -le 3 ]; do
    printf 'Повтор %s\n' "$counter"
    counter=$((counter + 1))
done

read получает строку со стандартного ввода; -r сохраняет обратные косые черты буквально. Условие цикла проверяется перед каждым повтором. Счётчик должен изменяться, иначе истинное условие может дать бесконечный цикл. Переменную темы используйте как данные, не как исполняемую команду.

После ввода темы программа повторяет её в сообщении. Цикл выводит строки Повтор 1, Повтор 2, Повтор 3, затем прекращается: значение 4 уже не удовлетворяет условию -le 3.

Цикл for

for account in student1 student2 student3; do
    printf 'Проверяемая учётная запись: %s\n' "$account"
done

Цикл выполнится три раза. Переменная account по очереди получает каждое значение из списка. Вывод имён не создаёт пользователей; в следующем разделе сценарий проверит, существуют ли эти записи.

Запомнить: цикл

Цикл повторяет блок команд. for перебирает заданные значения; while повторяет блок, пока проверочная команда возвращает код 0. В учебном цикле со счётчиком должна быть операция, приближающая завершение цикла.

7. Создание и запуск сценариев

Строка указания интерпретатора

Интерпретатор — программа, которая читает и выполняет команды на определённом языке. Для наших сценариев используется Bash, доступный по пути /bin/bash.

При непосредственном запуске текстового исполняемого файла Linux определяет интерпретатор по его первой строке:

#!/bin/bash

Первые два символа #! обозначают указание интерпретатора; после них записан путь к программе. Эта строка должна находиться в самом начале файла, без пустой строки или других символов перед #!. Обычный комментарий Bash начинается с # и не является командой; первая строка #! дополнительно используется ядром Linux при запуске файла.

Расширение .sh помогает человеку узнавать сценарий, но не выбирает интерпретатор и не даёт право выполнения. Для файлов, подготовленных в редакторе Windows, выбирайте окончания строк LF и UTF-8 без начальной метки BOM. Иначе прямой запуск может завершиться ошибкой поиска интерпретатора.

Записать: строка указания интерпретатора

Строка указания интерпретатора — первая строка сценария вида #!путь_к_программе. Для сценариев Bash в этой методичке используется #!/bin/bash. Право выполнения файла и выбор интерпретатора — разные условия его непосредственного запуска.

Сценарий отчёта

Создайте файл:

nano ~/chapter06/report.sh

Введите следующий текст, сохраните его и выйдите из редактора:

#!/bin/bash
set -u
printf 'Пользователь: %s\n' "$(id -un)"
printf 'Имя машины: %s\n' "$(hostname)"
printf 'Дата: %s\n' "$(date --iso-8601=seconds)"
for account in student1 student2 student3; do
    if getent passwd "$account" > /dev/null; then
        printf '%s: существует\n' "$account"
    else
        printf '%s: не создан\n' "$account"
    fi
done
bash -n ~/chapter06/report.sh
chmod u+x ~/chapter06/report.sh
~/chapter06/report.sh

bash -n проверяет синтаксис без выполнения команд, но не обнаруживает отсутствующие файлы и все логические ошибки. set -u считает обращение к неустановленной переменной ошибкой. Сценарий проверяет тестовые учётные записи; создавать их здесь не требуется, поскольку он корректно сообщает об их отсутствии.

Ожидаемый результат — текущий пользователь student, имя вашей машины от 562-01 до 562-13, дата и три сообщения о состоянии учётных записей. Для каждого пользователя выводится либо существует, либо не создан. Это штатные результаты проверки; сценарий никого не создаёт и не переключает учётную запись.

Три способа выполнения файла

Запись Что происходит Нужно ли право выполнения
bash report.sh Новый Bash читает команды из файла; интерпретатор указан в самой команде запуска Нет; файл должен быть доступен для чтения
./report.sh Запускается файл из текущего каталога; Linux использует строку указания интерпретатора Да; для чтения сценария интерпретатором также нужен доступ
source report.sh Команды читает текущий Bash; отдельная оболочка для файла не создаётся Нет; нужен доступ для чтения
cd ~/chapter06
bash report.sh
chmod u+x report.sh
./report.sh

Два запуска отчёта должны дать одинаковые сведения, кроме времени. u+x добавляет право выполнения владельцу. Префикс ./ явно задаёт текущий каталог: одного имени report.sh недостаточно, если каталог не входит в PATH.

source обычно применяют к файлам настроек. Он может изменить переменные и каталог текущей оболочки. Чтобы увидеть различие, создайте отдельный небольшой файл:

nano ~/chapter06/session-demo.sh

Содержимое файла:

#!/bin/bash
session_topic='Автоматизация'
cd "$HOME/chapter06"

Затем выполните:

cd ~
unset session_topic
bash ~/chapter06/session-demo.sh
printf 'После bash: <%s>\n' "${session_topic-}"
pwd
source ~/chapter06/session-demo.sh
printf 'После source: <%s>\n' "$session_topic"
pwd
unset session_topic

После bash переменная в текущей оболочке остаётся неустановленной, а каталог — /home/student. После source значение равно Автоматизация, а каталог — /home/student/chapter06. Поэтому сценарии копирования ниже запускайте отдельным процессом. Их exit при выполнении через source может завершить текущую оболочку.

Аргументы сценария

Запись Значение
$0 Имя или путь, использованный при запуске сценария
$1, $2 Первый и второй аргументы
$# Количество аргументов
"$@" Все аргументы с сохранением границ каждого
${1:-} Первый аргумент; пустая строка, если он отсутствует или пуст

Создайте ~/chapter06/arguments.sh через nano и сохраните:

#!/bin/bash
set -u
printf 'Количество аргументов: %s\n' "$#"
printf 'Первый аргумент: <%s>\n' "${1:-}"
for item in "$@"; do
    printf 'Отдельный аргумент: <%s>\n' "$item"
done
bash -n ~/chapter06/arguments.sh
bash ~/chapter06/arguments.sh 'Командная строка' student1 student2
bash ~/chapter06/arguments.sh

Первый запуск сообщает о трёх аргументах; Командная строка сохраняется как один. Второй сообщает о нуле аргументов и выводит пустое значение первого; цикл не выполняется. ${1:-} позволяет обратиться к необязательному аргументу даже при set -u. Внешние данные используйте как значения, а не превращайте в команды через eval.

Проверка и поиск ошибок

Сначала используйте bash -n файл, затем выполните сценарий на учебных данных и проверьте результат. Если нужно проследить команды, запустите bash -x ~/chapter06/report.sh: Bash напечатает выполняемые команды после подстановок. Такой вывод может раскрыть значения переменных; он подходит для разбора наших учебных примеров, но не для вывода паролей и других секретов.

Сообщение или ситуация Что проверить
Permission denied при прямом запуске Права файла и каталогов пути; добавлено ли владельцу u+x
Файл существует, но интерпретатор не найден Первую строку, путь /bin/bash, окончания LF и отсутствие BOM
command not found при вводе имени сценария Указан ли путь ./ или полный путь
unbound variable Не используется ли неустановленная переменная при set -u
Синтаксис принят, но результат неверен Значения переменных, пути, условия и реальные данные

8. Копирование через rsync и обработка ошибок

Установите инструмент до его использования:

sudo apt update
sudo apt install rsync
rsync -anv ~/chapter06/source/ ~/chapter06/copy/
rsync -av ~/chapter06/source/ ~/chapter06/copy/

-n выполняет пробный проход без копирования. Завершающий / у источника означает копирование содержимого каталога; без него в назначении может появиться дополнительный уровень. Архивный режим -a сохраняет ряд атрибутов, однако не включает всё возможное, например ACL и расширенные атрибуты. Копирование открытой базы данных не создаёт автоматически согласованную копию базы.

После реального копирования в copy должны находиться те же три файла, что и в source. При неизменённых файлах diff -r ниже не выводит различий и возвращает код 0.

Параметры обработки ошибок

Настройка Назначение
set -u Считать обращение к неустановленной переменной ошибкой; есть исключения для некоторых специальных параметров
set -e Завершать сценарий при неуспехе команд с учётом исключений для условий и других конструкций
set -o pipefail В конвейере возвращать код крайней справа команды с ненулевым кодом, либо 0, если все успешны
set -euo pipefail Включить все три настройки одной командой

Эти настройки помогают обнаружить часть ошибок, но не проверяют правильность выбранного пути и не заменяют анализ результата. Ошибки, ожидаемые по логике программы, обрабатывайте явно через if. Например, отсутствие пользователя в условии if getent passwd ... не должно прерывать отчёт.

false | true
printf 'Обычный код конвейера: %s\n' "$?"
(set -o pipefail; false | true)
printf 'Код с pipefail: %s\n' "$?"

В стандартной интерактивной оболочке без pipefail первый код равен 0, второй — 1. Скобки создают отдельную среду выполнения, поэтому настройка второго примера не меняет текущую оболочку. В этом упражнении не включайте set -e в интерактивном сеансе.

Теперь создайте ~/chapter06/copy-notes.sh:

#!/bin/bash
set -euo pipefail
source_dir="$HOME/chapter06/source"
target_dir="$HOME/chapter06/copy"
if [[ ! -d "$source_dir" ]]; then
    printf 'Нет исходного каталога: %s\n' "$source_dir" >&2
    exit 1
fi
mkdir -p -- "$target_dir"
rsync -av -- "$source_dir/" "$target_dir/"
printf 'Копирование завершено: %s\n' "$(date --iso-8601=seconds)"
bash -n ~/chapter06/copy-notes.sh
chmod u+x ~/chapter06/copy-notes.sh
~/chapter06/copy-notes.sh
diff -r ~/chapter06/source ~/chapter06/copy

Сценарий использует существующие учебные данные и не удаляет лишние файлы в назначении. set -e имеет исключения в условиях, списках команд и других конструкциях; это не замена явным проверкам. Здесь неуспех обычной команды копирования прервёт выполнение до сообщения об успехе.

Опция --delete у rsync удаляет из назначения объекты, отсутствующие в источнике. Она нужна для некоторых видов зеркалирования, но в базовом упражнении не используется. Ошибка в пути при зеркалировании может привести к потере файлов. Опции -b и --backup-dir сохраняют заменяемые версии; их применение требует отдельного плана хранения и очистки.

Запомнить: копия и резервная копия

Копия на том же виртуальном диске не защищает от потери этого диска. Резервное копирование предполагает отдельное хранение и проверенное восстановление. Зеркало может повторить ошибочное удаление источника.

Разбор сценария копирования:

Фрагмент Назначение
source_dir=..., target_dir=... Задать учебные пути в одном месте
[[ ! -d "$source_dir" ]] Проверить отсутствие исходного каталога
printf ... >&2 Направить сообщение об ошибке в стандартный поток ошибок
exit 1 Прекратить сценарий с ненулевым кодом
mkdir -p -- "$target_dir" Создать каталог назначения, если его нет
-- Завершить список опций команды; дальнейшие значения трактуются как операнды
rsync -av ... Скопировать данные с выводом сведений о работе

Сообщение Копирование завершено появляется только после успешного выполнения предшествующей команды rsync. exit завершает сценарий, а не только текущую ветвь if.

9. Зеркалирование и восстановление предыдущей версии

Продолжим пример резервирования в отдельных учебных каталогах. Этот вариант включает удаление из зеркала и поэтому не применяется к системным путям. Сначала подготовьте данные и маркеры назначения:

mkdir -p ~/chapter06/backup-source ~/chapter06/backup-mirror ~/chapter06/backup-versions
touch ~/chapter06/backup-source/.lecture-source
touch ~/chapter06/backup-mirror/.lecture-mirror
printf 'Первая версия конспекта\n' > ~/chapter06/backup-source/lesson.txt
printf 'Временный учебный файл\n' > ~/chapter06/backup-source/old.txt
nano ~/chapter06/backup-notes.sh

Текст сценария:

#!/bin/bash
set -euo pipefail
source_dir="$HOME/chapter06/backup-source"
target_dir="$HOME/chapter06/backup-mirror"
versions_dir="$HOME/chapter06/backup-versions"
if [[ ! -f "$source_dir/.lecture-source" || ! -f "$target_dir/.lecture-mirror" ]]; then
    printf 'Не найдены маркеры учебных каталогов\n' >&2
    exit 1
fi
options=(-avb --delete --exclude='.lecture-*')
if [ "$#" -gt 1 ]; then
    printf 'Использование: %s [--dry-run]\n' "$0" >&2
    exit 2
fi
case "${1:-}" in
    --dry-run) options+=(-n) ;;
    '') ;;
    *) printf 'Использование: %s [--dry-run]\n' "$0" >&2; exit 2 ;;
esac
mkdir -p -- "$versions_dir"
run_dir=$(mktemp -d "$versions_dir/run-$(date +%Y%m%d-%H%M%S)-XXXXXX")
rsync "${options[@]}" --backup-dir="$run_dir" -- "$source_dir/" "$target_dir/"
printf 'Каталог предыдущих версий: %s\n' "$run_dir"
bash -n ~/chapter06/backup-notes.sh
chmod u+x ~/chapter06/backup-notes.sh
~/chapter06/backup-notes.sh --dry-run
~/chapter06/backup-notes.sh
printf 'Вторая, более длинная версия конспекта\n' > ~/chapter06/backup-source/lesson.txt
rm -- ~/chapter06/backup-source/old.txt
~/chapter06/backup-notes.sh --dry-run
~/chapter06/backup-notes.sh
find ~/chapter06/backup-versions -type f -print
cat ~/chapter06/backup-mirror/lesson.txt

Перед реальным запуском прочитайте пробный вывод. --delete удалит из зеркала уже удалённый old.txt; -b вместе с --backup-dir перенесёт его и заменяемую версию lesson.txt в каталог текущего запуска. Исключённые маркеры остаются в своих каталогах. Массив options передаёт каждую опцию отдельным аргументом. mktemp создаёт уникальный каталог даже при двух запусках в одну секунду. Пробный запуск создаёт пустой каталог версий, но не изменяет зеркало.

Разбор сценария зеркалирования

Фрагмент Назначение
Проверки маркеров через `[[ ...
options=(-avb --delete --exclude='.lecture-*') Создать массив: каждая опция занимает отдельный элемент
[ "$#" -gt 1 ] Отклонить запуск с более чем одним аргументом
case "${1:-}" in ... esac Выбрать действие по значению первого аргумента
--dry-run) При этом значении добавить опцию пробного запуска
options+=(-n) Дописать элемент в массив
Ветвь с пустым шаблоном Принять запуск без аргумента, когда проверяемая строка пустая
*) Обработать остальные значения как ошибку использования
;; Завершить выбранную ветвь case
exit 2 Сообщить об ошибке использования по принятому в этом сценарии соглашению
mktemp -d ...XXXXXX Создать отдельный каталог со случайным суффиксом
"${options[@]}" Передать каждый элемент массива отдельным аргументом rsync
--backup-dir="$run_dir" Указать каталог для заменяемых и удаляемых файлов этого запуска

Строка с массивом не является обычной строковой переменной с пробелами. Запись "${options[@]}" сохраняет границы элементов. В case используется первое совпадение с шаблоном; пустая строка выбирает ветвь запуска без пробного режима. Этот учебный сценарий рассчитан на запуск без аргументов или с одним аргументом --dry-run.

В примере маркеры помогают отличить подготовленные каталоги от случайно указанных путей, но не заменяют проверку самих данных и назначения. Сценарий использует только каталоги chapter06 пользователя student.

Восстановление и проверка содержимого

После замены lesson.txt и удаления old.txt реальный запуск должен оставить в зеркале вторую версию конспекта и убрать old.txt. В одном из каталогов backup-versions/run-... будут первая версия конспекта и удалённый файл. Найдите сохранённые версии:

find ~/chapter06/backup-versions -type f -name lesson.txt -print
find ~/chapter06/backup-versions -type f -name old.txt -print

Скопируйте из вывода полный путь к старому lesson.txt. В следующем упражнении вставьте его после приглашения и нажмите Enter:

read -r -p 'Полный путь к сохранённому lesson.txt: ' saved_version
if [[ -f "$saved_version" ]]; then
    mkdir -p ~/chapter06/restored
    cp -i -- "$saved_version" ~/chapter06/restored/lesson.txt
    cat ~/chapter06/restored/lesson.txt
else
    printf 'Файл не найден: %s\n' "$saved_version" >&2
fi

В новом каталоге должна появиться строка Первая версия конспекта. Если упражнение повторяется и файл назначения уже существует, cp -i спросит о его замене. Каталоги источника, зеркала и сохранённых версий при этом восстановлении не изменяются.

Проверьте содержимое восстановленного файла по известной первой версии:

printf 'Первая версия конспекта\n' > ~/chapter06/restored/expected-first-version.txt
cmp -- ~/chapter06/restored/expected-first-version.txt ~/chapter06/restored/lesson.txt
restore_status=$?
printf 'Код сравнения восстановленного файла: %s\n' "$restore_status"

При совпадении cmp не выводит различий и возвращает код 0. Код 1 означает различие содержимого, код 2 — ошибку сравнения, например отсутствие файла. Если первая версия совпала, чтение и проверка восстановленных данных выполнены; одного сообщения об успешном копировании было бы недостаточно.

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

Записать: автоматизация копирования

Проверка исходного пути, пробный проход, сохранение заменяемых данных и проверка восстановления выполняют разные задачи. Успешный код rsync подтверждает выполненную операцию, но не доказывает достаточность всей схемы резервирования.

10. Практическое задание и контрольные вопросы

Выполняйте этапы по порядку. Используйте только домашний каталог student и подготовленные учебные данные.

Этап Действие Что предъявить в отчёте
1 Проверить текущую оболочку, повторить поиск по истории и создать псевдоним Результат ps -p $$ -o comm= и type ll; объяснение различия оболочки и терминала
2 Подготовить три текстовых файла и разобрать путь с пробелом Пример одного аргумента с кавычками и нескольких без них
3 Выполнить проверки файла, каталога и числового условия Условие, результат и пояснение кода завершения
4 Создать report.sh и проверить учётные записи Синтаксическая проверка и вывод с вашей машины 562-01–562-13
5 Запустить arguments.sh с тремя аргументами и без них Число аргументов и сохранённая граница строки с пробелом
6 Выполнить пробное и реальное копирование rsync Различие режимов, результат diff -r и его код
7 Заменить конспект, удалить учебный old.txt, обновить зеркало Вторая версия в зеркале и прежние файлы в каталоге версий
8 Восстановить первую версию в restored Содержимое файла и код сравнения cmp

Дополните report.sh количеством обычных файлов непосредственно в каталоге source. Добавьте следующие строки в конец сценария:

file_count=$(find "$HOME/chapter06/source" -maxdepth 1 -type f -printf 'x' | wc -c)
printf 'Обычных файлов в source: %s\n' "$file_count"

find печатает по одному символу x на найденный файл, а wc -c считает эти символы. Поэтому пробелы и переводы строк в именах не искажают результат. -maxdepth 1 ограничивает поиск самим каталогом и его непосредственным содержимым; -type f выбирает обычные файлы. Для подготовленного набора результат равен 3. Это число не включает каталоги и символические ссылки.

bash -n ~/chapter06/report.sh
~/chapter06/report.sh

Ответьте на вопросы:

  1. Какую задачу решает строка #!/bin/bash и почему её нельзя заменить расширением .sh?
  2. Чем различаются bash файл, непосредственный запуск файла и source файл?
  3. Зачем заключать пути и "$@" в кавычки?
  4. Когда выполняется вторая команда в записях с ;, && и ||?
  5. Чем отличаются цикл for и цикл while?
  6. Что проверяет bash -n и чего эта проверка не гарантирует?
  7. Почему --dry-run должен предшествовать реальному зеркалированию?
  8. Что подтверждает успешное сравнение восстановленного файла и почему хранение копий на том же диске остаётся учебным примером?

Выучить: последовательность проверки сценария

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

Документация: Bash в Ubuntu 26.04, запуск файлов и указание интерпретатора в Linux, официальное руководство rsync, сценарии копирования в документации Ubuntu. Сверка: 07.10.2026.