Perl: Вызовы функций, преобразования типов в выражениях, хеши, ссылки.

Оба варианта — print и printf — принимают произвольное количество аргументов, которые разделяются запятыми. Но вот вы видите join (...). Не напоминает ли вам это вызов функции? Чем же "этот join" отличается от print и printf?

По сути, ничем, print, printf и join — это обычные функции. Perl позволяет опускать круглые скобки в вызовах функций, если эта "экономия" не послужит причиной неоднозначности, поэтому обе формы приемлемы. В приведенной выше print-строке форма записи со скобками (join (...)) используется для явного отделения аргументов, передаваемых функции join, от аргументов, передаваемых функции print.

Выражение @ items [0,1] должно дать "списочный" результат, поскольку оно начинается с символа "0". В действительности это выражение означает "кусочек массива" (или подмассив), а список индексов 0,1 перечисляет индексы элементов исходного массива, которые должны быть включены в упомянутый "кусочек". Здесь Perl принимает диапазон значений, как и в эквивалентном выражении @items [ 0… 1 ]. Здесь также было бы допустимо использовать одиночный числовой индекс: например, выражение @items [0] означало бы список, содержащий один скаляр, т.е. строку "носки", что в данном случае было бы эквивалентно литералу ("носки").

Массивы в вызовах функций автоматически раскрываются, поэтому в выражении

join(" и ",  @items[0,1])

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

 

Преобразования типов в выражениях

В printf-строке при вычислении выражения $# items + 1 получим число 3. По определению выражение $# items содержит числовое значение, но это не дает основания считать, что выражение $#items + 1 предполагает выполнение арифметического действия; смешанный вариант "2" + 1 работает так же. "Магия" кроется в операторе "+", который всегда подразумевает арифметику, т.е. он преобразует свои аргументы в числа и генерирует числовой результат. Аналогично ему оператор "точка" (.), назначение которого — конкатенировать строки, преобразует свои операнды "по контексту", т.е. при вычислении выражения "2"   .    (12   **   2) получим в результате "2144".

 

Раскрытие строк и устранение неоднозначности при ссылках на переменные

Как и в командной оболочке bash, строки, заключенные в двойные кавычки, являются объектами для раскрытия переменных. Так же, как и в bash, вы можете заключить имена переменных в фигурные скобки, чтобы при необходимости устранить неоднозначность, как в выражении $ {items [2 ]}. (В данном случае фигурные скобки используются только для иллюстрации; на самом деле они не нужны.) Символ "$" информирует о том, что результат вычисления выражения должен быть скалярным. Переменная items определяет массив, но его любой элемент в отдельности — это скаляр, и сей факт отражен в соглашениях о присваивании имен переменным.

 

Хеши

Хеш (также именуемый ассоциативным массивом или хеш-массивом) представляет набор пар "ключ/значение". Хеш можно представить себе как массив, индексы, которого (ключи) являются произвольными скалярными значениями, т.е. они необязательно должны быть числами. Но на практике в качестве ключей используются именно числа и строки.

Имена хеш-переменных начинаются с символа "%" (например, %myhash), но их отдельными значениями являются скаляры, поэтому для работы с хеш-массивами нужно, как и в обычных массивах, использовать префикс разыменования "$". Индексация обозначается фигурными скобками, а не квадратными, например $myhash {‘ ron ‘}.

Для системных администраторов хеши — это очень важный инструмент, который вы будете использовать практически в каждом сценарии. В приведенном ниже коде мы считываем содержимое файла, анализируем его в соответствии с правилами, заданными структурой файла /etc/passwd, и формируем хеш с именем %names_by_uid. Значением каждого элемента в хеше становится имя пользователя, связанное с идентификатором UID.

#!/usr/bin/perl

while ($_ = <>) {

($name, $pw, $uid, $gid, $gecos, $path, $sh) = split /:/;

$names_by_uid{$uid} = $name; } %uids_by_name = reverse %names_by_uid;

print n\$names_by_uid{0} is $names_by_uid{0}\n";

print "\$uids_by_name{1root'} is $uids_by_name{'root'}\n";

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

$ peri hashexaxnple /etc/passwd

$names_by_uid{0}  is root $uids_by_name{'root' }  is 0

В каждом проходе цикла while ($_ = о) считывается одна входная строка, которая присваивается переменной $_ (напомним: значение всего оператора присваивания, как в языке С, определяется значением его правой части). При достижении конца входного потока, оператор о возвратит значение "Ложь", что послужит причиной завершения цикла.

