Это старая версия документа!
Скриптинг
Первой строкой указывается т.н. shebang (#!) - последовательность двух символов, далее указывается путь к исполняющему файлу под-оболочки, так же, в этой строке можно передавать аргументы.
Если оболочка не указана явно, используется так же, что и запускает данный скрипт.
Для исполнения скрипта, у файла должно быть разрешение выполнения (+x), но если передать скрипт аргументом команде bash ./script, тогда бит необязателен.
Если путь к скрипту указан в $PATH (например /usr/local/bin/ либо /bin/), то запускать можно просто по имени, без «./».
Комментарии предваряются символом решетки (#).
exit - сообщает результат (0-успех) работы родительскому процессу, при отсутствии используется результат последней команды.
Аргументы (доступ к ним)
$? - код возврата последнего процесса (функции или скрипта).
$№ - аргументы, по номеру. &0 - последний запущенный скрипт.
$# - кол-во переданных аргументов.
$* - все аргументы одной строкой.
$@ - все аргументы отдельной строкой.
$- - список флагов, переданных скрипту.
shift - команда, после нее первый аргумент в $@ теряется, остальные сдвигаются влево.
Переменные
Пробелы не используются при определении
Знак доллара используется только при чтении значения, работа с переменной без этого знака.
В фигурных скобках более строгая форма (${variable}).
Двойные кавычки - «нестрогие», и не влияют на механизм подстановки, в отличии от одинарных кавычек.
Конкатенация строк происходит без дополнительных символов: $Val1«_»$Val2.
Встроенные переменные
$BASH - путь к исполняемому файлу bash.
$GROUPS - группы пользователя.
$HOME - домашний каталог.
$HOSTNAME - сетевое имя хоста.
$SHLVL - уровень вложенности shell.
$SECONDS - время работы скрипта.
$REPLY - для ввода read, по умолчанию.
Условный оператор
Двойные квадратные скобки работают в целом так же, как и [одинарные квадратные скобки], но имеют дополнительные возможности вроде лучшей поддержки регулярных выражений.
Двойные круглые скобки это конструкция, позволяющая осуществлять арифметические вычисления внутри Bash.
if [[ "$name" == "Ryan" ]] && ! [[ "$time" -lt 2000 ]]; then
...
elif [[ "$day" == "New Year's Eve" ]] || [[ "$coffee_intake" -gt 9000 ]]; then
...
else
...
fi
if [ "$age" -gt 30 ]; then
echo "What an oldy."
fi
(( count++ ))
echo "$count"
if (( -57 + 30 + 27 )); then
echo "First one"
echo $(( (5 > 3) + (0 == 0) ))
В условии можно использовать код завершения любой команды
if grep -q coffee dialogue.txt; then .. fount/not found; fi
if cmp a b &> /dev/null; then ... fi
Old
Проверяет, является ли результат 0 (истина).
Условие условие проверяется с помощью команды test, на данный момент, команда является встроенной т.е. не вызывает аналогичную утилиту.
Условия можно указать следующими способами:
if test -z $1
if /usr/bin/test -z $1
if [ -z $1 ]
if /usr/bin/[ -z $1
if | < и >.
* **[ -z $1 ]** - условие может быть проверено из без оператора **if**
* **() или двойные** - выполняет арифметическое действие внутри. **Код возврата противоположен []**
Условный оператор проверяет код завершения **любой команды**, а не только результат выражения скобок.
<code bash>if cmp a b
then echo "Файлы идентичны"
else echo "Файлы различаются"
fi</code>
Оператор **if** можно и не использовать.
<code bash>[ -z $1 ] && echo result false
или
ping -c 1 8.8.8.8 &>/dev/null || echo not available
</code>
==== Else if (elif) ====
**elif** - краткая форма записи конструкции **else if**.
<code bash>if [ expr ]; then
action
elif [ expr ]; then
action
else
action
fi
</code>
</details>
===== Циклы =====
==== for ====
Обработка диапазонных значений.\\
**for** условие **do** действие **done**.
<code bash> for (( i=100; i>1; 1-- )); do action; done </code>
Внутри **((..)')** вычисляется арифметическое выражение и возвращается результат и позволяет работать с переменными в стиле С.\\
Так же, можно указать диапазон:
<code bash>for i in {100..104}; do action; done </code>
<code bash>for i in 100 101 102 103 104; do action; done </code>
<code bash>for i in $@; do action; done </code>
<code bash>numbers="1 2 3 4 5"
for i in `echo $numbers`; do action; done </code>
==== while ====
Выполняется пока условие истинно.\\
**while** условие **do** действие **done**.
<code bash>while [ $v1 -le $v2 ] do <action> done</code>
В двойных скобках символ **$** перед переменной можно опустить, так же, двойные скобки позволяют наращивать значение переменной **( (v+=1) )**
<code bash>while (( v1 <= v2 )') ; do action; done </code>
<details>
<summary> :!: Примеры </summary>
Бесконечный цикл
<code bash>
while true
do
echo "--==" $(uname -a) "==--"
sleep 2
done
</code>
</details>
==== Until ====
Противоположно циклу **while**, выполняется пока условие ложно.
**until** условие **do** действие **done**.
Все остальное аналогично.
===== case =====
<code bash>case $v1 in
val1)
action1;;
val2)
action2;;
*)
default action;;
esac
</code>
===== Работа со строками =====
**Длина строки:**
<code bash>echo ${#string}
echo `expr length $string`
</code>
**Извлечение подстроки**
<code bash>${string:position} # вместо $string можно поставить * или @
${string:position:length}
expr substr $string $position $length
</code>
**Удаление части строки**
<code bash>${string#(##)substring} # Удаляет самую короткую (длинную), ищет с начала строки
${string%(%%)substring} # Удаляет самую короткую (длинную), ищет с конца строки
</code>
**Замена подстроки**
<code bash>${string/substring/replacement} // Первое вхождение
${string//substring/replacement} // Все вхождения
</code>
===== Отладка =====
Для отладки, можно использовать команду **bash -x файл_скрипта**.
===== Примеры =====
**"Около-многопоточность"**\\
Используется минимум два скрипта, в первом выполнение работы, второй в цикле запускает подоболочку в фоне, не дожидаясь окончания каждого. Ньюанс в том что в конце головной скрипт не закрывается сам, думаю можно исправить аргументами.
<code bash> for CurrAddr in {1..25}; do
./oneping $CurrAddr & # Там происходит просто пинг переданного адреса
done
# В итоге, ждем тайм-аут один раз, для всех хостов
</code>
===== Утилита test =====
Unix утилита для проверки типа файла и сравнения значений.\\
Возвращает 0 (истина) или 1 (ложь). Выражения могут быть как унарными так и бинарными.\\
**Использование:** <code bash># test [expr]</code>
**Пример:** <code bash>if test -f file.txt или [ -f file.txt ]
then
rm file.txt
else
echo 'no found'
fi
</code>
==== Параметры запуска ====
* **-d file** - если file существует и является директорией.
* **-e file** - если file существует.
* **-f file** - если file существует и является обычным файлом.
* **-k file** - если file существует и ему установлен "sticky" бит.
* **-L file** - если file существует и является символьной ссылкой.
* **-r (-w/-x) file** - если file существует и читаем (записываем/исполняем).
* **-z str** - если длина 0.
* **-n str** - если длина не 0.
* **-a (-o)** - аналог && (||) в одинарных скобках.
* **str1 = ('!=') str2** - если строки равны (не равны).
===== Функции. Возврат значения =====
**function_name() {command... }**\\
Объявляются раньше вызова, **нет возможности предобъявления**\\
**return** возвращает только интовое значение, до 255\\
===== Примеры =====
<details>
<summary> :!: Обширный пример </summary>
<code bash>
#!/bin/bash
# Входные параметры можно передать аргументами, тогда скрипт не будет ничего запрашивать
# Если что то не передано, скрипт запросит пользователя интерактивно
isNewMultip=false
CRed='\033[31m'
CGreen='\033[32m'
CCyan='\033[36m'
CNone='\033[0m'
# Разбираем аргументы
while getopts :i:f:n:a: flag
do
case "${flag}" in
i) importName=$OPTARG;;
f) multipName=$OPTARG;;
n) multipName=$OPTARG
isNewMultip=true;;
a) hostIP=$OPTARG;;
*) printf "$0 [OPTIONS]\n -i Имя нового импорта\n -f Имя существующего файлмультипликатора\n -n Имя нового файлмультипликатора\n -a IP адрес хоста\n"
exit;;
esac
done
importName=$(sed 's/[\*|\/|\$|\@|\.| ]/_/g' <<< ${importName})
multipName=$(sed 's/[\*|\/|\$|\@|\.| ]/_/g' <<< ${multipName})
hostIP=$(grep -Po "([0-9]{1,3}\.){3}[0-9]{1,3}" <<< ${hostIP})
# -= Если не передано в аргументах или некорректно, запрашиваем у пользователя =-
if [ -z ${hostIP} ]; then
listIP=($( ip a | grep -Po "inet \K[^/]*" | grep -v "127.0.0.1"))
if [[ ${#listIP[*]} > 1 ; then
printf ' - %s\n' «${listIP[@]}»
while read -p «Укажите ip адрес хоста: » hostIP; do
hostIP=$(grep -Po «([0-9]{1,3}\.){3}[0-9]{1,3}» «< ${hostIP})
if [ -n «$hostIP» ]; then
break
fi
done
else
hostIP=${listIP[0]}
fi
fi
if [ -z «$importName» ]; then
listImports=($( cat /tank/BIN/MgaImport/app.conf | grep «WORKSPACE» | grep -Po «.*/\K.*» ))
echo -e «${CCyan}Существующие сервисы:${CNone}»
printf ' - %s\n' «${listImports[@]}»
while read -p «Введите имя нового импорта: » importName; do
importName=$(sed 's/[\*|\/|\$|\@|\.| ]/_/g' «< ${importName})
if [ -n «$importName» ]; then
break
fi
done
fi
if [ -z «$multipName» ]; then
listMultips=($( cat /tank/BIN/FileMultiplicators/app.conf | grep «WORKSPACE» | grep -Po «.*/\K.*» ))
echo -e «${CCyan}Существующие файлмультипликаторы:${CNone}»
printf ' - %s\n' «${listMultips[@]}»
while read -p «Введите имя файлмультипликатора: » multipName; do
multipName=$(sed 's/[\*|\/|\$|\@|\.| ]/_/g' «< ${multipName})
if [ -n «$multipName» ]; then
break
fi
done
if ! "${listMultips[*]}" =~ "${multipName}"; then
echo -e «Указанное имя не найдено, ${CCyan}создать файлмультипликатор${CNone} ? »
select yn in «Yes» «No»; do
case $yn in
Yes) isNewMultip=true; break;;
No) echo -e «Указаный файлмультипликатор ${CRed}не существует${CNone}»; exit;;
esac
done
fi
fi
# -= Ищем свободные порты =-
# Мультипликатор
# Команный порт
listPorts=($(cat /tank/BIN/FileMultiplicators/*/FileMultiplicator.toml | grep «listeningCommandPort» | grep -Po «\d*» | sort))
1)
while $(ss -tulpn | grep -q :${listPorts[-1]}); do
2)
done
multipCommandPort=${listPorts[-1]}
# Jmx порт
listPorts=($(cat /tank/BIN/FileMultiplicators/app.conf | grep «jmxremote.port» | grep -Po «\d*» | sort))
3)
while $(ss -tulpn | grep -q :${listPorts[-1]}); do
4)
done
multipJmxPort=${listPorts[-1]}
# Импорт
# Команный порт
listPorts=($(cat /tank/BIN/MgaImport/app.conf | grep «COMMAND_PORT» | grep -Po «\d*» | sort))
5)
while $(ss -tulpn | grep -q :${listPorts[-1]}); do
6)
done
importCommandPort=${listPorts[-1]}
# Jmx порт
listPorts=($(cat /tank/BIN/MgaImport/app.conf | grep «jmxremote.port» | grep -Po «\d*» | sort))
7)
while $(ss -tulpn | grep -q :${listPorts[-1]}); do
8)
done
importJmxPort=${listPorts[-1]}
# -= Создаем импорт =-
$(cp -r ClearImport «/tank/BIN/MgaImport/MgaImport_${importName}»)
# Меняем права доступа юзеру mgaimport
$(chown -R mgaimport:mgaimport «/tank/BIN/MgaImport/MgaImport_${importName}»)
# Пишем параметры в «cmd.properties»
changeFile=«/tank/BIN/MgaImport/MgaImport_${importName}/cmd.properties»
$(sed -i 's/%JmxPort%/'${importJmxPort}'/g' ${changeFile})
$(sed -i 's/%CommandPort%/'${importCommandPort}'/g' ${changeFile})
$(sed -i 's/%ImportName%/'${importName}'/g' ${changeFile})
# Пишем параметры в «./properties/mgaimport.toml»
changeFile=«/tank/BIN/MgaImport/MgaImport_${importName}/properties/mgaimport.toml»
$(sed -i 's/%InputDir%/'«\/tank\/MGA_IN\/${importName}»'/g' ${changeFile})
$(sed -i 's/%SepticDir%/'«\/tank_storage\/septics\/SEPTIC_${importName}»'/g' ${changeFile})
$(sed -i 's/%DBErrorDir%/'«\/tank_storage\/dberrors\/DBERROR_${importName}»'/g' ${changeFile})
$(sed -i 's/%CommandPort%/'${importCommandPort}'/g' ${changeFile})
# Делаем запись в общем конфиге
changeFile=«/tank/BIN/MgaImport/app.conf»
$(printf «\n[${importName}]
# —————————————- #
ENABLE=«true»
JAVA_HOME=/usr/lib/jvm/bellsoft-java14-full.x86_64/bin
START_USER=mgaimport
START_TIME=30
STOP_TIME=90
COMMAND_PORT=${importCommandPort}
WORKSPACE=/tank/BIN/MgaImport/MgaImport_${importName}
CMD='java -XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC
-XX:+DisableExplicitGC
-Xmx16G
-Xms3G
-XX:InitiatingHeapOccupancyPercent=75
-XX:G1NewSizePercent=1
-XX:G1MaxNewSizePercent=15
-XX:G1MixedGCLiveThresholdPercent=90
-XX:G1HeapWastePercent=5
-XX:G1ReservePercent=1
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=»/tank/BIN/MgaImport/MgaImport_${importName}/out_of_memory_dump/dump.hprof«
-XX:-OmitStackTraceInFastThrow
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=${importJmxPort}
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=${hostIP}
-Djava.net.preferIPv4Stack=true
-jar mgaimport.jar'\n» » ${changeFile})
if [ $isNewMultip ]; then
# -= Создаем мультипликатор =-
$(cp -r ClearFileMultiplicator «/tank/BIN/FileMultiplicators/${multipName}»)
# Меняем права доступа юзеру mgaimport
$(chown -R mgaimport:mgaimport «/tank/BIN/FileMultiplicators/${multipName}»)
# Пишем параметры
changeFile=«/tank/BIN/FileMultiplicators/${multipName}/FileMultiplicator.toml»
$(sed -i 's/%InputDir%/'«\/tank\/IN\/${multipName}»'/g' ${changeFile})
$(sed -i 's/%CommandPort%/'${multipCommandPort}'/g' ${changeFile})
$(sed -i 's/%ImportName%/'${importName}'/g' ${changeFile})
$(sed -i 's/%ImportPath%/'«\/tank\/MGA_IN\/in_${importName}»'/g' ${changeFile})
# Делаем запись в общем конфиге
changeFile=«/tank/BIN/FileMultiplicators/app.conf»
$(printf «\n[${multipName}]
# ———————————– #
ENABLE=«true»
JAVA_HOME=/usr/lib/jvm/bellsoft-java14-full.x86_64/bin
START_USER=mgaimport
START_TIME=5
STOP_TIME=10
WORKSPACE=/tank/BIN/FileMultiplicators/${multipName}
CMD='java
-XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC
-XX:+DisableExplicitGC
-Xmx2G
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=${multipJmxPort}
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=${hostIP}
-Djava.net.preferIPv4Stack=true
-jar filemultiplicator-all.jar'\n» » ${changeFile})
# Создаем входящую директорию мультипа
$(install -d -m 775 -o mgaimport -g mgaimport «/tank/IN/${multipName}»)
# Запись в самбу
changeFile=«/etc/samba/smb.conf»
$(printf «\n[${multipName}]
path = /tank/IN/${multipName}
guest ok = no
browsable = no
writeable = yes
create mask = 0770
directory mask = 0770
write list = xlogssupplier
valid users = xlogssupplier
force user = mgaimport
force group = mgaimport\n» » ${changeFile})
# Перезапускаем самбу
$(systemctl restart smb && systemctl status smb)
# Напоминаем пользователю что нужно примонтировать ее на источнике источника
printf «${CRed}Внимание!${CNone}
Создана входящая папка для файлмультипликатора ${CCyan} \»/tank/IN/${multipName}\« ${CNone}
К ней ${CRed}открыт сетевой доступ${CNone} через \»smb\«, ее необходимо примонтировать на сервере \»лог-дистрибьютора\« (либо какой то другой источник) для получения входящих логов\n»
else
# -= Используем существующий =-
# Дозапись в конфиг еще новый сервис
changeFile=«/tank/BIN/FileMultiplicators/${multipName}/FileMultiplicator.toml»
$(printf «\nDestDir
name = \»${importName}\«
destDir = \»/tank/MGA_IN/in_${importName}\«
pattern = \»^.*$\«\n» » ${changeFile})
# Перезапуск сервиса, для принятия изменений
$(«/tank/BIN/FileMultiplicators/${multipName}/restart.sh»)
$(«/tank/BIN/FileMultiplicators/${multipName}/status.sh»)
fi
# -= Создаем входящую папку импорта и папки для ошибок =-
$(install -d -m 775 -o mgaimport -g mgaimport «/tank/MGA_IN/IN_${importName}»)
$(install -d -m 775 -o mgaimport -g mgaimport «/tank_storage/dberrors/DBERROR_${importName}»)
$(install -d -m 775 -o mgaimport -g mgaimport «/tank_storage/septics/SEPTIC_${importName}»)
printf «\n${CGreen}Новый импорт успешно создан!${CNone}
Параметры следующие:
Имя: ${CCyan}${importName}${CNone}
Расположение: ${CCyan}'/tank/BIN/MgaImport/MgaImport_${importName}'${CNone}
Входящая директория: ${CCyan}'/tank/MGA_IN/IN_${importName}'${CNone}
Jmx-порт: ${CCyan}${importJmxPort}${CNone}
Командный порт: ${CCyan}${importCommandPort}${CNone}
Конфигурация импорта: ${CCyan}'/tank/BIN/MgaImport/MgaImport_${importName}/properties/mgaimport.toml'${CNone}
Общая конфигурация всех импортов: ${CCyan}'/tank/BIN/MgaImport/app.conf'${CNone}
Файл мультипликатор: ${CCyan}'/tank/BIN/FileMultiplicators/${multipName}${CNone}
Укажите ${CCyan}параметры импорта в БД${CNone} в файле конфигурации импорта
Используя Jmx-порт, создайте самостоятельно мониторинг в Zabbix, при необходимости\n»
</code>
</details>