Русский
Русский
English
Статистика
Реклама

Bash scripting

Как сделать из планшетного USB-сканера сетевой с памятью, используя Raspberry Pi. Видеолекция с демонстрацией

23.11.2020 18:14:08 | Автор: admin
Раз уж сегодня посыпались публикации про Raspberry Pi, вставлю свои пять копеек. Выложил на днях на Youtube лекцию с демонстрацией, как из Raspberry Pi и USB-сканера сделать девайс для сканирования с сетевым диском, используя SANE, Samba и WiringPi, владея немного электрикой и bash-ем. Всё продемонстрированное эксплуатируется в реале, хоть и в несколько адаптированном виде. Интересующихся приглашаю посмотреть ну и покритиковать тут в комментах и карме. :)


Лекция получилась почти на 2 часа, так что, наверное, лучше смотреть прямо на Youtube; в описании к ролику есть оглавление. Возможно, при наличии времени дополню здесь текстом.
Приятного просмотра. :)
Подробнее..

Как мы обучили сфинкса для голосового помощника

09.07.2020 12:23:49 | Автор: admin
В процессе разработки проекта голосовой помощник одним из требований была возможность распознавания управляющих команд в оффлайн режиме. Это было нужно, так как в противном случае пришлось бы постоянно слушать и посылать поток с аудиоданными на распознавание, получать ответ и анализировать его.

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



Что случилось


Изначально Pocketsphinx работал у нас на железе OrangePi, у которого были: RAM 512Mb, несколько ГБ свободного дискового пространства, а на борту крутилась Ubuntu. При этом мы использовали python интерфейс (в виде python пакета) для работы с Pocketsphinx.

Мы установили Pocketsphinx командой

`pip install pocketsphinx`

Python позволил использовать нам имеющиеся библиотеки и повысить скорость разработки кода. Однако, даже один только процесс потреблял порядка 30-40 Мб оперативной памяти.

Это могло стать проблемой, потому что выяснилось, что доступное по цене железо заметно скромнее: для операционной системы доступно 123 из 128 Мб RAM, часть которой отводится под нужды железа. Из них часть памяти уже использовалась ОС Linux. Для нашего ПО оставалось только 62 Мб RAM и несколько десятков Мб свободной флеш памяти для хранения кода и файлов. Нужно было искать альтернативу python.

Решили запустить часть с Pocketsphinx на Си.

Сборка Pocketsphinx на Си


Исходники для Pocketsphinx были взяты с сайта: cmusphinx.github.io

Для работы Pocketsphinx требуются обе части Sphinxbase и Pocketsphinx. Далее нужно было понять как кросс-компилировать имеющиеся исходники компилятором для нашей целевой аппаратной платформы.

Сборку кода решено было делать без поддержки python, для чего использовали ключ

`--without-python`

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

`sphinx/lib`

Для дальнейшей работы с Си кодом требуются библиотеки:

`libpocketsphinx.so libsphinxbase.so libsphinxad.so`

Нюансы


После запуска Pocketsphinx с использованием библиотек оказалось, что Си вариант гораздо менее требователен к ресурсам. Использование RAM сводилось буквально к нескольким Mb.

Приложение после запуска занимало не более 5Mb RAM, но в нем при этом использовались еще с десяток других модулей и ряд библиотек. Если в варианте с python загрузка ядра CPU была весьма заметной (50% или даже более), то тут загрузка ядра была 7-9% (и это на все приложение, а не только на Pocketsphinx).

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

Работа с Pocketsphinx


Работа с Pocketsphinx состоит из этапов:

  1. Подготовка записей в соответствии с текущей задачей.
  2. Подготовка базы данных для модели.
  3. Построение языковой модели: обучение модели на полученных записях. Получение Pocketsphinx model and language model.
  4. Реализация алгоритма использования API Pocketsphinx.
  5. Поиск оптимальных параметров Pocketsphinx эмпирическим методом.

Остановимся на каждом этапе подробнее.