Интерпретируя оператор <>, Perl проверяет командную строку, чтобы узнать, указаны ли там какие-нибудь имена файлов. Если вы не забыли это сделать, Perl открывает каждый файл по очереди и "прогоняет" его содержимое через цикл. Если вы не назвали ни одного файла в командной строке, Perl возьмет входные данные для циклической обработки из стандартного входного канала.

Внутри цикла указанные в скобках переменные получают значения, возвращаемые функцией split, которая "разрезает" входную строку, используя регулярное выражение, переданное ей в качестве разделителя полей. Здесь наш шаблон ограничивается слешами (символами "/"), действие которых подобно двойным кавычкам. Мы могли бы с таким же успехом написать split   ':1 или split   ":".

Строка, которую функция split должна разбить на подстроки в позициях сопоставления с двоеточиями, нигде явно не задана. Если второй аргумент функции split, т.е. подлежащая разбиению строка, опущен, Perl предполагает, что вы хотите разбить на подстроки значение $_. Яснее не бывает! По правде говоря, иногда можно опустить даже шаблон — в этом случае разбиение строки производится по пробельному символу, при этом начальные пробелы в каждой подстроке игнорируются.

Но и это еще не все. Даже исходное присваивание переменной $_ в первой строке цикла необязательно. Если просто написать так:

while  (о)   {,

то Perl автоматически сохранит каждую входную строку в переменной $_. Можно обрабатывать строки без создания явной ссылки на переменную, в которой они будут сохраняться. Использование переменной $_ в качестве операнда, действующего по умолчанию, — распространенная практика, и Perl позволяет такие "вольности" везде, где это имеет смысл.

Во множественном присваивании, которое "захватывает" содержимое каждого поля passwd, наличие списка в левой части

($name, $pw, $uid, $gid, $gecos, $path, $sh) = split /:/;

создает для функции split "контекст списка", что предписывает ей возвращать в результате список всех полей. Если бы в присваивании участвовала скалярная переменная, например

$n_fields = split /:/;,

функция split "переключилась" бы на "скалярный контекст" и вернула бы только количество обнаруженных полей. Пользовательские функции также могут отличать скаляры от списочного контекста с помощью функции wantarray. Она возвращает значение "Истина" в контексте списка, "Ложь" — в контексте скаляра и неопределенное значение — в void-контексте (для пустых типов данных). Строка

%uids_by_name = reverse %names_by_uid;

также заслуживает внимания. Хеш в контексте списка (здесь в качестве аргумента функции reverse) приводится к списку в форме (keyl, valuel, кеу2, value2, ...). Функция reverse меняет порядок следования элементов в списке на обратный: (valueN, keyN, ..., valuel, keyl). Наконец, присваивание хеш-переменной %uids_by_name преобразует этот список в вариант {keyl, valuel, ...), создавая тем самым перестановочный индекс.

Ссылки и их самооживление

Обозначенная в заголовке тема относится к более сложным, но мы посчитали себя не вправе обойти ее молчанием. По крайней мере, мы должны изложить здесь основные положения. Массивы и хеши могут хранить скалярные значения, но не исключено, что вам понадобится сохранить в них другие массивы и хеши. Например, возвращаясь к нашему предыдущему примеру анализа файла /etc/passwd, вы могли бы сохранить все поля каждой строки passwd в хеше, индексируемом значением идентификатора UID.

Если нельзя хранить массивы и хеши, то можно хранить ссылки (указатели) на массивы и хеши, которые сами по себе являются скалярами. Чтобы создать ссылку на массив или хеш, необходимо предварить имя соответствующей переменной обратной косой чертой (например, \@аггау) или использовать литеральный синтаксис ссылок на массив или на хеш. Например, наш цикл passwd-анализа мог выглядеть так.

while (о) {

$array_ref = [ split /:/ ]; $passwd_by_uid{$array_ref->[2]} = $array_ref;

}

Квадратные скобки возвращают ссылку на массив, содеращий результаты работы функции split. Выражение $array_ref-> [2 ] ссылается на поле UID (это третий член массива, адресуемого переменной $array_ref).

Выражение $array_ref [2 ] здесь не работает, поскольку мы не определили массив @array_ref; $array_ref и @array_ref — это разные переменные. Хуже того, вы не получите сообщение об ошибке, если ошибочно использовали здесь выражение $аг-rayref [2], поскольку @array_ref — это абсолютно легитимное имя для массива; вы ведь не присваивали ему никаких значений.

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

Комментарии (0)

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.