Основы bash-скриптинга для непрограммистов. часть 2

Некоторые команды

Дает возможность использовать алиасы в скриптах

Команда преобразует символы табуляции в пробелы. Часто используется в конвейерной обработке текста.
Команда преобразует пробелы в символы табуляции. Т.е. она является обратной по отношению к команде .

Выравнивает текст по ширине, разрывая, если это необходимо, слова. Особый интерес представляет ключ , который производит перенос строк по пробелам, стараясь не разрывать слова.

Форматирование по столбцам. Эта утилита преобразует текст, например какой либо список, в табличное, более «удобочитаемое», представление, вставляя символы табуляции по мере необходимости.

Утилита удаления колонок. Удаляет колонки (столбцы) сиволов из файла и выводит результат на . Например,

colrm 2 4 >filename

удалит символы со 2-го по 4-й включительно, в каждой строке в файле .

Возвращает имя файла, на который указывает символическая ссылка.

Команда (octal dump) производит преобразование ввода (или файла) в один или несколько форматов, в соответствии с указанными опциями.

Подстановка параметров

Символ имеет значение только когда parameter инициализирован «пустым» значением.

${!ref}	# косвенная ссылка на переменную
var='str'
ref='var'
echo ${!ref}	# str

${parameter-default}, ${parameter:-default}
# Если параметр отсутствует
# то используется значение по-умолчанию.

${parameter=default}, ${parameter:=default}
# Если значения параметров не задананы явно,
# то они принимают значения по-умолчанию.

${parameter+alt_value}, ${parameter:+alt_value}
# Если параметр имеет какое либо значение, # 
то используется alt_value, 
# иначе -- null ("пустая" строка).

${parameter?err_msg}, ${parameter:?err_msg}
# Если parameter инициализирован, 
# то используется его значение, 
# в противном случае -- выводится err_msg.

${!varprefix*}, ${!varprefix@}
# Поиск по шаблону всех, 
# ранее объявленных переменных, 
# имена которых начинаются с varprefix.

Вы знаете, что должны…

… «вылизывать» свои bash-скрипты так же, как ваш код на C, но почему-то на это никогда не хватает времени. Если ими будете пользоваться только вы, то можно особо не беспокоиться — до тех пор, пока вы не забудете, что делает скрипт и не запустите . Вот чёрт, теперь вспомнил — означает «halt» и останавливает работу! Упс…

Эта статья посвящена простому способу привести ваши скрипты в порядок, чтобы их работа была предсказуема, а опции и аргументы соответствовали соглашениям, принятым в Unix. Но сперва обсудим те проблемы, с которыми обычно сталкивается разработчик на bash.

Если ваши скрипты используют другие люди или если вы не можете полагаться на свою память (не переживайте — в один из прекрасных дней это обязательно случится), тогда становится настоящим мучением предусмотреть ответ на любой возможный ввод — а некоторые люди весьма изобретательны, «ломая» работу вашего скрипта синтаксисом, который вы и представить не можете.

Далее возникает вопрос соответствия стандартам. Вы вообще в курсе, что для подобных вещей существует ?

Например, поддерживает ли ваш скрипт «длинные» опции () наряду с «короткими» ()? Длинные опции обеспечивают «самодокументируемость» скрипта, короткие удобны опытным пользователям (и тем из нас, кто до сих пор сидит за экранным терминалом).

Адекватно ли реагирует ваш скрипт на пробелы между опциями и их аргументами? Например, должно восприниматься так же, как , и .

Можно ли задавать «длинные» опции с сокращением до уникальной подстроки, например, как сокращение для ?

Можно ли объединять «короткие» опции вместе, например, задавать как ?

Предусмотрено ли отображение справки по , (на стандартный вывод stdout, будьте любезны, чтобы пользователь мог передать его по конвейеру программе просмотра типа less)?

А как насчёт , или , ? … и так далее. Пользователи (ладно, пользователи, постигшие Unix) ожидают, что это будет работать — так что следование этим соглашениям всем упрощает жизнь.