1. Подготовка записей в соответствии с текущей задачей


Подготовить базу записей в формате PCM.

Мы использовали следующие настройки:

Format settings, Endianness: Little
Bit rate: 256 Kbps
Channel(s): 1 channel
Sampling rate: 16.0 KHz
Bit depth: 16 bits

В специальном помещении для записи голоса они произносили ряд слов, каждый в нескольких интонациях (удивление, злость, грусть) и частотах (громко, тихо, нормально). Было сделано 100 записей: 60 мужских и 40 женских.

Примечание: для изменения частоты дискретизации используйте инструмент sox: sox original.wav -b 16 sample.wav каналов 1 скорость 16.

2. Подготовка базы данных для модели


etc
your_db.dic (Phonetic dictionary)
your_db.phone (Phoneset file)
your_db.jsgf (Language grammer)
your_db.filler (List of fillers)
your_db_train.fileids (List of files for training)
your_db_train.transcription (Transcription for training)
your_db_test.fileids (List of files for testing)
your_db_test.transcription (Transcription for testing)
wav
speaker_1
file_1.wav (Recording of speech utterance)
speaker_2
file_2.wav

3. Построение языковой модели


  1. Подготовить базу данных;
  2. Создать учебные файлы и полный словарь и список слов;
  3. Создать словарь из ru.dic и wordlist;
  4. Создайте файл из ru.dic;
  5. Извлечь текст из файла транскрипции;
  6. Построить языковую модель. Скачать инструмент SRILM и создать пакет для построения модели.
  7. Конвертировать файлы lm в файлы dmp.

    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
    
  8. И, наконец, обучить модель на полученных записях.

Шаги обучения


После подготовки файлов мы должны сгенерировать файл конфигурации: из корневой папки модели мы запускаем:
sphinxtrain -t setup "имя модели"

в конфигурационном файле sphinx_train.cfg:

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

из вашего sphinx_train.cfg. Изменить можно так:

$ DEC_CFG_MODEL_NAME = "$ CFG_EXPTNAME.ci_cont";

или

$ DEC_CFG_MODEL_NAME = "$ CFG_EXPTNAME.ci _ $ {CFG_DIRLABEL}";

Последний шаг начать тренировки нашей модели: sphinxtrain run.

Языковую модель возможно создать с нуля при помощи Sphinxtrain или можно использовать готовую из источника.

Инструкция по создании модули с нуля можно найти на github.

4. Реализация алгоритма использования API Pocketsphinx


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

  1. Распознавание речи в записаном аудио файле;
  2. Распознавание речи в потоке данных от микрофона.

Обобщенный алгоритм взаимодействия с Pocketsphinx после инициализации необходимых ресурсов:

1. Начать распознавание речи:

`int ps_start_utt(ps_decoder_t *ps)`

2. Передать данные из входного потока в Pocketsphinx:

`int ps_process_raw(ps_decoder_t *ps, int16 const *data, size_t n_samples, int no_search, int full_utt)`

3. Закончить распознавание речи:

`int ps_end_utt(ps_decoder_t *ps)`

4. Получить результат распознавания в виде строки:

`char const *ps_get_hyp(ps_decoder_t *ps, int32 *out_best_score)`

Подробный пример взаимодействия с API Pocketsphinx, при чтении данных из микрофона:

```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);}```

Подробный пример взаимодействия с API Pocketsphinx, при чтении данных из файла:

```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;    }}```

5. Поиск оптимальных параметров Pocketsphinx эмпирическим методом


После создания или скачивая языковой модели в файлах модели возможно найти файл feat.params. Данный файл необходим для задания параметров работы Pocketshinx. Если параметра нет в данном файле, то используется значение по умолчанию, список всех параметров и их описание возможно найти в исходном коде проекта или на сайте mankier.

Пример вывода программы pocketsphinx_continuous при использовании языковой модели.

```$ 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`

Путь, где хранятся файлы внутренней кухни Pocketsphinx:

