Написание bash-скриптов в Linux

Это последня статья в разделе Linux. Здесь мы обсудим концепцию скриптинга — написания скриптов или сценариев. Это краткое введение в создание bash-скриптов.

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

Что такое bash-скрипты

Bash-скрипт (от англ. script — сценарий) — это последовательность команд, которые по очереди считывает и выполняет интерпретатор. В нашем случае это командная строка — bash.

Скрипт — что-то вроде театрального сценария. В театре это некий документ, в котором указано, кто что должен говорить и делать. Сценарий читает исполнитель — актер — и выполняет «команды». В компьютерных скриптах сценарий (скрипт) читает и выполняет компьютер.

Компьютерный сценарий — это обычный текстовый файл, в котором перечислены обычные команды. Отличие заключается в том, что раньше мы вводили эти команды вручную в терминал, а в скрипте их будет «вводить» интерпретатор — bash. Скрипт можно создать как и любой другой текстовый файл с помощью обычного текстового редактора: например, VI, который мы обсуждали в этой статье.  

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

Разберем на примере

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

echo <сообщение>

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

#!/bin/bash
# Демонстрационный скрипт
# karpaff 22/01/2022

echo Список файлов в текущем каталоге:
ls

Теперь давайте разбираться, как всё устроено, с помощью командой строки. Ниже — объяснение, что тут происходит. 

1. user@bash: cat myscript.sh
2. #!/bin/bash
3. # Демонстрационный скрипт
4. # karpaff 22/01/2022
5.
6. echo Список файлов в текущем каталоге:
7. ls
8. user@bash: 
9. user@bash: ls -l myscript.sh
10. -rwxr-xr-x 1 karpaff users 2 Jan 22 2022 myscript.sh 
11. user@bash:
12. user@bash: ./myscript.sh
13. Список файлов в текущем каталоге:
14. barry.txt bob example.png firstfile foo1 myoutput video.mpeg
15. user@bash: 
  • Строка 1. Посмотрим на наш скрипт. Linux — система без расширений, поэтому скрипты не обязаны иметь расширение .sh. Однако обычно его ставят, чтобы скрипты было сразу заметно.
  • Строка 2. Самой первой строкой скрипта всегда должна быть эта строка — она указывает, какой интерпретатор следует использовать. Первые два символа называются шебанг (shebang). После этого без пробелов указывается путь к интерпретатору. О лучших практиках shebang можно почитать здесь
  • Строки 3 и 4. Всё, что идет после # — комментарий. Интерпретатор игнорирует их, они нужно для нашего удобства. Хорошая практика — указывать ваше имя и дату написания скрипта, а также краткое описание того, что этот сценарий делает. 
  • Строка 6. Мы будем использовать программу под названием echo Она просто выводит на экран всё, что вы поместите справа от нее. 
  • Строка 7. Выводим на экран содержимое текущего каталога с помощью команды ls.
  • Строка 9. У сценария должно быть разрешение execute, чтобы его можно было запустить. Как вы видите, у нашего файла есть такое разрешение. 
  • Строка 12. Запускаем сам скрипт. Зачем нужно ./ расскажем чуть ниже.
  • Строки 13 и 14. Результат работы нашего скрипта.

Шебанг

Самая первая строчка скрипта указывает системе, какой интерпретатор следует использовать для этого файла. В ней не должно быть пробелов. Первые два символа #! — так называемый шебанг (shebang), который говорит системе, что сразу после них идет путь к используемому интерпретатору.

 #![путь к интепретатору]

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

which <команда>

1. user@bash: which bash
2. /bin/bash
3. user@bash:
4. user@bash: which ls
5. /usr/bin/ls
6. user@bash:

Строчку с указанием на интепретатор можно не писать, и bash-скрипт всё равно будет работать. Большинство оболочек, включая bash, в таком случае будут считать интерпретатором себя. Однако всегда указывать интепретатор — хорошая привычка. Если вы или кто-то другой запустите  скрипт в условиях, когда bash не используется в качестве оболочки, сценарий может работать не так, как задумывалось. 

Имя

Linux — это ОС без расширений. Это значит, что мы можем назвать наш скрипт как угодно, и это никак не повлияет на его выполнение. Обычно для скриптов все-таки записывают расширение .sh, но это делатеся исключитьельно для удобства. Наш скрипт можно было назвать и просто myscript или даже myscript.jpg — он бы всё равно работал так же.

Комментарии

Интепретатор игнорирует комментарии, они нужны исключительно для вашего удобства. Комментарием начинается после символа # (решетка) и заканчивается концом строчки. Комментарий может занимать всю строчку, а может следовать за командой. 

