Использование UNIX: Переносимая оболочка и скриптовый язык Squirrel

Пишем объектно-ориентированные shell-скрипты для различных платформ

Если вам не нужны проблемы с запуском одинаковых скриптов на разных платформах, попробуйте Squirrel Shell. Оболочка Squirrel Shell содержит продвинутый, объектно-ориентированный скриптовый язык, одинаково хорошо работающий в операционных системах UNIX®, Linux®, Mac OS X™ и Windows®. Напишите скрипт один раз и используйте его повсюду.

Мартин Стрейчер (Martin Streicher), независимый web-разработчик, WSO2 Inc

Мартин Стрейчер (Martin Streicher) (Jeff J. Li) - фотографияМартин Стрейчер (Martin Streicher) - независимый web-разработчик и бывший главный редактор Linux Magazine. Он имеет степень магистра компьютерных наук Университета Пардью (Purdue University) и занимается программированием в UNIX-подобных операционных системах с 1986 года. Он коллекционирует предметы искусства и игрушки.


developerWorks Contributing author
        level

10.07.2009

В 1799 году французский военный инженер сделал замечательную находку. Нет, это были не фуа-гра, камамбер, пастеризация или труды Сартра – это был Розеттский камень, ставший ключом к расшифровке большинства древнеегипетских иероглифов (см. рисунок 1).

Рисунок 1. Розеттский камень, весом 760 килограмм, с надписью о налогах на трех языках. Надпись гласит о снижении налогов для духовенства.
Розеттский камень

Надпись на камне была сделана в 196 году до н. э. и содержала одинаковый текст на трех языках: древнеегипетскими иероглифами, демотическим письмом (сокращенное египетское письмо) и на древнегреческом. Благодаря методу сравнительного перевода - связывания фраз из разных переводов - Розеттский камень позволил раскрыть значение многих ранее нерасшифрованных иероглифов.

Другими словами, Розеттский камень можно считать онлайновым переводчиком весом в семь центнеров. Уже в 196 году до н. э. существовало множество способов сказать одно и то же.

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

В целом выбор – это хорошо, но в то же время он может обескураживать. Какое решение выбрать? Соответствует ли данная технология предъявляемым требованиям? Окупятся ли затраты и время? Не устареют ли эти аккуратно написанные иероглифы (или код на Perl)? И самое страшное: придется ли все это переводить (переписывать) для других сред?

Если вам не нужны проблемы с оболочками Fish, Bash, Z, интерпретатором командной строки ОС Windows cmd.exe или какими-нибудь другими скриптовыми языками, попробуйте Squirrel Shell. Squirrel Shell содержит продвинутый, объектно-ориентированный скриптовый язык, одинаково хорошо работающий в операционных системах UNIX®, Linux®, Mac OS X™ и Windows®. Напишите скрипт один раз и используйте его повсюду.

Более того, чтобы пользоваться им, не нужен камень в семь центнеров весом.

Знакомство со Squirrel

Оболочка Squirrel Shell общедоступна и бесплатна для использования в соответствии с условиями лицензии GNU Public License version 3 (GPLv3). Последняя версия 1.2.2 выпущена 11 октября 2008 года. Создателем и разработчиком Squirrel Shell является Константин "Dinosaur" Макшин.

На странице загрузки Squirrel Shell (см. ссылку в разделе Ресурсы) размещен исходный код и двоичные файлы для 32- и 64-разрядной версий Windows. Если вы пользуетесь одной из разновидностей UNIX или Linux, проверьте репозиторий вашего дистрибутива на наличие соответствующих двоичных файлов или соберите Squirrel Shell с нуля.

Сборка из исходного кода является простой и очевидной. Загрузите и распакуйте архив с исходным кодом, измените начальный каталог и введите заклинание сборки, показанное листинге 1.