Но это действительно утомительно и очень сложно — добиться, чтобы всё работало правильно, когда вы пишете свой bash-код, и неудивительно, что так мало скриптов в конечном итоге получаются «пользователеустойчивыми».

Особенно мучительным это становится, когда вы начинаете такой код сопровождать. Даже при помощи getopt это может быть трудно (синтаксис getopt сам по себе довольно запутанный), поскольку структура команды getopt требует от вас повторять буквы и строки опций в трёх местах:

  • вызов getopt
  • оператор case, обрабатывающий эти опции
  • страницы справки и man

Из-за этого ошибкам гораздо проще проникнуть в ваш код.

Работа со строками

Длина строки

${#string}

Извлечение подстроки

${string:position}
# с position до конца

${string:position:length}
# с position длиной length символов

${string: -length}
# последние length символов

Удаление части строки

${string#substring}
# до первого с начала

${string##substring}
# до последнего с начала

${string%substring}
# до первого с конца

${string%%substring}
# до последнего с конца
${string/substring/replacement}
# первое вхождение

${string//substring/replacement}
# все вхождения

${var/#Pattern/Replacement}
# Если в переменной var найдено совпадение с Pattern,
# причем совпадающая подстрока 
# расположена в начале строки (префикс),
# то оно заменяется на Replacement. 
# Поиск ведется с начала строки

${var/%Pattern/Replacement}
# Если в переменной var найдено совпадение с Pattern,
# причем совпадающая подстрока
# расположена в конце строки (суффикс),
# то оно заменяется на Replacement.
# Поиск ведется с конца строки

Циклы. Цикл while.

Цикл сложнее цикла и используется для повторения команд, пока какое-то выражение истинно( код возврата = 0).
Синтаксис оператора следующий:

Пример работы цикла рассмотрим на следующем примере:

А теперь результат работы скрипта:

Как видим цикл выполняется до тех пор, пока мы не введем что-то отличное от «yes». Между и можно описывать любые структуры, операторы и т.п., все они будут выполнятся в цикле.Но следует быть осторожным с этим циклом, если вы запустите на выполнение в нём какую-либо команду, без изменения переменной выражения, вы можете попасть в бесконечный цикл.

Теперь об условии истинности. После , как и в условном операторе можно вставлять любое выражение или команду, которая возвращает код возврата, и цикл будет исполнятся до тех пор, пока код возврата = 0! Оператор аналог команды , которая проверяет истинность условия, которое ей передали.

Рассмотрим еще один пример, я взял его из книги Advanced Bash Scripting. Уж очень он мне понравился :), но я его немного упростил. В этом примере мы познакомимся с еще одним типом циклов UNTIL-DO. Эта практически полный аналог цикла WHILE-DO, только выполняется пока какое-то выражение ложно.
Вот пример:

Результат выполнения скрипта:

Работа с внешними программами при написании shell-скриптов

Для начала немного полезной теории.

Перенаправление потоков.

В bash (как и многих других оболочках) есть встроенные файловые дескрипторы: , , . — Стандартный вывод. Сюда попадает все что выводят программы — Стандартный ввод. Это все что набирает юзер в консоли — Стандартный вывод ошибок.
Для операций с этими дескрипторами, существуют специальные символы: (перенаправление вывода), (перенаправление ввода). Оперировать ими не сложно. Например:

или

Если есть необходимость дописывать в файл(при использовании «» он заменятеся), необходимо вместо «» использовать «»

после просьбы sudo ввести пароль, он возьмется из файла my_password, как будто вы его ввели с клавиатуры.
Если необходимо записать в файл только ошибки, которые могли возникнуть при работе программы, то можно использовать:

цифра 2 перед «» означает что нужно перенаправлять все что попадет в дескриптор 2(stderr).
Если необходимо заставить писать в , то это можно след. образом:

символ «» означает указатель на дескриптор
(Поумолчанию пишет на ту консоль, в котрой работает пользователь(вренее пишет на дисплей)).

2. Конвееры.

Конвеер — очень мощный инструмент для работы с консолью Bash. Синтаксис простой: — означает, что вывод команды 1 передастся на ввод команде 2
Конвееры можно группировать в цепочки и выводить с помощью перенаправления в файл, например:

вывод команды передается команде , которая отбирает все строки, в которых встретится слово hash, и передает команде сортировке , которая пишет результат в файл sorting_list. Все довольно понятно и просто.

Чаще всего скрипты на Bash используются в качестве автоматизации каких-то рутинных операций в консоли, отсюда иногда возникает необходимость в обработке одной команды и передача на другой команде, при этом результат выполнения одной команды должен быть неким образом обработан. В этом разделе я постораюсь объяснить основные принципы работы с внешними командами внутри скрипта. Думаю что примеров я привел достаточно и можно теперь писать только основные моменты.

1. Передача вывода в переменную.

Для того чтобы записать в переменную вывод какой-либо команды, достаточно заключить команду в ковычки, например

Результат работы: qwerty

Однако если вы захотите записать в переменную список директорий, то необходимо, должным образом обработать результат для помещения данных в переменную. Рассмотрим небольшой, пример:

Здесь мы используем цикл для архивирование всех директорий в папке /svn/ с помощью команды (что в нашем случае не имеет никого значения, просто как пример). Наибольшй интерес вызывает строка: LIST= В ней переменной LIST присваивается выполнение команды find, обработанной командами awk, sort, uniq,tr(все эти команды мы рассматривать не будем, ибо это отдельная статья). В переменной LIST будут имена всех каталогов в папке /svn/ пгомещенных в одну строку(для того чтобы её стравить циклу.

Syntax

getopts optstring optname [ arg ]

How it works

mycmd -a argument1 -b argument2

Every time you run getopts, it looks for one of the options defined in optstring. If it finds one, it places the option letter in a variable named optname. If the option does not match those defined in optstring, getopts sets variable optname to a question mark («?«).

If the option is expecting an argument, getopts gets that argument, and places it in $OPTARG. If an expected argument is not found, the variable optname is set to a colon («»). Getopts then increments the positional index, $OPTIND, that indicates the next option to be processed.

The special option of two dashes (««) is interpreted by getopts as the end of options.

getopts is designed to run multiple times in your script, in a loop, for example. It processes one option per loop iteration. When there are no more options to be processed, getopts returns false, which automatically terminates a while loop. For this reason, getopts and while are frequently used together.

Specifying the optstring

optstring is a string which defines what options and arguments getopts look for. For instance, in this call to getopts:

getopts "apZ" optname

The options expected by getopts are -a, -p, and -Z, with no arguments. These options can be combined in any order as -aZ, -pa, -Zap, etc.

Let’s say that you’d like the -a and -Z options to take arguments. You can specify this by putting a colon («») after that option in optstring. For example:

getopts "a:pZ:" optname

Now you can specify arguments to the -a and -Z options such as -a argument1 -pZ argument2. The -p option cannot take arguments, because there is no colon after the p in optstring.

Note

There are two reserved characters which cannot be used as options: the colon («») and the question mark («?«).

Verbose error checking

By default, getopts will report a verbose error if it finds an unknown option or a misplaced argument. It also sets the value of optname to a question mark («?«). It does not assign a value to $OPTARG.

If the option is valid but an expected argument is not found, optname is set to «?«, $OPTARG is unset, and a verbose error message is printed.

Silent error checking

However, if you put a colon at the beginning of the optstring, getopts runs in «silent error checking mode.» It will not report any verbose errors about options or arguments, and you need to perform error checking in your script.

In silent mode, if an option is unexpected, getopts sets optname to «?» and $OPTARG to the unknown option character.

If the option is OK but an expected argument is not found, optname is set to a colon («») and $OPTARG is set to the unknown option character.

Команды, синтаксис и примеры

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

Пример передачи аргументов в скрипте Bash

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

 sh stats.sh список песен 

Доступ к аргументам осуществляется внутри скрипта с использованием переменных $ 1 , $ 2 , $ 3 и т. Д. Переменная $ 1 ссылается на первый аргумент, $ 2 – на второй аргумент, а $ 3 – на третий аргумент. Это иллюстрируется в следующем примере:

 FILE1 = $ 1  wc $ FILE1 

Для удобства чтения назначьте переменную с описательным именем значению первого аргумента ( $ 1 ), а затем вызовите утилиту подсчета слов (wc) для переменной $ FILE1 ,

Если у вас есть переменное число аргументов, используйте переменную $ @ , которая является массивом всех входных параметров. Это использует цикл for для итеративной обработки каждого из них, как показано в следующем примере:

 для FILE1 в "$ @"  do  wc $ FILE1  сделано 

Вот пример того, как вызвать этот скрипт с аргументами из командной строки:

 sh stats.sh songlist1 songlist2 songlist3 

Если аргумент имеет пробелы, заключите его в одинарные кавычки. Например:

 sh stats.sh 'songlist 1' 'songlist 2' 'songlist 3' 

Флаги Метод

Часто сценарий написан так, что аргументы могут быть переданы в любом порядке с использованием флагов. С помощью метода flags некоторые аргументы можно сделать необязательными.

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

 makereport -u jsmith -p notebooks -d 10-20-2011 -f pdf 

Bash включает эту функцию с помощью функции getopts. Для приведенного выше примера используйте getopts следующим образом:

, пока getopts u: d: p: f: option  do  case "$ {option}"  in  u) USER = $ {OPTARG} ;;  d) DATE = $ {OPTARG} ;;  p) PRODUCT = $ {OPTARG} ;;  f) FORMAT = $ OPTARG ;;  esac  выполнено  

Это цикл while, который использует функцию getopts и так называемую строку опций – в данном случае u: d: p: f: – для итерации аргументов. Цикл while проходит по строке optstring, которая содержит флаги, используемые для передачи аргументов, и присваивает значение аргумента, предоставленное для этого флага, переменной option . Затем оператор CASE присваивает значение переменной option глобальной переменной, которая используется после прочтения всех аргументов.

Значения для двоеточий

Двоеточие в строке опций означает, что значения необходимы для соответствующих флагов. В приведенном выше примере u: d: p: f: за всеми флагами следует двоеточие. Это означает, что все флаги нуждаются в значении. Если, например, флаги d и f , как ожидается, не будут иметь значения, строкой опций будет u: dp: f .

Двоеточие в начале строки опций (например, : u: d: p: f: ) имеет совершенно другое значение. Он обрабатывает флаги, которые не представлены в строке optstring. В этом случае значение переменной option установлено в ? и значение OPTARG устанавливается на неожиданный флаг. Это отображает подходящее сообщение об ошибке, информирующее вас об ошибке.

Аргументы, которым не предшествует флаг, игнорируются getopts. Если флаги, указанные в строке опций, не предоставляются при вызове скрипта, то ничего не происходит, если вы специально не обработаете этот случай в своем коде. Любые аргументы, не обрабатываемые getops, могут по-прежнему регистрироваться с помощью обычных переменных $ 1 , $ 2 и $ 3 .

Написание функций Bash

Сначала нужно понять что такое функция в нашем контексте. Функция — это набор команд, объединенных одним именем, которые выполняют определенную задачу. Функция вызывается по ее имени, может принимать параметры и возвращать результат работы. Одним словом, функции Bash работают так же, как и в других языках программирования.

Синтаксис создания функции очень прост:

имя_функции() { список_команд }

Имя функции не должно совпадать ни с одной из существующих команд или функций, а все команды в теле функции пишутся с новой строки.

Простая функция

Давайте напишем небольшую функцию, которая будет выводить строку на экран:

$ vi function.sh

#!/bin/bash
printstr(){
echo «hello world»
}
printstr

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

Все работает, теперь усложним задачу, попробуем передать функции аргументы.

Аргументы функции

Аргументы функции нужно передавать при вызове, а читаются они точно так же, как и аргументы скрипта. Синтаксис вызова функции с параметрами bash такой:

имя_функции аргумент1 аргумент2 … аргументN

Как видите, все достаточно просто. Параметры разделяются пробелом. Теперь улучшим нашу функцию, чтобы она выводила заданную нами строку:

Можно сделать, чтобы параметров было несколько:

Есть и другой способ извлекать аргументы, как в Си, с помощью стека. Мы извлекаем первый аргумент, затем сдвигаем указатель стека аргументов на единицу и снова извлекаем первый аргумент. И так далее:

Возврат результата функции

Вы можете не только использовать функции с параметрами bash, но и получить от нее результат работы. Для этого используется команда return. Она завершает функцию и возвращает числовое значение кода возврата. Он может быть от 0 до 255:

Если вам нужно применить возврат значения функции bash, а не статус код, используйте echo. Строка не сразу выводится в терминал, а возвращается в качестве результата функции и ее можно записать в переменную, а затем использовать:

Экспорт функций

Вы можете сделать функцию доступной вне скрипта с помощью команды declare:

Затем запустите скрипт с помощью команды source:

Рекурсия

Вы можете вызвать функцию из нее же самой, чтобы сделать рекурсию:

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

Локальные переменные в функции

Если вы объявите обычную переменную в функции, то она будет доступной во всем скрипте, это удобно для возврата значения функции, но иногда может понадобиться сделать локальную переменную. Для этого существует команда local:

Библиотеки функций

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

test1(){
echo «Hello World from 1»;
}
test2(){
echo «Hello World from 2»;
}
test3(){
echo «Hello World from 3»;
}

Теперь создадим скрипт, который будет использовать наши функции. Импортировать библиотеку можно с помощью команды source или просто указав имя скрипта:

Description

getopts is the bash version of another system tool, getopt. Notice that the bash command has an s at the end, to differentiate it from the system command.

While the getopt system tool can vary from system to system, bash getopts is defined by the POSIX standard. So if you write a script using getopts, you can be sure that it runs on any system running bash in POSIX mode (e.g., set -o posix).

getopts parses short options, which are a single dash (««) and a letter or digit. Examples of short options are -2, -d, and -D. It can also parse short options in combination, for instance -2dD.

However, getopts cannot parse options with long names. If you want options like —verbose or —help, use getopt instead.

Examples

Here is a bash script using getopts. The script prints a greeting, with an optional name, a variable number of times. It takes two possible options: -n NAME and -t TIMES.

#!/bin/bash
NAME=""                                   # Name of person to greet.
TIMES=1                                   # Number of greetings to give. 
usage() {                                 # Function: Print a help message.
  echo "Usage: $0  " 1>&2 
}
exit_abnormal() {                         # Function: Exit with error.
  usage
  exit 1
}
while getopts ":n:t:" options; do         # Loop: Get the next option;
                                          # use silent error checking;
                                          # options n and t take arguments.
  case "${options}" in                    # 
    n)                                    # If the option is n,
      NAME=${OPTARG}                      # set $NAME to specified value.
      ;;
    t)                                    # If the option is t,
      TIMES=${OPTARG}                     # Set $TIMES to specified value.
      re_isanum='^+$'                # Regex: match whole numbers only
      if ! ] ; then   # if $TIMES not whole:
        echo "Error: TIMES must be a positive, whole number."
        exit_abnormal
        exit 1
      elif ; then       # If it's zero:
        echo "Error: TIMES must be greater than zero."
        exit_abnormal                     # Exit abnormally.
      fi
      ;;
    :)                                    # If expected argument omitted:
      echo "Error: -${OPTARG} requires an argument."
      exit_abnormal                       # Exit abnormally.
      ;;
    *)                                    # If unknown (any other) option:
      exit_abnormal                       # Exit abnormally.
      ;;
  esac
done
if ; then                 # If $NAME is an empty string,
  STRING="Hi!"                            # our greeting is just "Hi!"
else                                      # Otherwise,
  STRING="Hi, $NAME!"                     # it is "Hi, (name)!"
fi
COUNT=1                                   # A counter.
while ; do           # While counter is less than
                                          # or equal to $TIMES,
  echo $STRING                            # print a greeting,
  let COUNT+=1                            # then increment the counter.
done
exit 0                                    # Exit normally.

If this script is named greeting, here’s what the output looks like with different options:

./greeting
Hi!
./greeting -n Dave
Hi, Dave!
./greeting -t 3
Hi!
Hi!
Hi!
./greeting -t 4 -n Betty
Hi, Betty!
Hi, Betty!
Hi, Betty!
Hi, Betty!
./greeting -n
Error: -n requires an argument.
Usage: ./greeting  
./greeting -t
Error: -t requires an argument.
Usage: ./greeting  
./greeting -t -1
Error: TIMES must be a positive, whole number.
Usage: ./greeting  
./greeting -t 0
Error: TIMES must be greater than zero.
Usage: ./greeting  

Решение

В компилируемых языках существуют функции-обёртки для getopt(3), что позволяет значительно упростить работу и снизить вероятность ошибок при написании подобного кода. Среди них — argp(3) разработки GNU и popt(3) от Red Hat.

