Статьи

Написание сценария оболочки с нуля

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

Для этого урока мы напишем скрипт, который немного упростит процесс использования тестовой среды Jasmine . На самом деле, я бы не использовал этот скрипт сегодня; Я бы использовал Grunt.js или что-то подобное. Тем не менее, я написал этот сценарий еще до того, как появился Grunt, и обнаружил, что его написание оказалось отличным способом освоения сценариев оболочки, поэтому мы его используем.

Одно замечание: этот урок слабо связан с моим предстоящим курсом Tuts + Premium «Расширенные методы командной строки». Чтобы узнать больше о чем угодно из этого урока, следите за обновлениями этого курса. В дальнейшем в этом уроке он будет называться «курс».

Итак, наш сценарий, который я называю jazz , будет иметь четыре основные функции:

  • Он загрузит Jasmine из Интернета, разархивирует его и удалит пример кода.
  • Он создаст файлы JavaScript и связанные с ними файлы спецификаций и предварительно заполнит их небольшим количеством кода шаблона.
  • Это откроет тесты в браузере.
  • Он отобразит текст справки, в котором изложено выше.

Давайте начнем с файла сценария.


Написание сценария оболочки полезно, только если вы можете использовать его из терминала; чтобы иметь возможность использовать ваши собственные скрипты на терминале, вам нужно поместить их в папку, которая находится в переменной PATH вашего терминала (вы можете увидеть вашу PATH , запустив echo $PATH ). Я создал папку ~/bin (где ~ — домашний каталог) на моем компьютере, и там я хотел бы сохранить пользовательские сценарии (если вы сделаете то же самое, вам придется добавить его в свой путь). Итак, просто создайте файл с именем jazz и поместите его в свою папку.

Конечно, мы также должны сделать этот файл исполняемым; иначе мы не сможем его запустить. Мы можем сделать это, выполнив следующую команду:

1
chmod +x jazz

Теперь, когда мы действительно можем выполнить скрипт, давайте добавим очень важную часть. Все сценарии оболочки должны начинаться с шебанга ). Как говорится в Википедии, это должна быть первая строка сценария; в нем указывается, с каким интерпретатором или оболочкой следует запускать этот скрипт. Мы просто собираемся использовать базовую стандартную оболочку:

1
#!/bin/sh

Хорошо, со всей этой настройкой мы готовы начать писать настоящий код.


Ранее я указывал, какими должны быть различные возможности нашего сценария оболочки. Но как скрипт узнает, какую функцию запустить? Мы будем использовать комбинацию параметра оболочки и оператора case. При запуске сценария из командной строки мы будем использовать подкоманду, например:

1
2
3
4
jazz init
jazz create SomeFile
jazz run
jazz help

Это должно выглядеть знакомо, особенно если вы использовали Git:

1
2
3
git init
git status
git commit

Основываясь на этом первом параметре ( init , create , run , help ), наш оператор case решит, что запускать. Однако нам нужен случай по умолчанию: что произойдет, если не указан первый параметр или мы получим нераспознанный первый параметр? В этих случаях мы покажем текст справки. Итак, начнем!


Мы начнем с оператора if который проверяет наш первый параметр:

1
2
3
4
5
6
if [ $1 ]
then
    # do stuff
else
    # show help
fi

Сначала вы можете быть немного смущены, потому что оператор if в оболочке довольно сильно отличается от оператора if в «обычном» языке программирования. Чтобы лучше понять это, посмотрите видеоролик с условными заявлениями в курсе. Этот код проверяет наличие первого параметра ( $1 ); если он есть, мы выполним код then ; else мы покажем текст справки.

Это хорошая идея, чтобы обернуть печать текста справки в функцию, потому что мы должны вызывать ее несколько раз. Нам нужно определить функцию перед ее вызовом, поэтому мы поместим ее сверху. Мне это нравится, потому что теперь, как только я открываю файл, я вижу документацию для скрипта, которая может быть полезным напоминанием при возврате к коду, который вы давно не видели. Без дальнейших церемоний, вот функция help :

1
2
3
4
5
6
7
function help () {
    echo «jazz — A simple script that makes using the Jasmine testing framework in a standalone project a little simpler.»
    echo «
    echo » jazz init — include jasmine in the project»;
    echo » jazz create FunctionName — creates ./src/FunctionName.js ./spec/FunctionNameSpec.js»;
    echo » jazz run — runs tests in browser»;
}

