Шелл скрипты vs python/Perl
Кто вообще пишет шелл скрипты в 2019’ом? Хороший вопрос. Что же, я пишу. ¯\_(ツ)_/
На это есть веские причины, они описаны здесь и здесь, но всё вращается вокруг двух вещей:
- Командная строка представлена во всех Unix системах и использует встроенные фичи операционной системы.
- Командная строка — это «интерактивная командная функция», позволяющая принимать команды пользователя в процессе выполнения этих команд.
Аргументы
Когда вам необходимо передать аргумент (или ожидать его) в скрипт (так же, как передаётся параметр в функцию), вы будете использовать что-то вроде $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
целиком.
Если вы не уверены, как долго будет продолжаться ввод пользователем, и вы хотите захватить всё что он введёт, используйте $@
. В следующем примере я взял всю строку и вывел её слово за словом. Я разбил слова в соответствии с пробелами и поместил их в массив.
Скрипт run_this.sh
:
userInputs=($@)
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
. Потому что$1
берёт только первый ввод (а$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