Листинг 1. Сборка Squirrel Shell из исходного кода
$ ./configure --with-pcre=system && make && sudo make install
Checking CPU architecture...   x86
Checking for install...   /usr/bin/install
...
Configuration has been completed successfully.
   Build for x86 CPU architecture
   Installation prefix: /usr/local
   Allow debugging: no
   Build static libraries
   Use system PCRE 6.7 library
   Install MIME information: auto
   Create symbolic link: no
   Compile C code with 'gcc'
   Compile C++ code with 'g++'
   Create static libraries with 'ar rc'
   Create executables and shared libraries with 'g++'
   Install files with 'install'

Чтобы познакомиться со списком параметров настройки пакета, введите команду ./configure --help.

Для удобства Squirrel Shell укомплектована исходным кодом библиотеки Perl Compatible Regular Expression (PCRE), которая активно используется оболочкой. Если в системе отсутствует PCRE, ее можно легко и быстро собрать с помощью этого кода. Если PCRE уже имеется в системе, можно настроить ее использование при помощи параметра --with-pcre=system. В противном случае укажите параметр --with-pcre=auto, чтобы из двух копий библиотеки (системной или Squirrel Shell) использовать более новую.

Результатом сборки является новый двоичный файл с соответствующим именем squirrelsh. Предполагая, что программа установлена в каталог, перечисленный в переменной PATH, например /usr/local/bin, для запуска оболочки введите команду squirrelsh. Для вывода пути к домашнему каталогу введите в командной строке printl(getenv("HOME"));.

$ squirrelsh
> printl( getenv( "HOME" ) );
/home/strike
> exit();

Оболочка Squirrel Shell основана на языке программирования Squirrel (дополнительную информацию можно найти по ссылке в разделе Ресурсы). Этот язык подобен C++ и содержит функции, более напоминающие такие объектно-ориентированные скриптовые языки, как Python и Ruby. Оболочка Squirrel Shell объединяет в себе все функции и типы данных языка Squirrel и добавляет к ним ряд новых функций, созданных специально для решения типичных скриптовых задач, таких как копирование файлов и считывание переменных среды.

Хотя синтаксис Squirrel Shell слишком подробен для повседневного использования в командной строке, например, команда Bash echo $HOME в Squirrel Shell выглядит как printl( "~")—, он великолепно подходит для скриптов. Скрипт достаточно написать один раз, и он будет работать везде, - не надо писать код для UNIX и потом переписывать его для Windows. Как сказал о своей работе Dinosaur: «Squirrel Shell – это в первую очередь, интерпретатор скриптов».


Создание скриптов при помощи Squirrel

Давайте рассмотрим пример скрипта Squirrel Shell. В листинге 2 приведен скрипт listing2.nut, создающий рекурсивный список содержимого вашего домашнего каталога.

Листинг 2. Скрипт listing2.nut
#!/usr/bin/env squirrelsh

function reveal( filedir ) { 
  if ( !exist( filedir ) ) {
    return;
  }
    
  if ( filename( filedir ) == ".." || filename( filedir ) == "." ) {
    return;
  }
  		
  if ( filetype( filedir ) == FILE ) {
  	printl( filename( filedir, true ) );
  	return;
  }
  
  printl("directory: " + filename( filedir, true) );
  local names = readdir( filedir );
  
  foreach( index, name in names ) {
    reveal( name );
  }
}

local previous = getcwd();

chdir( "~" );

reveal( getcwd() );

chdir( previous );

exit( 0 );

В соответствии с соглашением первая строка каждого скрипта сообщает операционной системе, какую программу необходимо запустить для обработки данного скрипта. Как правило, в этой строке содержится команда #! /usr/bin/bash или #! /bin/zsh для запуска соответствующего интерпретатора или оболочки из указанного места.

Команда A#!/usr/bin/env squirrelsh работает немного по-другому. Она запускает специальную программу env, которая запускает первый элемент squirrelsh, найденный в переменной PATH. Следовательно, можно изменить значение переменной PATH в пользу локальной версии какой-нибудь программы, скажем, вашей собственной модифицированной копии squirrelsh на $HOME/bin/squirrelsh, не меняя содержимого shell-скрипта.

Примечание: Этот способ работает с любым интерпретатором. Например, команда #!/usr/bin/env ruby будет вызывать необходимую версию Ruby, указанную в настройках PATH. В целом, если вы планируете распространять любой из созданных вами shell-скриптов, используйте в первой строке форму #!/usr/bin/env приложение как более универсальную. В этом случае запускается версия приложения, указанная пользователем в его переменной PATH.

Остаток листинга 2 должен быть понятен, по крайней мере при ближайшем рассмотрении. Функция reveal() является рекурсивной:

  • Если функции reveal() передан неверный путь, «точка» (., текущий каталог) или «двойная точка» (.., родительский каталог) рекурсия прекращается.
  • В противном случае, если аргумент filedir является файлом, скрипт печатает его имя и возвращает управление, также прекращая дальнейшую рекурсию. Функция filename() может обрабатывать один или два аргумента. При одном аргументе или если второй аргумент равен false расширение имени файла опускается. Если второй аргумент принимает значение true, имя файла возвращается полностью.
  • Если аргументом является каталог, скрипт печатает его имя, а затем сканирует его содержимое. Обработка не обязательно происходит с перебором в глубину, поскольку содержимое каталога не упорядочено определенным образом. Это исправлено в следующем примере.

Интересный момент: поскольку вызов reveal() является последним оператором соответствующей функции, виртуальная машина (virtual machine, VM) Squirrel – устройство, обрабатывающее код скрипта – может изменить порядок итераций рекурсии при помощи т.н. концевой рекурсии. Вкратце концевая рекурсия позволяет избавиться от использования для рекурсии стека вызовов; в результате становится доступна произвольная глубина рекурсии и удается избежать переполнения стека.

Синтаксис Squirrel хорошо понятен, поэтому код на этом языке пишется быстро, особенно если у вас есть опыт работы с C, C++ или любым другим языком высокого уровня.

И, главное, этот код является универсальным. Скопируйте его на компьютер под управлением Windows, установите Squirrel Shell для Windows и запустите свой скрипт.


Работа с таблицами

Одной из удобных особенностей Squirrel является богатый по сравнению с обычными оболочками набор структур данных. Часто сложные проблемы быстро решаются просто путем правильной организации данных. В Squirrel имеются полнофункциональные объекты, гетерогенные массивы и ассоциативные массивы (в терминологии Squirrel – таблицы).

Таблица Squirrel состоит из слотов (пар ключ - значение). Ключом может служить любое значение, кроме нулевого; любое значение может быть присвоено слоту. Новый слот создается при помощи оператора «стрелка» (<-).

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

Листинг 3. Расширенная версия скрипта из листинга 2. Сначала выводит содержимое каталога, а затем рекурсивно сканирует подкаталоги.
#!/usr/bin/env squirrelsh

function reveal( filedir ) { 
  local tally = {};
  tally[FILE] <- [];
  tally[DIR] <- [];
  
  if ( !exist( filedir ) ) {
    return;
  }
    
  if ( filename( filedir ) == ".." || filename( filedir ) == "." ) {
    return;
  }
  		
  local names = readdir( filedir );
  
  foreach( index, name in names ) {
    tally[ filetype( name ) ].append( name ) ;
  }

  foreach( index, file in tally[FILE] ) {
    printl( file );
  }
 
  foreach( index, dir in tally[DIR] ) {
    printl( filename( dir ) + "/" );
  }
 	
  foreach( index, dir in tally[DIR] ) {
    reveal( dir );
  }

}

local entries = readdir( (__argc >= 2) ? __argv[1] : "." );

exit( 0 );

В данном случае таблица является идеальной структурой данных. Таблица в reveal() содержит два слота: один для файлов, а другой для каталогов. Функция filetype( имя ) возвращает значение либо константы FILE, либо константы DIR, собирая все значения файловой системы в соответствующих слотах.

