Статьи

Основы сценариев Bash

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


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

Проблема Fizz-Buzz очень проста. Он стал известным после того, как программист по имени Имран использовал его в качестве теста для интервью. Получается, что 90-99,5% кандидатов на работу по программированию просто не могут написать простейшую программу. Имран взял эту простую игру Fizz-Buzz и попросил кандидатов решить ее. Многие последовали примеру Имрана, и сегодня это один из наиболее часто задаваемых вопросов для работы программиста. Если вы нанимаете и нуждаетесь в способе фильтрации 90% кандидатов, это большая проблема для представления.

Вот правила:

  • Возьмите и распечатайте числа от 1 до 100.
  • Когда число делится на 3, вместо числа выведите «Fizz».
  • Когда это делится на 5, вместо этого выведите «Buzz».
  • Когда он делится на 3 и 5, выведите «FizzBuzz».

Это все, что нужно сделать. Я уверен, что большинство из вас уже могут представить два или три оператора if для решения этой проблемы. Давайте поработаем над этим, используя язык сценариев Bash.


Шебанг относится к комбинации символов хеш и восклицательного знака: #! , Загрузчик программы будет искать шебанг в первой строке скрипта и использовать указанный в нем интерпретатор. Шебанг состоит из следующего синтаксиса: #!interpreter [parameters] . Интерпретатор — это программа, которая используется для интерпретации нашего языка. Для сценариев bash это будет /bin/bash . Например, если вы хотите создать скрипт на PHP и запустить его в консоли, вы, вероятно, захотите использовать /usr/bin/php (или путь к исполняемому файлу PHP на вашем компьютере) в качестве интерпретатора.

1
2
3
#!/usr/bin/php
<?php
phpinfo();

Да, это действительно сработает! Разве это не просто? Просто убедитесь, что ваш файл исполняется первым. Как только вы это сделаете, этот скрипт выведет вашу информацию PHP так, как вы ожидаете.

Совет: чтобы ваш скрипт работал на максимально возможном количестве систем, вы можете использовать /bin/env в shebang. Таким образом, вместо /bin/bash вы можете использовать /bin/env bash , который будет работать в системах, где исполняемый файл bash находится вне /bin .


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

1
2
3
#!/bin/bash
 
echo «Hello World»

Запуск этого скрипта выведет «Hello World» в консоли.

1
2
3
csaba@csaba ~ $ ./helloWorld.sh
Hello World
csaba@csaba ~ $

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

1
2
3
4
#!/bin/bash
 
message=»Hello World»
echo $message

Этот код создает точно такое же сообщение «Hello World». Как видите, чтобы присвоить значение переменной, просто напишите ее имя — исключите знак доллара перед ним. Также будьте осторожны с пробелами; между именем переменной и знаком равенства не должно быть пробелов. Так что message="Hello" вместо message = 'Hello'

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

Совет: Точки с запятой не требуются в скриптах bash. Вы можете использовать их в большинстве случаев, но будьте осторожны: они могут иметь значение, отличное от того, что вы ожидаете.


Продолжая наш демонстрационный проект, нам нужно циклически перебрать все числа от 1 до 100. Для этого нам нужно использовать цикл for .

1
2
3
4
5
#!/bin/bash
 
for number in {1..100};
    echo $number
done

В этом примере стоит отметить несколько новых вещей, которые, кстати, распечатывают все числа от 1 до 100, по одному за раз.

  • Синтаксис for в Bash: for VARIABLE in RANGE; do COMMAND done for VARIABLE in RANGE; do COMMAND done .
  • В нашем примере фигурные скобки преобразуют 1..100 в диапазон. Они также используются в других контекстах, которые мы вскоре рассмотрим.
  • do и for на самом деле две отдельные команды. Если вы хотите поместить две команды в одну строку, вам нужно как-то разделить их. Одним из способов является использование точки с запятой. В качестве альтернативы вы можете написать код без точки с запятой, переместив команду do в следующую строку.
1
2
3
4
5
6
#!/bin/bash
 
for number in {1..100}
do
    echo $number
done

Теперь, когда мы знаем, как напечатать все числа от 1 до 100, пришло время принять наше первое решение.

1
2
3
4
5
6
7
8
9
#!/bin/bash
 
for number in {1..100};
    if [ $((number%3)) -eq 0 ];
        echo «Fizz»
    else
        echo $number
    fi
done

В этом примере будет выведено «Fizz» для чисел, делимых на 3. Опять же, нам нужно разобраться с новым синтаксисом. Давайте возьмем их один за другим.

  • if..then..else..fi — это классический синтаксис для оператора if в Bash. Конечно, часть else является необязательной, но в нашем случае требуется для нашей логики.
  • if COMMAND-RETURN-VALUE; then... if COMMAND-RETURN-VALUE; then...if будет выполняться, если возвращаемое значение команды равно нулю. Да, логика в Bash основана на нуле, что означает, что команды, которые успешно выполняются, завершаются с кодом 0. Если что-то пойдет не так, с другой стороны, будет возвращено положительное целое число. Для упрощения: все, кроме 0, считается false .
  • Математические выражения в Bash указываются в двойных скобках. $((number%3)) вернет оставшееся значение деления переменной number на 3. Обратите внимание, что мы не использовали $ в скобках — только перед ними.

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