`-hmm zero_ru.cd_cont_4000`

также файл feat.params.

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

`-kws <file-name.txt>`

Например, наш файл с ключевой фразой выглядит так:

```окей скай /1e-15/```

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

Итог


Краткая версия для спешащих: мы использовали Pocketsphinx на python, но потом эту стратегию пришлось изменить из-за ограничений памяти на железе. Мы выбрали язык Си. Столкнулись с некоторыми нюансами:

Сборка не проходила с первого раза, а чаще всего со второго;
Ключевое слово определялось не сразу: от нескольких долей секунд до нескольких минут.

Работа с Pocketsphinx состоит из этапов:

  1. Подготовка записей в соответствии с текущей задачей.
  2. Подготовка базы данных для модели.
  3. Построение языковой модели: обучение модели на полученных записях. Получение Pocketsphinx model and language model.
  4. Реализация алгоритма использования API Pocketsphinx.
  5. Поиск оптимальных параметров Pocketsphinx эмпирическим методом.

Сейчас мы собираем все вместе.
Подробнее..

Из песочницы Массивы bash

19.07.2020 16:19:03 | Автор: admin
Предлагаю вашему вниманию перевод статьи Митча Фрейзера (Mitch Frazier) "Bash Arrays" с сайта linuxjournal.com.

Если вы используете стандартную оболочку *NIX-системы, возможно, вы не знакомы с такой полезной особенностью bash как массивы. Хотя массивы в bash не так круты, как в P-языках (Perl, Python и PHP) и других языках программирования, они часто бывают полезны.
Bash-массивы имеют только численные индексы, но они не обязательны к использованию, то есть вы не должны определять значения всех индексов в явном виде. Массив целиком может быть определен путем заключения записей в круглые скобки:

  arr=(Hello World)

Отдельные записи могут быть определены с помощью знакомого всем синтаксиса (от Бейсика (да простит меня Дейкстра прим. переводчика) до Фортрана):

  arr[0]=Hello  arr[1]=World


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

  echo ${arr[0]} ${arr[1]}

Из страницы man:

"Фигурные скобки нужны для предотвращения конфликтов при разворачивании полных путей к файлам."

Кроме того, доступны следующие странноватые конструкции:

  ${arr[*]} # Все записи в массиве  ${!arr[*]}# Все индексы в массиве  ${#arr[*]}# Количество записей в массиве  ${#arr[0]}# Длина первой записи (нумерация с нуля)

${!arr[*]} сравнительно новое дополнение в bash и не является частью оригинальной реализации. Следующая конструкция демонстрирует пример простого использования массива. Обратите внимание на "[index]=value", это позволяет назначить конкретное значение конкретному номеру записи.

#!/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

Запуск скрипта породит следующий вывод:

Array size: 5

Array items:

one
two
three
four
five

Array indexes:

0
1
2
3
5

Array items and indexes:

0: one
1: two
2: three
3: four
5: five

Обратите внимание, что символ "@" может быть использован вместо "*" в конструкциях типа {arr[*]}, результат будет одинаковым за исключением разворачивания записи в кавычках. "$*" и "$@" выведут записи в кавычках, "${arr[*]}" вернет каждую запись как одно слово, "${arr[@]}" вернет каждую запись в виде отдельных слов.

Следующий пример покажет, как кавычки и конструкции без кавычек возвращают строки (особенно важно, когда в этих строках есть пробелы):

#!/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

Вывод при запуске:

Number of items in original array: 4

first item
second item
third
item

After unquoted expansion: 6

first
item
second
item
third
item

After * quoted expansion: 1

first item second item third item

After @ quoted expansion: 4

first item
second item
third
item
Подробнее..
Категории: *nix , Bash scripting

Основы Bash-скриптинга для непрограммистов

24.01.2021 20:20:26 | Автор: admin