Кроме того, каждый слот в массиве создается при помощи двух операторов tally[FILE] <- [] и tally[DIR] <- [];. ([] – это пустой массив.) Поскольку tally является локальной переменной внутри функции, она создается заново при каждом вызове и при возврате вызова выпадает из контекста и автоматически удаляется.

Функция массива append( arg ) добавляет аргумент arg в конец массива, таким образом составляя список процессов. После цикла foreach( index, name in names ), все значения оказываются помещены в список в одном из слотов. Остаток кода функции последовательно выводит названия файлов и каталогов, а затем выполняет рекурсию.

Разумеется, от shell-скрипта мало пользы без аргументов командной строки. Специальные переменные Squirrel Shell __argc и __argv содержат счетчик аргументов командной строки и список аргументов в виде соответствующего массива строк. В соответствии с соглашением __argv[0] всегда является именем скрипта, следовательно, если значение __argc равно двум или более, это означает, что скрипт имеет дополнительные аргументы. Для краткости наш скрипт обрабатывает только один дополнительный аргумент argv[1].

Для справки в листинге 4 приведен скрипт на Ruby (написанный Константином Макшиным), выполняющий те же функции, что и код в листинге 3. Несмотря на всю лаконичность Ruby, он все равно больше кода Squirrel Shell.

Листинг 4. Реализация функций из листинга 3 на Ruby
!/usr/bin/ruby

# Список содержимого каталога.

path = ARGV[0] == nil ? "." : ARGV[0].dup

# Удаляем конечные слэши
while path =~ /\/$/
  path.chop!
end

entries = Dir.open(path)
for entry in entries
  unless entry == "." || entry == ".."
    filePath    = "#{path}/#{entry}"
    fileStat = File.stat(filePath)
    if fileStat.directory?
      puts "dir : #{filePath}"
    elsif fileStat.file?
      puts "file: #{filePath}"
    end
  end
end

entries.close()

Для получения дополнительной информации о языке Squirrel обратитесь к Справочнику по языку программирования Squirrel (см. ссылку в разделе Ресурсы).

Замечательно то, что практически все функции в Squirrel Shell абстрагированы от функций операционной системы, поэтому код получается максимально универсальным. Например, функция filename() (использованная в первых двух листингах выше) вырезает начало пути к файлу, сокращая /home/example/some/directory/file.txt до file.txt, вне зависимости от используемой платформы. Подобным же образом readdir() и filetype() позволяют свободно игнорировать особенности и тонкости реальных операционных и файловых систем. Как правило, обычная оболочка не допускает подобного абстрагирования (в отличие от более продвинутых скриптовых языков).

Существуют и другие полезные функции, независимые от платформы: convpath() для конвертации пути в стандартный формат и run() для вызова других программ. Функция convpath() является реверсивной и весьма полезна при создании кроссплатформенных скриптов.


Регулярные выражения

Shell-скрипты обычно используются для автоматизации системного администрирования и служебных операций. Мощь такой автоматизации заключается в регулярных выражениях, настоящем наборе иероглифов для поиска, сравнения и обработки строк. Как упоминалось выше, для работы Squirrel Shell требуется библиотека PCRE, которую также можно найти в дистрибутивах Perl, PHP, Ruby и многих других интерпретаторов и программ. PCRE представляет собой самурайский меч для разделки и нарезки данных.

В целом реализация регулярных выражений в Squirrel Shell слегка отличается от традиционной и напоминает PHP. Для использования регулярного выражения в Squirrel Shell вы определяете регулярное выражение, компилируете его, выполняете сравнение, затем повторяете то же самое с результатами, если таковые есть.

В листинге 5 приведен пример программы, демонстрирующий работу регулярных выражений в Squirrel Shell (код написан Константином Макшиным и используется с его разрешения).

Листинг 5. Использование регулярных выражений в Squirrel Shell
#!/usr/bin/env squirrelsh

// Сопоставление регулярного выражения с текстом

