-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path05-scripting.tex
341 lines (321 loc) · 8.55 KB
/
05-scripting.tex
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
% !TeX root = bash.tex
\section{V. Bash Scripting}
\begin{frame}[fragile]
\frametitle{Bash is a programming language}
Let's say you want to print many files at once.
\newline \newline
Try this simple example:
\begin{lstlisting}[language=bash]
$ for i in {01..05}; do \
cat um-logo-$i.txt; \
done
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]
\frametitle{Variables}
A variable in bash is defined this way:
\begin{lstlisting}[language=bash]
$ i=0
$ s='s'
\end{lstlisting}
Surprisingly these do \textbf{not} work:
\begin{lstlisting}[language=bash]
$ i = 0 # WRONG
$ s = 's' # WRONG
\end{lstlisting}
They can be accessed with a dollar sign:
\begin{lstlisting}[language=bash]
$ echo $i # 0
$ echo $s # s
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]
\frametitle{Environment variables}
An \textbf{environment variable} can be set and accessed like this:
\begin{lstlisting}[language=bash]
$ export VAR=VALUE # set
$ echo $VAR # access
\end{lstlisting}
\tt{export} exposes the variable to programs that are not part of the shell
\footnote{aka not child processes}, such as Python.
\begin{block}{Note}
If an environment variable works for utility A, it may or may not work
for utility B. Refer to documentation when in doubt.
\end{block}
\end{frame}
\begin{frame}[fragile]
\frametitle{Environment variables}
Let's say you're downloading something from a completely legal website,
and you want the traffic to go through your completely legal local proxy
for completely legal reasons.
\begin{lstlisting}[language=bash]
# set environment variable for local proxy
$ export HTTPS_PROXY=http://localhost:8080/
# download the thing
$ curl -O https://legal.website/legal-thing
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]
\frametitle{Shell scripts}
Open your favorite text editor and edit \tt{05-scripting/um.sh}:
\begin{lstlisting}[language=bash]
# 05-scripting/um.sh
for i in {01..05}; do
cat um-logo-$i.txt
done
\end{lstlisting}
Save file, then come back to bash, \tt{cd} into \tt{05-scripting} and run:
\begin{lstlisting}[language=bash]
$ bash um.sh
\end{lstlisting}
\end{frame}
\begin{frame}
\frametitle{Exit status}
When a program exits, it emits an \textbf{exit status} (also called exit code).
By convention, an exit status of 0 implies success, and everything else means
something went wrong (consult respective man pages).
\newline \newline
For this very reason, in bash, \textbf{0 is boolean true} and
\textbf{everything else is false}.
\begin{block}{Note}
When you \tt{return 0;} at the end of \tt{int main()}, you are
emitting an exit status of zero.
\end{block}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{if} statements}
Usually, we use an \textbf{if} statement to:
\begin{itemize}
\item Run a command and see if it succeeds
\item Test the value of a variable
\item Check if a file exists
\end{itemize}
It looks like this:
\begin{lstlisting}[language=bash]
if CONDITION; then
BODY
elif CONDITION; then
BODY
fi
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{if} statements: exit code}
\begin{lstlisting}[language=bash]
# if-prog.sh
if mkdir temp; then
# write to file only if mkdir emits exit code 0
echo "temporary dir created" >> temp/log
fi
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{if} statements: compare integers}
\begin{lstlisting}[language=bash]
# if-comp.sh
i=3
if [[ $i -eq 3 ]]; then
echo "i is 3"
fi
\end{lstlisting}
Other operators:
\begin{table}
\centering
\begin{tabular}{ll}
\tt{-ge} & $\geq$ \\
\tt{-gt} & $>$ \\
\tt{-le} & $\leq$ \\
\tt{-lt} & $<$ \\
\tt{-ne} & $\neq$
\end{tabular}
\end{table}
\begin{block}{Note}
Spaces after \verb|[[| and before \verb|]]| are \textbf{required}.
\footnote{
Trivia: In bash the \tt{[[ ]]} is built-in, but in some shells
it's an executable called \tt{/usr/bin/[[}.
See \url{https://serverfault.com/questions/138951/what-is-usr-bin}
}
\end{block}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{if} statements: arithmetic}
\begin{lstlisting}[language=bash]
# if-arith.sh
i=3
if (( $i % 2 == 1 )); then
echo "i is odd"
fi
\end{lstlisting}
\begin{block}{Note}
We do not use \verb|-eq|, \verb|-gt| etc.~inside \verb|(( ))|.
\end{block}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{if} statements: test string variable}
\begin{lstlisting}[language=bash]
# if-str.sh
str="something"
if [[ -z $str ]]; then
echo "str is empty"
elif [[ $str = 'something' ]]; then
# = and == are both ok
echo "str is 'something'"
fi
\end{lstlisting}
Complementary operators to \tt{-z} and \tt{=}:
\begin{table}
\centering
\begin{tabular}{ll}
\tt{-n} & not empty \\
\tt{!=} & not equal to
\end{tabular}
\end{table}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{if} statements: file exists}
\begin{lstlisting}[language=bash]
# if-file.sh
file="something.txt"
if [[ -e $file ]]; then
echo "$file exists"
fi
\end{lstlisting}
Common counterparts to \tt{-e}:
\begin{table}
\centering
\begin{tabular}{ll}
\tt{-f} & exists and is regular file \\
\tt{-d} & exists and is directory
\end{tabular}
\end{table}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{for} statements}
The \tt{for} statement is usually used to:
\begin{itemize}
\item Iterate over a range
\item Iterate over a list of strings
\end{itemize}
It looks like:
\begin{lstlisting}[language=bash]
for VAR in LIST; do
BODY
done
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{for} statements: range}
\begin{lstlisting}[language=bash]
# for-timer.sh
for t in {10..1}; do
echo "$t seconds left"
sleep 1
done
\end{lstlisting}
\begin{block}{Note}
In \textbf{double quotes}, variables will be expanded.
\end{block}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{for} statements: list of strings}
A string is split into a list with respect to whitespace.
\newline \newline
Let's say you want to create a backup of every file inside current directory:
\begin{lstlisting}[language=bash]
# for-backup.sh
for file in $(ls); do
cp $file $file.backup
done
\end{lstlisting}
\begin{block}{Explanation}
\verb|$()| executes a command and takes its output.
It is called ``command substitution''.
\end{block}
\begin{block}{Observation}
Why does bash think ``My Documents'' are two files?
\end{block}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{for} statements: IFS}
Bash splits strings on spaces, tabs, and newlines.
This is why \tt{My Documents} was split into \tt{My} and \tt{Documents}.
\newline \newline
Adjust the \textbf{IFS} environment variable to delimit on \verb|\n| only:
\begin{lstlisting}[language=bash]
# for-backup.sh (modified)
IFS=$'\n'
for file in $(ls); do
cp $file $file.backup
done
\end{lstlisting}
\begin{block}{Explanation}
\verb|$''| is called ``ANSI-C quoting''. It is not used very often.
\footnote{
More on this:
\url{https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html}
}
\end{block}
\end{frame}
\begin{frame}[fragile]
\frametitle{\textbf{for} statements: accumulator}
Let's try keeping count with an integer variable.
\begin{lstlisting}[language=bash]
# for-backup.sh (modified)
IFS=$'\n'
count=0
for file in $(ls); do
cp $file $file.backup
count=$(( count + 1 ))
done
echo "$count files backed up"
\end{lstlisting}
\begin{block}{Explanation}
\verb|$(())| is called ``arithmetic expansion''.
\end{block}
\end{frame}
\begin{frame}[fragile]
\frametitle{Your turn}
Print the odd numbered lines of \tt{05-scripting/logos.txt}.
\pause
\begin{block}{Solution}
\begin{lstlisting}[language=bash]
IFS=$'\n'
count=0
for line in $(cat logos.txt); do
count=$(( count + 1 ))
if (( count % 2 == 1 )); then
echo $line
fi
done
\end{lstlisting}
\end{block}
\end{frame}
\begin{frame}[fragile]
\frametitle{The Ultimate Challenge}
Each line in \tt{05-scripting/obf.txt} begins with an 8-digit number.
Print every line with its number greater than any of the lines above.
\pause
\begin{block}{Solution}
\begin{lstlisting}[language=bash]
IFS=$'\n'
max=0
for line in $(cat obf.txt); do
num=$(echo $line | grep -oE '^[0-9]{8}')
if [[ $num -gt $max ]]; then
echo $line
max=$num
fi
done
\end{lstlisting}
\end{block}
\end{frame}
\begin{frame}
\frametitle{Conclusion}
\begin{itemize}
\item Regex matches apparent patterns in strings
\item Many tools support regex, but beware of standards
\item Bash scripts are for basic automation
\item When unwieldy, consider alternatives
\end{itemize}
\end{frame}