Статья рассчитана на тех, кто не имеет или имеет мало опыта работы с командной строкой Unix/Linux, но желает научиться с ней эффективно взаимодействовать и разрабатывать скрипты для выполнения своих задач. Приведенные примеры справедливы для выполнения в командной оболочке bash операционной системы Ubuntu/Debian, но могут быть использованы и в других оболочках и ОС с учетом их специфики.

1. Командные оболочки

Существует множество дистрибутивов(форков) операционных систем(ОС) семейства 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

2. Настройка тестовой среды

Если у вас уже есть виртуальная/реальная машина с установленным 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), мы рассмотрим это отдельно.

3. Первые команды

Итак, мы подключились к терминалу и находимся в 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. Для поиска выполненных ранее команд можно использовать клавиши Вверх и Вниз, команды из истории можно редактировать и повторно выполнять. В конце статьи приведен список полезных команд.

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

Подробнее..
Категории: *nix , Linux , Unix , Ubuntu , Bash , Bash scripting , Ssh , Virtualbox , Debian , Shells

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

30.01.2021 20:16:42 | Автор: admin

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

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

Скрипты

Для выполнения нескольких команд одним вызовом удобно использовать скрипты. Скрипт это текстовый файл, содержащий команды для 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, а также различные полезные команды.

Подробнее..
Категории: *nix , Linux , Unix , Ubuntu , Bash , Bash scripting , Ssh , Virtualbox , Debian , Shells

Основы Bash-скриптинга для непрограммистов. Часть 3

16.02.2021 20:15:11 | Автор: admin

Во второй части статьи мы обсудили файлы скриптов, их параметры и права доступа. Также поговорили про операторы условного выполнения, выбора и циклы. В этой, заключительной части мы рассмотрим функции и планировщик заданий 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

В случае, когда есть необходимость периодического запуска скриптов, полезно использовать планировщик 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 является очень гибким инструментом, позволяющим реализовать задачи различного уровня сложности. При подключении внешних утилит предоставляет большие возможности для автоматизации.

На этом пока все, надеюсь, было интересно!
Какие другие полезные команды вы знаете и используете в работе?
Какие интересные конструкции приходилось использовать?
Какие задачи решали?
Всем удачного скриптинга, делитесь мнениями в комментариях!

Подробнее..
Категории: *nix , Linux , Unix , Ubuntu , Bash , Bash scripting , Ssh , Virtualbox , Debian , Shells

Из песочницы Git compare быстрый способ сравнить две ветки

24.09.2020 12:15:14 | Автор: admin

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


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


В результате локальная ветка законченной задачи выглядит примерно так:


image


Наступает следующий этап:


1) Все коммиты из задачи объединяются в один большой (feature-all-private на картинке)


image


2) Этот большой коммит разбивается на аккуратные коммиты с внятными описаниями (feature-public на картинке):


image


Проблема: во время разбития коммитов я обычно провожу легкий рефекторинг, меняю коммиты местами и прочее, что потенциально может привести к незапланированным изменениям в коде (что-то потерялось при ребейзе или разрешении конфликтов, например)


image


Решение: в гите на тот момент не было инструмента быстро сравнить содержимое двух веток, поэтому я решил написать небольшой скрипт 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

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


image


Надеюсь, этот скрипт будет полезен кому-то еще
Исходники выложены на гитхабе
Картинки были созданы с помощью codepen.io

Подробнее..
Категории: Git , Tutorial , Bash scripting , Git-cmp

Из песочницы Масштабирование CICD монорепозитория

19.07.2020 18:07:47 | Автор: admin

Lerna


Дано


  1. Монорепозиторий на базе Lerna и Yarn workspaces.
  2. Десяток приложений, и десятки общих пакетов на TypeScript, Angular, NodeJS.
  3. Высокое покрытие тестами самых разных мастей (модульные, интеграционные, e2e).
  4. и Atlassian Bamboo CI/CD.

Задача


Ускорить имеющиеся пайплайны в 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 раза.


Fixed mode


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).

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru