`pip install pocketsphinx`
`--without-python`
`sphinx/lib`
`libpocketsphinx.so libsphinxbase.so libsphinxad.so`
sphinx_lm_convert -i model.lm -o model.dmpsphinx_lm_convert -i model.dmp -ifmt dmp -o model.lm -ofmt arpasphinx_lm_convert -i model.lm -o model.lm.binsphinx_lm_convert -i model.lm.bin -ifmt bin -o model.lm -ofmt arpa
sphinxtrain -t setup "имя модели"
1- set the value current to the variable CFG_CMN $CFG_CMN = 'current';2 -$CFG_INITIAL_NUM_DENSITIES = 256; change this value to 13- $CFG_FINAL_NUM_DENSITIES = 256; change this value to 84- $CFG_N_TIED_STATES = 200; set this value to 200$CFG_CD_TRAIN = 'yes'; set this value to no if the data set is small
$ CFG_CD_TRAIN
$ DEC_CFG_MODEL_NAME
$ CFG_EXPTNAME.ci_cont
$ DEC_CFG_MODEL_NAME = "$ CFG_EXPTNAME.ci_cont";
$ DEC_CFG_MODEL_NAME = "$ CFG_EXPTNAME.ci _ $ {CFG_DIRLABEL}";
`int ps_start_utt(ps_decoder_t *ps)`
`int ps_process_raw(ps_decoder_t *ps, int16 const *data, size_t n_samples, int no_search, int full_utt)`
`int ps_end_utt(ps_decoder_t *ps)`
`char const *ps_get_hyp(ps_decoder_t *ps, int32 *out_best_score)`
```Cfor (;;) { if ((k = ad_read(ad, adbuf, 2048)) < 0) E_FATAL("Failed to read audio\n"); ps_process_raw(ps, adbuf, k, FALSE, FALSE); in_speech = ps_get_in_speech(ps); if (in_speech && !utt_started) { utt_started = TRUE; E_INFO("Listening...\n"); } if (!in_speech && utt_started) { /* speech -> silence transition, time to start new utterance */ ps_end_utt(ps); hyp = ps_get_hyp(ps, NULL ); if (hyp != NULL) { printf("%s\n", hyp); fflush(stdout); } if (ps_start_utt(ps) < 0) E_FATAL("Failed to start utterance\n"); utt_started = FALSE; E_INFO("Ready....\n"); } sleep_msec(100);}```
```Cwhile ((k = fread(adbuf, sizeof(int16), 2048, rawfd)) > 0) { ps_process_raw(ps, adbuf, k, FALSE, FALSE); in_speech = ps_get_in_speech(ps); if (in_speech && !utt_started) { utt_started = TRUE; } if (!in_speech && utt_started) { ps_end_utt(ps); hyp = ps_get_hyp(ps, NULL); if (hyp != NULL) printf("%s\n", hyp); fflush(stdout); ps_start_utt(ps); utt_started = FALSE; }}```
```$ pocketsphinx_continuous -samprate 8000 -lm ru.lm -dict ru.dic -hmm zero_ru.cd_cont_4000 -remove_noise no -infile decoder-test.wavINFO: pocketsphinx.c(152): Parsed model-specific feature parameters from zero_ru.cd_cont_4000/feat.paramsCurrent configuration:
```
`-infile <file-name>` и `-inmic <yes/no>`
`-lm ru.lm`
`-dict ru.dic`
`-hmm zero_ru.cd_cont_4000`
`-kws <file-name.txt>`
```окей скай /1e-15/```
arr=(Hello World)
arr[0]=Hello arr[1]=World
echo ${arr[0]} ${arr[1]}
${arr[*]} # Все записи в массиве ${!arr[*]}# Все индексы в массиве ${#arr[*]}# Количество записей в массиве ${#arr[0]}# Длина первой записи (нумерация с нуля)
#!/bin/basharray=(one two three four [5]=five)echo "Array size: ${#array[*]}" # Выводим размер массиваecho "Array items:" # Выводим записи массиваfor item in ${array[*]}do printf " %s\n" $itemdoneecho "Array indexes:" # Выводим индексы массиваfor index in ${!array[*]}do printf " %d\n" $indexdoneecho "Array items and indexes:" # Выводим записи массива с их индексамиfor index in ${!array[*]}do printf "%4d: %s\n" $index ${array[$index]}done
#!/bin/basharray=("first item" "second item" "third" "item")echo "Number of items in original array: ${#array[*]}"for ix in ${!array[*]}do printf " %s\n" "${array[$ix]}"doneechoarr=(${array[*]})echo "After unquoted expansion: ${#arr[*]}"for ix in ${!arr[*]}do printf " %s\n" "${arr[$ix]}"doneechoarr=("${array[*]}")echo "After * quoted expansion: ${#arr[*]}"for ix in ${!arr[*]}do printf " %s\n" "${arr[$ix]}"doneechoarr=("${array[@]}")echo "After @ quoted expansion: ${#arr[*]}"for ix in ${!arr[*]}do printf " %s\n" "${arr[$ix]}"done
Статья рассчитана на тех, кто не имеет или имеет мало опыта работы с командной строкой Unix/Linux, но желает научиться с ней эффективно взаимодействовать и разрабатывать скрипты для выполнения своих задач. Приведенные примеры справедливы для выполнения в командной оболочке bash операционной системы Ubuntu/Debian, но могут быть использованы и в других оболочках и ОС с учетом их специфики.
Существует множество дистрибутивов(форков) операционных систем(ОС) семейства Linux, наиболее известные среди них: Ubuntu, Debian, CentOS, Red Hat, Fedora, SuSE, FreeBSD, Mint.
Здесь есть большая схема со всеми форками Linux.
В каждой ОС существуют различные командные оболочки (shell), их назначение одинаково (администрирование, автоматизация, вычисления, мониторинг и т.д.), как и большинство основных команд, но реализация может отличаться. В таблице ниже приведено сравнение, в котором видно, что некоторые возможности поддерживаются не всеми оболочками.
Feature |
Bourne |
C |
TC |
Korn |
Bash |
Псевдонимы (синонимы) |
Нет |
Да |
Да |
Да |
Да |
Редактор командной строки |
Нет |
Нет |
Да |
Да |
Да |
Расширенные шаблоны файлов |
Нет |
Нет |
Нет |
Да |
Да |
Автозавершение имени файла |
Нет |
Да |
Да |
Да |
Да |
Стеки директорий (pushd and popd) |
Нет |
Да |
Да |
Нет |
Да |
История |
Нет |
Да |
Да |
Да |
Да |
Функции |
Да |
Нет |
Нет |
Да |
Да |
Горячие клавиши |
Нет |
Нет |
Да |
Нет |
Да |
Управление заданиями |
Нет |
Да |
Да |
Да |
Да |
Исправление ошибок в командах |
Нет |
Нет |
Да |
Нет |
Да |
Формат приглашения командной строки |
Нет |
Нет |
Да |
Нет |
Да |
Список доступных командных оболочек можно получить командой
cat /etc/shells
При необходимости можно установить нужную командную оболочку
командой sudo apt install <имя_оболочки>
.
Например, для установки ksh нужно выполнить
sudo apt install ksh
Если у вас уже есть виртуальная/реальная машина с установленным
Linux, или вы знаете, как её настроить, то можно пропустить
информацию из спойлера ниже. Если есть доступ по ssh к какому-либо
серверу, то Вы также можете использовать его shell для тренировок.
Используйте имеющийся доступ с осторожностью, не тренируйтесь на
продуктовых(промышленных) серверах и других критичных окружениях.
Если вы не хотите развертывать виртуальную машину(ВМ), то для
тестов можно воспользоваться онлайн-терминалами, но нужно знать и
учитывать их особенности и ограничения. Примеры
онлайн-терминалов:
https://cocalc.com/app?anonymous=terminal
https://www.tutorialspoint.com/execute_bash_online.php
https://rextester.com/l/bash_online_compiler
VirtualBox ПО для виртуализации, позволяющее запускать одну ОС внутри другой. Разумеется, существуют и другие способы развертывания ВМ, здесь мы рассмотрим лишь один из них. В данном случае, предполагается, что у вас ПК под управлением Windows или Mac. Для начала нужно скачать VirtualBox отсюда и установить его.
Также нужно скачать образ ВМ с установленной ОС Ubuntu отсюда. Качайте самую новую версию, убедитесь, что скачиваемый образ имеет формат VirtualBox (VDI).
Создадим виртуальную машину. Сначала создаем на локальном диске папку для размещения в ней ВМ и их файлов (у меня C:\vmachines). Распакуем скачанный образ диска ВМ в созданную папку:
Для распаковки потребуется архиватор 7zip или другой, поддерживающий формат .7z. Далее запускаем VirtualBox и выбираем меню Машина -> Создать:
В открывшемся окне указываем имя ВМ (тип и версия в большинстве случаев подтягиваются автоматически, если это понятно из имени), а также созданную нами папку. Объем оперативной памяти устанавливается автоматически, 1Гб достаточно для выполнения тестов. Выбираем Использовать существующий виртуальный жесткий диск, нажимаем значок выбора образа жесткого диска, в открывшемся окне нажимаем Добавить, выбираем распакованный ранее файл образом ВМ и нажимаем Открыть:
Нажимаем Выбрать:
И нажимаем Создать:
Выбираем в списке созданную ВМ и нажимаем Запустить:
После запуска ВМ появится окно для входа в ОС:
Выбираем учетную запись osboxes.org и вводим пароль osboxes.org. После входа в систему нажимаем Ctrl+Alt+T, у нас откроется окно терминала, в котором можно выполнять тесты:
Для удобства можно развернуть окно виртуальной машины на весь экран, окно терминала также будет развернуто. Для того, чтобы можно было вставлять данные из буфера в терминал ВМ и копировать их, можно включить в настройках ВМ общий буфер обмена в меню Устройства -> Общий буфер обмена -> Двунаправленный:
Создадим тестового пользователя.
Для создания пользователя нам понадобятся права
суперпользователя (root). Для их получения выполним команду
sudo su -
, указав пароль текущего пользователя
(osboxes.org). Далее создадим пользователя test командой
adduser test
. Также выдадим пользователю test права на
повышение привилегий при необходимости, выполнив команду
usermod -aG sudo test
:
osboxes@osboxes:~$ sudo su -[sudo] password for osboxes:root@osboxes:~# adduser testAdding user `test' ...Adding new group `test' (1001) ...Adding new user `test' (1001) with group `test' ...The home directory `/home/test' already exists. Not copying from `/etc/skel'.New password:Retype new password:passwd: password updated successfullyChanging the user information for testEnter the new value, or press ENTER for the default Full Name []: Room Number []: Work Phone []: Home Phone []: Other []:Is the information correct? [Y/n]root@osboxes:~# usermod -aG sudo testroot@osboxes:~#
На данном этапе уже можно использовать тестовую среду, выполняя команды в терминале (Ctrl+Alt+T). Дополнительно можно настроить подключение по ssh клиентом (например, PuTTY), мы рассмотрим это отдельно.
Итак, мы подключились к терминалу и находимся в shell. Давайте
сориентируемся в пространстве.Чтобы узнать имя машины(сервера), на
которой мы находимся, введем hostname
и нажмем
Enter:
test@osboxes:~$ hostnameosboxes
Имя пользователя, под которым мы подключены, как правило
отображается в приглашении командной строки
(test@osboxes:~$). Если имя текущего пользователя
не отображается (например, если задан другой формат приглашения),
то его можно получить командой whoami
, или
id
(также отображает группы пользователя):
test@osboxes:~$ whoamitesttest@osboxes:~$ iduid=1001(test) gid=1001(test) groups=1001(test),27(sudo)
Чтобы узнать, в какой оболочке мы находимся, нужно выполнить
команду echo $SHELL
:
test@osboxes:~$ echo $SHELL/bin/bash
Разберем, что произошло. Команда echo
выводит
значение параметра, переданного ей, в нашем случае мы передали
$SHELL. Знак $ означает, что мы обращаемся к переменной, т.е.
$SHELL возвращает значение переменной SHELL, заданной в окружении.
Список значений всех переменных можно получить командой
env
. Таким образом, отобразив значение переменной, мы
видим, что мы находимся в оболочке bash. Оболочка конкретного
пользователя указана в файле /etc/passwd, содержимое которого можно
получить так:
test@osboxes:~$ cat /etc/passwdroot:x:0:0:root:/root:/bin/bash...test:x:1001:1001:,,,:/home/test:/bin/dash
Команда cat выводит содержимое файла (часть строк в приведенном выводе пропущена и заменена на ). Из файла видим, что для пользователя test указана оболочка /bin/dash. Изменить оболочку текущего пользователя, которая будет загружаться по умолчанию, можно следующей командой(chsh сокращенное от change shell):
test@osboxes:~$ chsh -s /bin/bashPassword:
Теперь давайте проверим, в каком каталоге мы находимся, для
этого выполним команду pwd
:
test@osboxes:~$ pwd/home/test
Для получения списка файлов текущей директории используем
команду ls
. По соглашению, скрытые файлы начинаются с
точки. Для отображения их с помощью команды ls нужно добавить ключ
a. Чтобы отобразить список в расширенном формате, добавим ключ l.
Таким образом команда и её вывод будут выглядеть так:
test@osboxes:~$ ls -altotal 36drwxr-xr-x 5 test test 4096 Nov 9 01:05 .drwxr-xr-x 5 root root 4096 Nov 8 11:39 ..-rw------- 1 test test 9 Nov 8 12:28 .bash_history-rw-r--r-- 1 test test 220 Nov 8 11:39 .bash_logout-rw-r--r-- 1 test test 3771 Nov 8 11:39 .bashrcdrwxr-xr-x 4 test test 4096 Nov 8 11:40 .cachedrwxr-xr-x 4 test test 4096 Nov 8 11:40 .configdrwxr-xr-x 3 test test 4096 Nov 8 11:40 .local-rw-r--r-- 1 test test 807 Nov 8 11:39 .profile-rw-r--r-- 1 test test 0 Nov 9 01:05 .sudo_as_admin_successfultest@osboxes:~$
Для команды ls с параметрами может быть задан синоним (alias),
что упрощает её ввод. Список уже заданных синонимов можно получить
командой alias
:
test@osboxes:~$ aliasalias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'alias egrep='egrep --color=auto'alias fgrep='fgrep --color=auto'alias grep='grep --color=auto'alias l='ls -CF'alias la='ls -A'alias ll='ls -alF'alias ls='ls --color=auto'
Видим, что команда ll
является синонимом команды
ls -alF
. Синонимы могут ссылаться на другие синонимы.
Так, в приведенном выше примере команда ll
выполняет
команду ls -alF
, в которой ls
в свою
очередь также является синонимом команды ls
--color=auto
. Для любой команды с целью упрощения её ввода
при частом использовании можно задать синоним. В синонимах также
можно использовать переменные среды. Например, чтобы иметь
возможность из любой директории получить список файлов домашней
директории, можно задать синоним командой alias lshome='ls
-alF $HOME'
, таким образом, можно выполнить:
test@osboxes:~$ cd /tmptest@osboxes:/tmp$ lshometotal 40drwxr-xr-x 5 test test 4096 Nov 9 02:29 ./drwxr-xr-x 5 root root 4096 Nov 8 11:39 ../-rw------- 1 test test 47 Nov 9 02:36 .bash_history-rw-r--r-- 1 test test 220 Nov 8 11:39 .bash_logout-rw-r--r-- 1 test test 3771 Nov 8 11:39 .bashrcdrwxr-xr-x 5 test test 4096 Nov 9 02:29 .cache/drwxr-xr-x 5 test test 4096 Nov 9 02:29 .config/drwxr-xr-x 3 test test 4096 Nov 8 11:40 .local/-rw-r--r-- 1 test test 807 Nov 8 11:39 .profile-rw-rw-r-- 1 test test 72 Nov 9 02:29 .selected_editor-rw-r--r-- 1 test test 0 Nov 9 01:05 .sudo_as_admin_successful
Здесь командой cd /tmp
мы перешли в директорию
/tmp, и, находясь в ней, выполнили команду lshome
,
которая вывела список файлов директории /home/test. При этом в
синониме мы ссылаемся на переменную $HOME, в которой содержится
путь к домашней директории текущего пользователя.
Алиасы задаются в файле ~/.bash_aliases. Тильда (~) означает домашнюю директорию пользователя. В нашем случае /home/test. Также можно задать их в файле ~/.bashrc. Здесь нужно сказать пару слов об этих файлах.
При запуске bash в качестве оболочки, сначала выполняется файлы (в случае их наличия), в которых могут быть заданы различные настройки профиля и выполнены различные действия в процессе входа до появления командной строки: сначала выполняется файл /etc/profile, далее bash последовательно ищет и выполняет первый из найденных файлов в следующем порядке: ~/.bash_profile, ~/.bash_login, ~/.profile. Из указанных файлов могут вызываться и другие.
Так, например, из файла ~./profile вызывается файл ~/.bashrc, из которого, в свою очередь, в числе прочих, вызывается файл .bash_aliases при его наличии. А файл /etc/profile выполняет также все файлы .sh, находящиеся в директории etc/profile.d.
В bash существуют внутренние команды, их список можно получить
командой help. Помощь по конкретной внутренней команде можно
получить с помощью команды help <имя_команды>
,
например:
test@osboxes:~$ help pwdpwd: pwd [-LP] Print the name of the current working directory. Options: -L print the value of $PWD if it names the current working directory -P print the physical directory, without any symbolic links By default, `pwd' behaves as if `-L' were specified. Exit Status: Returns 0 unless an invalid option is given or the current directory cannot be read.
Некоторые внутренние команды bash дублированы в виде исполняемых
файлов. Это сделано, чтобы скрипты можно было корректно выполнять в
оболочках, в которых не реализованы эти команды. Например,
существует исполняемый файл /usr/bin/echo, который реализует
функционал внутренней команды echo. Команда help echo
выведет справку по внутренней команде, а команда
/usr/bin/echo help
выведет справку для
соответствующего исполняемого файла. Вывод справки для этих команд
отличается, но в целом они реализуют идентичный функционал.
Таким образом, если в оболочке не реализована внутренняя команда echo, скрипт, содержащий вызов echo, сможет успешно выполниться, т.к. для обработки вызова будет использован исполняемый файл /usr/bin/echo. Для поиска выполненных ранее команд можно использовать клавиши Вверх и Вниз, команды из истории можно редактировать и повторно выполнять. В конце статьи приведен список полезных команд.
Если данный материал интересен, то в продолжении статьи я расскажу про скрипты и их параметры, права доступа к файлам, операторы условного выполнения, выбора и циклы, функции и планировщик заданий.
В первой части статьи мы рассмотрели командные оболочки, профили, синонимы и первые команды. Под спойлером я также рассказал, как развернуть тестовую виртуальную машину.
В этой части речь пойдет о файлах скриптов, их параметрах и правах доступа. Также я расскажу про операторы условного выполнения, выбора и циклы.
Для выполнения нескольких команд одним вызовом удобно использовать скрипты. Скрипт это текстовый файл, содержащий команды для shell. Это могут быть как внутренние команды shell, так и вызовы внешних исполняемых файлов.
Как правило, имя файла скрипта имеет окончание .sh, но это не является обязательным требованием и используется лишь для того, чтобы пользователю было удобнее ориентироваться по имени файла. Для интерпретатора более важным является содержимое файла, а также права доступа к нему.
Перейдем в домашнюю директорию командой cd ~
и
создадим в ней с помощью редактора nano (nano
script.sh
)файл, содержащий 2 строки:
#!/bin/bashecho Hello!
Чтобы выйти из редактора nano после набора текста скрипта, нужно нажать Ctrl+X, далее на вопрос "Save modified buffer?" нажать Y, далее на запрос "File Name to Write:" нажать Enter. При желании можно использовать любой другой текстовый редактор.
Скрипт запускается командой ./<имя_файла>
,
т.е. ./
перед именем файла указывает на то, что нужно
выполнить скрипт или исполняемый файл, находящийся в текущей
директории. Если выполнить команду script.sh
, то будет
выдана ошибка, т.к. оболочка будет искать файл в директориях,
указанных в переменной среды PATH, а также среди встроенных команд
(таких, как, например, pwd):
test@osboxes:~$ script.shscript.sh: command not found
Ошибки не будет, если выполнять скрипт с указанием абсолютного
пути, но данный подход является менее универсальным:
/home/user/script.sh
. Однако на данном этапе при
попытке выполнить созданный файл будет выдана ошибка:
test@osboxes:~$ ./script.sh-bash: ./script.sh: Permission denied
Проверим права доступа к файлу:
test@osboxes:~$ ls -l script.sh-rw-rw-r-- 1 test test 22 Nov 9 05:27 script.sh
Из вывода команды ls
видно, что отсутствуют права
на выполнение. Рассмотрим подробнее на картинке:
Права доступа задаются тремя наборами: для пользователя, которому принадлежит файл; для группы, в которую входит пользователь; и для всех остальных. Здесь r, w и x означают соответственно доступ на чтение, запись и выполнение.
В нашем примере пользователь (test) имеет доступ на чтение и
запись, группа также имеет доступ на чтение и запись, все остальные
только на чтение. Эти права выданы в соответствии с правами,
заданными по умолчанию, которые можно проверить командой
umask -S
. Изменить права по умолчанию можно, добавив
вызов команды umask с нужными параметрами в файл профиля
пользователя (файл ~/.profile), либо для всех пользователей в
общесистемный профиль (файл /etc/profile).
Для того, чтобы установить права, используется команда
chmod <параметры> <имя_файла>
. Например,
чтобы выдать права на выполнение файла всем пользователям, нужно
выполнить команду:
test@osboxes:~$ chmod a+x script.sh
Чтобы выдать права на чтение и выполнение пользователю и группе:
test@osboxes:~$ chmod ug+rx script.sh
Чтобы запретить доступ на запись (изменение содержимого) файла всем:
test@osboxes:~$ chmod a-w script.sh
Также для указания прав можно использовать маску. Например, чтобы разрешить права на чтение, запись, выполнение пользователю, чтение и выполнение группе, и чтение для остальных, нужно выполнить:
test@osboxes:~$ chmod 754 script.sh
Будут выданы права -rwxr-xr--
:
test@osboxes:~$ ls -la script.sh-rwxr-xr-- 1 test test 22 Nov 9 05:27 script.sh
Указывая 3 цифры, мы задаем соответствующие маски для каждой из трех групп. Переведя цифру в двоичную систему, можно понять, каким правам она соответствует. Иллюстрация для нашего примера:
Символ перед наборами прав доступа указывает на тип файла (
означает обычный файл, d
директория, l
ссылка, c
символьное устройство, b
блочное устройство, и т. д.). Соответствие числа, его двоичного
представления и прав доступ можно представить в виде таблицы:
Число |
Двоичный вид |
Права доступа |
0 |
000 |
Нет прав |
1 |
001 |
Только выполнение (x) |
2 |
010 |
Только запись (w) |
3 |
011 |
Запись и выполнение (wx) |
4 |
100 |
Только чтение (r) |
5 |
101 |
Чтение и выполнение (rx) |
6 |
110 |
Чтение и запись (rw) |
7 |
111 |
Чтение, запись и выполнение (rwx) |
Выдав права на выполнение, можно выполнить скрипт:
test@osboxes:~$ ./script.shHello!
Первая строка в скрипте содержит текст #!/bin/bash
.
Пара символов #!
называется Шебанг (англ. shebang) и
используется для указания интерпретатору, с помощью какой оболочки
выполнять указанный скрипт. Это гарантирует корректность исполнения
скрипта в нужной оболочке в случае, если у пользователя будет
указана другая.
Также в скриптах можно встретить строку #!/bin/sh
.
Но, как правило, /bin/sh является ссылкой на конкретный shell, и в
нашем случае /bin/sh ссылается на /bin/dash, поэтому лучше явно
указывать необходимый интерпретатор. Вторая строка содержит команду
echo Hello!
, результат работы которой мы видим в
приведенном выводе.
Для того, чтобы обеспечить некоторую универсальность, существует
возможность при вызове передавать скрипту параметры. В этом случае
вызов скрипта будет выглядеть так: <имя_скрипта>
<параметр1> <параметр2>
, например
./script1.sh Moscow Russia
.
Для того, чтобы получить значение первого параметра, необходимо
в скрипте указать $1
, второго - $2
, и
т.д. Существует также ряд других переменных, значения которых можно
использовать в скрипте:
$0
имя скрипта
$#
количество переданных параметров
$$
PID(идентификатор) процесса, выполняющего
скрипт
$?
код завершения предыдущей команды
Создадим файл script1.sh следующего содержания:
#!/bin/bashecho Hello, $USER!printf "Specified City is: %s, Country is: %s\n" $1 $2
Выдадим права на выполнение и выполним скрипт с параметрами:
test@osboxes:~$ chmod u+x script1.shtest@osboxes:~$ ./script1.sh Moscow RussiaHello, test!Specified City is: Moscow, Country is: Russia
Мы передали 2 параметра, указывающие город и страну, и использовали их в скрипте, чтобы сформировать строку, выводимую командой printf. Также для вывода в строке Hello использовали имя пользователя из переменной USER.
Для того, чтобы передать значения параметров, состоящие из нескольких слов (содержащие пробелы), нужно заключить их в кавычки:
test@osboxes:~$ ./script1.sh "San Francisco" "United States"Hello, test!Specified City is: San Francisco, Country is: United States
При этом нужно доработать скрипт, чтобы в команду printf параметры также передавались в кавычках:
printf "Specified City is: %s, Country is: %s\n" "$1" "$2"
Из приведенных примеров видно, что при обращении к переменной для получения её значения используется символ $. Для того, чтобы сохранить значение переменной просто указывается её имя:
COUNTRY=RUSSIAecho $COUNTRY
Так же, как и в языках программирования, в bash существуют операторы условного выполнения выполнение определенных действий при определенных условиях. Кроме того, существует возможность повторного выполнения определенного блока команд пока выполняется заданное условие операторы цикла. Рассмотрим каждый из них подробнее.
Оператор условного выполнения представляет собой конструкцию вида:
if [ <условие> ]then <команда1>else <команда2>fi
Создадим скрипт, проверяющий длину введенной строки (например, для проверки длины пароля), которая должна быть не меньше (т.е. больше) 8 символов:
#!/bin/bashecho Hello, $USER!echo -n "Enter string: "read strif [ ${#str} -lt 8 ]then echo String is too shortelse echo String is okfi
Выполним 2 теста, с длиной строки 5 и 8 символов:
test@osboxes:~$ ./script2.shHello, test!Enter string: abcdeString is too shorttest@osboxes:~$ ./script2.shHello, test!Enter string: abcdefghString is ok
Командой read str
мы получаем значение, введенное
пользователем и сохраняем его в переменную str. С помощью выражения
${#str}
мы получаем длину строки в переменной str и
сравниваем её с 8. Если длина строки меньше, чем 8 (-lt
8
), то выдаем сообщение String is too short, иначе String is
ok.
Условия можно комбинировать, например, чтобы указать, чтоб длина
должна быть не меньше восьми 8 и не больше 16 символов, для условия
некорректных строк нужно использовать выражение [ ${#str} -lt
8 ] || [ ${#str} -gt 16 ]
. Здесь ||
означает
логическое "ИЛИ", а для логического "И" в bash используется
&&
.
Условия также могут быть вложенными:
#!/bin/bashecho Hello, $USER!echo -n "Enter string: "read strif [ ${#str} -lt 8 ]then echo String is too shortelse if [ ${#str} -gt 16 ] then echo String is too long else echo String is ok fifi
Здесь мы сначала проверяем, что строка меньше 8 символов, отсекая минимальные значения, и выводим "String is too short", если условие выполняется. Если условие не выполняется(строка не меньше 8 символов) - идем дальше(первый else) и проверяем, что строка больше 16 символов. Если условие выполняется - выводим "String is too long", если не выполняется(второй else) - выводим "String is ok".
Результат выполнения тестов:
test@osboxes:~$ ./script2.shHello, test!Enter string: abcdefString is too shorttest@osboxes:~$ ./script2.shHello, test!Enter string: abcdefghijklmnopqrstuvString is too longtest@osboxes:~$ ./script2.shHello, test!Enter string: abcdefghijklString is ok
Оператор выбора выглядит следующим образом:
case "$переменная" in "$значение1" ) <команда1>;; "$значение2" ) <команда2>;;esac
Создадим новый скрипт, который будет выводить количество спутников для указанной планеты:
#!/bin/bashecho -n "Enter the name of planet: "read PLANETecho -n "The $PLANET has "case $PLANET in Mercury | Venus ) echo -n "no";; Earth ) echo -n "one";; Mars ) echo -n "two";; Jupiter ) echo -n "79";; *) echo -n "an unknown number of";;esacecho " satellite(s)."
Тест:
test@osboxes:~$ ./script3.shEnter the name of planet: MercuryThe Mercury has no satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: VenusThe Venus has no satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: EarthThe Earth has one satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: MarsThe Mars has two satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: JupiterThe Jupiter has 79 satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: Alpha555The Alpha555 has an unknown number of satellite(s).
Здесь в зависимости от введенного названия планеты скрипт
выводит количество её спутников.
В case мы использовали выражение Mercury | Venus
, где
|
означает логическое "ИЛИ" (в отличие от if, где
используется ||
), чтобы выводить "no" для Меркурия и
Венеры, не имеющих спутников. В case также можно указывать
диапазоны с помощью []
. Например, скрипт для проверки
принадлежности диапазону введенного символа будет выглядеть
так:
#!/bin/bashecho -n "Enter key: "read -n 1 keyechocase "$key" in [a-z] ) echo "Lowercase";; [A-Z] ) echo "Uppercase";; [0-9] ) echo "Digit";; * ) echo "Something else";;esac
Мы проверяем символ на принадлежность одному из четырех диапазонов(английские символы в нижнем регистре, английские символы в верхнем регистре, цифры, все остальные символы). Результат теста:
test@osboxes:~$ ./a.shEnter key: tLowercasetest@osboxes:~$ ./a.shEnter key: PUppercasetest@osboxes:~$ ./a.shEnter key: 5Digittest@osboxes:~$ ./a.shEnter key: @Something else
Цикл может задаваться тремя разными способами:
Выполняется в интервале указанных значений (либо указанного множества):
for [ <условие> ] do <команды> done
Выполняется, пока соблюдается условие:
while [ <условие> ] do <команды> done
Выполняется, пока не перестанет соблюдаться условие:
until [ <условие> ] do <команды> done
Добавим в скрипт с планетами цикл с условием while и будем выходить из скрипта, если вместо имени планеты будет введено EXIT
#!/bin/bashPLANET="-"while [ $PLANET != "EXIT" ]do echo -n "Enter the name of planet: " read PLANET if [ $PLANET != "EXIT" ] then. echo -n "The $PLANET has " case $PLANET in Mercury | Venus ) echo -n "no";; Earth ) echo -n "one";; Mars ) echo -n "two";; Jupiter ) echo -n "79";; *) echo -n "an unknown number of";; esac echo " satellite(s)." fidone
Здесь мы также добавили условие, при котором оператор выбора будет выполняться только в случае, если введено не EXIT. Таким образом, мы будем запрашивать имя планеты и выводить количество её спутников до тех пор, пока не будет введено EXIT:
test@osboxes:~$ ./script4.shEnter the name of planet: EarthThe Earth has one satellite(s).Enter the name of planet: JupiterThe Jupiter has 79 satellite(s).Enter the name of planet: Planet123The Planet123 has an unknown number of satellite(s).Enter the name of planet: EXIT
Нужно отметить, что условие while [ $PLANET != "EXIT"
]
можно заменить на until [ $PLANET == "EXIT"
]
. ==
означает "равно", !=
означает "не равно".
Приведем пример циклов с указанием интервалов и множеств:
#!/bin/bashrm *.datecho -n "File count: "read countfor (( i=1; i<=$count; i++ ))do head -c ${i}M </dev/urandom >myfile${i}mb.datdonels -l *.datecho -n "Delete file greater than (mb): "read maxsizefor f in *.datdo size=$(( $(stat -c %s $f) /1024/1024)) if [ $size -gt $maxsize ] then. rm $f echo Deleted file $f fidonels -l *.datread
Сначала мы запрашиваем у пользователя количество файлов, которые
необходимо сгенерировать (read count
).
В первом цикле (for (( i=1; i<=$count; i++ ))
)
мы генерируем несколько файлов, количество которых задано в
переменной count, которую введет пользователь. В команду
head
передаем количество мегабайт, считываемых из
устройства /dev/random, чтение из которого позволяет получать
случайные байты.
Символ <
указывает перенаправление входного
потока (/dev/urandom) для команды head
.
Символ >
указывает перенаправление выходного
потока (вывод команды head -c ${i}M
) в файл, имя
которого мы генерируем на основе постоянной строки с добавлением в
неё значения переменной цикла (myfile${i}mb.dat
).
Далее мы запрашиваем размер, файлы больше которого необходимо удалить.
Во втором цикле (for f in *.dat
) мы перебираем все
файлы .dat в текущей директории и сравниваем размер каждого файла
со значением, введенным пользователем. В случае, если размер файла
больше, мы удаляем этот файл.
В конце скрипта выводим список файлов .dat, чтобы отобразить
список оставшихся файлов (ls -l *.dat
). Результаты
теста:
test@osboxes:~$ ./script5.shFile count: 10-rw-rw-r-- 1 test test 10485760 Nov 9 08:48 myfile10mb.dat-rw-rw-r-- 1 test test 1048576 Nov 9 08:48 myfile1mb.dat-rw-rw-r-- 1 test test 2097152 Nov 9 08:48 myfile2mb.dat-rw-rw-r-- 1 test test 3145728 Nov 9 08:48 myfile3mb.dat-rw-rw-r-- 1 test test 4194304 Nov 9 08:48 myfile4mb.dat-rw-rw-r-- 1 test test 5242880 Nov 9 08:48 myfile5mb.dat-rw-rw-r-- 1 test test 6291456 Nov 9 08:48 myfile6mb.dat-rw-rw-r-- 1 test test 7340032 Nov 9 08:48 myfile7mb.dat-rw-rw-r-- 1 test test 8388608 Nov 9 08:48 myfile8mb.dat-rw-rw-r-- 1 test test 9437184 Nov 9 08:48 myfile9mb.datDelete file greater than (mb): 5Deleted file myfile10mb.datDeleted file myfile6mb.datDeleted file myfile7mb.datDeleted file myfile8mb.datDeleted file myfile9mb.dat-rw-rw-r-- 1 test test 1048576 Nov 9 08:48 myfile1mb.dat-rw-rw-r-- 1 test test 2097152 Nov 9 08:48 myfile2mb.dat-rw-rw-r-- 1 test test 3145728 Nov 9 08:48 myfile3mb.dat-rw-rw-r-- 1 test test 4194304 Nov 9 08:48 myfile4mb.dat-rw-rw-r-- 1 test test 5242880 Nov 9 08:48 myfile5mb.dat
Мы создали 10 файлов (myfile1mb.dat .. myfile10mb.dat) размером
от 1 до 10 мегабайт и далее удалили все файлы .dat размером больше
5 мегабайт. При этом для каждого удаляемого файла вывели сообщение
о его удалении (Deleted file myfile10mb.dat
). В конце
вывели список оставшихся файлов (myfile1mb.dat ..
myfile5mb.dat).
В следующей части мы рассмотрим функции, планировщик заданий cron, а также различные полезные команды.
Во второй части статьи мы обсудили файлы скриптов, их параметры и права доступа. Также поговорили про операторы условного выполнения, выбора и циклы. В этой, заключительной части мы рассмотрим функции и планировщик заданий cron. Также приведу различные полезные команды и ссылки.
Часто используемые, повторяющиеся блоки имеет смысл выделять в отдельные функции, чтобы при необходимости их запускать, передавая параметры.
Определение функции выглядит следующим образом:
<имя_функции>() { <команды> return <число>}funciton <имя_функции>() { <команды> return <число>}
Первый вариант ближе к синтаксису языка С и считается более переносимым, во втором варианте круглые скобки можно не указывать. Также может отсутствовать оператор return, если функция не возвращает значения.
Самый простой пример скрипта, содержащего объявление и вызов функции будет выглядеть так:
#!/bin/bashf() { echo Test}f
Мы объявили функцию f
, которая выводит слово Test,
и затем вызвали её:
test@osboxes:~$ ./script6.shTest
Так же, как и скрипт, функция может принимать параметры и использовать их, ссылаясь по номеру ($1, $2, , $N). Вызов функции с параметрами в скрипте осуществляется так:
<имя функции> <параметр1> <параметр2> <параметрN>
Функция может возвращать результат своего выполнения (код
завершения) в виде числового значения в диапазоне от 0 до 255.
Принято считать, что если функция возвращает 0, то она выполнилась
успешно, во всех остальных случаях значение содержит код ошибки.
Чтобы получить код завершения функции в скрипте, необходимо
обратиться к переменной $?
. Добавив параметры и
возвращаемое значение, получим следующий скрипт:
#!/bin/bashsumm() { re='^[0-9]+$' if ! [[ $1 =~ $re ]] ; then return 1 elif ! [[ $2 =~ $re ]] ; then return 2 else s=$(($1 + $2)) return 0 fi}summ $1 $2case $? in 0) echo "The sum is: $s" ;; 1) echo "var1 is not a nubmer" ;; 2) echo "var2 is not a nubmer" ;; *) echo "Unknown error" ;;esac
Здесь мы создали функцию summ, которая принимает 2 параметра и с
помощью регулярного выражения ^[0-9]+$
проверяет,
является ли каждый из переданных параметров числом. В случае, если
первый параметр не число, то код завершения функции будет 1, если
второй параметр не число, то код завершения функции будет 2. Во
всех остальных случаях функция вычисляет сумму переданных
параметров, сохраняя результат в глобальной переменной s.
Скрипт вызывает функцию, передавая её на вход параметры, которые
были переданы ему самому при вызове. Далее проверяется код
завершения функции и выдается соответствующая ошибка, если код не
равен 0, иначе выдается сумма, сохраненная в переменной
s
. Протестируем скрипт:
test@osboxes.org:~$ ./script7.sh abc 123var1 is not a nubmertest@osboxes.org:~$ ./script7.sh 234 defvar2 is not a nubmertest@osboxes.org:~$ ./script7.sh 10 15The sum is: 25
По умолчанию переменные объявляются глобальными, т.е. видны в любом блоке скрипта. Переменные, объявленные как локальные, имеют ограниченную область видимости, и видны только в пределах блока, в котором они были объявлены. В случае с функцией это означает, что локальная переменная "видна" только в теле функции, в которой она была объявлена.
Для того, чтобы объявить переменную локальной, используется
слово local, например local loc_var=123
. Важно
отметить, все что переменные, объявляемые в теле функции, считаются
необъявленными до тех пор, пока эта функция не будет вызвана.
Объединим все воедино, создав на основе рассмотренных ранее структур следующий скрипт:
#!/bin/bashclearFiles() { rm *.dat if [ $? -eq 0 ] then echo Files deleted fi}genFiles() { for (( i=1; i<=$1; i++ )) do head -c ${i}M </dev/urandom >myfile${i}mb.dat done ls -l *.dat}delFiles() {for f in *.dat do size=$(( $(stat -c %s $f) /1024/1024 )) if [ $size -gt $1 ] then rm $f echo Deleted file $f fi done ls -l *.dat}showWeather() { curl -s "https://weather-broker-cdn.api.bbci.co.uk/en/observation/rss/$1" | grep "<desc" | sed -r 's/<description>//g; s/<\/description>//g'}menu() { clear echo 1 - Delete all .dat files echo 2 - Generate .dat files echo 3 - Delete big .dat files echo 4 - List all files echo 5 - Planet info echo 6 - Show weather echo "x/q - Exit" echo -n "Choose action: " read -n 1 key echo}while truedo case "$key" in "x" | "q" | "X" | "Q") break ;; "1") clearFiles read -n 1 ;; "2") echo -n "File count: " read count genFiles $count read -n 1 ;; "3") echo -n "Delete file greater than (mb): " read maxsize delFiles $maxsize read -n 1 ;; "4") ls -la read -n 1 ;; "5") ./script4.sh read -n 1 ;; "6") echo -n "Enter city code: " # 524901 498817 5391959 read citycode showWeather $citycode read -n 1 ;; esac menudone
В данном скрипте мы объявили 5 функций:
clearFiles
genFiles
delFiles
showWeather
menu
Далее реализован бесконечный цикл с помощью оператора while с условием true, в который вложен оператор выбора в зависимости от нажатой клавиши, а также вызов функции menu для отображения списка доступных действий. Данный скрипт в интерактивном режиме позволяет выполнить следующие действия:
Удалить все файлы .dat в текущей директории
Создать указанное количество файлов
Удалить файлы больше определенного размера
Вывести список всех файлов текущей директории
Запустить скрипт, выдающий информацию о планетах
Отобразить погоду по коду указанного города
Не будем подробно разбирать все строки кода, скажем только, что в скрипте демонстрируется вызов другого скрипта, получение информации из интернет, и её парсинг (выделение нужной информации), команда break для выхода из цикла и ряд других возможностей. Предлагаю желающим самостоятельно протестировать скрипт, посмотреть, какие могут быть ошибки при его работе и при вводе различных значений, какие проверки можно добавить, и что можно улучшить. Приведем результаты тестирования:
test@osboxes.org:~$ ./script8.sh1 - Delete all .dat files2 - Generate .dat files3 - Delete big .dat files4 - List all files5 - Planet info6 - Show weatherx/q - ExitChoose action: 4total 40drwxr-xr-x 2 test test 4096 Feb 16 15:56 .drwxr-xr-x 6 root root 4096 Feb 16 15:54 ..-rw------- 1 test test 42 Feb 16 15:55 .bash_history-rw-r--r-- 1 test test 220 Feb 16 15:54 .bash_logout-rw-r--r-- 1 test test 3771 Feb 16 15:54 .bashrc-rw-r--r-- 1 test test 807 Feb 16 15:54 .profile-rw-r--r-- 1 test test 1654 Feb 16 12:40 input.xml-rwxr-xr-x 1 test test 281 Feb 16 14:02 script4.sh-rwxr-xr-x 1 test test 328 Feb 16 13:40 script7.sh-rwxr-xr-x 1 test test 1410 Feb 16 15:24 script8.sh
1 - Delete all .dat files2 - Generate .dat files3 - Delete big .dat files4 - List all files5 - Planet info6 - Show weatherx/q - ExitChoose action: 2File count: 8-rw-rw-r-- 1 test test 1048576 Feb 16 16:00 myfile1mb.dat-rw-rw-r-- 1 test test 2097152 Feb 16 16:00 myfile2mb.dat-rw-rw-r-- 1 test test 3145728 Feb 16 16:00 myfile3mb.dat-rw-rw-r-- 1 test test 4194304 Feb 16 16:00 myfile4mb.dat-rw-rw-r-- 1 test test 5242880 Feb 16 16:00 myfile5mb.dat-rw-rw-r-- 1 test test 6291456 Feb 16 16:00 myfile6mb.dat-rw-rw-r-- 1 test test 7340032 Feb 16 16:00 myfile7mb.dat-rw-rw-r-- 1 test test 8388608 Feb 16 16:00 myfile8mb.dat
1 - Delete all .dat files2 - Generate .dat files3 - Delete big .dat files4 - List all files5 - Planet info6 - Show weatherx/q - ExitChoose action: 3Delete file greater than (mb): 5Deleted file myfile6mb.datDeleted file myfile7mb.datDeleted file myfile8mb.dat-rw-rw-r-- 1 test test 1048576 Feb 16 16:00 myfile1mb.dat-rw-rw-r-- 1 test test 2097152 Feb 16 16:00 myfile2mb.dat-rw-rw-r-- 1 test test 3145728 Feb 16 16:00 myfile3mb.dat-rw-rw-r-- 1 test test 4194304 Feb 16 16:00 myfile4mb.dat-rw-rw-r-- 1 test test 5242880 Feb 16 16:00 myfile5mb.dat
1 - Delete all .dat files2 - Generate .dat files3 - Delete big .dat files4 - List all files5 - Planet info6 - Show weatherx/q - ExitChoose action: 1Files deleted
1 - Delete all .dat files2 - Generate .dat files3 - Delete big .dat files4 - List all files5 - Planet info6 - Show weatherx/q - ExitChoose action: 5Enter the name of planet: MarsThe Mars has two satellite(s).
1 - Delete all .dat files2 - Generate .dat files3 - Delete big .dat files4 - List all files5 - Planet info6 - Show weatherx/q - ExitChoose action: 6Enter city code: 524901 Latest observations for Moscow from BBC Weather, including weather, temperature and wind information Temperature: -11C (11F), Wind Direction: Northerly, Wind Speed: 0mph, Humidity: 84%, Pressure: 1018mb, , Visibility: Moderate
Примечание: для тестирования работы с данными из Интернет (пункт
6 в меню выбора скрипта) может потребоваться установка curl, это
можно сделать командой sudo apt install curl
.
В случае, когда есть необходимость периодического запуска скриптов, полезно использовать планировщик cron, он позволяет задать расписание запуска скрипта и не требует присутствия администратора.
Просмотр заданий пользователя выполняется командой crontab
l
. Для редактирования и создания новых задания используется
команда crontab e
. Строки для запуска команд
планировщика в файле конфигурации cron имеют следующий формат:
m h dom mon dow command parameters
Где m минута, h час, dom день месяца, mon месяц, dow день недели, command команда, parameters список параметров. Наглядно этот формат можно представить так:
Например, для того, чтобы в 10 и 30 минут каждого часа каждый день месяца весь год по будням запускать команду, нужно указать следующее:
10,30 * * * 1-5 command parameter1 parameter2
Более простой пример, каждые 15 минут выполнять команду:
*/15 * * * * command
Создадим скрипт для резервного копирования домашней директории пользователя, который будет создавать новый файл бэкапа при каждом запуске:
#!/bin/bashUSER=`whoami`BACKUP_DIR=/tmp/backup_${USER}BACKUP_FILE=${USER}_$(date +%Y%m%d%M%H%S).tgzmkdir -p $BACKUP_DIRcd /tar -zcf $BACKUP_DIR/$BACKUP_FILE home/$USER
Поставим скрипт на выполнение каждый день в 22:00, выполнив
команду crontab -e
и добавив с помощью открывшегося
редактора строку:
00 22 * * * ./backup_home.sh
Проверить, что задача добавлена в планировщик, можно командой
crontab -l
:
test@osboxes.org:~$ crontab -l00 22 * * * ./backup_home.sh
В результате каждый день в 22:00 будет создаваться резервная копия домашней директории пользователя (в приведенном примере для демонстрации запуск скрипта выполняется каждую минуту):
test@osboxes.org:~$ cd /tmp/backup_test/test@osboxes:/tmp/backup_test$ lltotal 80drwxrwxr-x 2 test test 4096 Feb 16 16:38 ./drwxrwxrwt 17 root root 4096 Feb 16 16:30 ../-rw-rw-r-- 1 test test 4431 Feb 16 16:30 test_20210216301601.tgz-rw-rw-r-- 1 test test 4431 Feb 16 16:31 test_20210216311601.tgz-rw-rw-r-- 1 test test 4431 Feb 16 16:32 test_20210216321601.tgz-rw-rw-r-- 1 test test 4431 Feb 16 16:33 test_20210216331601.tgz-rw-rw-r-- 1 test test 4431 Feb 16 16:34 test_20210216341601.tgztest@osboxes:/tmp/backup_test$
Нужно отметить, что директория /tmp в примере использована исключительно для тестов, т.к. она предназначена для хранения временных файлов, и использовать её для хранения резервных копий нельзя. Правильное место размещения бэкапов может подсказать системный администратор.
Список встроенных команд интерпретатора: help
Помощь по команде: <команда> --help
Мануал по команде: man <команда>
Версия команды: <команда> --version
Список доступных оболочек: cat /etc/shells
Список пользователей и их оболочек: cat /etc/passwd
Текущая директория: pwd
Список файлов текущей директории: ls -la
Текущий пользователь: id
Переменные среды: set
Версия ОС: cat /etc/os-release
Версия ядра: uname -a
Получить привилегии суперпользователя: sudo su -
Установка программы в Debian: apt install mc
Посмотреть утилизацию(загрузку): top
Свободное место: df -h
Сколько занимает директория: du -ks /var/log
Конфигурация сетевых интерфейсов: ifconfig -a
Объем оперативной памяти: free -m
Информация о блочных устройствах(дисках): lsblk
Информация о процессорах: cat /proc/cpuinfo
Список установленных пакетов: apt list --installed
Список и статус сервисов: service --status-all
Перезапуск сервиса: service apache2 restart
Скачать файл: wget
https://www.gnu.org/graphics/gplv3-with-text-136x68.png
Получить веб-страницу по URL: curl https://www.google.com
Показать задания планировщика: crontab -l
Редактировать задания планировщика: crontab -e
Вывести новые сообщения в системном логе: tail -f
/var/log/syslog
Подсчитать количество строк в выводе команды: <команда> | wc
-l
Изменить права доступа к файлу (разрешить выполнение всем): chmod
a+x <файл>
Список процессов: ps -ef
Проверить, запущен ли процесс: ps -ef | grep <процесс>
Перейти в предыдущий каталог: cd -
Завершить процесс (сигнал kill): kill -9
Удаление файла: rm <имя файла>
Удаление директории: rm -rf <имя директории>
Редактировать файл: nano <имя_файла>
Топ 10 процессов по использованию памяти: ps aux | awk '{print
$6/1024 " MB\t\t" $11}' | sort -nr | head
Руководство по bash: GNU Bash manual
Расширенное руководство по Bash: Advanced Bash-Scripting
Guide
Статья на Википедии: Bash
Описание команд и утилит оболочки bash: SS64
Часто задаваемые вопросы о Debian GNU/Linux: Debian FAQ
В данной статье мы рассмотрели основы разработки скриптов с использованием bash, изучили базовые структуры, позволяющие реализовывать логику работы скрипта в зависимости от различных условий, познакомились с планировщиком. Bash является очень гибким инструментом, позволяющим реализовать задачи различного уровня сложности. При подключении внешних утилит предоставляет большие возможности для автоматизации.
На этом пока все, надеюсь, было интересно!
Какие другие полезные команды вы знаете и используете в работе?
Какие интересные конструкции приходилось использовать?
Какие задачи решали?
Всем удачного скриптинга, делитесь мнениями в комментариях!
Сегодня я хочу поделиться с вами небольшим bash-скриптом, который я успешно использую уже в течение нескольких лет.
Для начала опишу ситуацию, часто у меня возникающую и по сей день, которая и сподвигла на написание скрипта. Во время работы над новой задачей я периодически сохраняю текущее состояние проекта в гите, при этом сами коммиты не несут никакой смысловой нагрузки ни по содержанию ни по коммит-сообщениям.
В результате локальная ветка законченной задачи выглядит примерно так:
Наступает следующий этап:
1) Все коммиты из задачи объединяются в один большой
(feature-all-private
на картинке)
2) Этот большой коммит разбивается на аккуратные
коммиты с внятными описаниями (feature-public
на
картинке):
Проблема: во время разбития коммитов я обычно провожу легкий рефекторинг, меняю коммиты местами и прочее, что потенциально может привести к незапланированным изменениям в коде (что-то потерялось при ребейзе или разрешении конфликтов, например)
Решение: в гите на тот момент не было
инструмента быстро сравнить содержимое двух веток, поэтому я решил
написать небольшой скрипт git-cmp
Инструкция по установке достаточно простая нужно локально
сохранить bash-скрипт и добавить алиас в гите.
Теперь мы можем одной командой сравнить "рабочую" версию задачи
(feature-private
) с конечной "публикуемой" версией
(feature-public
):
git checkout feature-publicgit cmp feature-private
Вывод которой покажет, что либо изменений нет и ветки идентичны:
$ git cmp feature-private common parent commit: 758d3fa cleaning... ...done
Либо изменения есть и они будут показаны с использованием
git-diff
:
$ git cmp feature-privatecommon parent commit: 758d3fadiff --git a/test.txt b/test.txtindex 1e65656..2013c09 100644--- a/test.txt+++ b/test.txt@@ -1,4 +1,4 @@-oldLine+newLinecleaning......done
Таким образом мы можем быстро убедиться, что мы ничего не сломали при разбитии коммитов и ветка спокойно может быть опубликована:
Надеюсь, этот скрипт будет полезен кому-то еще
Исходники выложены на гитхабе
Картинки были созданы с помощью
codepen.io
Ускорить имеющиеся пайплайны в 2 раза (до, хотя бы, получаса). Попутно повысив стабильность до 90%.
Забегая вперед, скажу что требуемые показатели были достигнуты.
Для инкрементальной сборки lerna filter options:
lerna run build:packages --since --include-merged-tags --include-dependencies
Чтобы попасть в инкремент пакеты должны проходить фазу lerna publish в артифакторий (JFrog):
# Masterlerna publish patch --yes# Featurelerna publish prepatch --yes --no-push --preid "${PREID}"
При такой организации pipeline, возможно только вертикальное масштабирование путём увеличения мощностей elastic агентов.
Этот подход крайне ограничен. И с ростом числа пакетов средняя длительность постепенно росла (~1ч).
Надо заметить, что в силу короткого релизного цикла (сутки), стабильность JFrog и, как следствие, всего pipeline была низка (~70%).
Собирать каждое приложение независимо от остальных.
На входе монорепозиторий
На выходе production image приложения.
Тестировать тоже независимо от остальных.
На входе production image (зависимости устанвлены, все пакеты
собраны)
На выходе отчеты о тестировании и покрытии.
Это позволит собирать и тестировать в параллельном режиме, масштабируясь горизонтально по мере развития линейки продуктов.
Но в таком случае размер node_modules составил бы ~1.5Gb (суммарные зависимости всех пакетов монорепозитория). Что негативно отразилось бы на размере image, времени его загрузки в AWS ECR, и времени развертывания.
Чтобы "урезать" ("сфокусировать") монорепозиторий для сборки, тестирования и развертывания одного конкретного приложения, достаточно найти подмножество пакетов в общем графе пакетов и переписать декларацию workspaces в корневом package.json непосредственно перед сборкой на CI.
#!/usr/bin/env nodeconst { spawnSync } = require('child_process');const { existsSync, promises: { readFile, writeFile } } = require('fs');const { join, dirname, relative, normalize } = require('path');const PACK_JSON_PATH = './package.json';(async (apps) => { const packJSON = JSON.parse((await readFile(PACK_JSON_PATH)).toString()); await spawnSync('yarn', ['global', 'add', 'lerna'], { shell: true }); const locations = await listPackages(apps); const [someLocation] = locations; const basePath = findBasePath(someLocation); // All paths should be relative to monorepo root const workspaces = locations.map((loc) => normalize(relative(basePath, loc))); packJSON.workspaces.packages = workspaces; const packJSONStr = JSON.stringify(packJSON, undefined, 2); await writeFile(PACK_JSON_PATH, `${packJSONStr}\n`);})( process.argv.slice(2),);async function listPackages(apps = []) { const filterOptions = apps.flatMap((app) => ['--scope', app]); const { stdout } = await spawnSync( 'lerna', ['ls', '-pa', '--include-dependencies', ...filterOptions], { shell: true }, ); return String(stdout).split(/[\r\n]+/).filter(Boolean);}function findBasePath(packageLocation) { return existsSync(join(packageLocation, 'lerna.json')) ? packageLocation : findBasePath(dirname(packageLocation));}
После "фокусировки" все команды (в том числе и changed) будут относится лишь к подмножетсву пакетов конкретного приложения.
Размер node_modules удалось снизить в среднем в 3 раза.
Lerna fixed mode, отказ от lerna publish
и
артифактория позволили повысить стабильность и упростить логику
pipeline.
Но как же быть с инкрементальностью сборок?
Для инкремента достаточно отслеживать изменения через команду lerna changed
lerna changed -a --include-merged-tags
Если изменений не обнаружено, то можно переиспользовать latest image приложения для развертывания и тестирования:
#!/usr/bin/env bashAPP=$1lerna-focus.js "${APP}"function nothing_changed() { [[ -z "$(lerna changed -a --include-merged-tags || true)" ]]}function pull_latest_image() {...}function push_latest_image() {...}function tag_latest_with_version() {...}pull_latest_imageif nothing_changed; then tag_latest_with_version exitfibuild-app.sh "${APP}"if is-master.sh; then push_latest_imagefi
Сейчас активно набирают обороты такие решения как Nx: Extensible Dev Tools for Monorepos. Это предмет следующих разборов.
Если эта статья окажется полезной, то в следующей расскажу о горизонтальном масштабировании "на коленке" модульных тестов (Angular, Jest, ElasticSearch, Bamboo CI).