1. user@bash: cat myscript.sh
2. #!/bin/bash
3. # Этот комментарий занимает всю строчку
4. ls # Этот комментарий идет после команды
5. user@bash:

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

Комментировать каждую строчку не нужно. Большинство строчек сами объясняют, что они делают. Вставляйте комментарии только для важных строк или для объяснения конкретной команды, действие которой может быть понятно не сразу.

Зачем мы используем ./

Когда мы вводим команду в терминале, система проходит по заданному ряду каталогов в поисках этой команды. Этот путь можно увидеть с помощью переменной PATH

1. user@bash: echo $PATH
2. /usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/X11R6/bin:/usr/games:/usr/lib/mit/bin:/usr/lib/mit/sbin
3. user@bash:

Сначала система ищет команду в первой директории. Если она там есть, команда запускается, если нет — система проверяет вторую директорию и так далее. Каталоги разделяются : (двоеточие).

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

В статье «Основы навигации» мы уже обсуждали, что . (точка) означает текущий каталог, поэтому, когда мы пишем ./myscript.sh, мы фактически говорим системе искать скрипт в текущем каталоге.

Мы можем также использовать абсолютный путь /home/karpaff/linuxtutorialwork/myscript.sh или относительный путь, если мы не находимся в той же директории, что и скрипт ../linuxtutorialwork/myscript.sh.

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

Разрешения

Для запуска у сценария должно быть разрешение на выполнение — даже если вы являетесь владельцем файла. По соображениям безопасности, по умолчанию у вас нет разрешения execute, поэтому его необходимо добавить. Чтобы убедиться, что ваш скрипт настроен правильно, можно ввести команду chmod 755 <скрипт>.

chmod 755 <скрипт>

Переменные

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

• Чтобы создать переменную, нужно указать её имя, затем = (знак равенства) и значение — оно будет хранится в «контейнере». Слева и справа от знака равно не должно быть пробелов.

name='karpaff'

• Когда мы ссылаемся на переменную, нужно поставить $ (знак доллара) перед именем переменной.

$name

Разберемся на примере:

1. user@bash: cat variableexample.sh
2. #!/bin/bash
3. # Демонстрация переменных
4. # karpaff 22/1/2022
5.
6. user@bash: name='karpaff'
7. echo Hello $name
8. user@bash: 
9. user@bash: ./variableexample.sh
10. Hello karpaff
11. user@bash: 

Переменные, связанные с параметрами

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

  • $0 — содержит имя скрипта.
  • $1$9 — содержат параметры, переданные скрипту: $1 — первый аргумент, $2 — второй и так далее.
  • $# — содержит количество параметров, переданных скрипту.
  • $* — содержит все параметры, переданные скрипту.

Существуют и другие переменные, но для начала этих должно быть достаточно.

Разберемся на примере: 

1. user@bash: cat morevariables.sh
2. #!/bin/bash
3. # Демонстрация использования переменных
4. # karpaff 22/1/2022
5.
6. echo Меня зовут $0 и мне передали $# параметра(ов)
7. echo Вот они: $*
8. echo А второй параметр вот такой: $2
9. user@bash: 
10. user@bash: ./morevariables.sh bob fred sally
11. Меня зовут morevariables.sh и мне передали 3 параметра(ов)
12. Вот они: bob fred sally
13. А второй параметр вот такой: fred
14. user@bash: 

Переменная, содержащая вывод команды

