-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtips.sh
More file actions
258 lines (198 loc) · 13 KB
/
tips.sh
File metadata and controls
258 lines (198 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
Интересные приемы программирования на Bash
Эти приемы были описаны во внутреннем проекте компании Google «Testing on the Toilet» (Тестируем в туалете — распространение листовок в туалетах, что бы напоминать разработчикам о тестах).
В данной статье они были пересмотрены и дополнены.
Безопасность
Я начинаю каждый скрипт со следующих строк
#!/bin/bash
set -o nounset
set -o errexit
Это защищает от двух частых ошибок
1) Попыток использовать не объявленные переменные
2) Игнорирование аварийного завершения команд
Если команда может завершиться аварийно, и нас это устраивает, можно использовать следующий код:
if ! <possible failing command> ; then
echo "failure ignored"
fi
Нужно помнить что некоторые команды не возвращают код аварийного завершения например “mkdir -p” и “rm -f”.
Так же есть сложности с вызовом цепочек подпрограмм (command1 | command 2 | command3) из скрипта, для обхода этого ограничения можно использовать следующую конструкцию:
(./failing_command && echo A)
В этом случае оператор '&&' не позволит выполниться следующей команде, подробнее — 'http://fvue.nl/wiki/Bash:_Error_handling'
Функции
Bash позволяет использовать функции как обычные команды, это очень сильно повышает читаемость вашего кода:
Пример 1:
ExtractBashComments() {
egrep "^#"
}
cat myscript.sh | ExtractBashComments | wc
comments=$(ExtractBashComments < myscript.sh)
Пример 2:
SumLines() { # iterating over stdin - similar to awk
local sum=0
local line=””
while read line ; do
sum=$((${sum} + ${line}))
done
echo ${sum}
}
SumLines < data_one_number_per_line.txt
Пример 3:
log() { # classic logger
local prefix="[$(date +%Y/%m/%d\ %H:%M:%S)]: "
echo "${prefix} $@" >&2
}
log "INFO" "a message"
Попробуйте перенести весь ваш код в функции оставив только глобальные переменные/константы и вызов функции «main» в которой будет вся высокоуровневая логика.
Объявление переменных
Bash позволяет объявлять переменные нескольких типов, самые важные:
local (Для переменных используемых только внутри функций)
readonly (Переменные попытка переназначения которых вызывает ошибку)
## Если DEFAULT_VAL уже объявлена, то использовать ее значение, иначе использовать '-7'
readonly DEFAULT_VAL=${DEFAULT_VAL:-7}
myfunc() {
# Использование локальной переменной со значением глобальной
local some_var=${DEFAULT_VAL}
...
}
Есть возможность сделать переменную типа readonly из уже объявленной:
x=5
x=6
readonly x
x=7 # failure
Стремитесь к тому что бы все ваши переменные были либо local, либо readonly, это улучшит читаемость и снизит количество ошибок.
Используйте $() вместо обратных кавычек ``
Обратные кавычки плохо читаются и в некоторых шрифтах легко могут быть перепутаны с одинарными кавычками.
Конструкция $() так же позволяет использовать вложенные вызовы без головной боли с экранированием:
# обе команды выводят: A-B-C-D
echo "A-`echo B-\`echo C-\\\`echo D\\\`\``"
echo "A-$(echo B-$(echo C-$(echo D)))"
Используйте двойные квадратные скобки [[]] вместо одинарных []
Двойные квадратные скобки позволяют избежать непреднамеренного использования путей вместо переменных:
$ [ a < b ]
-bash: b: No such file or directory
$ [[ a < b ]]
В некоторых случаях упрощают синтаксис:
[ "${name}" \> "a" -o ${name} \< "m" ]
[[ "${name}" > "a" && "${name}" < "m" ]]
А так же предоставляют дополнительную функциональность:
Новые операторы:
|| Логическое ИЛИ (logical or) — только с двойными скобками.
&& Логическое И (logical and) — только с двойными скобками.
< Сравнение строковых переменных (string comparison) — с двойными скобками экранирование не нужно.
== Сравнение строковых переменных с подстановкой (string matching with globbing) — только с двойными скобками.
=~ Сравнение строковых переменных используя регулярные выражения (string matching with regular expressions) — только с двойными скобками.
Дополненные/измененные операторы:
-lt Цифровое сравнение (numerical comparison)
-n Строковая переменная не пуста (string is non-empty)
-z Строковая переменная пуста (string is empty)
-eq Цифровое равенство (numerical equality)
-ne Цифровое не равенство (numerical inequality)
Примеры:
t="abc123"
[[ "$t" == abc* ]] # true (globbing)
[[ "$t" == "abc*" ]] # false (literal matching)
[[ "$t" =~ [abc]+[123]+ ]] # true (regular expression)
[[ "$t" =~ "abc*" ]] # false (literal matching)
Начиная с версии bash 3.2 регулярные выражения или выражения с подстановкой не должны заключаться в кавычки, если ваше выражение содержит пробелы, вы можете поместить его в пеерменную:
r="a b+"
[[ "a bbb" =~ $r ]] # true
Сравнене строковых переменных с подстановкой так же доступно в операторе case:
case $t in
abc*) <action> ;;
esac
Работа со строковыми переменными:
В bash встроено несколько (недооцененных) возможностей работы со строковыми переменными:
Базовые:
f="path1/path2/file.ext"
len="${#f}" # = 20 (длина строковой переменной)
# выделение участка из переменной: ${<переменная>:<начало_участка>} или ${<переменная>:<начало_участка>:<размер_участка>}
slice1="${f:6}" # = "path2/file.ext"
slice2="${f:6:5}" # = "path2"
slice3="${f: -8}" # = "file.ext" (обратите внимание на пробел перед знаком '-')
pos=6
len=5
slice4="${f:${pos}:${len}}" # = "path2"
Замена с подстановкой:
f="path1/path2/file.ext"
single_subst="${f/path?/x}" # = "x/path2/file.ext" (змена первого совпадения)
global_subst="${f//path?/x}" # = "x/x/file.ext" (замена всех совпадений)
Разделение переменных:
f="path1/path2/file.ext"
readonly DIR_SEP="/"
array=(${f//${DIR_SEP}/ })
second_dir="${arrray[1]}" # = path2
Удаление с подстановкой:
# Удаление с начала строки, до первого совпадения
f="path1/path2/file.ext"
extension="${f#*.}" # = "ext"
# Удаление с начала строки, до последнего совпадения
f="path1/path2/file.ext"
filename="${f##*/}" # = "file.ext"
# Удаление с конца строки, до первого совпадения
f="path1/path2/file.ext"
dirname="${f%/*}" # = "path1/path2"
# Удаление с конца строки, до последнего совпадения
f="path1/path2/file.ext"
root="${f%%/*}" # = "path1"
Избавляемся от временных файлов
Некоторые команды ожидают на вход имя файла, с ними нам поможет оператор '<()', он принимает на вход команду и преобразует в нечто что можно использовать как имя файла:
# скачать два URLa и передать их в diff
diff <(wget -O - url1) <(wget -O - url2)
Использование маркера для передачи многострочных переменных:
# MARKER — любое слово.
command << MARKER
...
${var}
$(cmd)
...
MARKER
Если нужно избежать подстановки, то маркер можно взять в кавычки:
# конструкция вернет '$var' а не значение переменной
var="text"
cat << 'MARKER'
...
$var
...
MARKER
Встроенные переменные
$0 Имя скрипта (name of the script)
$1 $2… $n Параметры переданные скрипту/фнукции (positional parameters to script/function)
$$ PID скрипта (PID of the script)
$! PID последней команды выполненной в фоне(PID of the last command executed (and run in the background))
$? Статус возвращенный последней командой (exit status of the last command (${PIPESTATUS} for pipelined commands))
$# Количество параметров переданных скрипту/функции (number of parameters to script/function)
$@ Все параметры переданные скрипту/функции, представленные в виде слов (sees arguments as separate word)
$* Все параметры переданные скрипту/функции, представленные в виде одного слова (sees arguments as single word)
Как правило:
$* Редко является полезной
$@ Корректно обрабатывает пустые параметры и параметры с пробелами
$@ При использовании обычно заключается в двойные кавычки — "$@"
Пример:
for i in "$@"; do echo '$@ param:' $i; done
for i in "$*"; do echo '$! param:' $i; done
вывод:
bash ./parameters.sh arg1 arg2
$@ param: arg1
$@ param: arg2
$! param: arg1 arg2
Отладка
Проверка синтаксиса (экономит время если скрипт выполняется дольше 15 секунд):
bash -n myscript.sh
Трассировка:
bash -v myscripts.sh
Трассировка с раскрытием сложных команд:
bash -x myscript.sh
Параметры -v и -x можно задать в коде, это может быть полезно если ваш скрипт работает на одной машине а журналирование ведется на другой:
set -o verbose
set -o xtrace
Признаки того, что вы не должны использовать shell скрипты:
Ваш скрипт содержит более нескольких сотен строк.
Вам нужны структуры данных сложнее обычных массивов.
Вас задолбало заниматься непотребствами с кавычками и экранированием.
Вам необходимо обрабатывать/изменять много строковых переменных.
У вас нет необходимости вызывать сторонние програмы и нет необходимости в пайпах.
Для вас важна скорость/производительность.
Если ваш проект соответствует пунктам из этого списка, рассмотрите для него языки языки Python или Ruby.
Ссылки:
Advanced Bash-Scripting Guide: tldp.org/LDP/abs/html/
Bash Reference Manual: www.gnu.org/software/bash/manual/bashref.html
Оригинал статьи: robertmuth.blogspot.ru/2012/08/better-bash-scripting-in-15-minutes.html