1
2
3
4
5
6
7
8
csaba@csaba ~ $ which [
/usr/bin/[
csaba@csaba ~ $ [ 0 -eq 1 ]
csaba@csaba ~ $ echo $?
1
csaba@csaba ~ $ [ 0 -eq 0 ]
csaba@csaba ~ $ echo $?
0

Совет: Выходное значение команды всегда возвращается в переменную ? (вопросительный знак). Он перезаписывается после выполнения каждой новой команды.


Пока у нас все хорошо. У нас есть «Fizz»; Теперь давайте сделаем часть «Buzz».

01
02
03
04
05
06
07
08
09
10
11
#!/bin/bash
 
for number in {1..100};
    if [ $((number%3)) -eq 0 ];
        echo «Fizz»
    elif [ $((number%5)) -eq 0 ];
        echo «Buzz»
    else
        echo $number
    fi
done

Выше мы ввели еще одно условие делимости на 5: утверждение elif . Это, конечно, преобразуется в else if и будет выполнено, если следующая за ним команда вернет true (или 0 ). Как вы можете заметить, условные операторы внутри [] обычно оцениваются с помощью параметров, таких как -eq , что означает «равно».

Для синтаксиса arg1 OP arg2 , OP является одним из -eq , -ne , -lt , -le , -gt или -ge . Эти арифметические бинарные операторы возвращают true если arg1 равно, не равно, меньше, меньше или равно, больше или больше или равно arg2 , соответственно. arg1 и arg2 могут быть положительными или отрицательными целыми числами. Руководство по Bash

Когда вы пытаетесь сравнить строки, вы можете использовать хорошо известный знак == , или подойдет даже один знак равенства. != возвращает true если строки разные.


Пока что код выполняется, но логика неверна. Когда число делится на 3 и 5, наша логика будет отображать только «Fizz». Давайте изменим наш Код для соответствия последнему требованию FizzBuzz.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
#!/bin/bash
 
for number in {1..100};
    output=»»
    if [ $((number%3)) -eq 0 ];
        output=»Fizz»
    fi
    if [ $((number%5)) -eq 0 ];
        output=»${output}Buzz»
    fi
    if [ -z $output ];
        echo $number
    else
        echo $output;
    fi
done

Опять же, нам пришлось внести несколько изменений. Наиболее заметным является введение переменной, а затем, при необходимости, конкатенация «Buzz» к ней. Строки в bash обычно определяются между двойными кавычками («). Также можно использовать одинарные кавычки, но для облегчения конкатенации лучше использовать двойные. В этих двойных кавычках вы можете ссылаться на переменные: some text $variable some other text » замените $variable ее содержимым. Если вы хотите объединить переменные со строками без пробелов между ними, вы можете предпочесть поместить имя переменной в фигурные скобки. В большинстве случаев, как и в PHP, вам не нужно этого делать, но это очень помогает, когда дело доходит до читабельности кода.

Совет: Вы не можете сравнивать пустые строки. Это вернуло бы отсутствующий параметр.

Поскольку аргументы внутри [ ] обрабатываются как параметры, для "[" они должны отличаться от пустой строки. Таким образом, это выражение, хотя и логичное, выведет ошибку: [ $output != "" ] . Вот почему мы использовали [ -z $output ] , который возвращает true если длина строки равна нулю.


Один из способов улучшить наш пример — извлечь в функции математическое выражение из операторов if , например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
 
function isDivisibleBy {
    return $(($1%$2))
}
 
for number in {1..100};
    output=»»
    if isDivisibleBy $number 3;
        output=»Fizz»
    fi
    if isDivisibleBy $number 5;
        output=»${output}Buzz»
    fi
    if [ -z $output ];
        echo $number
    else
        echo $output;
    fi
done

Мы взяли выражения, сравнивающие остальные с нулем, и переместили их в функцию. Более того, мы исключили сравнение с нулем, потому что ноль означает для нас истину. Нам нужно только вернуть значение из математического выражения — очень просто!

Совет: определение функции должно предшествовать ее вызову.

В Bash вы можете определить метод как function func_name { commands; } function func_name { commands; } При желании существует второй синтаксис для объявления функций: func_name () { commands; } func_name () { commands; } Таким образом, мы можем удалить строку, function и добавить "()" после ее имени. Я лично предпочитаю эту опцию, как показано в примере выше. Это более явно и напоминает традиционные языки программирования.

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

Полученные параметры автоматически присваиваются переменным по номеру. Первый параметр имеет значение $1 , второй $2 и т. Д. Специальная переменная $0 указывает имя файла текущего скрипта.

01
02
03
04
05
06
07
08
09
10
11
#!/bin/bash
 
function exampleFunc {
    echo $1
    echo $0
    IFS=»X»
    echo «$@»
    echo «$*»
}
 
exampleFunc «one» «two» «three»

Этот код выдаст следующий вывод:

1
2
3
4
5
csaba@csaba ~ $ ./parametersExamples.sh
one
./parametersExamples.sh
one two thre
oneXtwoXthre

Давайте проанализируем источник построчно.

  • Последняя строка — это вызов функции. Мы называем это с тремя строковыми параметрами.
  • Первая строка после шебанга — это определение функции.
  • В первой строке функции выводится первый параметр: «один». Пока все просто.
  • Вторая строка выводит имя файла текущего скрипта. Опять очень просто.
  • Третья строка заменяет символьный разделитель по умолчанию на букву «X». По умолчанию это «» (пробел). Вот как Bash знает, как разделены параметры.
  • В четвертой строке выводится специальная переменная $@ . Он представляет все параметры как одно слово, точно так же, как указано в вызове функции.
  • В последней строке выводится другая специальная переменная, $* . Он представляет все параметры, взятые один за другим и объединенные с первой буквой переменной IFS. Вот почему результат — один oneXtwoXthre .

Как я отмечал ранее, функции в Bash могут возвращать только целые числа. Таким образом, написание return "a string" будет неверным кодом. Тем не менее, во многих ситуациях вам нужно больше, чем просто ноль или единица. Мы можем реорганизовать наш пример FizzBuzz, чтобы в операторе for мы просто вызывали функцию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
 
function isDivisibleBy {
    return $(($1%$2))
}
 
function fizzOrBuzz {
    output=»»
    if isDivisibleBy $1 3;
        output=»Fizz»
    fi
    if isDivisibleBy $1 5;
        output=»${output}Buzz»
    fi
    if [ -z $output ];
        echo $1
    else
        echo $output;
    fi
}
 
for number in {1..100};
    fizzOrBuzz $number
done

Ну, это первый шаг. Мы просто извлекли весь код в функцию с именем fizzOrBuzz , а затем заменили $number на $1 . Однако весь вывод происходит в функции fizzOrBuzz . Мы хотим вывести из цикла for оператор echo , чтобы мы могли добавлять каждую строку другой строкой. Мы должны зафиксировать fizzOrBuzz функции fizzOrBuzz .

1
2
3
4
5
6
#[…]
for number in {1..100};
    echo «-`fizzOrBuzz $number`»
    fizzBuzzer=$(fizzOrBuzz $number)
    echo «-${fizzBuzzer}»
done

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

Первое решение для захвата вывода функции или другой команды заключается в использовании обратных галочек. В 99% случаев это будет работать просто отлично. Вы можете просто ссылаться на переменную внутри обратных тегов по их именам, как мы делали с $number . Первые несколько строк вывода теперь должны выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
csaba@csaba ~/Personal/Programming/NetTuts/The Basics of BASH Scripting/Sources $ ./fizzBuzz.sh
-1
-1
-2
-2
-Fizz
-Fizz
-4
-4
-Buzz
-Buzz
-Fizz
-Fizz
-7
-7

Как видите, все продублировано. Тот же вывод.

Для второго решения мы решили сначала присвоить возвращаемое значение переменной. В этом назначении мы использовали $() , который в данном случае разветвляет скрипт, выполняет код и возвращает его вывод.


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

Более сложный случай — использовать && между двумя командами. Да, это логическое И; это означает, что вторая команда будет выполнена, только если первая вернет true (она завершается с 0). Это полезно; мы можем упростить операторы if в этих сокращениях:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
 
function isDivisibleBy {
    return $(($1%$2))
}
 
function fizzOrBuzz {
    output=»»
    isDivisibleBy $1 3 && output=»Fizz»
    isDivisibleBy $1 5 && output=»${output}Buzz»
    if [ -z $output ];
        echo $1
    else
        echo $output;
    fi
}
 
for number in {1..100};
    echo «-`fizzOrBuzz $number`»
done

Поскольку наша функция isDivisibleBy возвращает правильное возвращаемое значение, мы можем затем использовать && чтобы установить isDivisibleBy переменную. Что после && будет выполнено, только если условие true . Таким же образом мы можем использовать || (символ двойной трубы) как логическое ИЛИ. Вот быстрый пример ниже.

1
2
3
4
5
csaba@csaba ~ $ echo «bubu» ||
bubu
csaba@csaba ~ $ echo false ||
false
csaba@csaba ~ $

Так что это делает для этого урока! Я надеюсь, что вы выбрали несколько новых советов и техник для написания своих собственных скриптов Bash. Спасибо за чтение, и следите за новостями по этой теме.