Как использовать коды завершения в Bash-скриптах

Пояснения по конструкциям test , [, [[, ((, и if-then-else

Чтение из STDIN

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

Баш вмещает трубопроводы и перенаправление посредством специальных файлов. Каждый процесс получает собственный набор файлов (один для STDIN, STDOUT и STDERR соответственно), и они связаны при вызове или перенаправлении. Каждый процесс получает следующие файлы:

  • STDIN — /proc/<processID>/fd/0
  • STDOUT — /proc/<processID>/fd/1
  • STDERR — /proc/<processID>/fd/2

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

  • STDIN — /dev/stdin or /proc/self/fd/0
  • STDOUT — /dev/stdout or /proc/self/fd/1
  • STDERR — /dev/stderr or /proc/self/fd/2

fd в дорожках выше обозначает дескриптор файла.

Чтение из STDIN

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

summary

Shell #!/bin/bash # Основное резюме моего отчета о продажах echo Here is a summary of the sales data: echo ==================================== echo cat /dev/stdin | cut -d’ ‘ -f 2,3 | sort

12345678 #!/bin/bash# Основное резюме моего отчета о продажах echo Here is a summary of the sales data:echo ====================================echo cat /dev/stdin | cut d‘ ‘ f 2,3 | sort

Давайте разберем это:

  • Строки 4, 5, 6 — Распечатайте заголовок для вывода
  • Строка 8 — cat файл, представляющий STDIN, вырезает установку разделителя на пробел, поля 2 и 3 затем сортируют вывод.

Алгоритм Рабина — Карпа

Этот алгоритм старается уменьшить количество проверок во внутренним цикле простого поиска за счёт использования хэш-функции.

Понятие хэш-функции

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

Читайте также:  iOS 12 — дата выхода и самые ожидаемые новые функции

Например, у нас есть множество целых чисел от 0 до 1 000 000 и ограничение на значение хэш-функции в 10 битов. 10 битов это доступных значений функции. Существуют различные способы написать такую функцию, например:

  1. методом деления, когда входное число делится на количество доступных значений хэш-функции, а результатом является остаток от такого деления: ;
  2. метод середины квадрата умножает входное число само на себя, а из его середины берутся необходимое количество знаков;
  3. метод умножения имеет вид , где — количество символов представимых машинным словом (для 32-битной системы это будет ), а — взаимно простое число к .

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

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

Для работы алгоритма необходимо написать хэш-функцию для заданной строки. Если пронумеровать символы алфавита из которых состоит строка, то можно написать тривиальный скользящий хэш, представляющий из себя сумму индексов каждого символа. Например, когда задан алфавит {A, B, C, D}, а символы имеют индексы от 1 до 4, то для строки ACDDAB будем иметь, что 1 + 3 + 4 + 4 + 1 + 2 = 15.

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

PH = hash(P) SH = hash(S(0, )) for i = 0 to do if PH == SH and S(i, i + ) = P then return i end if SH = SH — S[i] + S[i + ] end for return -1

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

Как работает код поиска слов в строке?

В библиотеке strings есть функции для операций над строками. В данном случае используется функция Contains. Функция Contains просто проверяет, есть в рассматриваемой строке указанное слово, или подстрока. Фактически, в функции Contains используется функция Index.

Чтобы проверить, начинается ли строка с указанной подстроки, используется функция HasPrefix. Чтобы проверить, заканчивается ли строка указанной подстрокой, используется функция HasSuffix.

Фактически функция Contains реализуется через использование функции Index из того же пакета. Можно догадаться, что реализация осуществляется следующим образом: если индекс данной подстроки больше -1, тогда функция Contains возвращает true.

Как работает код поиска слов в строке?

Функции HasPrefix и HasSuffix работают иначе: внутренняя реализация просто проверяет длину строки и подстроки, и если они одинаковы или строка длиннее, тогда запрашиваемая часть строки сравнивается.

Vasile Buldumac

Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.

E-mail: @

Образование

Технический Университет Молдовы (), Факультет Вычислительной Техники, Информатики и Микроэлектроники

  • 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»

Операции со строками

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

  • выделить блок оперативной памяти под массив;
  • проинициализировать строку.

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

Для ввода строки использована функция scanf(), причем введенная строка не может превышать 9 символов. Последний символ будет содержать '\0'.

Функции ввода строк

Для ввода строки может использоваться функция scanf(). Однако функция scanf() предназначена скорее для получения слова, а не строки. Если применять формат "%s" для ввода, строка вводится до (но не включая) следующего пустого символа, которым может быть пробел, табуляция или перевод строки. Для ввода строки, включая пробелы, используется функция char * gets_s(char *); В качестве аргумента функции передается указатель на строку, в которую осуществляется ввод. Функция просит пользователя ввести строку, которую она помещает в массив, пока пользователь не нажмет Enter.

Функции вывода строк

Для вывода строк можно воспользоваться рассмотренной ранее функцией

или в сокращенном формате int puts (char *s);

которая печатает строку s и переводит курсор на новую строку (в отличие от printf()). Функция puts() также может использоваться для вывода строковых констант, заключенных в кавычки.

Функция ввода символов

Для ввода символов может использоваться функция

Функция вывода символов

Для вывода символов может использоваться функция 1234567891011121314151617181920212223242526#include <stdio.h>#include <string.h>#include <stdlib.h>int main() { char s[80], sym; int count, i; system("chcp 1251"); system("cls"); printf("Введите строку : "); gets_s(s); printf("Введите символ : "); sym = getchar(); count = 0; for (i = 0; s[i] != ‘\0’; i++) { if (s[i] == sym) count++; } printf("В строке\n"); puts(s); // Вывод строки printf("символ "); putchar(sym); // Вывод символа printf(" встречается %d раз", count); getchar(); getchar(); return 0;}

Результат выполнения

Как использовать коды завершения в Bash-скриптах

Удаление из скрипта команды echo позволило нам получить код завершения. Что делать, если нужно сделать разные действия в случае успешного и неуспешного выполнения команды touch? Речь идёт о печати stdout в случае успеха и stderr в случае неуспеха.

Проверяем коды завершения

Выше мы пользовались специальной переменной $?, чтобы получить код завершения скрипта. Также с помощью этой переменной можно проверить, выполнилась ли команда touch успешно.

#!/bin/bash touch /root/test 2> /dev/null if [ $? -eq 0 ] then echo «Successfully created file» else echo «Could not create file» >&2 fi

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

  • Если команда touch выполняется с кодом 0, скрипт с помощью echo сообщает об успешно созданном файле.
  • Если команда touch выполняется с другим кодом, скрипт сообщает, что не смог создать файл.

Любой код завершения кроме 0 значит неудачную попытку создать файл. Скрипт с помощью echo отправляет сообщение о неудаче в stderr.

Выполнение:

$ ./ Could not create file

Создаём собственный код завершения

Наш скрипт уже сообщает об ошибке, если команда touch выполняется с ошибкой. Но в случае успешного выполнения команды мы всё также получаем код 0.

$ ./ Could not create file $ echo $? 0

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

#!/bin/bash touch /root/test 2> /dev/null if [ $? -eq 0 ] then echo «Successfully created file» exit 0 else echo «Could not create file» >&2 exit 1 fi

Теперь в случае успешного выполнения команды touch скрипт с помощью echo сообщает об успехе и завершается с кодом 0. В противном случае скрипт печатает сообщение об ошибке при попытке создать файл и завершается с кодом 1.

Выполнение:

$ ./ Could not create file $ echo $? 1