Библиотека сайта rus-linux.net
Руководство по Bash для начинающих | ||
---|---|---|
Назад | Глава 8: Пишем интерактивные скрипты | Вперед |
Получение данных, вводимых пользователем
Использование встроенной команды read
Встроенная команда read является противоположностью командам echo и printf. Синтаксис команды read следующий:
read [options] NAME1 NAME2 ... NAMEN
Из стандартного входного потока или из дескриптора файла, указываемого в качестве аргумента в параметре -u
, считывается одна строка. Первое слово из строки присваивается первому имени NAME1, второе слово — второму имени и так далее, оставшиеся слова и имеющиеся между ними разделители назначаются последнему имени NAMEN. Если слов, считываемых из входного потока меньше, чем имен, оставшимся именам присваиваются пустые значения.
Для разбиения входной строки на слова или лексемы используются символы, хранящиеся в переменной
IFS
; смотрите раздел "Разбиение на слова". Для отмены особого свойства следующего символа и для продолжения строки можно использовать символ обратного слеша.
Если имена не указываются, то считанная строка назначается переменной REPLY
.
Код возврата команды read равен нулю, если не встретился символ конца файла, если не возник таймаут команды read или если дескриптор файла, указанный в качестве аргумента параметра -u
, не оказался недопустимым.
Во встроенной команде read поддерживаются следующие параметры:
Таблица 8.2. Параметры встроенной команды read
Параметр | Значение |
-a | Слова присваиваются подряд идущим элементам массива |
-d | В качестве завершающего элемента входной строки используется символ |
-e | Для чтения строки используется программа readline. |
-n | Команда read возвращает управление после чтения символов |
-p | Перед попыткой прочитать какие-нибудь входные данные, сначала выдается |
-r | Если задан этот параметр, то обратный слеш не используется для отмены свойств специальных символов. Обратный слеш считается частью строки. В частности, пару "обратный слэш и символ новой строки" нельзя будет использовать для указания продолжения строки. |
-s | Тихий режим. Если входные данные поступают из терминала, на терминал не выдается эхо-ответ. |
-t | Если в течение |
-u | Чтение входных данных из дескиптора файла |
Это простой пример представляет собой улучшенный вариант скрипта leaptest.sh
из предыдущей главы:
michel ~/test> cat leaptest.sh #!/bin/bash # This script will test if you have given a leap year or not. echo "Type the year that you want to check (4 digits), followed by [ENTER]:" read year if (( ("$year" % 400) == "0" )) || (( ("$year" % 4 == "0") && ("$year" % 100 != "0") )); then echo "$year is a leap year." else echo "This is not a leap year." fi michel ~/test> leaptest.sh Type the year that you want to check (4 digits), followed by [ENTER]: 2000 2000 is a leap year.
Приглашение пользователю ввести данные
В следующем примере показано, как можно использовать приглашения, объясняющие, что пользователь должен ввести.
michel ~/test> cat friends.sh #!/bin/bash # This is a program that keeps your address book up to date. friends="/var/tmp/michel/friends" echo "Hello, "$USER". This script will register you in Michel's friends database." echo -n "Enter your name and press [ENTER]: " read name echo -n "Enter your gender and press [ENTER]: " read -n 1 gender echo grep -i "$name" "$friends" if [ $? == 0 ]; then echo "You are already registered, quitting." exit 1 elif [ "$gender" == "m" ]; then echo "You are added to Michel's friends list." exit 1 else echo -n "How old are you? " read age if [ $age -lt 25 ]; then echo -n "Which colour of hair do you have? " read colour echo "$name $age $colour" >> "$friends" echo "You are added to Michel's friends list. Thank you so much!" else echo "You are added to Michel's friends list." exit 1 fi fi michel ~/test> cp friends.sh /var/tmp; cd /var/tmp michel ~/test> touch friends; chmod a+w friends michel ~/test> friends.sh Hello, michel. This script will register you in Michel's friends database. Enter your name and press [ENTER]: michel Enter your gender and press [ENTER] :m You are added to Michel's friends list. michel ~/test> cat friends
Обратите внимание, что здесь не показаны строки, выдаваемые скриптом. Скрипт только запоминает информацию о людях, которыми интересуется Мишель, но скрипт всегда сообщит вам, что вы добавлены в список, если вас в нем не было.
Теперь другие могут запустить скрипт:
[anny@octarine tmp]$ friends.sh Hello, anny. This script will register you in Michel's friends database. Enter your name and press [ENTER]: anny Enter your gender and press [ENTER] :f How old are you? 22 Which colour of hair do you have? black You are added to Michel's friends list.
Через некоторое время список friends
будет выглядеть следующим образом:
tille 24 black anny 22 black katya 22 blonde maria 21 black --output omitted--
Конечно, эта ситуация не идеальна, поскольку каждый может редактировать (но не удалять) файлы Мишеля. Вы можете решить эту проблему с помощью специальных режимов доступа к файлу скрипта; смотрите описание SUID и SGID в руководстве "Введение в Linux".
Перенаправление и дескрипторы файлов
Общие положения
Как вы знаете из основных положений об использовании командной оболочки, прежде, чем команда будет выполнена, можно с помощью специальной нотации — операторов перенаправления, которые интерпретируются командной оболочкой, перенаправлять ввод и вывод команды. Перенаправлением можно пользоваться для открытия и закрытия файлов в текущей среде исполнения командной оболочки.
В скрипте перенаправление можно также использовать для того, чтобы скрипт мог, например, получать входные данные из файла или направлять вывод в файл. Позже, пользователь может просмотреть выходной файл, либо выходной файл можно использовать в другом скрипте в качестве входных данных.
Ввод из файла и вывод в файл сопровождается обработкой целых чисел, с помощью которых отслеживаются все файлы, открываемые в данном процессе. Эти числовые значения называются дескрипторами файлов. Самыми известными являются дескрипторы файлов stdin, stdout и stderr, номера дескрипторов файлов для которых равны 0, 1 и 2, соответственно. Под этими номерами зарезервированы соответствующие им устройства. В Bash в качестве дескрипторов файлов можно также использовать порты TCP и UDP сетевых хостов.
В приведенных ниже выходных данных показывается, как зарезервированные дескрипторы файлов указывают на фактически имеющиеся устройства:
michel ~> ls -l /dev/std* lrwxrwxrwx 1 root root 17 Oct 2 07:46 /dev/stderr -> ../proc/self/fd/2 lrwxrwxrwx 1 root root 17 Oct 2 07:46 /dev/stdin -> ../proc/self/fd/0 lrwxrwxrwx 1 root root 17 Oct 2 07:46 /dev/stdout -> ../proc/self/fd/1 michel ~> ls -l /proc/self/fd/[0-2] lrwx------ 1 michel michel 64 Jan 23 12:11 /proc/self/fd/0 -> /dev/pts/6 lrwx------ 1 michel michel 64 Jan 23 12:11 /proc/self/fd/1 -> /dev/pts/6 lrwx------ 1 michel michel 64 Jan 23 12:11 /proc/self/fd/2 -> /dev/pts/6
Обратите внимание, что каждый процесс имеет в /proc/self
свое собственное представление, т.к. фактически это символическая
ссылка на /proc/<идентификатор процесса>
.
Вы можете с помощью info MAKEDEV и info proc посмотреть дополнительную информацию о директории /proc
и о том, что в вашей системе в каждом запущенном процессе делается с дескрипторами стандартных файлов.
Когда выполняется конкретная команда, ее выполнение состоит из следующих шагов и происходит в приведенном ниже порядке:
- Если стандартный вывод предыдущей команды подключен через
конвейер к стандартному входу текущей команды, то
/proc/<идентификатор текущего процесса>/fd/0
будет заменен на тот же самый анонимный конвейер, который использовался для/proc/<идентификатор предыдущего процесса>/fd/1
. - Если стандартный вывод текущей команды подключен через конвейер к стандартному входу следующей команды,
/proc/<идентификатор текущего процесса>/fd/1
будет заменен на еще один анонимный конвейер. - Перенаправление для текущей команды обрабатываются слева направо
- Перенаправление "N>&M" или "N<&M", указываемое после команды, представляет собой создание или изменении символической ссылки
/proc/self/fd/N
, так чтобы она ссылалась туда, куда и/proc/self/fd/M
. - Перенаправление "N>файл" и "N<файл" представляет собой создание или изменение символической ссылки
/proc/self/fd/N
, так чтобы она указывала на целевой файл. - Если указывается "N>&-", то это вызывает удаление
символической ссылки
/proc/self/fd/N
. - Только после этого происходит выполнение текущей команды.
Когда скрипт запускается из командной строки, то ничего меняться не будет, поскольку процесс в дочерней командной оболочке будет использовать те же самые дескрипторы файлов, что и в родительской оболочке. Когда родительская оболочка отсутствует, например, когда вы запускаете скрипт с помощью cron, дескрипторами стандартных файлов будут конвейеры или другие (временные) файлы, если не используется какой-нибудь вариант перенаправления. Это демонстрируется в приведенном ниже примере, в котором показан выход простого скрипта at:
michel ~> date Fri Jan 24 11:05:50 CET 2003 michel ~> at 1107 warning: commands will be executed using (in order) a) $SHELL b) login shell c)/bin/sh at> ls -l /proc/self/fd/ > /var/tmp/fdtest.at at> <EOT> job 10 at 2003-01-24 11:07 michel ~> cat /var/tmp/fdtest.at total 0 lr-x------ 1 michel michel 64 Jan 24 11:07 0 -> /var/spool/at/!0000c010959eb (deleted) l-wx------ 1 michel michel 64 Jan 24 11:07 1 -> /var/tmp/fdtest.at l-wx------ 1 michel michel 64 Jan 24 11:07 2 -> /var/spool/at/spool/a0000c010959eb lr-x------ 1 michel michel 64 Jan 24 11:07 3 -> /proc/21949/fd
И с cron:
michel ~> crontab -l # DO NOT EDIT THIS FILE - edit the master and reinstall. # (/tmp/crontab.21968 installed on Fri Jan 24 11:30:41 2003) # (Cron version -- $Id: chap8.xml,v 1.9 2006/09/28 09:42:45 tille Exp $) 32 11 * * * ls -l /proc/self/fd/ > /var/tmp/fdtest.cron michel ~> cat /var/tmp/fdtest.cron total 0 lr-x------ 1 michel michel 64 Jan 24 11:32 0 -> pipe:[124440] l-wx------ 1 michel michel 64 Jan 24 11:32 1 -> /var/tmp/fdtest.cron l-wx------ 1 michel michel 64 Jan 24 11:32 2 -> pipe:[124441] lr-x------ 1 michel michel 64 Jan 24 11:32 3 -> /proc/21974/fd
Перенаправление ошибок
Из предыдущих примеров понятно, что вы можете для скрипта указать входной и выходной файлы (подробности смотрите в разделе "Ввод из файла и вывод в файл"), но некоторые могут забыть о перенаправлении ошибок - выход, который может позже потребоваться. К тому же, если повезет, ошибки будут отсылаться вам по почте и, возможно, удастся выявить возможные причины ошибок. Если вы не настолько удачный, ошибки могут привести к отказу в работе скрипта, а обнаружить и отослать ошибку не удастся, из-за чего вы не сможете выполнить какую-либо отладку.
Обратите внимание, что при перенаправлении ошибок важен порядок выполнения действий. Например, следующая команда, которая есть в /var/spool
ls -l * 2> /var/tmp/unaccessible-in-spool
будет перенаправлять стандартный выходной поток команды ls в файл unaccessible-in-spool
, находящийся в /var/tmp
. Команда
ls -l * > /var/tmp/spoollist 2>&1
будет перенаправлять стандартный ввод и стандартный поток ошибок в spoollist
. Команда
ls -l * 2 >& 1 > /var/tmp/spoollist
перенаправляет в файл только стандартный вывод, поскольку перед тем, как стандартный вывод будет перенаправлен, стандартный поток ошибок копируется в стандартный вывод.
Для удобства, ошибки, если есть уверенность в том, что они не нужны, часто перенаправляются в /dev/null
. Среди скриптов запуска, имеющихся в вашей системе, можно найти сотни примеров.
Bash позволяет перенаправить в файл как стандартный вывод, так и стандартный вывод ошибок; имя файла FILE
будет подставлено в следующую конструкцию:
&> FILE
Это эквивалентно конструкции > FILE
2>&1, которая использовалась в предыдущих примерах. Кроме того, она объединяется с перенаправлением в /dev/null
, например, когда вы просто хотите выполнять команду, независимо от того, будут ошибки или нет.
Ввод из файла и вывод в файл
Использование /dev/fd
В директории /dev/fd
находятся записи с именами 0
, 1
, 2
и так далее. Открытие файла /dev/fd/N
эквивалентно дублированию дескриптора файла N. Если в вашей системе есть /dev/stdin
, /dev/stdout
и /dev/stderr
, вы сможете увидеть, что они, соответственно, эквивалентны /dev/fd/0
, /dev/fd/1
и /dev/fd/2
.
Файлы директория /dev/fd
используются, главным образом, из командной оболочки. Этот механизм позволяет программам, которые используют пути к файлам в качестве аргументов, обрабатывать стандартный вход и стандартный выход точно также, как и пути к другим файлам. Если в системе нет директория /dev/fd
, вам потребуется искать способ обойти проблему. Это можно сделать, например, при помощи дефиса (-), указывающего, что программа должна читать из конвейера. Например:
michel ~> filter body.txt.gz | cat header.txt - footer.txt This text is printed at the beginning of each print job and thanks the sysadmin for setting us up such a great printing infrastructure. Text to be filtered. This text is printed at the end of each print job.
Команда cat сначала читает файл header.txt
, затем свой собственный стандартный ввод, который является выходом команды filter, и, затем, файл footer.txt. Во многих программах не учитывается особое свойство дефиса, который, когда он используется в качестве аргумента командной строки, является ссылкой на стандартный ввод или стандартный вывод. Проблемы также могут возникнуть, если дефис используется в качестве первого аргумента, т. к. он может рассматриваться как параметр предыдущей команды. Использование /dev/fd
ведет к единообразию и предотвращает путаницу:
michel ~> filter body.txt | cat header.txt /dev/fd/0 footer.txt | lp
В этом примере все выходные данные дополнительно через конвейер направляются в команду lp, которая отправляет их на используемый по умолчанию принтер.
Read и exec
Назначение файлам дескрипторов
Другой взгляд на файловые дескрипторы - рассматривать их как способ назначить файлу числовое значение. Вместо того, чтобы пользоваться именем файла, вы можете пользоваться номером дескриптора файла. Для замены текущего процесса или для изменения дескриптора файла можно пользоваться встроенной командой exec. Например, ею можно пользоваться при назначении файлу дескриптора. Используйте команду
exec fdN> file
для назначения дескриптора N выходному файлу file
и
exec fdN< file
для назначения дескриптора N входному файлу file
. После назначения файлу дескриптора, дескриптор можно использовать с командами перенаправления так, как это показано в следующем примере:
michel ~> exec 4> result.txt michel ~> filter body.txt | cat header.txt /dev/fd/0 footer.txt >& 4 michel ~> cat result.txt This text is printed at the beginning of each print job and thanks the sysadmin for setting us up such a great printing infrastructure. Text to be filtered. This text is printed at the end of each print job.
Дескриптор файла 5 | |
Использование этого дескриптора файла может быть причиной проблем, смотрите главу 16 в книге "Advanced Bash-Scripting Guide" ("Искусство программирования на языке сценариев командной оболочки"). Мы настоятельно рекомендуем его не использовать. |
Команда read в скриптах
Ниже приведен пример, в котором показано, каким образом можно чередовать ввод данных из файла и из командной строки:
michel ~/testdir> cat sysnotes.sh #!/bin/bash # This script makes an index of important config files, puts them together in # a backup file and allows for adding comment for each file. CONFIG=/var/tmp/sysconfig.out rm "$CONFIG" 2>/dev/null echo "Output will be saved in $CONFIG." # create fd 7 with same target as fd 0 (save stdin "value") exec 7<&0 # update fd 0 to target file /etc/passwd exec < /etc/passwd # Read the first line of /etc/passwd read rootpasswd echo "Saving root account info..." echo "Your root account info:" >> "$CONFIG" echo $rootpasswd >> "$CONFIG" # update fd 0 to target fd 7 target (old fd 0 target); delete fd 7 exec 0<&7 7<&- echo -n "Enter comment or [ENTER] for no comment: " read comment; echo $comment >> "$CONFIG" echo "Saving hosts information..." # first prepare a hosts file not containing any comments TEMP="/var/tmp/hosts.tmp" cat /etc/hosts | grep -v "^#" > "$TEMP" exec 7<&0 exec < "$TEMP" read ip1 name1 alias1 read ip2 name2 alias2 echo "Your local host configuration:" >> "$CONFIG" echo "$ip1 $name1 $alias1" >> "$CONFIG" echo "$ip2 $name2 $alias2" >> "$CONFIG" exec 0<&7 7<&- echo -n "Enter comment or [ENTER] for no comment: " read comment; echo $comment >> "$CONFIG" rm "$TEMP" michel ~/testdir> sysnotes.sh Output will be saved in /var/tmp/sysconfig.out. Saving root account info... Enter comment or [ENTER] for no comment: hint for password: blue lagoon Saving hosts information... Enter comment or [ENTER] for no comment: in central DNS michel ~/testdir> cat /var/tmp/sysconfig.out Your root account info: root:x:0:0:root:/root:/bin/bash hint for password: blue lagoon Your local host configuration: 127.0.0.1 localhost.localdomain localhost 192.168.42.1 tintagel.kingarthur.com tintagel in central DNS
Закрытие дескрипторов файла
Поскольку дескрипторы открытых файлов наследуются в дочерних процессах, на практике важно закрывать дескриптор файла, когда он больше не нужен. Это делается следующим образом
exec fd<&-
В приведенном выше примере, дескриптор файла 7, который был назначен стандартному вводу, закрывается каждый раз, когда пользователю необходим доступ к реальному устройству стандартного ввода, как правило, к клавиатуре.
Ниже приводится простой пример перенаправления в конвейер только стандартного потока ошибок:
michel ~> cat listdirs.sh #!/bin/bash # This script prints standard output unchanged, while standard error is # redirected for processing by awk. INPUTDIR="$1" # fd 6 targets fd 1 target (console out) in current shell exec 6>&1 # fd 1 targets pipe, fd 2 targets fd 1 target (pipe), # fd 1 targets fd 6 target (console out), fd 6 closed, execute ls ls "$INPUTDIR"/* 2>&1 >&6 6>&- \ # Closes fd 6 for awk, but not for ls. | awk 'BEGIN { FS=":" } { print "YOU HAVE NO ACCESS TO" $2 }' 6>&- # fd 6 closed for current shell exec 6>&-
Встраиваемые документы (Here documents)
Часто ваш скрипт может вызывать другую программу или скрипт, в которых нужно вводит данные. С помощью встраиваемых документов (here documents) можно указать командной оболочке считывать входные данные из некоторого источника до тех пор, пока в нем не будет найдена строка, содержащая определенную подстроку (ею не могут быть завершающие пробелы). Затем все строки, считанные до этого места, будут использованы в вызванной программе или скрипте в качестве стандартного входного потока.
В результате вам не потребуется вызывать файлы по отдельности; вы можете пользоваться специальными символами командной оболочки и это будет выглядеть гораздо лучше, чем куча команд echo:
michel ~> cat startsurf.sh #!/bin/bash # This script provides an easy way for users to choose between browsers. echo "These are the web browsers on this system:" # Start here document cat < BROWSERS mozilla links lynx konqueror opera netscape BROWSERS # End here document echo -n "Which is your favorite? " read browser echo "Starting $browser, please wait..." $browser & michel ~> startsurf.sh These are the web browsers on this system: mozilla links lynx konqueror opera netscape Which is your favorite? opera Starting opera, please wait...
Хотя здесь мы говорим о встраиваемом документе, предполагается, что он находится внутри того же самого скрипта. Приведенный ниже пример выполняет автоматическую установку пакета даже в том случае, когда от вас требуется подтверждать установку:
#!/bin/bash # This script installs packages automatically, using yum. if [ $# -lt 1 ]; then echo "Usage: $0 package." exit 1 fi yum install $1 < CONFIRM y CONFIRM
И о том, как работает скрипт: когда выдается строка "Is this ok [y/N]" ("Это правильно [y/N]"), скрипт автоматически отвечает "y":
[root@picon bin]# ./install.sh tuxracer Gathering header information file(s) from server(s) Server: Fedora Linux 2 - i386 - core Server: Fedora Linux 2 - i386 - freshrpms Server: JPackage 1.5 for Fedora Core 2 Server: JPackage 1.5, generic Server: Fedora Linux 2 - i386 - updates Finding updated packages Downloading needed headers Resolving dependencies Dependencies resolved I will do the following: [install: tuxracer 0.61-26.i386] Is this ok [y/N]: EnterDownloading Packages Running test transaction: Test transaction complete, Success! tuxracer 100 % done 1/1 Installed: tuxracer 0.61-26.i386 Transaction(s) Complete
Предыдущий раздел: | Оглавление | Следующий раздел: |
Отображение пользовательских сообщений | Подводим итоги главы 8 |