Теперь просто замените эту функцию # show help вызовом функции help .

1
2
3
else
    help
fi

Если есть первый параметр, нам нужно выяснить, какой это. Для этого мы используем оператор case :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
case «$1» in
    init)
     
    ;;
    create)
     
    ;;
    run)
     
    ;;
    *)
        help
    ;;
esac

Мы передаем первый параметр в оператор case ; затем он должен соответствовать одной из четырех вещей: «init», «create», «run» или наш подстановочный знак, регистр по умолчанию. Обратите внимание, что у нас нет явного случая «помощи»: это только наш случай по умолчанию. Это работает, потому что все, кроме «init», «create» и «run», не являются командами, которые мы распознаем, поэтому оно должно получить текст справки.

Теперь мы готовы написать функциональный код, и мы начнем с jazz init .


Весь код, который мы здесь напишем, пойдет в нашем случае init) из приведенного выше оператора case. Первым шагом является загрузка автономной версии Jasmine, которая поставляется в виде zip-файла:

1
2
echo «Downloading Jasmine …»
curl -sO $JASMINE_LINK

Сначала мы выводим небольшое сообщение, а затем используем curl для загрузки zip. Флаг s делает его без вывода сообщений (без вывода), а флаг O сохраняет содержимое zip-файла в файл (в противном случае он выводит его в стандартный поток). Но что с этой переменной $JASMINE_LINK ? Ну, вы могли бы поместить туда фактическую ссылку на zip-файл, но я предпочитаю помещать ее в переменную по двум причинам: во-первых, это удерживает нас от повторения части пути, как вы увидите через минуту. Во-вторых, с этой переменной в верхней части файла, это позволяет легко изменить версию Jasmine, которую мы используем: просто измените одну переменную. Вот это объявление переменной (я поместил его вне оператора if , в верхней части):

1
JASMIME_LINK=»http://cloud.github.com/downloads/pivotal/jasmine/jasmine-standalone-1.3.1.zip»

Помните, что в этой строке не должно быть пробелов вокруг знака равенства.

Теперь, когда у нас есть наш zip-файл, мы можем распаковать его и подготовить содержимое:

1
2
unzip -q <code>basename $JASMINE_LINK</code>
rm -rf <code>basename $JASMINE_LINK</code> src/*.js spec/*.js

В двух из этих строк мы используем basename $JASMINE_LINK ; команда basename просто уменьшает путь к базовому имени: поэтому path/to/file.zip становится просто file.zip . Это позволяет нам использовать эту переменную $JASMINE_LINK для ссылки на наш локальный zip-файл.

После распаковки мы удалим этот zip-файл, а также все файлы JavaScript в каталогах src и spec . Это примеры файлов, которые поставляет Jasmine, и они нам не нужны.

Далее у нас есть проблема только для Mac. По умолчанию, когда вы загружаете что-то из Интернета на Mac, когда вы пытаетесь запустить это в первый раз, вам будет предложено подтвердить, что вы хотите запустить это. Это из-за расширенного атрибута com.apple.quarantine который Apple помещает в файл. Нам нужно удалить этот атрибут.

1
2
3
4
if which xattr > /dev/null && [ «<code>xattr SpecRunner.html</code>» = «com.apple.quarantine» ]
then
    xattr -d com.apple.quarantine SpecRunner.html
fi

Мы начнем с проверки наличия команды xattr , поскольку она не существует в некоторых системах Unix (я не уверен, но это может быть программа только для Mac). Если вы смотрели экранную трансляцию курса по условным выражениям, вы будете знать, что мы можем передать любую команду if ; если он имеет статус выхода, отличный от 0 , он ложен. Если команда найдет команду xattr , она завершится с 0 ; в противном случае он выйдет с 1 . В любом случае, which будет отображать некоторый вывод; мы можем предотвратить это, перенаправив его в /dev/null (это специальный файл, который отбрасывает все записанные в него данные).

Этот двойной амперсанд является логическим И; это для второго условия, которое мы хотим проверить. То есть есть ли у SpecRunner.html этот атрибут? Мы можем просто запустить команду xattr для файла и сравнить ее вывод со строкой, которую мы ожидаем. (Мы не можем просто ожидать, что файл будет иметь атрибут, потому что вы действительно можете отключить эту функцию в Mac OS X, и мы получим ошибку при попытке удалить его, если файл не имеет атрибута).

Итак, если xattr найден и файл имеет атрибут, мы удалим его с флагом d (для удаления). Довольно просто, правда?

Последний шаг — редактирование SpecRunner.html . В настоящее время он содержит теги сценариев для примеров файлов JavaScript, которые мы удалили; мы также должны удалить эти теги сценариев. Я случайно знаю, что эти теги сценариев занимают строки от 12 до 18 в файлах. Итак, мы можем использовать редактор потоков sed для удаления этих строк:

1
2
sed -i «» ‘12,18d’ SpecRunner.html
echo «Jasmine initialized!»

Флаг i указывает sed редактировать файл на месте или сохранять выходные данные команды в тот же файл, который мы передали; пустая строка после флага означает, что мы не хотим, чтобы sed создавал для нас резервную копию файла; если вы этого хотите, вы можете просто вставить расширение файла в эту строку (например, SpecRunner.html.bak , чтобы получить SpecRunner.html.bak ).

Наконец, мы дадим пользователю знать, что Jasmine был инициализирован. И это все для нашей jazz init команды jazz init .


Далее, мы собираемся позволить нашим пользователям создавать файлы JavaScript и связанные с ними файлы спецификаций. Эта часть кода будет помещена в раздел «create» оператора case мы написали ранее.

1
2
3
4
5
6
if [ $2 ]
then
    # create files
else
    echo «please include a name for the file»
fi

При использовании jazz create нам необходимо включить имя файла в качестве второго параметра: например, jazz create View . Мы будем использовать это для создания src/View.js и spec/ViewSpec.js . Итак, если второго параметра нет, мы напомним пользователю добавить его.

Если есть имя файла, мы начнем с создания этих двух файлов (внутри части then ):

1
2
echo «function $2 () {\n\n}» > src/$2.js
echo «describe(‘$2’, function () {\n\n});»

Конечно, вы можете поместить в файл src все, что захотите. Я делаю что-то простое здесь; поэтому jazz create View создаст src/View.js с этим:

1
2
3
function View () {
 
}

Вы можете заменить эту первую строку echo на это:

1
echo «var $2 = (function () {\n\tvar $2Prototype = {\n\n\t};\n\n\treturn {\n\t\tcreate : function (attrs) {\n\t\t\tvar o = Object.create($2Prototype);\n\t\t\textend(o, attrs);\n\t\t\treturn o;\n\t\t}\n \t};\n}());»

И тогда jazz create View приведет к этому:

01
02
03
04
05
06
07
08
09
10
11
12
13
var View = (function () {
    var ViewPrototype = {
     
    };
 
    return {
        create : function (attrs) {
            var o = Object.create(ViewPrototype);
            extend(o, attrs);
            return o;
        }
    };
}());

Так что, действительно, ваше воображение это предел. Конечно, вы захотите, чтобы файл спецификаций был стандартным кодом спецификации Jasmine, что я и описал выше; но вы можете настроить это так, как вам нравится.

Следующим шагом является добавление тегов сценария для этих файлов в SpecRunner.html . Поначалу это может показаться сложным: как мы можем программно добавить строки в середину файла? Еще раз, это sed который делает работу.

1
2
3
4
sed -i «» «11a\\
<script src=’src/$2.js’></script>\\
<script src=’spec/$2Spec.js’></script>
» SpecRunner.html

Мы начинаем так же, как и раньше: редактирование на месте без резервного копирования. Затем наша команда: в строке 11 мы хотим добавить две следующие строки. Важно избежать двух новых строк, чтобы они появлялись в тексте. Как видите, здесь просто вставляются эти два тега сценария, именно то, что нам нужно для этого шага.

Мы можем закончить с некоторыми выводами:

1
2
3
4
5
echo «Created:»
echo «\t- src/$2.js»
echo «\t- spec/$2Spec.js»
echo «Edited:»
echo «\t- SpecRunner.html»

И это jazz create !


Последний шаг — запустить тесты. Это означает открытие файла SpecRunner.html в браузере. Здесь есть небольшая оговорка. В Mac OS X мы можем использовать команду open чтобы открыть файл в программе по умолчанию; это не будет работать на любой другой ОС, но я так и делаю здесь. К сожалению, я не знаю настоящего кроссплатформенного способа сделать это. Если вы используете этот скрипт в Cygwin под Windows, вы можете использовать cygstart вместо open ; в противном случае попробуйте поискать «скрипт браузера вашей операционной системы», откройте браузер »и посмотрите, что вы придумали. К сожалению, некоторые версии Linux (по крайней мере, Ubuntu, по моему опыту) имеют команду open для чего-то совершенно другого. Все это говорит о том, что ваш пробег со следующим может отличаться.

1
2
3
4
5
6
if [ «`which open`» = ‘/usr/bin/open’ ]
then
    open SpecRunner.html
else
    echo «Please open SpecRunner.html in your browser»
fi

К настоящему времени вы точно знаете, что это делает: если мы open , мы откроем SpecRunner.html , в противном случае мы просто напечатаем сообщение, сообщающее пользователю открыть файл в браузере.

Первоначально, if условие выглядело так:

1
if which open > /dev/null

Как и в случае с xattr , он просто проверял существование open ; однако, поскольку я узнал, что в Linux есть другая команда open (даже на моем сервере Ubuntu, который не может даже открыть браузер!), я подумал, что было бы лучше сравнить путь open программы, поскольку Linux один находится в /bin/open (опять же, по крайней мере, на сервере Ubuntu).

Все эти лишние слова об open могут звучать как оправдание моего отсутствия хорошего решения, на самом деле это указывает на что-то важное в командной строке. Не путайте понимание терминала с пониманием конфигурации компьютера. Это руководство и связанный с ним курс научили вас немного больше об оболочке Bash (и оболочке Z), но это не значит, что каждый используемый вами компьютер будет настроен одинаково; Есть много способов установить новые команды (или разные версии команд), а также удалить команды. Будьте разработчиком .

Ну вот и весь сценарий! Вот и снова все вместе:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!
 
function help () {
    echo «jazz — A simple script that makes using the Jasmine testing framework in a standalone project a little simpler.»
    echo «»
    echo » jazz init — include jasmine in the project»;
    echo » jazz create FunctionName — creates ./src/FunctionName.js ./spec/FunctionNameSpec.js»;
    echo » jazz run — runs tests in browser»;
}
 
JASMIME_LINK=»http://cloud.github.com/downloads/pivotal/jasmine/jasmine-standalone-1.3.1.zip»
 
if [ $1 ]
then
  case «$1» in
  init)
    echo «Downloading Jasmine . . .»
    curl -sO $JASMIME_LINK
    unzip -q `basename $JASMIME_LINK`
    rm `basename $JASMIME_LINK` src/*.js spec/*.js
     
    if which xattr > /dev/null && [ «`xattr SpecRunner.html`» = «com.apple.quarantine» ]
    then
      xattr -d com.apple.quarantine SpecRunner.html
    fi
 
    sed -i «» «12,18d» SpecRunner.html
    echo «Jasmine initialized!»
  ;;
  create)
    if [ $2 ]
    then
      echo «function $2 () {\n\n}» > ./src/$2.js
      echo «describe(‘$2’, function () {\nit(‘runs’);\n});»
      sed -i «» «11a\\
      <script src=’src/$2.js’></script>\\
      <script src=’spec/$2Spec.js’></script>
      » SpecRunner.html
      echo «Created:»
      echo «\t- src/$2.js»
      echo «\t- spec/$2Spec.js»
      echo «Edited:»
      echo «\t- SpecRunner.html»
    else
      echo ‘please add a name for the file’
    fi
  ;;
  «run»)
    if [ «`which open`» = ‘/usr/bin/open’ ]
    then
      open ./SpecRunner.html
    else
      echo «Please open SpecRunner.html in your browser»
    fi
  ;;
  *)
    help;
  ;;
  esac
else
  help;
fi

Ну, давай, попробуй!

1
2
3
4
5
6
mkdir project
cd project
jazz init
jazz create Dog
# edit src/Dog.js and spec/DogSpec.js
jazz run

Кстати, если вы хотите повеселиться с этим проектом, вы можете найти его на Github .


Так что у вас есть это! Мы только что написали скрипт оболочки промежуточного уровня; это было не так уж плохо, правда? Не забывайте следить за моими предстоящими курсами Tuts + Premium ; Вы узнаете намного больше о многих методах, используемых в этой статье, а также о бесчисленном количестве других. Веселитесь на терминале!