Bash-скрипты, часть 6: функции и разработка библиотек

https://likegeeks.com/bash-functions/
  • Перевод
Bash-скрипты: начало
Bash-скрипты, часть 2: циклы
Bash-скрипты, часть 3: параметры и ключи командной строки
Bash-скрипты, часть 4: ввод и вывод
Bash-скрипты, часть 5: сигналы, фоновые задачи, управление сценариями
Bash-скрипты, часть 6: функции и разработка библиотек
Bash-скрипты, часть 7: sed и обработка текстов
Bash-скрипты, часть 8: язык обработки данных awk
Bash-скрипты, часть 9: регулярные выражения
Bash-скрипты, часть 10: практические примеры
Bash-скрипты, часть 11: expect и автоматизация интерактивных утилит

Занимаясь разработкой bash-скриптов, вы рано или поздно столкнётесь с тем, что вам периодически приходится использовать одни и те же фрагменты кода. Постоянно набирать их вручную скучно, а копирование и вставка — не наш метод. Как быть? Хорошо бы найти средство, которое позволяет один раз написать блок кода и, когда он понадобится снова, просто сослаться на него в скрипте.



Оболочка bash предоставляет такую возможность, позволяя создавать функции. Функции bash — это именованные блоки кода, которые можно повторно использовать в скриптах.



Объявление функций


Функцию можно объявить так:

functionName {
}

Или так:

functionName() {
}

Функцию можно вызвать без аргументов и с аргументами.

Использование функций


Напишем скрипт, содержащий объявление функции и использующий её:

#!/bin/bash
function myfunc {
echo "This is an example of using a function"
}
count=1
while [ $count -le 3 ]
do
myfunc
count=$(( $count + 1 ))
done
echo "This is the end of the loop"
myfunc
echo "End of the script"

Здесь создана функция с именем myfunc. Для вызова функции достаточно указать её имя.


Результаты вызова функции

Функцию можно вызывать столько раз, сколько нужно. Обратите внимание на то, что попытавшись использовать функцию до её объявления, вы столкнётесь с ошибкой. Напишем демонстрирующий это скрипт:

#!/bin/bash
count=1
while [ $count -le 3 ]
do
myfunc
count=$(( $count + 1 ))
done
echo "This is the end of the loop"
function myfunc {
echo "This is an example of using a function"
}
echo "End of the script"

Как и ожидается, ничего хорошего после его запуска не произошло.


Попытка воспользоваться функцией до её объявления

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

#!/bin/bash
function myfunc {
echo "The first function definition"
}
myfunc
function myfunc {
echo "The second function definition"
}
myfunc
echo "End of the script"

Как видно, новая функция преспокойно затёрла старую.


Переопределение функции

Использование команды return


Команда return позволяет задавать возвращаемый функцией целочисленный код завершения. Есть два способа работы с тем, что является результатом вызова функции. Вот первый:

#!/bin/bash
function myfunc {
read -p "Enter a value: " value
echo "adding value"
return $(( $value + 10 ))
}
myfunc
echo "The new value is $?"

Команда echo вывела сумму введённого числа и числа 10.


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

Функция myfunc добавляет 10 к числу, которое содержится в переменной $value, значение которой задаёт пользователь во время работы сценария. Затем она возвращает результат, используя команду return. То, что возвратила функция, выводится командой echo с использованием переменной $?.
Если вы выполните любую другую команду до извлечения из переменной $? значения, возвращённого функцией, это значение будет утеряно. Дело в том, что данная переменная хранит код возврата последней выполненной команды.

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

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


Ещё один способ возврата результатов работы функции заключается в записи данных, выводимых функцией, в переменную. Такой подход позволяет обойти ограничения команды return и возвращать из функции любые данные. Рассмотрим пример:

#!/bin/bash
function myfunc {
read -p "Enter a value: " value
echo $(( $value + 10 ))
}
result=$( myfunc)
echo "The value is $result"

Вот что получится после вызова данного скрипта.


Запись результатов работы функции в переменную

Аргументы функций


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

Функции могут использовать стандартные позиционные параметры, в которые записывается то, что передаётся им при вызове. Например, имя функции хранится в параметре $0, первый переданный ей аргумент — в $1, второй — в $2, и так далее. Количество переданных функции аргументов можно узнать, обратившись к переменной $#. Если вы знакомы с третьей частью этого цикла материалов, вы не можете не заметить, что всё это очень похоже на то, как скрипты обрабатывают переданные им параметры командной строки.

Аргументы передают функции, записывая их после её имени:

myfunc $val1 10 20

Вот пример, в котором функция вызывается с аргументами и занимается их обработкой:

#!/bin/bash
function addnum {
if [ $# -eq 0 ] || [ $# -gt 2 ]
then
echo -1
elif [ $# -eq 1 ]
then
echo $(( $1 + $1 ))
else
echo $(( $1 + $2 ))
fi
}
echo -n "Adding 10 and 15: "
value=$(addnum 10 15)
echo $value
echo -n "Adding one number: "
value=$(addnum 10)
echo $value
echo -n "Adding no numbers: "
value=$(addnum)
echo $value
echo -n "Adding three numbers: "
value=$(addnum 10 15 20)
echo $value

Запустим скрипт.


Вызов функции с аргументами

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

Обратите внимание на то, что функция не может напрямую работать с параметрами, которые переданы скрипту при его запуске из командной строки. Например, напишем такой сценарий:

#!/bin/bash
function myfunc {
echo $(( $1 + $2 ))
}
if [ $# -eq 2 ]
then
value=$( myfunc)
echo "The result is $value"
else
echo "Usage: myfunc  a b"
fi

При его запуске, а точнее, при вызове объявленной в нём функции, будет выведено сообщение об ошибке.


Функция не может напрямую использовать параметры, переданные сценарию

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

#!/bin/bash
function myfunc {
echo $(( $1 + $2 ))
}
if [ $# -eq 2 ]
then
value=$(myfunc $1 $2)
echo "The result is $value"
else
echo "Usage: myfunc a b"
fi

Теперь всё работает правильно.


Передача функции параметров, с которыми запущен скрипт

Работа с переменными в функциях


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

Существуют два вида переменных:

  • Глобальные переменные.
  • Локальные переменные.

▍Глобальные переменные


Глобальные переменные — это переменные, которые видны из любого места bash-скрипта. Если вы объявили глобальную переменную в основном коде скрипта, к такой переменной можно обратиться из функции.

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

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

#!/bin/bash
function myfunc {
value=$(( $value + 10 ))
}
read -p "Enter a value: " value
myfunc
echo "The new value is: $value"

Вот что выведет этот сценарий.


Обращение к глобальной переменной из функции

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

Что если такое поведение нас не устраивает? Ответ прост — надо использовать локальные переменные.

▍Локальные переменные


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

local temp=$(( $value + 5 ))

Если за пределами функции есть переменная с таким же именем, это на неё не повлияет. Ключевое слово local позволяет отделить переменные, используемые внутри функции, от остальных переменных. Рассмотрим пример:

#!/bin/bash
function myfunc {
local temp=$[ $value + 5 ]
echo "The Temp from inside function is $temp"
}
temp=4
myfunc
echo "The temp from outside is $temp"

Запустим скрипт.


Локальная переменная в функции

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

Передача функциям массивов в качестве аргументов


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

#!/bin/bash
function myfunc {
echo "The parameters are: $@"
arr=$1
echo "The received array is ${arr[*]}"
}
myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"
myfunc $myarray


Неправильный подход к передаче функциям массивов

Как видно из примера, при передаче функции массива, она получит доступ лишь к его первому элементу.

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

#!/bin/bash
function myfunc {
local newarray
newarray=("$@")
echo "The new array value is: ${newarray[*]}"
}
myarray=(1 2 3 4 5)
echo "The original array is ${myarray[*]}"
myfunc ${myarray[*]}

Запустим сценарий.


Сборка массива внутри функции

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

Рекурсивные функции


Рекурсия — это когда функция сама себя вызывает. Классический пример рекурсии — функция для вычисления факториала. Факториал числа — это произведение всех натуральных чисел от 1 до этого числа. Например, факториал 5 можно найти так:

5! = 1 * 2 * 3 * 4 * 5

Если формулу вычисления факториала написать в рекурсивном виде, получится следующее:

x! = x * (x-1)!

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

#!/bin/bash
function factorial {
if [ $1 -eq 1 ]
then
echo 1
else
local temp=$(( $1 - 1 ))
local result=$(factorial $temp)
echo $(( $result * $1 ))
fi
}
read -p "Enter value: " value
result=$(factorial $value)
echo "The factorial of $value is: $result"

Проверим, верно ли работает этот скрипт.


Вычисление факториала

Как видите, всё работает как надо.

Создание и использование библиотек


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

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

Ключ к использованию библиотек — в команде source. Эта команда используется для подключения библиотек к скриптам. В результате функции, объявленные в библиотеке, становятся доступными в скрипте, в противном же случае функции из библиотек не будут доступны в области видимости других скриптов.

У команды source есть псевдоним — оператор «точка». Для того, чтобы подключить файл в скрипте, в скрипт надо добавить конструкцию такого вида:

. ./myscript

Предположим, что у нас имеется файл myfuncs, который содержит следующее:

function addnum {
echo $(( $1 + $2 ))
}

Это — библиотека. Воспользуемся ей в сценарии:

#!/bin/bash
. ./myfuncs
result=$(addnum 10 20)
echo "The result is: $result"

Вызовем его.


Использование библиотек

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

Вызов bash-функций из командной строки


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

Отредактируйте .bashrc, добавив в него такую строку (путь к файлу библиотеки в вашей системе, естественно, будет другим):

. /home/likegeeks/Desktop/myfuncs

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

$ addnum 10 20


Вызов функции из командной строки

Ещё приятнее то, что такая вот библиотека оказывается доступной всем дочерним процессам оболочки, то есть — ей можно пользоваться в bash-скриптах, не заботясь о подключении к ним этой библиотеки.

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

Итоги


Функции в bash-скриптах позволяют оформлять блоки кода и вызывать их в скриптах. А наиболее часто используемые функции стоит выделить в библиотеки, которые можно подключать к скриптам, используя оператор source. Если же среди ваших функций найдутся такие, без которых вы прямо таки жить не можете — библиотеки с ними можно подключить в файле .bashrc. Это позволит удобно пользоваться ими в командной строке или в других скриптах. Главное — чтобы имена ваших функций не совпадали с именами встроенных команд.

На сегодня это всё. В следующий раз поговорим об утилите sed — мощном средстве обработки строк.



Уважаемые читатели! А вы пользуетесь функциями собственной разработки для решения повседневных задач?

RUVDS.com

745,53

RUVDS – хостинг VDS/VPS серверов

Поделиться публикацией
Комментарии 33
    –2
    Спасибо!
      0
      Автор написал свой пост в 16:10. Через полтора часа никакой реакции от сообщества не было. Я решил поддержать автора и написал ему слова благодарности. Тем более нашёл в его публикации что-то полезное для себя.

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

      Мне кажется, минусовать без пояснения, в данном случае, — некрасиво, это — обнажать какие-то свои скрытые комплексы.
        0

        Вы читали комментарии к прошлым "статьям" (точнее переводам)? Я читал. Включая комментарии.


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


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

          –1
          Кроме лирики конкретика какая-нибудь будет?

          Я, например, не считаю ЭТУ статью плохой. И по комментариям к ЭТОЙ статье я это тоже вижу. Идёт здоровая дискуссия, аналогичная дискуссиям в комментариях к другим статьях на Хабре.

          Я не знаю, может у Вас что-то этакое есть, позволяющие Вам безапелляционно навешивать всякие ярлыки («Тут всё плохо!» и далее «Я всех спасу!»). Вы, уж, предъявите тогда это, чтобы всем было понятно, что Вы за фрукт. Какую-нибудь ссылку на свою шикарную статью по данной тематике, например, или, может быть, даже книгу.

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

          Как-то неубедительно Вы звучите пока.
            +1

            Если говорить об этой статье: неправильная работа с массивами, использование . в скриптах. Вполне достаточно.


            Плюс, эти проблемы уже освещались в комментариях к этой статье. Например, пользователем khim.

              –3
              Не согласен, абсолютно. Доводы — никакие совсем.

              Во-первых, как-то малова-то т.н. «недочётов» для такой обширной статьи, чтобы называть её плохой.

              Во-вторых, про массивы. В том контексте, в котором упоминается работа с массивами, я, например, не вижу ничего неправильного. Скрипт отработает так, как нужно.

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

              Я, например, хотел бы, чтобы эта дискуссия про Bash на Хабре продолжилась. Поэтому, спасибо автору и комментаторам, что они тратят своё личное время на это!

              В-третьих, про точку в скриптах. Я не понял, что тут неправильного? Поясните, пожалуйста. Если открыть наугад, идущий в стандартной установки Linux, скрипт в /etc/init.d или /etc/rc.d (или что-то ещё похожее, не знаю, с чем Вы работаете), наверняка Вы там встретите использование точки. Я точку встречаю во многих скриптах.

              Кроме того, автор не призывает использовать или не использовать точку в скриптах. Автор поделился вполне конкретной информацией о том, что source имеет псевдоним — точку. Разве это неправильная информация?
                +1
                Во-вторых, про массивы. В том контексте, в котором упоминается работа с массивами, я, например, не вижу ничего неправильного. Скрипт отработает так, как нужно.

                В некоторых частных случаях. А учить писать на баше так, что обычно работает, но может сломаться в самый удачный момент, на мой взгляд, в корне неверно. Баш — минное поле и очень неприятное legacy.


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

                Часть комментаторов уже сдалась, т. к. в этой серии ошибки кочуют из статьи в статью.


                В-третьих, про точку в скриптах. Я не понял, что тут неправильного? Поясните, пожалуйста. Если открыть наугад, идущий в стандартной установки Linux, скрипт в /etc/init.d или /etc/rc.d (или что-то ещё похожее, не знаю, с чем Вы работаете), наверняка Вы там встретите использование точки. Я точку встречаю во многих скриптах.

                Использование точки, равно как и коротких опций в скриптах — code smell. Это ухудшает поддерживаемость скрипта. Насчёт же init.d/rc.d на моей системе оно выглядит так:


                -> % ls /etc/init.d /etc/rc.d
                ls: cannot access '/etc/rc.d': No such file or directory
                /etc/init.d:
                vmware

                На centos7 похуже, там больше всякого legacy-барахла. Вообще поддерживать старые добрые init-скрипты на баше даже под пару версий дистрибутива уже не очень приятно. А уж ориентироваться на них, как на образец написания программ на баше так вообще не стоит. Говнокода там хватает.


                Я понял, что вам понравилась эта серия статей. Не буду мешать вам оставаться при своём мнении. Предлагаю завершить дискуссию.

                  +3
                  Автор поделился вполне конкретной информацией о том, что source имеет псевдоним — точку.

                  Вот только нихр это не верное утверждение. Если сильно притянуть за уши — то это source псевдоним синоним «точки», поскольку точка перекочевала из оригинального борн шела а source — нововведение (причём я не рискну сказать баша или ksh).
                  Но если посмотреть мануал, а ещё лучше вообще исходники 23:52, то выяснится что это две абсолютно равнозначных встроенных команды (built-in) вызывающих одну и ту же функцию source_builtin

                  Я точку встречаю во многих скриптах.

                  А вы вообще знаете почему вы встречаете точку-то? Нет? А эта «статья» почему-то этот весьма важный момент опустила, не смотря на то, что об этих встроенных командах обмолвилась.
                  В качестве повышения квалификации предлагаю вам эту информацию нарыть и поделиться с другими читателями. Ключевое слово — POSIX. В догонку можете поискать на что ресолвится /bin/sh в современных линуксовых дистрах (для упрощения давайте возьмём debian, ubuntu, centos, opensuse, arch), и что будет если скрипт с шебангом #!/bin/sh этому делу скормить и в чём будет разница между source и точкой.

                  И о такого рода недочётах, которые рано или поздно начинающих приведут к «выстрели себе в ногу рикошетом в голову», я могу к каждой из этих публикаций писать полотна, но, зачем?! Ниже iig написал про absg. Этот материал я упоминал в комментариях к предыдущим статьям как минимум трижды.
                  Мой добрый совет — читайте мной ранее упомянутые вещи, а не это.
                    –3
                    Спасибо огромное за совет! У меня он тоже есть для Вас.

                    Учитесь слышать людей.

                    Ваш опус в контексте статьи — ненужная шелуха, информация, чтобы блеснуть умом перед девочками. К чему Вы его написали-то? (Подумайте над этим вопросом и дайте себе ответ, только правдивый.)

                    Он доказывает, что точку в скриптах использовать неправильно? Нет. Мимо.

                    Он как-то показывает, что, если я буду использовать точку вместо source, я получу другой результат? Опять нет. «Молоко».

                    Автор статьи всего лишь пытался донести, что вместо source с таким же успехом можно использовать точку. Не больше, не меньше. А Вы эту простую мысль-то и не услышали! (И давай обнажать свой «ум».)

                    А так да, Вы написали много правильных слов. Но, как-то не туда, понимаете. Мимо. Может Вам книгу лучше написать, с таким умищем-то? И девочкам это нравится ещё больше.

                    Короче, перечитывайте этот мой комментарий много раз. Он вам поможет научиться слышать людей. Это Вам мой добрый совет.
                      +1
                      Короче, перечитывайте этот мой комментарий много раз. Он вам поможет научиться слышать людей. Это Вам мой добрый совет.

                      Хамство вас не красит, успокойтесь

                        –2
                        Успокойтесь, пожалуйста! Нам с ghostinushanka суфлёр не нужен. Мы сами разберёмся.

                        Кроме того нашу с Вами дискуссию мы уже прекратили (кстати, по Вашей же инициативе).

                        Не тратьте, пожалуйста, своё личное время на меня. Я своё точно уже на Вас тратить не собираюсь. Если хотите как-то отреагировать, то просто продолжайте меня минусовать, это точно не отнимет у Вас много Вашего личного времени.

                        Услышьте меня, пожалуйста, наконец!
                        +2
                        Иными словами вы не стали ничего узнавать про POSIX.
                        В контекте вот этой вашей фразы, это, к великому моему сожалению, весьма печально:
                        Он как-то показывает, что, если я буду использовать точку вместо source, я получу другой результат?

                        Да получите, причём в самый неожиданный момент. Или вы серьёзно считаете, что я со злым умыслом и от нечего делать вас направил что-то поискать почитать?

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

                        #irony #rhetorical
                        А где мне спасибо на прямые ссылки на «Advanced bash scripting guide», которые я приводил комментариями к предыдущим статьям?

                        P.S. Прошу прощения у хабрапублики. Я надеюсь что эти комментарии всё же помогут начинающим и не очень писателям шелл скриптов.
                        Подписываюсь под этим абсолютно верным комментарием к следующей «статье из цикла».
                          –3
                          Очень жаль, что Вы даже и не попробовали научиться слышать людей!
                          Да получите, причём в самый неожиданный момент.

                          Вы уж расскажите, пожалуйста, не томите.

                          Вы обиделись, а зря! Вы что, серьёзно считаете, что я со злым умыслом и от нечего делать Вас попытался направить научиться хоть немного слышать людей? Отнюдь.

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

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

                          Вот, Вы всё зачем-то вставляете в своих комментариях «POSIX, POSIX...». Поймите Вы, наконец, что очки — это ещё не признак ума. А, вот, умение слышать собеседника, по крайней мере, даёт надежду на его наличие.

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

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

                          Например.
                          Иными словами вы не стали ничего узнавать про POSIX.

                          Разве Вы слышали от меня, что я ничего не знаю про POSIX? Как я могу, например, писать многопоточные сервера и не знать про POSIX? Я POSIX уже хорошо знал ещё в 90-х. Но я не вижу в этом ничего экстраординарного — знать POSIX. С таким же успехом Вы могли бы мне в также назидательном тоне посоветовать изучать, например, Linux. Моё восприятие Ваших слов совершенно бы не изменилось. Ваш совет меня искренне улыбнул. :)

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

                          Ещё, например.

                          Я совершенно не сомневался, что Вы обязательно напишите мне что-то типа этого:
                          А где мне спасибо на прямые ссылки на «Advanced bash scripting guide», которые я приводил комментариями к предыдущим статьям?


                          Вы умудрились обидеться (хотя и прикрылись якобы иронией) на комментарий, который предназначался не для Вас. Я, конечно, оброщался к iig, но для Вас этот комментарий тоже предназначался, но, только в качестве эксперимента. Мне же надо было как-то получить от Вас косвенные подтверждения, Вашей обидчивости. Спасибо, тест сдан, Вы это подтвердили! Поразмышляйте над этим тоже.

                          Продолжим.

                          P.S. Прошу прощения у хабрапублики. Я надеюсь что эти комментарии всё же помогут начинающим и не очень писателям шелл скриптов.
                          Подписываюсь под этим абсолютно верным комментарием к следующей «статье из цикла».


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

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

                          Хотите перейти на сторону реальных ребят? — научитесь для начала слышать людей.

                          Лимит моего личного времени на дискуссию с Вами, к сожалению, закончился. Но Вы, если у Вас есть что ответить, обязательно ответьте, а я обязательно прочту. Я не обидчивый. Мне людей изучать тоже интересно.
                  0
                  Канонический Advanced bash scripting guide не намного больше по размеру, я бы его рекомендовал.
                    –1
                    Спасибо, добрый человек!

                    Хотя за слова благодарности тут есть много минусующих, я всё-таки решил поблагодарить за реально полезную информацию. Кто-нибудь, случайно наткнувшись на эту статью, сможет легко двинуться дальше (как и я, например).
                  –1
                  Без обид, но если уж на то пошло — соберитесь вместе с умными людьми, и напишите свою серию статей!
                  И 100% найдётся люди умнее и также будут комментировать…

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

                  Я, например, пол года назад совершенно не знал об администрировании серверов, а сейчас приходится содержать 2 сервера, и для меня хоть какие-то примеры — это послание от бога! Но я согласен, что примеры не ахти моментами, но что поделать…
                    +1

                    Не беспокойтесь, без ваших мудрых советов от активного автора разберусь чему посвятить своё свободное время. И тут мои и не только мои open source проекты куда в большем приоритете, чем статьи на хабре.

              0
              Тут стоит отметить, что для того, чтобы вышеприведённый пример заработал, может понадобиться выйти из системы, а потом войти снова.
              Если добавляли в .bashrc, то не надо выходить из системы. Достаточно либо запустить новую копию баша (например, открыть новую вкладку в терминале или прямо в текущей вкладке запустить ещё один дочерний bash), либо в текущей командной строке вызвать ту же команду для подгрузки функций, которую добавляли в файл.

              Кроме того, обратите внимание на то, что если имя функции из библиотеки совпадёт с именем какой-нибудь стандартной команды, вместо этой команды будет вызываться функция.
              И зачастую это очень удобно. Подменяем grep на функцию, которая запускает «настоящий» grep, динамически выбирая для него разные опции в зависимости от того, запущен ли в терминале (а не, скажем, в pipe-цепочке команд):
              function grep {
                      if [ -t 1 ] ; then
                              /bin/grep --color -n "$@"
                      else
                              /bin/grep "$@"
                      fi
              }
              
                0
                source просто вставляет кусок кода из другого файла. Внутри может и не быть никакой библиотеки.
                  +5
                  И опять у нас статья, которая учит пользователей писать неправильные скрипты на bash'е. Давайте немного модифицируем пример с передачей массивов:
                  #!/bin/bash
                  function myfunc {
                  local newarray
                  newarray=("$@")
                  echo "The new array value is:"
                  for value in "${newarray[@]}" ; do
                    echo "  $value"
                  done
                  }
                  myarray=(1 "2 3" "4 5")
                  echo "The original array is:"
                  for value in "${myarray[@]}" ; do
                    echo "  $value"
                  done
                  myfunc ${myarray[*]}
                  

                  Запустим, и…
                  
                  The original array is:
                    1
                    2 3
                    4 5
                  The new array value is:
                    1
                    2
                    3
                    4
                    5
                  
                  Таки у нас проблема.

                  А как надо? А вот так:
                  #!/bin/bash
                  function myfunc {
                  local newarray
                  newarray=("$@")
                  echo "The new array value is:"
                  for value in "${newarray[@]}" ; do
                    echo "  $value"
                  done
                  }
                  myarray=(1 "2 3" "4 5")
                  echo "The original array is:"
                  for value in "${myarray[@]}" ; do
                    echo "  $value"
                  done
                  myfunc "${myarray[@]}"
                  Теперь — порядок:
                  
                  The original array is:
                    1
                    2 3
                    4 5
                  The new array value is:
                    1
                    2 3
                    4 5
                  
                  Собственно я вообще не могу припомнить ни одного учебника по bash, где не было бы каких-либо ошибок (в bash есть много разных способов «выстрелить себе в ногу).

                  И вот это вот — «простой» язык? Да ни в жизнь. Иначе бы „гуру“, пишущие учебники, уж наверное, не совершали бы „детских“ ошибок.

                  При этом — всё ведь есть! Давайте например, распечатаем не весь массив в функции, а подмассив:
                  #!/bin/bash
                  function myfunc {
                  local newarray
                  newarray=("$@")
                  echo "The new array value is:"
                  for value in "${newarray[@]:1:2}" ; do
                    echo "  $value"
                  done
                  }
                  myarray=(1 "2 3" "4 5" 6 7)
                  echo "The original array is:"
                  for value in "${myarray[@]}" ; do
                    echo "  $value"
                  done
                  myfunc "${myarray[@]}"
                  Работает отлично:
                  
                  The original array is:
                    1
                    2 3
                    4 5
                    6
                    7
                  The new array value is:
                    2 3
                    4 5
                  
                  А теперь попробуем „отрезать кусочек“ в момент его создания:
                  #!/bin/bash
                  function myfunc {
                  local newarray
                  newarray=("${@:1:2}")
                  echo "The new array value is:"
                  for value in "${newarray[@]}" ; do
                    echo "  $value"
                  done
                  }
                  myarray=(1 "2 3" "4 5" 6 7)
                  echo "The original array is:"
                  for value in "${myarray[@]}" ; do
                    echo "  $value"
                  done
                  myfunc "${myarray[@]}"
                  Всё сьехало:
                  The original array is:
                    1
                    2 3
                    4 5
                    6
                    7
                  The new array value is:
                    1
                    2 3
                  Довайте поэкспериментерием:
                  #!/bin/bash
                  function myfunc {
                  local newarray
                  newarray=("${@:0}")
                  echo "The new array value is:"
                  for value in "${newarray[@]}" ; do
                    echo "  $value"
                  done
                  }
                  myarray=(1 "2 3" "4 5" 6 7)
                  echo "The original array is:"
                  for value in "${myarray[@]}" ; do
                    echo "  $value"
                  done
                  myfunc "${myarray[@]}"
                  И:
                  The original array is:
                    1
                    2 3
                    4 5
                    6
                    7
                  The new array value is:
                    test.sh
                    1
                    2 3
                    4 5
                    6
                    7
                  
                  Ага. Теперь понятно: $0 — ведь это название скрипта (скрипта, а не функции, заметьте!) и когда мы „массив агументов“ обрабатываем — про это нужно помнить… Но „для удобства“ когда мы работает с "$@" нулевой элемент выкидывается автоматически!

                  Ну что за прелесть этот bash! PHP — это ужас, как всем известно, но bash… bash — это ужас-ужас-ужас-ужас-ужас!
                    +3

                    И это вы ещё поверхность не поцарапали...


                    cd $dir
                    rm -fr *

                    Спойлер: представим, что $dir не существует или пустой.


                    file=/etc/passwd
                    foo() {
                      # local file
                      for file in /*; do # file теперь глобально видна всем
                        :
                      done
                    }
                    foo
                    # ой!
                    echo $file

                    copy_file_to_tmp() {
                      cp "$1" /tmp # работает ровно до момента появления файла с названием "-f"
                    }

                    Тысячи их.


                    А ещё bash -ue script.sh.


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


                    P.S.


                    count=$(( $count + 1 ))
                    # так тоже работает
                    ((count++))

                    P.P.S. В systemd поняли ошибку и переписали всё с шелл-портянок на Си. Но в последний момент всё равно что-то пошло не так...

                      +1
                      Ну дык. Решение первой проблемы «всем известно»:
                      #!/bin/bash
                      set -eu
                      dir=oops
                      cd "$dir"
                      rm -rf ./*
                      Получаем:
                      $ bash test.sh 
                      test.sh: строка 4: cd: oops: Нет такого файла или каталога
                      
                      Писать скрипты на bash'е без set -eu — нарываться на неприятности.

                      Для второй проблемы существует директива local.

                      Для cp — тоже есть решение, даже два:
                      1. Можно использовать cp -- "$1" /tmp
                      2. Можно сделать функцию примерно такого вида:
                      function make_safe {
                        if [[ "${1::1}" != "/" && "${1::2}" != "./" ]]; then
                          echo -n "./$1"
                        else
                          echo -n "$1"
                        fi
                      }
                      После чего имея переменную unsafe_name вы можете «легко» сделать её безопасной:
                      IFS= read -rd '' safe_name < <(make_safe "$unsafe_name")
                      В общем если принять «как данность», что написание скриптов на bash'е — это скорее разгадывание ребусов, то всё можно сделать. Но на нормальное программирование это ни разу не похоже.

                      Если же скрипты пишутся для себя, то часто можно обойтись более простыми средствами. Если имена файлов, с которыми вам приходится работать не заканчиваются на возврат каретки и не начинаются со знака минус, то ведь и просто cp "$1" /tmp сработает!
                        0
                        cd $dir
                        rm -fr *
                        


                        Выстрелить в ногу и жаловаться, что теперь больно.

                        cd "$dir" && rm -fr *
                        


                        И тут оказывается, что rm -fr * не удаляет скрытые файлы (имена которых начинаются с точки)…
                          0
                          И тут оказывается, что rm -fr * не удаляет скрытые файлы (имена которых начинаются с точки)…
                          Я так понимаю, что на это и был рассчёт. Хотя фиг его знает. Целиком скрипта мы не видим, так что…

                          А вообще — set -eu надёжнее, чем
                          cd "$dir" && rm -fr *
                          Потому что просто тупо игнорировать тот факт, что перейти куда-то не удалось — нехорошо. Нужно либо вывалиться, либо if cd "$dir" сделать и потом как-то обработать ошибку…
                            0
                            У каждого свой набор шаблонов чтобы подпирать особенности программ на bash ;)
                            Ошибки желательно обрабатывать, кто ж против.
                      +3
                      И опять у нас статья, которая учит пользователей писать неправильные скрипты на bash'е.

                      После того как все мои замечания к предыдущим «статьям» остались без внимания, я просто забил их комментировать.
                      Было бы неплохо, еслиб НЛО вывесило баннер над этими статьями в стиле «Бородатые комментаторы советуют к статьям относиться со скепсисом»
                        0
                        Бородатые комментаторы весь баш выкинули уже 3 года как
                        и увольняют за попытку его использования

                        Ansible проще поддерживать и развивать
                          0
                          ну-ну, если б они пореже функционал меняли. Обновился, а переменные уже локальные для include и переопределить их можно только через set_fact. Или with_item синтаксис поменяли, или loop_var. Я уже заколебался переписывать. В баше такого нет :)
                      0
                      :(){ :|:& };:
                      

                      ;)
                        0
                        Зачем людей плохому-то учить?
                          0
                          Это краш-тест начинающих и любопытных башо-писателей, к тому же в рамках статьи :).
                          0
                          Все зависло!!!
                            0

                            Толсто. У кого в современном миру не настроены ulimits и cgroups?

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

                        Самое читаемое