Лекция №23 - bash. Объединение команд

На этой лекции мы продолжаем изучать bash. Сегодня поговорим об объединении команд в bash, о конструкции case и некоторых других командах. Но сначала о незаслуженно забытой на прошлой лекции конструкции case.

case

case удобнее использовать для ветвления, когда значение необходимо проверять на точное соответствие и оно может принимать три и более значений. Вместо case можно было бы использовать if, но такие конструкции выглядят громоздко и их не так удобно “читать” в скриптах.

Общий синтаксис команды case следующий:

case значение in
(шаблон)
блок команд
;;
(шаблон)
блок команд
;;
...
...
(шаблон)
блок команд
;;
esac

Для нас case важна еще и потому, что содержится во всех скриптах каталога /etc/init.d/. В качестве шаблона могут указываться буквы, цифры, строки и шаблоны вида [a-z], [1-9], а также ? - один любой символ и * - любая комбинация. Хотя шаблон в общем синтаксисе указан в круглых скобках, на практике разрешается первую скобку не писать. Давайте рассмотрим практический пример:

#!/bin/bash
case $1 in
[a,b,c,d])                    #значения заданные в шаблоне явно
        echo "a b c d"
        ;;
[0-9])                         #последовательность числовых значений
        echo "Это цифра $1"
        ;;
[a-z])                          #последовательность
        echo "Это буква $1"
        ;;
??)
        echo "Это два любых символа"
        ;;
stop|restart|start)
        echo "Это слово stop или restart или start"
        ;;
*)
        echo "Другое значение"
        ;;
esac

В качестве значения здесь использована конструкция $1, которая содержит первый передаваемый скрипту параметр. На прошлой лекции мы говорили, что параметры можно передавать не только функции, но и скрипту. Значение начинает сверятся с шаблонами сверху вниз и как только будет найдено соответствие, будет выполнен блок команд между шаблоном и ;;. Пример работы скрипта:

1
2
3
4
5
6
7
8
9
10
igor@adm-ubuntu:~/linux$ ./case.sh a
a b c d
igor@adm-ubuntu:~/linux$ ./case.sh k
Это буква k
igor@adm-ubuntu:~/linux$ ./case.sh qq
Это два любых символа
igor@adm-ubuntu:~/linux$ ./case.sh 1
Это цифра 1
igor@adm-ubuntu:~/linux$ ./case.sh start
Это слово stop или restart или start

В каталоге /etc/init.d/ расположены скрипты управления службами Linux. С помощью case в них реализован механизм обработки передаваемых скрипту параметров: start, stop, restart, reload и других. Ниже фрагмент скрипта /etc/init.d/reboot:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case "$1" in
  start)
        # No-op
        ;;
  restart|reload|force-reload)
        echo "Error: argument '$1' not supported" >&2
        exit 3
        ;;
  stop)
        do_stop
        ;;
  *)
        echo "Usage: $0 start|stop" >&2
        exit 3
        ;;
esac

Подобный case вы найдете в каждом скрипте каталога /etc/init.d/.

Объединение команд

Я уже говорил, что если в одной строке скрипта мы пишем два ключевых слова какой либо конструкции, то их нужно разделять символом ;. Точка с запятой и есть простейший способ объединения команд (ключевые слова тоже воспринимаются bash как команды). Команды записанные таким образом будут выполняться последовательно в независимости от результата выполнения предыдущей команды. Примеры ниже можно набирать как в командной строке так и в скрипте.

1
2
3
4
igor@adm-ubuntu:~/linux$ a=127; echo $a; b=172; echo $b; let c=$a+$b; echo $c
127
172
299

Как видите из примера на одной строке размещено 6 команд, которые были выполнены последовательно.

Для параллельного запуска команд используют уже знакомый вам амперсанд &. Такие команды будут запущены в фоновом режиме и выполняться будут параллельно.

1
2
3
4
igor@adm-ubuntu:~$ sleep 10& sleep 10& echo Hello!
[1] 8043
[2] 8044
Hello!

Дальше уже более интересные конструкции. После своего выполнения любая команды возвращает числовой код результата выполнения команды. Если это 0 - значит команда выполнена успешно, если это число отличное от нуля - значит команда завершилась с ошибкой. Можно строить выполнение последовательности команд учитывая этот момент. Для этого существуют следующие элементы: двойной амперсанд - && и двойной “пайп” - ||. Если объединение команд происходит только через &&, то каждая следующая команда будет выполняться, только в случае успешного завершения предыдущей команды. Как только какая либо команда цепочки вернет код возврата отличный от нуля, следующие после нее команды не будут выполнены. Для примера воспользуемся командами true (всегда возвращает 0) и false (всегда возвращает 1).

1
2
igor@ubuntu:~$ true && echo "1" && false && echo "2"
1

В примере команда true вернула 0 и следующая команда echo “1″ была выполнена и тоже вернула 0, после чего была выполнена команда false, которая вернула 1 и выполнение команд прекратилось (команда echo 2 не выполнилась).

Команда || работает с точностью до наоборот. То есть следующая команда будет выполнена только в том случае, если предыдущая команда вернула код возврата отличный от нуля (правило действует в таком виде если все команды объединены только ||). Если команда вернула 0, то выполнение дальнейшей цепочки команд прерывается. Если в примере выше заменить && на ||, то выполнится только команда true, которая вернет 0 и дальнейшие команды не будут выполнены. Если заменить true на false, то получим такой же результат:

1
2
igor@ubuntu:~$ false || echo "1" || false || echo "2"
1

Комбинируя && и || можно строить довольно сложные варианты объединения команд. Но в этому случае нужно глубже понимать, как происходит выполнение команд при таком объединении.

