Добавить в цитаты Настройки чтения

Страница 95 из 113

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 в этом сценарии. Она препятствует выполнению ненужного кода после завершения затребованного действия. Наличие нескольких точек выхода из программы вообще считается дурным тоном (логику работы такой программы труднее понять), но в данном сценарии нас это устраивает.