На Python можно использовать .

Для скриптов на bash ничего подобного нет с тех пор, как «застопорился» проект getoptx. Но во всех своих скриптах последние несколько лет я использую собственную библиотеку: process-getopt. Это «обёртка» к getopt, значительно упрощающая жизнь разработчикам на bash и тем, кто эти скрипты сопровождает и использует.

В качестве примера приведу крошечный скрипт, использующий её для обработки командной строки:

#!/bin/bash
PROG=$(basename $0)
VERSION=»1″
USAGE=»A tiny example»

# вызываем функции process-getopt, чтобы определить некоторые опции:
source ./process-getopt
SLOT_func() { && SLOT=»yes»; } # обратный вызов для опции SLOT
add_opt SLOT «boolean option» s «» slot
TOKEN_func() { && TOKEN=»$2″; } # обратный вызов для опции TOKEN
add_opt TOKEN «this option takes a value» t n token number
add_std_opts # определяем стандартные опции: —help и т.д.

# Следующие 4 строки вызывают функции обратного вызова и удаляют опции из командной строки:
TEMP=$(call_getopt «$@») || exit 1
eval set — «$TEMP»
process_opts «$@»
shift «$?» # удаляем опции из командной строки

# Всё самое трудное позади — $@ просто содержит наши аргументы:
echo «SLOT=$SLOT»
echo «TOKEN=$TOKEN»
echo «args=$@»

