Операции ввода-вывода

Теперь, познакомившись с тем, как файлы через создаваемые дескрипторы становятся доступными сценарию Perl, мы перейдем к изучению основных операций ввода-вывода данных: заключение в обратные кавычки строк данных, операции «ромб» (<>) ввода информации из стандартного устройства ввода и внешних файлов и операции печати print на стандартное устройство вывода и в файлы. Мы уже знакомы с этими операциями, но дополним и систематизируем наши о них знания в свете работы с файлами через их дескрипторы. Это утверждение, собственно говоря, относится к двум последним перечисленным операциям, а первая операция — выполнение и получение результатов команды операционной системы — рассматривается нами здесь всего лишь для полноты изложения средств ввода-вывода в языке Perl.

Выполнение системных команд

Мы знаем, что заключенная в обратные кавычки (`) строка символов является всего лишь удобной формой записи операции qx{ } ввода в сценарий результатов выполнения команды операционной системы. Поэтому все, что в этом разделе будет сказано относительно операции заключения в обратные кавычки, будет относиться и к обобщенной форме синтаксиса этой операции — qx{ }.
Когда интерпретатор Perl встречает строковый литерал в обратных кавычках, то после подстановки в него в случае необходимости значений скалярных переменных и элементов массивов интерпретатор передает получившуюся строку как команду на выполнение операционной системе. Отображаемая этой командой на стандартном устройстве вывода информация (если бы эта команда выполнялась из командной строки операционной системы) сохраняется в строковом литерале. В связи с таким «поведением» строкового литерала в обратных кавычках его иногда называют псевдолитералом, так как его содержимое при выполнении программы является на самом деле не тем содержимым, которое мы видим в тексте программы, — оно заменяется результатом выполнения команды операционной системы, заданной в этом литерале.
Операция заключения строки символов в обратные кавычки различает скалярный и списковый контексты, в которых она может выполняться. В скалярном контексте возвращается одна строка, содержащая весь вывод на экран монитора выполненной команды, включая символы новой строки в случае многострочного вывода. В списковом контексте возвращается список значений. Каждый элемент списка содержит одну строку вывода выполненной команды операционной системы. Листинг 6.1 демонстрирует использование операции заключения в обратные кавычки в соответствующих контекстах.
Листинг 6.1. Операция заключения в обратные кавычки в скалярном и списковом контекстах
#! perl -w
$command = "dir"; # Печать содержимого текущего
                  # каталога в ОС Windows 
$scalar = ‘$command‘; # Скалярный контекст.
@list = ‘$command‘;   # Списковый контекст, 
print "Содержимое скалярной переменной\n"; 
print "-" х 80, $scalar; 
print "-" х 80;
print "Содержимое первых двух элементов массива\n";
print "-" х 80, $list[0], $list[1];
print "-" х 80;
При выполнении операции заключения в кавычки сначала осуществляется подстановка значения скалярной переменной $command, а потом полученная строка передается на выполнение операционной системе. Переменная $scalar (скалярный контекст) содержит весь вывод на экран монитора содержимого текущего каталога, поэтому при ее печати мы увидим все, что вывела бы команда dir, будучи выполненной из командной строки. Когда результаты выполнения этой же команды присваиваются массиву @list (списковый контекст), то каждая строка вывода команды становится элементом массива, поэтому последний оператор печати из листинга 6.1 выводит первую и вторую строки (при выполнении этого примера следует учесть, что первая строка вывода команды dir пустая). Результат выполнения программы из листинга 6.1 в операционной системе Windows 98 русифицированной версии может выглядеть так:
Содержимое скалярной переменной:
-----------------------------------------------
Том в устройстве С имеет метку SYSTEM
Серийный номер тома:  1D26-1BFC
Содержимое папки C:\01d_D\My Books\Perl Manual\ch06
.             <ПАПКА>  13.05.01   10:38.
. .           <ПАПКА>  13.05.01   10:38..
CH06 DOC 236 544   11.07.01 20:56 ch06.doc
EX06-01 PL   404   11.07.01 21:26 ex06-01.pl
F    F        33   11.07.01 13:46 f.f
3 файлов	236 981 Байт
2 папок	12 938.20 МБ свободно
-----------------------------------------------
Содержимое первых двух элементов массива:
-----------------------------------------------
Том в устройстве С имеет метку SYSTEM
-----------------------------------------------
В списковом контексте разбиение вывода команды операционной системы на элементы списка осуществляется в соответствии со значением специальной переменной $/, которое используется в качестве разделителя. По умолчанию эта переменная содержит символ конца строки \n — поэтому и разбиение на элементы происходит по строкам. Присваивая этой переменной новое значение, мы тем самым определяем новое значение разделителя, которое будет использоваться при формировании элементов списка. Разделителем может быть любая последовательность символов.

Операция «ромб»

Унарная операция <> многофункциональна, но ее основное предназначение — прочитать одну строку или все строки файла, дескриптор которого представляет операнд этой операции. Заметим, что, в отличие от других операций Perl, операнд этой операции расположен внутри угловых скобок.
Что будет прочитано этой операцией, зависит от контекста ее использования. Например, если мы хотим прочитать первую строку из файла, нам следует использовать эту операцию в скалярном контексте:
open (FH, "<", "file.dat"); # Открыли файл и создали
                            # для него дескриптор FH
$line = <FH>                # Использовали дескриптор файла
                            # для чтения первой строки
В этом фрагменте, так как в операции open не указано полное имя файла (путь и имя), Perl считает, что файл расположен в том же каталоге, в котором находится и файл самой программы, если только операцией chdir( ) другой каталог не сделан рабочим. При использовании операции <> следует учитывать, что строка файла читается полностью вместе с завершающим ее символом новой строки \n. При следующем выполнении в программе операции чтения из этого же файла в скалярном контексте будет прочитана следующая строка и т. д., пока не дойдем до конца файла. В этом случае операция вернет неопределенное значение, которое можно проверить функцией defined( ). (На самом деле при чтении из файла для этих целей используется специальная функция eof( ), определяющая достижение конца файла при последовательном чтении его строк, но об этом чуть позже.)
ВНИМАНИЕ. При чтении строки из стандартного устройства ввода или из файла операцией <> следует помнить, что завершающий символ перехода на новую строку также будет прочитан.
Итак, мы знаем, что операция <> в скалярном контексте читает одну строку файла. А что происходит, когда она используется в списковом контексте? В списковом контексте эта же операция прочитает все строки файла. Если в предыдущем фрагменте вместо скалярной переменной $line, создающей скалярный контекст для правого операнда операции присваивания, использовать массив скаляров @line, создающий списковый контекст для правого операнда операции присваивания, то результатом его выполнения будет чтение всех строк файла, причем все его строки будут последовательно сохранены в элементах указанного массива:
@line = <FH>;  # Читаем сразу все строки файла
Эта же операция может быть использована и с пустым операндом <>. В этом случае читается строка или из стандартного устройства ввода (предопределенный дескриптор файла STDIN), или из файла, имя которого задано в качестве параметра программы Perl при ее запуске из командной строки операционной системы.
При запуске программы Perl в системе UNIX или из командной строки Windows в нее можно передать параметры, задаваемые после имени файла, содержащего программу Perl, и отделяемые от него и друг от друга пробелами. Например, при следующем запуске программы program.pl из командной строки в нее передаются три параметра — par1, раr2 и рar3:
perl program.pl par1 раr2 раr3
Параметрами могут быть ключи (обычно символ с лидирующим дефисом, например -v), которые устанавливают определенные режимы работы программы, или имена файлов, содержимое которых должна обработать программа. Все передаваемые в программу параметры сохраняются в специальном встроенном массиве @ARGV. Если не передается ни одного параметра, то этот массив пуст.
Операция <> без операнда при первом своем вычислении проверяет, пуст ли массив @ARGV. Если он пуст, то программа переходит в режим ожидания ввода пользователем из стандартного устройства ввода (дескриптор файла STDIN). Если массив @ARGV не пуст, то его элементы содержат параметры, переданные программе при ее запуске. В этом случае операция <> трактует каждый из них как имя файла и считает, что строки этих файлов и есть то, что пользователь как бы «вводит» на стандартном устройстве ввода. Примененная в выражении-условии в циклах whilе/until или for, эта операция передает в программу последовательно все строки всех файлов, указанных в командной строке. Рассмотрим простейшую программу (листинг 6.2), состоящую из одного цикла while с операцией <>, и проанализируем ее поведение при разных способах запуска.
Листинг 6.2. Применение операции <> без операнда
#! perl -w
while ($line = <>) {
  print $line;
}
При запуске без параметров она будет ожидать ввода с клавиатуры и в цикле распечатывать вводимые строки, пока пользователь не завершит ввод комбинацией клавиш Ctrl+Z, интерпретируемой как конец файла.
При выполнении операции <> ввода строк (в цикле или нет, из файла или из стандартного устройства ввода) в специальной переменной $. хранится порядковый номер прочитанной строки, причем нумерация строк для каждого файла, включая файл STDIN, своя. В случае задания нескольких имен файлов в командной строке при последовательном вводе их строк операцией <> эта переменная продолжает увеличивать свое значение при переходе на чтение строк очередного файла, так как в этом случае содержимое всех файлов рассматривается как содержимое одного файла с дескриптором STDIN. Листинг 6.3 демонстрирует чтение из разных файлов и нумерацию прочитанных из них строк, сохраняемую в специальной переменной $.
Листинг 6.3. Применение операции <>
#!perl  -w
open FH, "<", "f.f";
$l = <FH>;
print "Файл FH: строка $.: $l";
$l = <>;
print "Файл STDIN: строка $.:  $l";
$l = <FH>;
print "Файл FH: строка $.:   $l";
$l = <>;
print "Файл STDIN: строка $.:  $l";
close FH;
Вывод этой программы, которая вперемежку читает строки из файла f.f и вводимые пользователем на стандартном устройстве ввода, представлен ниже (полужирным шрифтом выделены вводимые пользователем строки):
Файл FH: строка 1:  line 1
клавиатура 1
Файл STDIN: строка 1:  клавиатура 1
Файл FH:  строка 2:  line 2
клавиатура 2
Файл STDIN: строка 2: клавиатура 2
Обратите внимание, что для каждого файла сохраняется своя нумерация введенных строк.
Операцию <> и массив @ARGV можно совместно использовать для ввода в программу содержимого нескольких файлов, не связывая передачу их имен в качестве параметров сценария при запуске его из командной строки. В любом месте программы перед первым использованием в программе (в цикле или нет) операции ввода <> можно в массив @ARGV занести имена файлов, содержимое которых необходимо обработать как ввод со стандартного устройства ввода:
@ARGV = ("file1.dat", "file2.dat", "file3.dat");
for (;<>;) {
  Операторы обработки строк файлов 
}
Этот фрагмент программы в цикле for последовательно обработает строки трех файлов: file1.dat, file2.dat и file3.dat.
Обратите внимание, что в этом фрагменте, в отличие от всех предыдущих случаев использования нами операции <>, мы не присваиваем никакой переменной результатов выполнения операции ввода строки. Каким же образом можно в теле цикла обрабатывать введенные строки? Дело в том, что результат ее выполнения, то есть введенная строка, сохраняется также в специальной встроенной переменной $_. Например, цикл while программы из листинга 6.2 можно записать итак:
while (<>) {
  print; 
}
Здесь также используется то обстоятельство, что функция print без параметров по умолчанию выводит содержимое переменной $_.
Если мы хотим передать в программу некоторые ключи (обычно символ с предшествующим дефисом), с помощью которых устанавливается режим ее работы, то в начале программы следует поместить цикл, который проверяет содержимое массива @ARGV на наличие ключей в командной строке вызова программы. Один из способов подобной проверки приводится в листинге 6.4, где предполагается, что программе могут быть переданы ключи -d, -s и -е.
Листинг 6.4. Проверка наличия ключей, переданных в программу
#! perl -w
while ($_ = $ARGV[0], /^-/) { 
  if(/^-d/ || /^-s/ || /^-е/) {
    print $ARGV[0], "\n";
  }
  else {
    print "Неизвестный ключ $ARGV[0]\n";
  } 
  shift;
}
При вычислении выражения условия цикла while осуществляется присваивание переменной $_ значения первого элемента массива @ARGV и проверка наличия дефиса (-) в качестве первого символа содержимого этой переменной (операция /^-/). Оператор if проверяет содержимое переменной $_ на соответствие известным ключам и отображает их. Если в программу переданы неизвестные ключи, то печатается соответствующее сообщение. (В реальных программах используется множественный оператор if, в каждом блоке которого в соответствии с определенным ключом осуществляются некоторые предопределенные ключом действия.) Функция shift удаляет из массива @ARGV первое значение, сдвигая оставшиеся в нем элементы на одну позицию влево: второй становится первым, третий вторым и т. д. Цикл повторяется до тех пор, пока переданные через командную строку параметры начинаются с дефиса.
ВНИМАНИЕ Программа из листинга 6.4 работает правильно, но в случае, если в нее передаются только ключи, появится предупреждающее сообщение о том, что в выражении-условии цикла в операции поиска по образцу переменная не инициализирована. Это связано с тем, что при отсутствии других параметров, задаваемых после ключей, после операции сдвига элементов массива @ARGV в нем не будет содержаться ни одного элемента — он просто будет пуст.
Еще одно применение операции <> связано с получением в программе имен файлов определенного каталога, удовлетворяющих заданному шаблону. Если в качестве операнда этой операции используется шаблон имен файлов, то в скалярном контексте она возвращает первое найденное имя файла в текущем каталоге, в списковом контексте — список имен файлов, удовлетворяющих заданному шаблону. (В шаблоне можно использовать метасимволы: * — для произвольной цепочки символов, включая пустую, ? — для произвольного одиночного символа.) Если в каталоге не найдены файлы с именами, удовлетворяющими шаблону, то операция возвращает неопределенное значение. Например, выполнение следующей операции:
$first = <*.pl>;
приведет к сохранению в переменной $first имени первого файла из списка всех файлов текущего каталога с расширением pl, если таковые файлы в каталоге есть, иначе эта переменная будет иметь неопределенное значение. В списке файлы упорядочены в алфавитном порядке.
Эта же операция в списковом контексте
@files = <*.pl>;
возвращает список всех файлов с расширением pl. После выполнения этой операции элементы массива @files содержат имена всех файлов с расширением pl.
ПРИМЕЧАНИЕ Имена подкаталогов текущего каталога считаются файлами без расширения. Например, в возвращаемом операцией <*.*> списке файлов будут содержаться и имена подкаталогов текущего каталога.
Если при задании шаблона файла явно указать каталог, то эта операция возвратит список файлов из указанного каталога, имена которых удовлетворяют заданному шаблону. Например, операция
@files = </perl/*.pl>;
сохранит в массиве @files имена всех файлов каталога /perl с расширением pl.
ПРИМЕЧАНИЕ В системе Windows эта операция найдет все файлы с расширением рl в каталоге /perl текущего диска. Для задания конкретного диска следует использовать принятый в Windows синтаксис для полного имени файла; <d:/perl/*.*>. Эта операция возвратит список всех файлов каталога /perl, расположенного на диске d:.
При использовании этой операции в выражении условия цикла while или for она последовательно на каждом шаге цикла возвращает очередное имя файла, удовлетворяющее заданному шаблону:
while ($file = <*.pl>) {
  print "$file\n";
}
ПРИМЕЧАНИЕ Операция получения имен файлов, соответствующих заданному шаблону, реализуется с помощью внутренней функции glob, единственным параметром которой является шаблон имен файлов. Эту функцию можно использовать самостоятельно для получения соответствующих имен файлов: @scripts=glob "*.pl";, В скалярном контексте она возвращает имя первого файла, удовлетворяющего заданному шаблону, в списковом — список имен всех файлов. Употребленная без параметра, она использует в качестве параметра специальную переменную $_.

Вывод информации функцией print

Наиболее часто для вывода информации из сценария Perl используется функция print. Синтаксис ее вызова имеет следующий вид:
print ДЕСКРИПТОР СПИСОК;
Здесь ДЕСКРИПТОР — это дескриптор файла, в который функция выводит строковые данные, представленные списком вывода СПИСОК. Дескриптор может состоять из переменных, элементов массивов и выражений, вычисляемых как строковые данные. Дескриптор файла может быть опущен, и в этом случае вывод осуществляется на стандартное устройство вывода, ассоциированное с дескриптором STDOUT. Как уже отмечалось ранее, стандартное устройство вывода обычно представляет экран монитора компьютера.
ВНИМАНИЕ Дескриптор файла в функции print отделяется от списка выводимых элементов пробелом, а не запятой. Типичная ошибка — отделить его запятой от выводимых элементов.
Функция print при выводе своего списка не завершает его символом новой строки \n. Это означает, что следующая функция print начнет вывод на экран или в файл непосредственно после последнего символа, выведенного предыдущей функцией print. Для того чтобы следующая в программе функция print начала вывод с новой строки файла или экрана монитора, следует список вывода каждой функции print явно завершать строкой, содержащей символ новой строки, или включать его последним символом последнего элемента списка вывода. Листинг 6.5 демонстрирует вывод с помощью функции print.
Листинг 6.5. Вывод функцией print
#! perl -w
open FILEOUT, ">", "file.dat"
print "String 1:";
print "String 2:\n";
print "String 3:", "\n";
print STDOUT "String 4:\n";
print FILEOUT "String 4:\n";
Вывод первых четырех функций print из листинга 6.5 на стандартное устройство вывода представлен ниже:
String l:String 2: String 3:
String 4:
Вторая функция print начинает свой вывод на той же строке, на которой завершила вывод первая функция, так как в ее списке вывода нет завершающего символа перехода на новую строку. В четвертой функции print явно указан дескриптор стандартного файла вывода STDOUT, поэтому она будет осуществлять также вывод на экран монитора. Относительно пятой функции скажем, что она запишет в файл file.dat, открытый для записи (второй параметр функции орел представлен символом >), единственную строку String 4;.
Функция print, как и большинство других функций, определенных в языке Perl, является списковой операцией, и все элементы списка вывода вычисляются в списковом контексте. Это обстоятельство следует учитывать при использовании в качестве элементов списка вывода выражений с вызовами функций.
Все, что было сказано относительно списковых операций и их использования в качестве термов выражений в главе 4, относится, естественно, и к функции print. Если ее параметры, включая дескриптор файла, заключены в круглые скобки, то такая синтаксическая конструкция считается термом и в выражении имеет наивысший приоритет вычисления. Например, следующий оператор:
print ($m + $n) ** 2;
напечатает сумму значений переменных $m и $n, а не их сумму, возведенную в квадрат. Компилятор perl, обнаружив после лексемы print левую круглую скобку, найдет правую круглую скобку и будет рассматривать содержащееся между ними как список параметров функции print. А так как такая конструкция есть терм, то сначала будет выполнена операция печати суммы значений переменных, а потом результат этой операции («истина», если вывод произойдет нормально, и «ложь» в противном случае) будет возведен в квадрат. Добавление необязательного дескриптора стандартного файла вывода STDOUT исправит подобную ошибку, так как теперь все, что расположено за дескриптором, рассматривается как список выводимых функцией print элементов:
print STDOUT ($m + $n) ** 2;  # Выведет ($m + $n) ** 2
Если в функции печати print не задан список вывода, то она по умолчанию выводит содержимое специальной переменной $_ в файл, определенный параметром ДЕСКРИПТОР; если не задан дескриптор файла, то вывод осуществляется на стандартное устройство вывода. В связи с перечисленными параметрами по умолчанию следующие операторы печати являются допустимыми операторами Perl:
print;         # Вывод переменной $_ на экран монитора
print STDOUT;  # Эквивалентен предыдущему оператору. 
print FILEOUT; # Вывод переменной $_ в файл,
               # ассоциированный с дескриптором FILEOUT

Следующая страница Содержание главы


Реклама