CodingStyle/Shell/TypicalCode

Материал из Etersoft wiki
Перейти к навигацииПерейти к поиску

Типовые конструкции при программировании на Shell.

Оформление скрипта

В начале скрипта пишется конструкция

#!/bin/sh

если вы пишете на стандартном shell и

#!/bin/bash

если обязательны некоторые особенности bash.


Переменные

Определение локальных и глобальных переменных

Глобальные переменные указываем так:

MYBIGVAR=5

Локальные (в функциях):

local my_local_var="some var"

или так:

local my_local_var your_local_var
my_local_var=5
your_local_var=$my_local_var

Пример:

myfunc()
{
    local my_local_var
    if [ -n "$MYBIGVAR" ] ; then
        my_local_var="$MYBIGVAR"
    fi
}

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

Константы

Для уверенности, что присвоенное значение никто не изменит, нужно использовать модификатор readonly:

MYBIGVAR=5
...
readonly MYBIGVAR
...
MYBIGVAR=new # error here

в этом примере мы сначала присвоили значение, а потом решили, что оно будет read-only. Можно присваивать и сразу:

readonly MYBIGVAR=5

Для локальных переменных записывается так:

readonly local my_local_var

Интересная особенность: глобальная read-only переменная не даёт определять и присваивать локальные переменные с таким же именем.

Варианты использования

Присваивание значений

Обратите внимание:

VAR=5 command

вызывает command, у которой среди переменных окружения будет VAR, равно 5.

VAR= 5 command

вызывает команду 5 с параметром command и пустым значением переменной VAR

VAR = 5 command

вызывает команду VAR с параметрами = 5 command

Пример:

func()
{
    echo $VAR
}

VAR=10
VAR=5 func
echo $VAR

вызывает функцию func, у которой среди переменных окружения будет VAR, равно 5. Выведет:

5
10

Занесение вывода команды в переменную

MYVAR="$(ls | head)"


Команды и функции

Определение функции

my_func()
{
    echo "some function code"
}

Все внешние команды ищутся по путям, записанным в переменную окружения PATH.

Вызов команды или функции

Обычно нет разницы, вызываем мы внешнюю команду или функцию.

myfunc 1 none
somecommand params

Но стоит помнить, что вызвать функцию из других программ мы не можем:

find | xargs my_func

работать не будет, потому что тут внешняя команда xargs будет пытаться выполнить команду my_func.

Внутреннее использование допустимо и правильно:

find | my_filter

Возвращаемые из функции значения

Из функции можно вернуть только статус возврата 0..255.

Не делайте лишних проверок внутри функции.

func()
{
    # 1. первый параметр должен быть не пустой, иначе выходим
    [ -n "$1" ] || return

    # 2. параметры совпадают, дальше не идём
    [ "$1" = "$2" ] && return

    # 3. если второй параметр пустой, вернёмся
    [ -z "$2" ] && return 2

    # 4. выполняем действие
    superfunc "$2" "$1"
}

Обратите внимание, что в конце функции не надо писать return $?, поскольку при любом выходе из функции она возвращает статус последней команды.

Коды возврата нашей func из примера:

  1. Если 1-й параметр пустой, сразу вернёт 1 (false)
  2. Если параметры совпадают, сразу вернём true
  3. Если 2-й параметр пустой, сразу вернём 2 (false)
  4. выполним функции superfunc и вернём статус её возврата, поскольку она последняя.

Использование параметров по умолчанию:

func()
{
    local PARAM="$1"
    [ -n "$PARAM" ] || PARAM="default"
    echo "$PARAM"
}

func 10
func

Условия

Сравнение

Обычно для сравнения разных величин используется команда test. Она же «[». В зависимости от параметров test имеет статус возврата true или false:

func()
{
    [ -n "$MYVAR" ]
    return
}

func вернёт 1 (false), если MYVAR пустая.


Условие через if

Главное: if проверяет статус возврата (true или false), поэтому там может быть вписана любая команда.

if echo Hello | grep "e" ; then
   echo "There is «e» letter."
fi

Пример:

if [ -z "$MYBIGVAR" ] ; then
    my_local_var=5
fi
echo "Very long command" && true
if [ $? = 0 ] ; then
    echo Successful
fi

Условие через && ||

Для соединения команд в любом месте можно использовать логические операции && и ||.

Например,

[ -n "$MYBIGVAR" ] || MYBIGVAR=5

(MYBIGVAR наполнена или присваиваем ей 5, то есть: проверяем, есть ли в переменной MYBIGVAR что-то непустое; если там пусто, присваиваем 5)


Таким образом записываются и сложные условия в if:

if [ -z "$MYZERO" ] && [ "$2" != "$MYVAR" ] ; then
    echo "1"
elif [ "$MYZERO = "$MYVAR" ] ; then
    echo "2"
else
    echo "default"
fi

switch / case

Большие ветвления с выбором удобно делать через case:

case "$VAR" in
    a|b|c)
        echo "some char"
        ;;
    alpha|beta|gamma)
        echo "some char name"
        ;;
    a*)
        echo "some word started with a letter"
        ;;
    *)
        echo "something undetected: $VAR"
        ;;
esac

Циклы

for

local i
for i in Q W E ; do
    echo $i
done

выведет
Q
W
E
local i
for i in $(somecommand) ; do
    echo $i
done

Файлы на a в каталоге test

local i
for i in test/a* ; do
    echo $i
done

while

while true; do
  echo "We are printing infinitely."
  sleep 1
done

while read

Чтение табличных данных из файла, например

$ cat input.data
apple яблоко 5
berry ягода 4
#В конце последней строки должен быть перенос
while read engname rusname count ; do
    echo "$engname в переводе на русский: $rusname"
done < input.data

Если нужно обработать с помощью while вывод команды, то вряд ли вас устроит вариант

print something | while read NN ; do
    MyVar=$NN
done

Дело в том, что while из-за участия в pipe будет порождённым процессом, и изменённые им переменные не будут переданы в основной код.

Есть bash-специфичная конструкция <<<, которая исправит ситуацию:

while read NN ; do
    MyVar=$NN
done <<<"$(print something)"

Работа с путями

Включение файлов

Для объединения кода, разбитого на несколько файлов, применяется вставка:

. /path/to/file

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

Переход в место расположения скрипта

cd $(dirname "$0")
cd $(pwd)/$(dirname "$0")

Вычисления

a=10
echo $(($a+5))

Как не надо писать

[ $(echo | grep) ]

command1 || command2 && command3

( потому что результат не очевиден (не ожидаем) )

func()
{
    for i in A B C ; do
        echo "$i: $1"
    done
}

for i in alpha beta gamma ; do
    func $i
    echo $i
done

( поскольку явно не указана локальность i в функции func, то произойдёт затирание значения i в основном цикле)

Cсылки

https://habrahabr.ru/company/mailru/blog/311762/