&2         exit 1 fi Этот сценарий определяет, попадает ли целочисленное значение INT в диапазон между MIN_VAL и MAX_VAL. Эта операция выполняется единственной командой [[" />
355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Уильям Шоттс » Командная строка Linux » Текст книги (страница 25)
Командная строка Linux
  • Текст добавлен: 12 апреля 2017, 12:30

Текст книги "Командная строка Linux"


Автор книги: Уильям Шоттс


Жанр:

   

ОС и Сети


сообщить о нарушении

Текущая страница: 25 (всего у книги 30 страниц)

Таблица 27.4. Логические операторы

Операция

test

[[ ]] и (( ))

И

–a

&&

ИЛИ

–o

||

НЕ

!

!

Ниже приводится пример использования операции И (AND). Следующий сценарий определяет вхождение целочисленного значения в определенный диапазон:

#!/bin/bash

# test-integer3: проверка вхождения целочисленного значения

# в определенный диапазон.

MIN_VAL=1

MAX_VAL=100

INT=50

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then

        if [[ INT -ge MIN_VAL && INT -le MAX_VAL ]]; then

                echo "$INT is within $MIN_VAL to $MAX_VAL."

        else

                echo "$INT is out of range."

        fi

else

        echo "INT is not an integer." >&2

        exit 1

fi

Этот сценарий определяет, попадает ли целочисленное значение INT в диапазон между MIN_VAL и MAX_VAL. Эта операция выполняется единственной командой [[ ]], включающей два выражения, разделенных оператором &&. Ту же проверку можно выполнить с помощью test:

if [ $INT -ge $MIN_VAL -a $INT -le $MAX_VAL ]; then

        echo "$INT is within $MIN_VAL to $MAX_VAL."

else

        echo "$INT is out of range."

fi

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

#!/bin/bash

# test-integer4: проверка выхода целочисленного значения

# за границы определенного диапазона.

MIN_VAL=1

MAX_VAL=100

INT=50

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then

        if [[ ! (INT -ge MIN_VAL && INT -le MAX_VAL) ]]; then

                echo "$INT is outside $MIN_VAL to $MAX_VAL."

        else

                echo "$INT is in range."

        fi

else

        echo "INT is not an integer." >&2

        exit 1

fi

Здесь выражение заключено в круглые скобки для группировки. Если этого не сделать, оператор отрицания будет применяться к результату первого выражения, а не к объединению двух выражений. Ту же проверку можно реализовать с помощью test:

if [ ! ( $INT -ge $MIN_VAL -a $INT -le $MAX_VAL ) ]; then

        echo "$INT is outside $MIN_VAL to $MAX_VAL."

else

        echo "$INT is in range."

fi

Поскольку все выражения и операторы в команде test интерпретируются командной оболочкой как аргументы (в отличие от [[ ]] и (( ))), символы, имеющие специальное значение для bash, такие как <, >, ( и ), необходимо заключать в кавычки или экранировать.

Учитывая, что команды test и [[ ]] до определенной степени равноценны, возникает вопрос: какой из них отдать предпочтение? Команда test является традиционной (и частью стандарта POSIX), тогда как команда [[ ]] характерна для bash. Уметь пользоваться командой test крайне важно, потому что она применяется очень широко, но команда [[ ]] проще и удобнее в использовании.

Переносимость – беспочвенные страхи от непонимания

Если вам доведется побеседовать с «истинными» пользователями Unix, вы быстро обнаружите, что многие из них Linux терпеть не могут. Они оценивают его как нечто нечистое и греховное. Один из принципов таких ревнителей Unix – все должно быть переносимым. То есть любой сценарий, написанный вами, должен работать без изменений в любой Unix-подобной системе.

Пользователи Unix имеют веские основания рассчитывать на это. Наблюдая последствия для мира Unix, вызванные внедрением проприетарных расширений команд и командных оболочек до появления POSIX, они естественно опасаются влияния Linux на их любимую ОС.

Но переносимость имеет серьезный недостаток. Она тормозит прогресс и требует приведения всего и вся к «наименьшему общему знаменателю». Для сценариев на языке командной оболочки это означает, что они должны быть совместимы с sh, оригинальной командной оболочкой Bourne.

Этот недостаток служит отговоркой, которой пользуются производители проприетарных расширений для их оправдания, только они называют их «новшествами». Но в действительности они замыкают пользователей на себя.

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

Операторы управления: еще один способ ветвления

bash поддерживает два оператора управления, которые используются для ветвления. Операторы && (И) и || (ИЛИ) действуют подобно логическим операторам в составной команде [[ ]]. Они имеют следующий синтаксис:

команда1 && команда2

и

команда1 || команда2

Важно понимать, как они действуют. В последовательности с оператором && первая команда выполняется всегда, а вторая – только если первая завершилась успехом. В последовательности с оператором || первая команда выполняется всегда, а вторая – только если первая завершилась неудачей.

В практическом смысле это означает, что можно выполнить следующую последовательность команд:

[me@linuxbox ~]$ mkdir temp && cd temp

Она создаст каталог с именем temp и, если эта операция завершится успехом, каталог temp будет назначен текущим рабочим каталогом. Попытка выполнить вторую команду будет произведена, только если команда mkdir завершится успехом. Аналогично, следующая команда

[me@linuxbox ~]$ [ -d temp ] || mkdir temp

проверит существование каталога temp, и только если проверка не увенчается успехом, будет выполнена команда его создания. Такие конструкции очень удобно использовать для обработки ошибок в сценариях, о чем подробнее рассказывается в следующих главах. Например, в сценарии можно предусмотреть такую последовательность:

[ -d temp ] || exit 1

Если сценарий требует наличия каталога temp, а он не существует, тогда сценарий завершится с кодом 1.

Заключительное замечание

Мы начали эту главу с вопроса, оставшегося без ответа в предыдущей главе: как сценарию sys_info_page определить, имеет ли текущий пользователь права на чтение всех домашних каталогов? После знакомства с инструкцией if эту проблему можно решить, добавив следующий код в функцию report_home_space:

report_home_space () {

        if [[ $(id -u) -eq 0 ]]; then

                cat <<– _EOF_

                        

Home Space Utilization (All Users)

                        

$(du -sh /home/*)

                        _EOF_

        else

                cat <<– _EOF_

                        

Home Space Utilization ($USER)

                        

$(du -sh $HOME)

                        _EOF _

        fi

        return

}

Здесь проверяется вывод команды id. Если вызвать команду id с параметром -u, она выведет числовой идентификатор действующего пользователя. Суперпользователю всегда присваивается числовой идентификатор 0. Зная это, мы сконструировали два разных вложенных документа: один пользуется преимуществом привилегий суперпользователя, а другой ограничивается домашним каталогом текущего пользователя.

Теперь мы немного отдохнем от программы sys_info_page, но не волнуйтесь. Мы еще вернемся к нему. А пока затронем те темы, знание которых потребуется, когда мы возобновим разработку.

28. Чтение ввода с клавиатуры

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

#!/bin/bash

# test-integer2: проверка целочисленного значения.

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then

        if [ $INT -eq 0 ]; then

                echo "INT is zero."

        else

                if [ $INT -lt 0 ]; then

                        echo "INT is negative."

                else

                        echo "INT is positive."

                fi

                if [ $((INT % 2)) -eq 0 ]; then

                        echo "INT is even."

                else

                        echo "INT is odd."

                fi

        fi

else

        echo "INT is not an integer." >&2

        exit 1

fi

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

read – чтение значений со стандартного ввода

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

read [-параметры] [переменная...]

где параметры – это один или несколько параметров из перечисленных в табл. 28.1, а переменная – имя одной или нескольких переменных для сохранения введенного значения. Если имя переменной не указано, строка с данными сохраняется в переменной REPLY.

Таблица 28.1. Параметры команды read

Параметр

Описание

–a массив

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

–d разделитель

Использовать в качестве признака конца ввода первый символ из строки разделитель, а не символ перевода строки

–e

Использовать Readline для обработки ввода. Это позволяет редактировать ввод так же, как в командной строке

–n число

Прочитать указанное число символов, а не всю строку

–p приглашение

Показывать указанное приглашение к вводу

–r

Режим без промежуточной обработки. Не интерпретировать символы обратного слеша как экранирующие символы

–s

Безмолвный режим. Не производить эхо-вывод символов на экран в процессе ввода. Этот режим может пригодиться для организации ввода паролей и другой конфиденциальной информации

–t секунды

Предельное время ожидания. Завершить ввод по истечении указанного числа секунд. По истечении указанного интервала read вернет ненулевое значение

–u дескриптор

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

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

#!/bin/bash

# read-integer: проверка целочисленного значения.

echo -n "Please enter an integer -> "

read int

if [[ "$int" =~ ^-?[0-9]+$ ]]; then

        if [ $int -eq 0 ]; then

                echo "$int is zero."

        else

                if [ $int -lt 0 ]; then

                        echo "$int is negative."

                else

                        echo "$int is positive."

                fi

                if [ $((int % 2)) -eq 0 ]; then

                        echo "$int is even."

                else

                        echo "$int is odd."

                fi

        fi

else

        echo "Input value is not an integer." >&2

        exit 1

fi

Сначала мы использовали команду echo с параметром -n (подавляющим вывод символа перевода строки в конце) для вывода приглашения к вводу, а затем команду read для ввода значения в переменную int. Запуск этого сценария приводит к следующим результатам:

[me@linuxbox ~]$ read-integer

Please enter an integer -> 5

5 is positive.

5 is odd.

Команда read может сохранять ввод в множестве переменных, это показано в следующем сценарии:

#!/bin/bash

# read-multiple: чтение нескольких значений с клавиатуры

echo -n "Enter one or more values > "

read var1 var2 var3 var4 var5

echo "var1 = '$var1'"

echo "var2 = '$var2'"

echo "var3 = '$var3'"

echo "var4 = '$var4'"

echo "var5 = '$var5'"

Этот сценарий вводит, присваивает переменным и выводит до пяти значений. Обратите внимание, как действует команда read, когда получает разное число значений:

[me@linuxbox ~]$ read-multiple

Enter one or more values > a b c d e

var1 = 'a'

var2 = 'b'

var3 = 'c'

var4 = 'd'

var5 = 'e'

[me@linuxbox ~]$ read-multiple

Enter one or more values > a

var1 = 'a'

var2 = ''

var3 = ''

var4 = ''

var5 = ''

[me@linuxbox ~]$ read-multiple

Enter one or more values > a b c d e f g

var1 = 'a'

var2 = 'b'

var3 = 'c'

var4 = 'd'

var5 = 'e f g'

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

Если не передать переменные команде read, весь ввод будет сохранен в переменной командной оболочки REPLY:

#!/bin/bash

# read-single: чтение множества значений в переменную по умолчанию

echo -n "Enter one or more values > "

read

echo "REPLY = '$REPLY'"

Запуск этого сценария приводит к следующим результатам:

[me@linuxbox ~]$ read-single

Enter one or more values > a b c d

REPLY = 'a b c d'

Параметры

read поддерживает параметры, перечисленные выше в табл. 28.1.

Множество поддерживаемых параметров открывает доступ к довольно интересным способам использования read. Например, параметр -p позволяет определить строку приглашения к вводу:

#!/bin/bash

# read-single: чтение множества значений в переменную по умолчанию

read -p "Enter one or more values > "

echo "REPLY = '$REPLY'"

Параметры -t и -s позволяют писать сценарии, реализующие ввод «секретных» данных и прерывающие ввод по истечении заданного времени:

#!/bin/bash

# read-secret: ввод секретного пароля

if read -t 10 -sp "Enter secret passphrase > " secret_pass; then

        echo -e "nSecret passphrase = '$secret_pass'"

else

        echo -e "nInput timed out" >&2

        exit 1

fi

Сценарий предлагает пользователю ввести секретный пароль и ждет 10 секунд. Если в течение этого времени ввод не был завершен, сценарий завершается с кодом ошибки. Поскольку в команду включен параметр -s, символы пароля не выводятся на экран в процессе ввода.

Выделение полей в строке ввода с помощью IFS

Обычно командная оболочка выполняет разбиение ввода на слова перед передачей его команде read. Как мы уже знаем, это означает, что слова во вводе, разделенные одним или несколькими пробелами, становятся отдельными значениями и присваиваются командой read разным переменным. Такое поведение командной оболочки регулируется переменной с именем IFS (от Internal Field Separator – внутренний разделитель полей). По умолчанию переменная IFS хранит символы пробела, табуляции и перевода строки, каждый из которых может служить разделителем полей.

Изменяя значение переменной IFS, можно управлять делением ввода на поля перед передачей команде read. Например, файл /etc/passwd хранит строки данных, в которых поля отделяются друг от друга двоеточием. Присвоив переменной IFS значение, состоящее из единственного двоеточия, можно с помощью read прочитать содержимое /etc/passwd и благополучно разделить строки на поля для присваивания разным переменным. Ниже приводится сценарий, который именно так и действует:

#!/bin/bash

# read-ifs: чтение полей из файла

FILE=/etc/passwd

read -p "Enter a username > " user_name

file_info=$(grep "^$user_name:" $FILE) (1)

if [ -n "$file_info" ]; then

        IFS=":" read user pw uid gid name home shell <<< "$file_info" (2)

        echo "User      = '$user'"

        echo "UID       = '$uid'"

        echo "GID       = '$gid'"

        echo "Full Name = '$name'"

        echo "Home Dir. = '$home'"

        echo "Shell     = '$shell'"

else

        echo "No such user '$user_name'" >&2

        exit 1

fi

Этот сценарий предлагает пользователю ввести имя учетной записи в системе и затем выводит разные поля, найденные в соответствующей записи в файле /etc/passwd. В сценарии есть две интересные строки. Первая, отмеченная знаком (1), присваивает результат команды grep переменной file_info. Регулярное выражение гарантирует извлечение из файла /etc/passwd единственной строки, соответствующей введенному имени пользователя.

Вторая интересная строка, отмеченная знаком (2), состоит из трех частей: присваивания значения переменной, команды read со списком имен переменных в виде аргументов и незнакомого нам, нового оператора перенаправления. Рассмотрим сначала присваивание значения переменной.

Командная оболочка позволяет выполнять в одной строке одно или несколько операций присваивания значений переменным непосредственно перед командой, на поведение которой эти переменные влияют. Они изменяют окружение, в котором выполняется команда. Действие этих операций присваивания носит временный характер, окружение изменяется только на время выполнения команды. В данном случае в переменной IFS сохраняется символ двоеточия. То же самое можно выразить иначе:

OLD_IFS="$IFS"

IFS=":"

read user pw uid gid name home shell <<< "$file_info"

IFS="$OLD_IFS"

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

read нельзя использовать в конвейере

Даже при том, что команда read способна принимать данные со стандартного ввода, она не позволяет использовать ее следующим образом:

echo "foo" | read

Можно было бы ожидать, что этот прием сработает, но это не так. Внешне все будет выглядеть так, как будто команда успешно отработала, но при этом переменная REPLY всегда будет оставаться пустой. Почему?

Объясняется это особенностью обработки конвейеров командной оболочкой. В bash (и в других командных оболочках, таких как sh) конвейеры создают подоболочки (subshells). Они являются копиями родительской оболочки и ее окружения и используются для выполнения команд в конвейерах. В предыдущем примере команда read выполняется в подоболочке.

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

Использование встроенных строк – один из способов обойти эту проблему. Еще один способ мы увидим в главе 36.

Оператор <<< отмечает встроенную строку. Встроенная строка (here string) подобна встроенному документу, только короче, она простирается лишь до конца текущей строки кода. В данном примере строка с данными из файла /etc/passwd подается на стандартный ввод команды read. У кого-то может возникнуть вопрос, почему был выбран такой, несколько необычный, способ вместо

echo "$file_info" | IFS=":" read user pw uid gid name home shell

Скажем так: на то есть свои причины…

Проверка ввода

Использование новой для нас возможности приема ввода с клавиатуры влечет за собой дополнительную проблему: необходимость проверки введенных данных. Очень часто хорошо написанная программа отличается от плохо написанной готовностью к неожиданностям. Зачастую неожиданности возникают в форме ввода ошибочных данных. Мы уже сделали кое-что, чтобы противостоять неожиданностям в программах проверки целочисленных значений из предыдущей главы, где предусмотрено отсеивание пустых значений и значений с нецифровыми символами. Такого рода программные проверки должны выполняться для любых вводимых данных, чтобы обезопасить программу от недопустимых значений. Это особенно актуально для программ, используемых множеством пользователей. Отказ от защитных мер ради экономии простителен, только если программа пишется для однократного использования автором с целью решения некоей специальной задачи. Но даже в этом случае, если программа выполняет потенциально опасные операции, такие как удаление файлов, на всякий случай включите в нее проверку данных.

Далее приводится пример программы, проверяющий входные данные разного вида:

#!/bin/bash

# read-validate: проверка ввода

invalid_input () {

        echo "Invalid input '$REPLY'" >&2

        exit 1

}

read -p "Enter a single item > "

# пустой ввод (недопустимо)

[[ -z $REPLY ]] && invalid_input

# ввод множества элементов (недопустимо)

(( $(echo $REPLY | wc -w) > 1 )) && invalid_input

# введено допустимое имя файла?

if [[ $REPLY =~ ^[-[:alnum:]._]+$ ]]; then

        echo "'$REPLY' is a valid filename."

        if [[ -e $REPLY ]]; then

                echo "And file '$REPLY' exists."

        else

                echo "However, file '$REPLY' does not exist."

        fi

        # введено вещественное число?

        if [[ $REPLY =~ ^-?[[:digit:]]*.[[:digit:]]+$ ]]; then

                echo "'$REPLY' is a floating point number."

        else

                echo "'$REPLY' is not a floating point number."

        fi

        # введено целое число?

        if [[ $REPLY =~ ^-?[[:digit:]]+$ ]]; then

                echo "'$REPLY' is an integer."

        else

                echo "'$REPLY' is not an integer."

        fi

else

        echo "The string '$REPLY' is not a valid filename."

fi

Этот сценарий предлагает пользователю ввести элемент данных и затем последовательно анализирует его содержимое. Как видите, в сценарии использовано множество идей, с которыми мы уже познакомились, включая функции [[ ]], (( )), операторы управления && и if, а также разумную дозу регулярных выражений healthy.

Меню

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

Выберите команду:

1. Вывести информацию о системе

2. Вывести информацию о дисковом пространстве

3. Вывести информацию об объеме домашнего каталога

0. Выйти

Введите номер выбранной команды [0-3] >

Используя все, что мы узнали в ходе создания программы sys_info_page, можно сконструировать программу, реализующую решение задач, перечисленных в меню, приведенном выше:

#!/bin/bash

# read-menu: программа вывода системной информации,

#            управляемая с помощью меню

clear

echo "

Please Select:

1. Display System Information

2. Display Disk Space

3. Display Home Space Utilization

0. Quit

"

read -p "Enter selection [0-3] > "

if [[ $REPLY =~ ^[0-3]$ ]]; then

        if [[ $REPLY == 0 ]]; then

                echo "Program terminated."

                exit

        fi

        if [[ $REPLY == 1 ]]; then

                echo "Hostname: $HOSTNAME"

                uptime

                exit

        fi

        if [[ $REPLY == 2 ]]; then

                df -h

                exit

        fi

        if [[ $REPLY == 3 ]]; then

                if [[ $(id -u) -eq 0 ]]; then

                        echo "Home Space Utilization (All Users)"

                        du -sh /home/*

                else

                        echo "Home Space Utilization ($USER)"

                        du -sh $HOME

                fi

                exit

        fi

else

        echo "Invalid entry." >&2

        exit 1

fi

Этот сценарий делится на две логические части. Первая часть выводит меню и вводит выбор пользователя. Вторая часть идентифицирует выбор и выполняет соответствующие действия. Обратите внимание, как используется команда exit в этом сценарии. Она препятствует выполнению ненужного кода после завершения затребованного действия. Наличие нескольких точек выхода из программы вообще считается дурным тоном (логику работы такой программы труднее понять), но в данном сценарии нас это устраивает.

Заключительное замечание

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

Дополнительные сведения

Постарайтесь внимательно изучить программы из этой главы и достичь полного понимания их логической структуры, потому что программы, которые последуют далее, будут еще сложнее. В качестве упражнения перепишите программы этой главы, используя команду test вместо составной команды [[ ]]. Подсказка: используйте grep для сопоставления с регулярными выражениями, а затем проверяйте код завершения. Это станет для вас хорошей практикой.


    Ваша оценка произведения:

Популярные книги за неделю