# Bash скрипты

При запуске скрипта создается дочерняя консоль, в ней выполняется скрипт и результат возвращается в консоль.

Можно запускать через source, имя (если . в PATH), ./... , и ./ ... &amp; С фоновой задачей аккуратно с объемом вывода, ^C не работает.

**Shebang**

Лучше использовать shebang в начале (указатель, какую консоль использовать)

\#!/bin/bash

<table border="1" id="bkmrk-%23%21%2Fbin%2Fbash--x-%D0%92%D1%8B%D0%B2%D0%BE%D0%B4" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 20.6548%;"></col><col style="width: 79.4644%;"></col></colgroup><tbody><tr><td>\#!/bin/bash -x</td><td>Выводит все команды во время исполнения</td></tr><tr><td>\#!/bin/bash -r</td><td>Блокирует потенциально опасные действия</td></tr></tbody></table>

Параметры можно указывать при запуске скрипта, тогда shebang работать не будет.

**Удобства**

Комментарии #

bash -n script.sh проверка синтаксиса без исполнения скрипта

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

```
#!/bin/bash
...
set -x
...potentially error part...
set +x
```

Операция ; запускает несколько команд независимо от их статуса завершения

```
ls; ps; whoami
```

выполнит все команды

**Перенаправления результата выполнения**

<table border="1" id="bkmrk-%3E-stdout-%D0%B2-%D1%84%D0%B0%D0%B9%D0%BB%2C-%D1%84%D0%B0%D0%B9" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 18.7501%;"></col><col style="width: 81.3691%;"></col></colgroup><tbody><tr><td>&gt;</td><td>Stdout в файл, файл очищается</td></tr><tr><td>&gt;&gt;</td><td>Stdout в файл, файл дополняется</td></tr><tr><td>&amp;&gt; or &gt;&amp;</td><td>Stdout и Stderr в файл, файл очищается</td></tr><tr><td>&amp;&gt;&gt;</td><td>Stdout и Stderr в файл, файл дополняется</td></tr><tr><td>&lt;</td><td>Перенаправление ввода в команду</td></tr><tr><td>&lt;&lt;</td><td>Перенаправление многострочного ввода в команду</td></tr></tbody></table>

Номера потоков

0 - stdin, 1 - stdout, 2 - stderr

```
lzl -l 1> stdout.txt 2>stderr.txt
```

выведет в stderr.txt

**Функции**

```
function functname{
shell commands
}

functname(){
shell commands
}
```

Используют позиционные переменные.

functname "one" $7

Если внутри функции нужна локальная переменная, то local var1="something", иначе используется глобальная переменная.

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

```bash
cd baddir
echo $?
```

По умолчанию возвращается статус вызова последней команды. Либо через return N

```bash
builtin mkdir "/var/first"
es=$?
echo "$OLDPWD --> $PWD"
return $es

```

Желательно возвращать код результата, а не просто цифру.

**Программы для работы с функциями**

<table border="1" id="bkmrk-declare--f-%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA-%D1%84%D1%83" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 16.7263%;"></col><col style="width: 83.3929%;"></col></colgroup><tbody><tr><td>declare</td><td>-f список функций с описанием</td></tr><tr><td>  
</td><td>-F только имена функций</td></tr><tr><td>unset fname</td><td>удалить функцию из памяти</td></tr><tr><td>type fname</td><td>Описание функции</td></tr><tr><td>  
</td><td>-t выведет тип</td></tr></tbody></table>

**Приоритет при вызове из консоли**

1. Aliases
2. Служебные слова типа if, ...
3. Функции
4. Встроенные команды типа cd, type, ...
5. Скрипты и исполняемые команды

**Переменные**

**Строковые (по умолчанию)**

Описывается $varname или ${varname} Например есть переменная UID. Для вывода 0\_ нужно использовать echo ${varname}\_

Удаление переменной unset var1

<table border="1" id="bkmrk-%241%2C-%242%2C-...-%D0%9F%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D0%BE%D0%BD" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 13.631%;"></col><col style="width: 86.4882%;"></col></colgroup><tbody><tr><td>$1, $2, ...</td><td>Позиционные параметры</td></tr><tr><td>$\*</td><td>Строка, содержащая все параметры через пробел</td></tr><tr><td>$@</td><td>Равна N строкам-параметрам. Актуально при передаче в функции.</td></tr><tr><td>$#</td><td>Количество аргументов</td></tr><tr><td>var1=$(cmd)</td><td>Сохранение результата cmd в переменную</td></tr></tbody></table>

Определение глобальной переменной: var1="something"

Обработка значений переменных перед вводом

<table border="1" id="bkmrk-%24%7Bvarname%3A-word-%7D-%D0%95%D1%81" style="border-collapse: collapse; width: 100%; height: 165.781px;"><colgroup><col style="width: 23.9882%;"></col><col style="width: 76.131%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">${varname:-word }</td><td style="height: 29.7969px;">Если переменная не существует или существует но пустая - вернет word</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">${varname:=word}</td><td style="height: 29.7969px;">Если переменная не существует или существует но пустая - присвоит ей word и вернет word</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">${varname:?message}</td><td style="height: 46.5938px;">Если переменная не существует или существует но пустая - выведет сообщение и завершит скрипт</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">${varname:+word}</td><td style="height: 29.7969px;">Если переменная существует или пустая - вернет word, иначе null</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">${varname:offset:length}</td><td style="height: 29.7969px;">Срез.

count=frogfootman

${count:4} вернет footman

${count:4:4} вернет foot

</td></tr></tbody></table>

Обработка переменных

<table border="1" id="bkmrk-%24%7Bvar%23pattern%7D-%C2%A0-%D0%95%D1%81%D0%BB" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 23.9882%;"></col><col style="width: 76.131%;"></col></colgroup><tbody><tr><td>${var#pattern}

</td><td>Если шаблон совпадает с началом значения переменной - удалить самую короткую часть от начала строки с совпадением и вернуть результат

var="/home/user/docs/file.txt"  
echo ${var#\*/} # home/user/docs/file.txt (отрезан только первый "/")

</td></tr><tr><td>${var##pattern}</td><td>var="/home/user/docs/file.txt"

echo ${var##\*/} # file.txt (отрезано всё до последнего "/")

</td></tr><tr><td>${variable%pattern}</td><td>file="archive.tar.gz"

echo ${file%.\*} # archive.tar

</td></tr><tr><td>${variable%%pattern}</td><td>file="archive.tar.gz"

echo ${file%%.\*} # archive

</td></tr><tr><td>${variable/pattern/string}</td><td>Наибольшее совпадение шаблона с переменной заменяется строкой. Первое совпадение

</td></tr><tr><td>${variable//pattern/ string}</td><td>Наибольшее совпадение шаблона с переменной заменяется строкой. Все совпадения

</td></tr><tr><td>  
</td><td> echo -e ${PATH//:/'\\n'}

выведет разделенное через : значение в значения списком

</td></tr></tbody></table>

Длина значения ${#filename}

**Типизированные**

При помощи declare &lt;param&gt; var

<table border="1" id="bkmrk--a-%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2--i-%D0%A6%D0%B5%D0%BB%D0%BE%D0%B5-%D1%87" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 23.631%;"></col><col style="width: 76.4882%;"></col></colgroup><tbody><tr><td>-a</td><td>Массив</td></tr><tr><td>-i</td><td>Целое число</td></tr><tr><td>-r</td><td>Переменная только для чтения</td></tr><tr><td>-x</td><td>Переменная экспортируется в окружение</td></tr><tr><td>-f</td><td>Хрень. Возвращает тело функции если она есть.

```bash
myfunc() {
    echo "Hello from myfunc"
}
declare -f myfunc
```

</td></tr><tr><td>-F</td><td>Возвратит имя функции если она есть.</td></tr></tbody></table>

Пример:

```bash
declare -i val3=12 val4=5
declare -i result2
result2=val3*val
echo $result2
```

Выведет 60

Использование let

```
let result="4 * 5"
echo $result
```

Также можно использовать двойные скобки

```
let result=$((4 * 5))
echo $result
```

Есть операции +,-,/,\*,%-модуль, += увеличение на константу, -=

**Массивы**

```
arr = (1 2 3)
echo "${arr[*]}" #весь массив
echo "${arr[0]}" #1
unset ${arr[1]} #удалит 2 из массива
${arr[0]}=10 #перезапишет 1 в 10
```

**Условия**

**Условия завершения программы**

Условие анализируется не на логическом, а на статусе результата (exit status) выполнения программы. 0 - ОК, 1-255 ошибка.

```
if condition; then
statements
[elif condition; then 
statements...]
[else
statements]
fi
```

**Условие анализа строк**

 конструкция \[\], внутри нее возможны условия проверки и сравнения строк.

<table border="1" id="bkmrk-str1-%3D-str2-%D0%A1%D1%82%D1%80%D0%BE%D0%BA%D0%B0-1" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 21.3691%;"></col><col style="width: 78.7501%;"></col></colgroup><tbody><tr><td>str1 = str2</td><td>Строка 1 равна строке 2</td></tr><tr><td>str1 != str2</td><td>Строка 1 не равна строке 2</td></tr><tr><td>str1 &gt; str2 str1 &lt; str2</td><td>Больше/меньше</td></tr><tr><td>-n str1</td><td>Строка 1 не пустая</td></tr><tr><td>-z str1</td><td>Строка 1 пустая</td></tr></tbody></table>

**Условия сравнения чисел**

<table border="1" id="bkmrk--lt-%D0%9C%D0%B5%D0%BD%D0%B5%D0%B5-%D1%87%D0%B5%D0%BC--le-%D0%9C%D0%B5" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 21.131%;"></col><col style="width: 78.9882%;"></col></colgroup><tbody><tr><td>-lt</td><td>Менее чем</td></tr><tr><td>-le</td><td>Меньше или равно</td></tr><tr><td>-eq</td><td>Равно</td></tr><tr><td>-ge</td><td>Больше или равно</td></tr><tr><td>-gt</td><td>Больше</td></tr><tr><td>-ne</td><td>Не равно</td></tr></tbody></table>

Можно комбинировать

```bash
if command && [ condition ]; then
```

Логика внутри условия

<table border="1" id="bkmrk-%26%26-%D0%B8%D0%BB%D0%B8--a-%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C-" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 21.131%;"></col><col style="width: 78.9882%;"></col></colgroup><tbody><tr><td>&amp;&amp; или -a</td><td>Выполнить команду 1, если статус 0 - выполнить команду 2

```bash
if statement1 && statement2
then
...
fi
```

</td></tr><tr><td>|| или -o</td><td>Выполнить команду 1, если статус не 0 - выполнить команду 2

```bash
if statement1 || statement2
then
...
fi
```

</td></tr><tr><td>!</td><td>Отрицание

</td></tr></tbody></table>

Свойства файла, проверяемые в условиях

<table border="1" id="bkmrk--a-file%2C--e-file-%D0%A4%D0%B0%D0%B9" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 21.012%;"></col><col style="width: 79.1072%;"></col></colgroup><tbody><tr><td>-a file, -e file</td><td>Файл существует</td></tr><tr><td>-d file</td><td>Директория существует</td></tr><tr><td>-f file</td><td>Файл существует и является файлом (не директория или специальный файл)</td></tr><tr><td>-r file, -w file</td><td>Есть право чтения файла, Есть право записи в файл, </td></tr><tr><td>-x file</td><td>Есть право исполнения файла или право поиска в директории</td></tr><tr><td>-s file</td><td>Файл существует и не пустой</td></tr><tr><td>-N file</td><td>Файл был изменен с момента последнего чтения</td></tr><tr><td>-O file, -G file</td><td>Запустивший скрипт является владельцем файла, Группа запустившего скрипт входит в группу файла</td></tr><tr><td>file1 -nt file2</td><td>file1 новее file2 (сравниваются время модификации)</td></tr><tr><td>file1 -ot file2</td><td>file1 старее file2 (сравниваются время модификации)</td></tr></tbody></table>

**Цикл for**

Итерируется только по полному списку.

```
for name [in list]; do
    statements that can use $name...
    done
```

Если list не определен, то используется $@

Есть переменная IFS

```bash
IFS=:
for dir in $PATH
  do
    ls -ld $dir
  done
```

break выход из цикла, continue продолжить следующую итерацию

**Цикл while**

```bash
while condition; do
statements...
done
```

Для бесконечного цикла можно использовать while true; do

Просмотр каждой строки вывода программы (например ping)

```bash
#!/bin/bash

ping 172.16.10.1 | while read -r line; do
  echo Пинг $line | awk '{print $1 $6}'
  done

```

**Цикл until**

```bash
until command; do
statements...
done

```

**Case**

```bash
for filename in "$@"; do
  pnmfile=${filename%.*}.ppm
  case $filename in
    *.jpg ) exit 0 ;;
    *.tga ) tgatoppm $filename > $pnmfile ;;
    *.xpm ) xpmtoppm $filename > $pnmfile ;;
    *.pcx ) pcxtoppm $filename > $pnmfile ;;
    *.tif ) tifftopnm $filename > $pnmfile ;;
    *.gif ) giftopnm $filename > $pnmfile ;;
    * ) echo "procfile: $filename is an unknown graphics file." 
        exit 1 ;;
  esac
  outfile=${pnmfile%.ppm}.new.jpg
  pnmtojpeg $pnmfile > $outfile
  rm $pnmfile
done
```

**Опции исполнения**

shift смещает переменные влево.

```
myshift.sh
echo $1 $2
shift
echo $1 $2
Использование: ./myshift.sh one two
Вывод:
one two
two
```

Вариант скрипта, считывающего параметры со случайным размещением параметров

```bash
while [ -n "$(echo $1 | grep '-')" ]; do
  case $1 in
    -a ) aopt=$2
         shift;;
    -b ) bopt="True"
        ;;
    -c ) copt=$2
        shift;;
    *  ) echo 'usage: alice [-a] [-b] [-c] args...'
         exit 1
  esac
  shift
done
```

Опция -b является одинарной, -a и -c параметризованные.

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

```
echo "First arg"
read -r farg
echo "First arg is ${farg}"
```

**Вызов другого скрипта**

Если есть права на исполнение и правильный shebang то вызов по имени. Скрипт исполняется в подпроцессе.

```bash
#!/bin/bash
echo "Это первый скрипт"

./script2.sh   # вызов второго скрипта
```

Если директория script2.sh не в PATH то нужен полный путь.

Для выполнения скрипта в одном процессе, все переменные передаются:

```bash
#!/bin/bash
echo "Это первый скрипт"

source script2.sh
```

Также можно передавать аргументы.