Задача: сделать заголовок настраиваемым
В первом уроке мы создали скрипт report.sh. Он выводит заголовок, дату и рабочий каталог. Заголовок у него жёстко зашит: всегда Study report. Это нормально для первого раза, но теперь хочется большего. Что если завтра мы захотим запустить тот же скрипт, но с другим заголовком, например Weekly report или Report for alice? Переписывать файл каждый раз — неудобно и рискованно: легко что-то задеть, забыть сохранить, случайно стереть нужную строку.
Гораздо лучше, если скрипт сможет принимать заголовок при запуске. Мы набираем команду, дописываем к ней текст — и скрипт использует этот текст как заголовок. В Bash это делается через аргументы скрипта. Сегодня мы научимся передавать данные в скрипт и сохранять их в переменных внутри него.
Заодно посмотрим, как внутри скрипта работают переменные. Мы уже знакомы с переменными в командной строке: присваивали значения, подставляли через $, использовали подстановку команд через $( ). Всё это работает и в скрипте. Но здесь переменные живут в собственном процессе, и у этого есть последствия, о которых мы поговорим.
Освежаем переменные
Прежде чем менять скрипт, вспомним, как устроены переменные в Bash. У нас был урок про переменные окружения и shell-переменные, поэтому сейчас коротко.
Переменная — это имя, за которым закреплено значение. Мы создаём её простым присваиванием:
report_title='Study report'
Здесь мы говорим: пусть имя report_title теперь означает строку Study report. Обратите внимание: вокруг знака равно не должно быть пробелов. Если написать report_title = 'Study report', Bash воспримет это не как присваивание, а как запуск команды report_title с аргументами — и выдаст ошибку. В скриптах это правило действует так же строго, как и в командной строке.
Чтобы получить значение переменной, мы ставим перед её именем знак доллара:
printf '%s\n' "$report_title"
Команда выведет Study report. Мы уже говорили, почему двойные кавычки важны: они позволяют подстановку переменной и при этом сохраняют значение как единое целое. Если в переменной окажется текст с пробелами, кавычки не дадут оболочке разбить его на несколько аргументов.
Подстановка команд $( ) тоже знакома. Мы выполняли команду внутри скобок, и её вывод подставлялся на место выражения. Например:
report_date=$(date +%Y-%m-%d)
Внутри скобок запускается date с нужным форматом, она выдаёт строку вроде 2026-09-09, и эта строка становится значением переменной report_date. На экран при этом ничего не выводится: весь вывод захвачен.
Всё это мы умеем делать в командной строке. Вопрос сегодняшнего урока — как перенести эти действия внутрь скрипта и как передать скрипту данные извне.
Как скрипт получает аргументы
Когда мы запускаем скрипт, мы можем дописать к команде дополнительные слова. Например:
bash report.sh 'Study report'
Здесь report.sh — первый аргумент для команды bash. А вот 'Study report' — уже аргумент для нашего скрипта. Bash, который читает файл, получает эти данные и кладёт их в специальные переменные.
Как эти переменные называются? Очень просто: по номерам. Первые девять аргументов доступны как $1, $2, $3 и так далее до $9. Начиная с десятого нужны фигурные скобки, например ${10}, чтобы Bash не прочитал запись как $1 и символ 0. Оболочка сама разбивает строку запуска по пробелам, учитывая кавычки, и нумерует каждый аргумент.
Важно не перепутать: для программы bash наш скрипт — это аргумент. Но для самого скрипта его собственные аргументы начинаются с $1. То, что мы написали после имени скрипта, и будет первым аргументом. В примере выше $1 внутри скрипта равен строке Study report. Кавычки в этом аргументе не сохраняются: они нужны оболочке, чтобы не разбить два слова на два аргумента. А сам скрипт получает уже готовую строку целиком.
Кроме нумерованных переменных, есть ещё несколько полезных. $# показывает, сколько всего аргументов передано. $0 содержит имя, с которым скрипт был вызван. При запуске bash report.sh значение $0 будет report.sh, а при ./report.sh — ./report.sh. Имя интерпретатора bash и вся строка запуска в $0 не входят. А специальная запись "$@" означает «все аргументы, каждый отдельно». Мы скоро посмотрим, чем она отличается от простого перечисления.
Показываем аргументы наглядно
Прежде чем менять основной скрипт, давайте разберёмся с аргументами на отдельном примере. Создадим ещё один скрипт, который просто печатает то, что ему передали. Сначала проверим, что имя свободно:
ls -l ~/linux-scripts/arguments.sh
Если файла нет — отлично. Если есть — выберите другое имя, например arguments-demo.sh. В нашем случае файла нет, поэтому используем arguments.sh.
Перед созданием вспомним, что мы в каталоге ~/linux-scripts. Если вы перезапускали терминал, вернитесь туда:
cd ~/linux-scripts
Теперь создадим файл:
nano arguments.sh
Наберём такой текст:
#!/bin/bash
printf 'Argument count: %s\n' "$#"
printf '<%s>\n' "$@"
Разберём. Первая строка — уже знакомый shebang. Вторая — выводит число аргументов. Третья — нечто новое: "$@". Мы просим printf напечатать каждый аргумент в угловых скобках, по одному на строку.
Почему угловые скобки? Это простой приём, чтобы видеть границы каждого аргумента. Если бы мы просто вывели текст, пробел внутри аргумента было бы не отличить от пробела между аргументами. А скобки показывают: вот начало значения, вот конец. Внутри между < и > лежит ровно один аргумент, каким его видит скрипт.
Сохраним файл и выйдем из nano. Запустим с двумя аргументами, причём второй пусть содержит пробел:
bash arguments.sh Linux 'command line'
Вывод будет таким:
Argument count: 2
<Linux>
<command line>
Смотрим внимательно. $# равен двум — столько аргументов мы передали. А "$@" сохранил границы: первый аргумент Linux, второй command line целиком, с пробелом внутри. Кавычки при запуске не стали частью аргумента, но они уберегли его от разбиения. Скрипт получил именно два значения, а не три.
Теперь запустим без аргументов:
bash arguments.sh
Вывод:
Argument count: 0
<>
Первая строка понятна: аргументов ноль. Но откуда взялась вторая строка со скобками? Это свойство команды printf. Формат '<%s>\n' говорит: подставь один аргумент и выведи его в скобках. А аргументов данных нет: остаётся только формат. printf в Bash не отбрасывает формат, если аргументы закончились, — она подставляет пустую строку. Получается <>.
Это не означает, что нам передали один пустой аргумент. Нет, $# честно сообщил: ноль. Просто формат printf один, и команда обработала его один раз, даже без данных. Если бы мы не хотели видеть такую пустую строку, нужно было бы проверять число аргументов до вывода. Но этому мы научимся позже, в уроке про условия. Пока просто запомним: <> при нуле аргументов — следствие формата, а не скрытый аргумент.
Теперь давайте проверим ещё один запуск, с тремя аргументами:
bash arguments.sh one two three
Вывод:
Argument count: 3
<one>
<two>
<three>
Каждый аргумент на своей строке, в своих скобках. Скрипт честно показывает всё, что ему дали, и ничего не добавляет от себя.
Обновляем report.sh
Теперь вернёмся к основному скрипту. Мы хотим, чтобы заголовок приходил из аргумента, а дата сохранялась в переменной внутри скрипта. Перед редактированием обязательно прочитаем текущую версию:
cat report.sh
Мы увидим тот текст, который писали в первом уроке: shebang, printf 'Study report', date, printf 'Working directory: ', pwd. Теперь откроем файл в редакторе:
nano report.sh
И заменим содержимое полностью. Вот новая версия:
#!/bin/bash
report_title=$1
report_date=$(date +%Y-%m-%d)
printf '%s\n' "$report_title"
printf 'Date: %s\n' "$report_date"
printf 'Working directory: '
pwd
Разберём построчно, потому что здесь собрано всё, о чём мы говорили.
Первая строка — shebang. Мы её сохранили, потому что скрипт могут запускать и напрямую, через ./report.sh, как мы научились в прошлом уроке.
Вторая строка — report_title=$1. Это присваивание. Мы берём первый аргумент скрипта и сохраняем его в переменную с понятным именем report_title. Зачем здесь нужна переменная, если можно было сразу использовать $1 в printf? Затем, что дальше мы обращаемся к заголовку, и имя report_title говорит о его роли. Когда скрипт вырастет, читать "$report_title" будет понятнее, чем загадочное $1. Это хорошая привычка: один раз взять аргумент в осмысленную переменную, а дальше работать с ней.
Третья строка — report_date=$(date +%Y-%m-%d). Здесь две вещи сразу: подстановка команды и присваивание. Сначала выполняется date +%Y-%m-%d, получается строка с датой. Потом эта строка становится значением переменной report_date. На экран ничего не выводится.
Четвёртая строка — printf '%s\n' "$report_title". Выводим заголовок из переменной. Если при запуске нам дали аргумент, он окажется здесь. Если не дали — переменная будет пустой, и строка окажется пустой.
Пятая строка — printf 'Date: %s\n' "$report_date". Печатаем подпись Date: , затем дату из переменной и перевод строки. Мы добавили слово Date:, чтобы в выводе было видно, что это за значение. В первом уроке дата печаталась просто так, без пояснения. Теперь отчёт стал чуть более говорящим.
Шестая и седьмая строки — как в первом уроке: printf 'Working directory: ' без перевода строки, затем pwd, которая допишет каталог и поставит перевод.
Сохраним файл и выйдем из редактора.
Обратите внимание: каждая команда на отдельной строке. В скрипте так и принято: одна строка — одно действие. Оболочка читает файл сверху вниз и выполняет каждую строку по очереди. Никаких специальных разделителей, кроме перевода строки, не нужно.
Запускаем обновлённый скрипт
Теперь запустим скрипт с аргументом:
bash report.sh 'Study report'
Вывод:
Study report
Date: 2026-09-09
Working directory: /home/student/linux-scripts
Первая строка — наш заголовок, который пришёл из аргумента. Вторая — дата с подписью. Третья — рабочий каталог. Скрипт стал настраиваемым: завтра мы можем передать другой заголовок.
Проверим:
bash report.sh 'Weekly report'
Вывод:
Weekly report
Date: 2026-09-09
Working directory: /home/student/linux-scripts
Тот же скрипт, другой заголовок. Мы не открывали редактор, не меняли файл. Просто передали новое значение при запуске. Именно ради этой гибкости и нужны аргументы.
Что будет, если запустить без аргумента?
bash report.sh
Вывод:
Date: 2026-09-09
Working directory: /home/student/linux-scripts
Первая строка пустая. Почему? Потому что $1 не задан, переменная report_title пуста, и printf честно печатает пустую строку с переводом. Это не ошибка в глазах Bash: скрипт выполнился, просто заголовка нет.
Конечно, в настоящем отчёте пустая строка вместо заголовка — некрасиво. Хорошо бы проверить, передали ли нам аргумент, и вывести подсказку, если нет. Но для этого нужно условие, а условия у нас впереди, в четвёртом уроке. Пока мы не будем забегать: примем, что скрипт рассчитывает на аргумент, и запускаем его правильно. Чуть позже добавим проверку, и тогда скрипт станет дружелюбнее к забывчивому пользователю.
Переменные скрипта не трогают родителя
Мы уже говорили, что скрипт работает в отдельном процессе. Это было важно в первом уроке, когда разбирали рабочий каталог. С переменными та же история: всё, что скрипт присваивает внутри себя, остаётся внутри его процесса.
Проверим. Сначала создадим в родительской оболочке переменную с тем же именем, что и в скрипте:
report_title='old value'
Посмотрим на неё:
printf '%s\n' "$report_title"
Вывод: old value. Теперь запустим скрипт с другим заголовком:
bash report.sh 'new value'
Скрипт внутри себя присвоит переменной report_title значение new value, выведет его и завершится. А что в нашей оболочке? Проверим:
printf '%s\n' "$report_title"
Вывод снова old value. Наша переменная не изменилась. Скрипт создал собственную переменную в своём процессе, а когда процесс завершился, это значение перестало существовать в нём. Родительская оболочка ничего не заметила.
Это поведение часто удивляет новичков. Кажется: скрипт же присвоил переменной значение, почему в терминале оно не сохранилось? Ответ: потому что скрипт — это дочерний процесс. Новый Bash получает экспортированные переменные через окружение. Неэкспортированные переменные родительской оболочки автоматически ему не передаются. В нашем примере скрипт сам создаёт report_title из $1, и его присваивание не меняет одноимённую переменную родителя. Мы знаем это по работе с cd: если бы скрипт сменил каталог, наша оболочка осталась бы на месте. С переменными точно так же.
Если нам понадобится, чтобы скрипт передал какое-то значение обратно в родительскую оболочку, мы сделаем это через вывод скрипта и подстановку команд. Например, можно было бы набрать result=$(bash report.sh 'Study report') и получить весь вывод скрипта в переменную result. Но сам скрипт не может напрямую поменять наши переменные. Это важная граница, и хорошо, что мы её прочувствовали на простом примере.
Что у нас теперь есть
В каталоге ~/linux-scripts два скрипта. report.sh печатает отчёт с настраиваемым заголовком, датой и рабочим каталогом. arguments.sh показывает, сколько аргументов ему передали и какие именно, с границами каждого. Оба созданы нами, старые файлы из других проектов мы не трогали.
Мы увидели, как Bash передаёт скрипту данные через нумерованные переменные $1, $2 и так далее. Узнали про $# для числа аргументов и "$@" для списка, где каждый аргумент сохраняет свои границы. Перенесли знакомое присваивание и подстановку команд внутрь скрипта. И убедились, что переменные скрипта живут только в его процессе.
Впереди — ввод и вывод внутри скриптов. Мы научимся не только печатать, но и читать данные, которые пользователь вводит во время работы. А пока закрепим то, что изучили сегодня.
Bash передаёт данные в скрипт через аргументы командной строки. Первый аргумент доступен как $1, второй как $2, число всех аргументов — как $#, а запись "$@" означает все аргументы, каждый отдельно. Мы создали демонстрационный скрипт arguments.sh, который печатает число аргументов и каждый аргумент в угловых скобках. На примере с аргументом 'command line' видно, что кавычки при запуске сохраняют пробел внутри аргумента, а скобки показывают его границы. При запуске без аргументов printf с форматом '<%s>\n' печатает <> — это следствие формата, а не пустой аргумент: счётчик $# честно показывает ноль.
Основной скрипт report.sh мы обновили так, чтобы заголовок приходил из $1. Внутри скрипта присваивание report_title=$1 кладёт аргумент в осмысленную переменную, а report_date=$(date +%Y-%m-%d) сохраняет дату через подстановку команды. Дальше printf выводит обе переменные, а рабочий каталог по-прежнему печатает pwd. Запуск bash report.sh 'Study report' даёт отчёт с заголовком, а без аргумента первая строка оказывается пустой — проверку входа добавим позже, когда изучим условия. Переменные внутри скрипта не меняют переменные запускающей оболочки: скрипт работает в дочернем процессе, и его присваивания исчезают вместе с ним.
Научитесь передавать данные в скрипт через аргументы командной строки. Обновите report.sh так, чтобы заголовок приходил из первого аргумента, а дата сохранялась в переменную внутри скрипта.
Обновление report.sh с аргументом
Файл report.sh уже существует из прошлого урока. Его нужно переписать под новую задачу.
Вы в каталоге ~/bash-practice-start. Откройте report.sh и замените его содержимое так, чтобы: 1) заголовок брался из первого аргумента скрипта и сохранялся в переменную report_title, 2) дата сохранялась в переменную report_date через подстановку команды, 3) вывод был в формате: <заголовок> Date: <дата> Working directory: <каталог> Запишите новое содержимое файла целиком и команду запуска с заголовком 'Weekly report'. Для даты используйте формат YYYY-MM-DD. Это отдельная учебная копия сценария.
Специальные переменные Bash
Соотнесите специальные переменные и записи с их смыслом при работе со скриптом.
Каждая запись из левого столбца означает одно и то же, что и одна запись из правого. Сопоставьте пары.
Переменные скрипта и родительская оболочка
Проверьте границу между процессом скрипта и вашей оболочкой.
Рассмотрите команды без выполнения. Сценарий report.sh присваивает report_title=$1 и запускается отдельным процессом: report_title='old value' bash report.sh 'new value' printf '%s\n' "$report_title" Что напечатает последняя команда? Объясните, в каком процессе менялась переменная.