Рассмотрим два примера объединения команд и на результат их выполнения:

1
2
3
4
5
igor@ubuntu:~$ false || echo "1" || false || echo "2"
1
igor@ubuntu:~$ false || echo "1" || false && echo "2"
1
2

В первом варианте команда false вернет код 1, и будет выполнена следующая команда echo “1″, которая вернет код выполнения 0. И далее цепочка команд прерывается как и описано выше. Во втором случае должна быть, такая же ситуация, но команда echo “2″ выполняется. Все дело в том, что на самом деле происходит проверка всех операторов объединения, которые участвуют в цепочке. Если команда вернула 1, а все дальнейшие операторы объединения && - то поэтому и не будет выполнена ни одна команда. Но если в цепочке встретиться оператор ||, то команда после него будет выполнена и проверка будет идти дальше.

Рассмотрим более подробно эту цепочку:

1) Выполняется команда false и возвращает код выполнения 1
2) Далее стоит оператор ||, а значит следующая команда будет выполняться
3) Выполняется команда echo “1″ и возвращает код 0
4) Далее стоит оператор ||, а значит следующая команда не будет выполнятся
5) Проверяется следующий оператор - это оператор &&, значит следующая команда (echo “2″) будет выполнена.

Посмотрите еще несколько примеров:

1
2
igor@ubuntu:~$ true || echo "1" && false || echo "2"
2
1
2
3
igor@ubuntu:~$ true && echo "1" || false && echo "2"
1
2
1
2
3
igor@adm-ubuntu:~/linux$ true || echo "1" || echo "2" || echo "3" || false && echo "4" && echo "5" || echo "6"
4
5

Команды можно объединять в блоки с помощью круглых скобок:

1
2
3
4
igor@adm-ubuntu:~/linux$ true || echo "1" && (echo "2"; echo "3" || echo "4") && echo "5" || echo "6"
2
3
5

или фигурных:

1
2
3
4
igor@adm-ubuntu:~/linux$ true || echo "1" && { echo "2"; echo "3" || echo "4"; } && echo "5" || echo "6"
2
3
5

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

Отличия между круглыми и фигурными скобками в том, что команды в фигурных скобках выполняются в рамках того же процесса, что и скрипт, а команды в круглых скобках выполняются как бы в своем подпроцессе. Посмотрим пример скрипта:

1
2
3
4
#!/bin/bash
true && (echo a; sleep 1 && exit 0 || echo b) && echo c
echo d
echo e

и результат выполнения:

1
2
3
4
5
igor@adm-ubuntu:~/linux$ ./script.sh
a
c
d
e

А теперь такой же пример с фигурными скобками:

1
2
3
4
#!/bin/bash
true && { echo a; sleep 1 && exit 0 || echo b; } && echo c
echo d
echo e

и результат его выполнения:

1
2
igor@adm-ubuntu:~/linux$ ./script.sh
a

В первом случае команда exit прервала выполнение команд расположенных в круглых скобках, но не скрипта в целом. Во втором случае происходит остановка всего скрипта.

В завершении лекции еще несколько команд.

Если из скрипта необходимо запустить исполняемый файл, достаточно написать имя файла (если он расположен в каталоге описанном в переменной PATH) или полный путь к файлу если он расположен в специфическом каталоге:

1
/home/igor/linux/testscript.sh

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

1
2
3
4
...
exec /home/igor/linux/testscript.sh
echo "1"
echo "2"

При таком варианте запуска команды echo 1 и echo 2 выполнены не будут, так как после exec /home/igor/linux/testscript.sh скрипт завершит свою работу.

Также работу скрипта можно в любом месте прервать командой exit. Желательно при этом указать код результата выполнения скрипта. Например, exit 0.

Читать другие лекции по курсу Администратор ПК с Linux

Статьи и новости схожей тематики:

Комментариев: 7

  1. Igorka:

    Если не совсем понятно с объединением команд - спрашивайте, попробую еще объяснить. Так как сам, до этой лекции считал что немного по другому все происходит.

    Ответить

  2. Pe1ro:

    О, большое спасибо! Всё очень толково написано. Получил удовольствие не только от результата (наконец разобрался с && и ||), но и от процесса чтения статьи.

    Ответить

    Igorka Reply:

    Спасибо. Если сказать честно, то с && и || пришлось самому разбираться. Лектор так подробно эту тему не рассказывал.

    Ответить

  3. юотыч:

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

    Ответить

  4. Антон:

    я так понял просто || имеет более низкий приоритет как бы относительно && или нет? ну есть некая аналогия с вариантом где расставлены скобки вокруг операторов с || между блоками соединенными через &&

    Ответить

  5. мимокрокодил:

    а откуда такая нелогичность (хотя возможно она только кажется) чтобы false возвращаоа 1 а true наоборот. как-то 0 и 1 наоборот ассоциировались: 0 как false, 1 как true

    Ответить

    Igorka Reply:

    В линуксе, если какая либо команда выполнена успешно, то она возвращает 0 - код успешного выполнения (как правило). То есть, например, нашла команда locate искомый файл - вернет 0 как результат своей работы, не нашла - вернет что-то отличное от нуля (единица была взята для примера). Так как команда true у нас ассоциируется с чем-то положительным (успешным), то она и возвращает код успешного выполнения, а команда false возвращает код НЕ успешного выполнения. Если посмотреть man то там не говорится, что true возвращает 0, а false - 1. Там написано, что
    true - do nothing, successfully
    false - do nothing, unsuccessfully

    Ответить

Оставьте свой отзыв