Вывод команды тоже мождно сохранить в переменную — для этого используется ` (обратный апостроф).

Примечание. Обратите внимание, что это именно обратный апостроф, а не одинарная кавычка. Обычно обратный апостроф находится на клавиатуре на букве ё — слева от клавиши 1.

Разберемся на примере: 

1. user@bash: cat backticks.sh
2. #!/bin/bash
3. # Используем обратный апостроф
4. # karpaff 22/1/2022
5. 
6. lines=`cat $1 | wc -l`
7. echo Количество строчек в файле $1 равно $lines
8. user@bash:
9. user@bash: ./backticks.sh testfile.txt
10. Количество строчек в файле testfile.txt равно 12
11. user@bash

Создаем резервные копии с помощью скрипта

Теперь давайте используем всё, что мы изучили, в сценарий, который действительно делает что-то полезное. Допустим, вы храните все свои проекты в отдельных каталогах в директории под названием Projects, которая лежит в домашнем каталоге. Вам нужно регулярно делать резервные копии этих проектов и хранить их в датированных папках в каталоге под названием Projectbackups — она тоже в домашнем каталоге.

1. user@bash: cat projectbackup.sh
2. #!/bin/bash
3. # Создает резервную копию проекта
4. # karpaff 22/1/2022
5. 
6. date=`date +%F`
7. mkdir ~/projectbackups/$1_$date
8. cp -R ~/projects/$1 ~/projectbackups/$1_$date
9. echo Резервное копирование папки $1 завершено
10. user@bash: 
11. user@bash: ./projectbackup.sh ocelot
12. Резервное копирование папки ocelot завершено
13. user@bash:

Вы можете заметите, что в приведенном выше скрипте мы использовали относительные пути. Это делает сценарий более универсальным. Если кто-то из ваших коллег захочет использовать его, можно будет просто дать ему копию скрипта, и он будет работать так же хорошо без изменений.

Вы всегда должны думать о том, как сделать скрипты гибкими и универсальными, чтобы их легко могли использовать другие пользователи или чтобы их можно было адаптированы к аналогичным ситуациям. Чем более «многоразовый» у вас скрипт — тем лучше.

Оператор условия if

У нас получился классный сценарий резервного копирования, но что если в процессе выполнения произойдет какая-то ошибка? Тогда скрипт не сработает, а мы об этом даже не узнаем. Чтобы такого не случилось, напишем в сценарии сообщения об ошибках. Так мы поймем, что именно пошло не так. Для этого нам понадобится оператор условия if

1. user@bash: cat projectbackup.sh
2. #!/bin/bash
3. # Создает резервную копию проекта
4. # karpaff 22/1/2022
5. 
6. if [ $# != 1 ]
7. then
8.    echo 'Должен быть указан 1 аргумент — папка, для которой нужно провести резервное копирование'
9.    exit
10. fi
11. if [ ! -d ~/projects/$1 ]
12. then
13.    echo 'Переданная папка не существует'
14.    exit
15. fi
16. date=`date +%F`
17.
18. # Мы уже создали папку резервного копирования для сегодняшней даты?
19. if [ -d ~/projectbackups/$1_$date ]
20. then
21.    echo 'Для этого проекта уже создана резервная копия, перезаписать?'
22.    read answer
23.    if [ $answer != 'y' ]
24.    then
25.        exit
26.    fi
27. else
28.    mkdir ~/projectbackups/$1_$date
29. fi
30. cp -R ~/projects/$1 ~/projectbackups/$1_$date
31. echo Резервное копирование папки $1 завершено
32. user@bash: 

Давайте разберемся, что здесь происходит: 

  • Строка 6. Открываем условную конструкцию с помощью оператора if. Форматирование здесь очень важно: обратите внимание, где находятся пробелы, они необходимы для правильной работы. В этом выражении мы спрашиваем, не равно ли != количество аргументов $# единице.
  • Строка 8. Если нет, скрипт был вызван неправильно. Выводим сообщение, объясняющее, как следует использовать наш сценарий.
  • Строка 9. Поскольку скрипт не был вызван должным образом, нужно выйти из скрипта, прежде чем продолжить работу.
  • Строка 10. Закрываем условную конструкцию с помощью оператора fi — if наоборот.
  • Строка 11. ! (восклицательный знак) означает отрицание, то есть «не», символ -d означает «путь существует и является каталогом». Всё условие в итоге читается так: «Если данный каталог не существует».
  • Строка 22. Просим пользователя ввести данные с помощью команды read. Команд принимает один аргумент, который является переменной для хранения ответа.
  • Строка 23. Смотрим, как ответил пользователь, действуем соответственно соответственно.

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

Совет. Конструция if на самом деле использует команду test. Если вы хотите узнать обо всех возможных сравнениях загляните на страницу руководства для команды test.

Что нужно запомнить

Команды

#!
Шебанг. Указывает, какой интепретатор должен запускать скрипт. 

echo
Выводит сообщение на экран.

which
Выводит путь к определенной команде.

$
Позволяет обращаться к значению переменной.

` ` 
Обратные апострофы. Сохраняют вывод команды в переменную.

date
Выводит на экран дату.

if [ ] then else fi
Условная конструкция. 

Идеи

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

Форматирование важно
Bash-скрипты чутки к форматированию. Убедитесь, что пробелы есть там, где они нужны, и отсутсвуют там, где они не нужны.

Практические задания

Для решения этих задач вам потребуются навыки и знания из этой и всех предыдущих статей.

1. Напишите свой скрипт резервного копирования и постепенно совершенствуйте его. 
2. Напишите скрипит, который будет предоставлять отчёт об определенной папке. Вот, на какие вопросы должен отвечать ваш скрипт:
      • Сколько файлов находится в каталоге?
      • Сколько подпапок находится в каталоге?
      • Какой самый большой файл в каталоге?
      • Какой файл изменялся или был создан последним?
      • Кто создавал файлы в каталоге?
codechick

СodeСhick.io - простой и эффективный способ изучения программирования.

2024 ©