И всё. Вот вывод, который вы получаете без какого-либо дополнительного кода:

$ tiny --help 
Usage: tiny    
A tiny example 

Options: 
  -s, --slot                boolean option 
  -t n, --token=number      this option takes a value 
  -h, --help                print this help and exit 
  -V, --version             print version and exit 
  -v, --verbose             do it verbosely 
  -q, --quiet               do it quietly (negates -v) 
  --                        explicitly ends the options 

А вот пример использования опций и аргументов командной строки:

$ tiny -s --token="my token" arg1 arg2 
SLOT=yes 
TOKEN=my token 
args=arg1 arg2 

Переработать ваши существующие скрипты для использования process-getopt довольно просто: следуйте примерам и руководствам, которые можно найти здесь:

А вот — прямая ссылка на руководство:

Пользуйтесь на здоровье!

Об авторе

Боб Хэппл — самый молодой среди раздражительных стариков из Prompty Corp (Золотой Берег, Австралия), заслуживший свои нашивки юниксоида в Hewlett-Packard в 1981 году. С тех пор он работал в Азии и Австралии с различными поставщиками Unix и компаниями, специализирующимися на криптографии — но всегда только в UNIX и GNU/Linux.

Потомок землемера из северо-восточной Англии, после 30 лет проживания в «стране Оз»
[Австралийцы именуют себя Aussie, что произносится как «Оззи» — отсюда и жаргонное
название страны. — прим.перев.]
он считает себя настоящим австралийцем,
но британский акцент выдаёт его с головой.

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Мой редактор ОС
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: