Shell

Шелл скрипты vs python/Perl

Кто вообще пишет шелл скрипты в 2019’ом? Хороший вопрос. Что же, я пишу. ¯\_(ツ)_/

На это есть веские причины, они описаны здесь и здесь, но всё вращается вокруг двух вещей:

  1. Командная строка представлена во всех Unix системах и использует встроенные фичи операционной системы.
  2. Командная строка — это «интерактивная командная функция», позволяющая принимать команды пользователя в процессе выполнения этих команд.

Аргументы

Когда вам необходимо передать аргумент (или ожидать его) в скрипт (так же, как передаётся параметр в функцию), вы будете использовать что-то вроде $1 в качестве первого параметра, $2 в качестве второго. Вот пример того, как это выглядит:

Скрипт run_this.sh:

echo "The input message was $1."

Выполнение:

./run_this.sh userInput
The input message was userInput.

Обратите внимание, что параметры отделены пробелом, поэтому если вы хотите ввести строку как параметр, содержащий пробел, это будет выглядеть примерно так: ./run_this.sh "user input" . "user input" будет считаться как $1 целиком.

Если вы не уверены, как долго будет продолжаться ввод пользователем, и вы хотите захватить всё что он введёт, используйте [email protected]. В следующем примере я взял всю строку и вывел её слово за словом. Я разбил слова в соответствии с пробелами и поместил их в массив.

Скрипт run_this.sh:

userInputs=([email protected])
for i in "${userInputs[@]}";; do
  echo "$i"
done

Выполнение:

./run_this.sh who knows how long this can go
who
knows
how
long
this
can
go

Функции

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

Примечание: если вы не знали, LOC (lines of code) плохая метрика для определения чего-либо в программировании. Это сказал не я, Билл Гейтс:

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

Так выглядит обычная функция:

# Declaring the function
doSomething() {
}
# Calling the function
doSomething

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

Параметры

Если вы передаёте параметр в функцию в Java, то нужно объявить его при объявлении функции. Это выглядит так:

public static void main(String[] args) {
    doSomething("random String");
}
private static void doSomething (String words) {
    System.out.println(words);
}

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

doSomething() {
    echo $1
}
doSomething "random String"
  1. Похожим образом, если вы хотите принять всё, используйте [email protected] вместо $1. Потому что $1 берёт только первый ввод (а $2 — второй ввод и т.д.).
  2. Функции должны объявляться раньше, чем они вызываются (обычно в начале файла, перед любыми основными операциями).

Возврат

Давайте создадим скрипт с именем run_this.sh:

doSomething() {
    echo "magic"
    return 0
}
output=`doSomething`
echo $output

Теперь запустим его и посмотрим, что присвивается переменной output.

$ ./run_this.sh
magic

Обратите внимание, что вместо 0 на выходе мы видим magic, потому что, когда вы выполняете output=`doSomething`, назначается сообщение output вместо возвращаемого значения. Так происходит, поскольку выходные сообщения — это способ коммуникации в шелл сценариях почти во всех случаях.

В каких случаях имеет смысл использовать вызов return? Когда вы используете его как часть выражения if. Например так:

Скрипт run_this.sh:

doSomething() {
    echo "magic"
    return 0
}
if doSomething; then
    echo "Its true!"
fi

Выполнение:

./run_this.sh
Its true!

В этом случае return 0 означает true, когда return 1 в традиционном булевом смысле означает false.

Multi-line echo

Есть несколько способов вывести сообщение, состоящее из нескольких строк. Самый простой способ — использовать echo несколько раз:

echo "line1"
echo "line2"
echo "line3"

Это не самый элегантный способ, хотя и рабочий. Вместо этого можно использовать cat << EOF. Вот так:

cat << EOF
line1
line2
line3
EOF

Обратите внимание, что перед EOF не должно быть ничего, включая пробелы и табуляцию. В if выражении можно записать это так:

if [ "a" == "a" ]; then
  cat << EOF
line1
line2
line3
EOF
fi

Имейте в виду, что даже сами сообщения выравниваются влево. Это связано с тем, что, если вы сделаете табуляцию, выходное сообщение, выводимое в командной строке, тоже будет с табуляцией. Кроме того, если в EOF есть табуляция, шелл обычно выдаёт ошибку и завершает сценарий.

Флаги/Параметры

Вы, вероятно, встречали скрипты или команды, в которые можно добавлять флаги (а иногда и аргументы для некоторых флагов). Что-то вроде: git commit -a -m "Some commit message".

Вот небольшой пример того, как это выглядит:

Скрипт run_this.sh:

while getopts ac: opt; do
    case $opt in
        a)
            echo "\"a\" was executed."
            ;;
        c)
            echo "\"c\" was executed with parameter \"$OPTARG\"."
            ;;
        \?)
            echo "Invalid option: -$opt"
            exit 1
            ;;
        :)
            echo "option -$opt requires an argument."
            exit 1
            ;;
    esac
done

Выполнение:

./run_this.sh
./run_this.sh -a
"a" was executed.
./run_this.sh -c
option -c requires an argument.
./run_this.sh -c abcd
"c" was executed with parameter "abcd".
./run_this.sh -a -c abc
"a" was executed.
"c" was executed with parameter "abc".
./run_this.sh -x
Invalid option: -x

В этом примере различия между параметрами -a и -c заключаются в том, что в строке getopts после c стоит двоеточие (:). Это сообщает программе ожидать параметр. Ещё следует иметь в виду, что параметры должны быть объявлены в алфавитном порядке. Если вы объявите что-то вроде acb, то b будет проигнорировано. В таком случае использование флага -b приведёт к ошибке.


Перевод статьи BinHong Lee: Functional and flexible shell scripting tricks