print("Text: ");
local text = scan();

print("Pattern: ");
local pattern = scan();

local re = regcompile(pattern);
if (!re)
{
	printl("Failed to compile regular expression - " + regerror());
	exit(1);
}

local matches = regmatch(re, text);
if (!matches)
{
	printl("Failed to match regular expression - " + regerror());
	regfree(re);
	exit(1);
}

regfree(re);
printl("Matches found:");
foreach (match in matches)
	printl("\t\"" + substr(text, match[0], match[1]) + "\"");

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

Получив шаблон, функция reqgcompile() компилирует шаблон, что ускоряет обработку повторяющихся совпадений. Вы можете включить или отключить учет регистра при помощи флага функции reqgcompile() (то же самое, что и параметр PCRE /i), также можно искать совпадения для отдельных строк или для нескольких строк сразу при помощи другого параметра (то же самое, что и параметр PCRE /m). Если регулярное выражение не скомпилировать, все сопоставления возвратят отрицательный ответ.

Функция regmatch(re, text) сравнивает регулярное выражение с текстом, возвращая либо Null, если нет совпадений, либо массив пар целых чисел (двухэлементный массив). Первое целое число в каждой паре является началом совпадения, а второе число концом. Это объясняет использование substr(text, match[0], match[1]) в последней строке кода.

После выполнения сравнения можно повторить операцию с полученными результатами. Если скомпилированное регулярное выражения уже не нужно, удалите его при помощи regfree(). Кроме того, имеется функция regfreeall(), удаляющая все ресурсы, выделенные для всех скомпилированных выражений.


Орешки для белки

В идеальном мире одинаковая программная логика должна работать и в UNIX, и в Linux, и в Windows, а программисты должны быть как минимум счастливее, если не эффективнее. Увы, операционные системы различаются между собой, и случается, что нам приходится писать специальный код для конкретной системы.

В тех случаях, когда ни вы, ни Squirrel Shell не можете абстрагироваться от определенной платформы, Squirrel Shell предоставляет удобные функции для проверки того, что код может работать в данной операционной системе.

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

Листинг 6. Функция platform() возвращает тип операционной системы
print( "Made by ... ");

local platform = platform();

switch ( platform ) {
  case "linux":
    printl( "Linus." );
    break;

  case "macintosh":
    printl( "Steve." );
    break;

  case "win32":
  case "win64":
    printl( "Bill." );
    break;

  default:
  	printl( "Unknown" );
}

Кроме того, можно узнать текущий тип платформы при помощи переменной среды Squirrel Shell PLATFORM:

> printl( PLATFORM );
linux

Переменная среды CPU_ARCH возвращает тип процессора, для которого была скомпилирована оболочка:

> printl( CPU_ARCH );
x86

Еще об орешках

Оставшиеся функции Squirrel Shell управляют файлами, выполняют операции со средой и выполняют вычисления. На самом деле только для тригонометрии имеется около 20 встроенных функций. В настоящее время планируется версия 2.0, которая будет содержать больше классов, поддержку Unicode, усовершенствованный интерактивный режим и модульную архитектуру с поддержкой плагинов.

Squirrel Shell не слишком приспособлена для работы в качестве интерактивной оболочки, но это не проблема. Для этого существует множество других вариантов. Squirrel Shell намного лучше в качестве среды выполнения скриптов. Ее структуры данных более функциональны, чем в обычных оболочках, синтаксис привычен, а встроенная виртуальная машина поддерживает все от перечисляемых типов до потоков. Кроме того, ядро Squirrel очень компактно - менее 6000 строк кода. Вы можете даже встроить Squirrel целиком в другое приложение.

Испытайте Squirrel Shell, если вам нужно писать код для нескольких платформ. Squirrel избавит вас от многих проблем.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=AIX и UNIX
ArticleID=407923
ArticleTitle=Использование UNIX: Переносимая оболочка и скриптовый язык Squirrel
publish-date=07102009