-
Notifications
You must be signed in to change notification settings - Fork 135
Expand file tree
/
Copy pathshell-scripting.html
More file actions
729 lines (625 loc) · 57 KB
/
Copy pathshell-scripting.html
File metadata and controls
729 lines (625 loc) · 57 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
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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
<!doctypehtml><html class="sidebar-visible no-js light"lang=en><head><meta charset=UTF-8><title>Shell Scripting - Linux Command Line Computing</title><meta content="text/html; charset=utf-8"http-equiv=Content-Type><meta content="Linux Command Line and Shell Scripting for beginner to intermediate level users"name=description><meta content=width=device-width,initial-scale=1 name=viewport><meta content=#ffffff name=theme-color><meta content="Linux Command Line Computing"property=og:title><meta content=website property=og:type><meta content="Linux Command Line and Shell Scripting for beginner to intermediate level users"property=og:description><meta content=https://learnbyexample.github.io/cli-computing/ property=og:url><meta content=https://learnbyexample.github.io/cli-computing/images/cli_computing_ls.png property=og:image><meta content=1280 property=og:image:width><meta content=720 property=og:image:height><meta content=summary_large_image property=twitter:card><meta content=@learn_byexample property=twitter:site><link href=favicon.svg rel=icon><link rel="shortcut icon"href=favicon.png><link href=css/variables.css rel=stylesheet><link href=css/general.css rel=stylesheet><link href=css/chrome.css rel=stylesheet><link href=FontAwesome/css/font-awesome.css rel=stylesheet><link href=fonts/fonts.css rel=stylesheet><link href=highlight.css rel=stylesheet><link href=tomorrow-night.css rel=stylesheet><link href=ayu-highlight.css rel=stylesheet><link href=style.css rel=stylesheet><body><script>var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";</script><script>try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }</script><script>var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');</script><script>var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);</script><nav aria-label="Table of contents"class=sidebar id=sidebar><div class=sidebar-scrollbox><ol class=chapter><li class="chapter-item expanded affix"><a href=cover.html>Cover</a><li class="chapter-item expanded affix"><a href=buy.html>Buy PDF/EPUB versions</a><li class="chapter-item expanded affix"><a href=preface.html>Preface</a><li class="chapter-item expanded"><a href=introduction-setup.html><strong aria-hidden=true>1.</strong> Introduction and Setup</a><li class="chapter-item expanded"><a href=command-line-overview.html><strong aria-hidden=true>2.</strong> Command Line Overview</a><li class="chapter-item expanded"><a href=managing-files-directories.html><strong aria-hidden=true>3.</strong> Managing Files and Directories</a><li class="chapter-item expanded"><a href=shell-features.html><strong aria-hidden=true>4.</strong> Shell Features</a><li class="chapter-item expanded"><a href=viewing-part-or-whole-file-contents.html><strong aria-hidden=true>5.</strong> Viewing Part or Whole File Contents</a><li class="chapter-item expanded"><a href=searching-files-and-filenames.html><strong aria-hidden=true>6.</strong> Searching Files and Filenames</a><li class="chapter-item expanded"><a href=file-properties.html><strong aria-hidden=true>7.</strong> File Properties</a><li class="chapter-item expanded"><a href=managing-processes.html><strong aria-hidden=true>8.</strong> Managing Processes</a><li class="chapter-item expanded"><a href=multipurpose-text-processing-tools.html><strong aria-hidden=true>9.</strong> Multipurpose Text Processing Tools</a><li class="chapter-item expanded"><a href=sorting-stuff.html><strong aria-hidden=true>10.</strong> Sorting Stuff</a><li class="chapter-item expanded"><a href=comparing-files.html><strong aria-hidden=true>11.</strong> Comparing Files</a><li class="chapter-item expanded"><a href=assorted-text-processing-tools.html><strong aria-hidden=true>12.</strong> Assorted Text Processing Tools</a><li class="chapter-item expanded"><a class=active href=shell-scripting.html><strong aria-hidden=true>13.</strong> Shell Scripting</a><li class="chapter-item expanded"><a href=shell-customization.html><strong aria-hidden=true>14.</strong> Shell Customization</a><li class="chapter-item expanded affix"><a href=exercise-solutions.html>Exercise Solutions</a></li><br><hr><li class="chapter-item expanded"><i class="fa fa-github"id=git-repository-button></i><a href=https://github.com/learnbyexample/cli-computing> Source code</a><li class="chapter-item expanded"><i class="fa fa-home"id=home-button></i><a href=https://learnbyexample.github.io/> My Blog</a><li class="chapter-item expanded"><i class="fa fa-book"id=book-button></i><a href=https://learnbyexample.github.io/books/> My Books</a><li class="chapter-item expanded"><i class="fa fa-twitter"id=twitter-button></i><a href=https://twitter.com/learn_byexample> Twitter</a></ol></div><div class=sidebar-resize-handle id=sidebar-resize-handle></div></nav><div class=page-wrapper id=page-wrapper><div class=page><div id=menu-bar-hover-placeholder></div><div class="menu-bar sticky bordered"id=menu-bar><div class=left-buttons><button aria-label="Toggle Table of Contents"title="Toggle Table of Contents"aria-controls=sidebar class=icon-button id=sidebar-toggle type=button><i class="fa fa-bars"></i></button><button aria-label="Change theme"title="Change theme"aria-controls=theme-list aria-expanded=false aria-haspopup=true class=icon-button id=theme-toggle type=button><i class="fa fa-paint-brush"></i></button><ul aria-label=Themes class=theme-popup id=theme-list role=menu><li role=none><button class=theme id=light role=menuitem>Light (default)</button><li role=none><button class=theme id=rust role=menuitem>Rust</button><li role=none><button class=theme id=coal role=menuitem>Coal</button><li role=none><button class=theme id=navy role=menuitem>Navy</button><li role=none><button class=theme id=ayu role=menuitem>Ayu</button></ul><button aria-label="Toggle Searchbar"title="Search. (Shortkey: s)"aria-controls=searchbar aria-expanded=false aria-keyshortcuts=S class=icon-button id=search-toggle type=button><i class="fa fa-search"></i></button></div><h1 class=menu-title>Linux Command Line Computing</h1><div class=right-buttons><a aria-label=Blog href=https://learnbyexample.github.io title=Blog> <i class="fa fa-home"id=home-button></i> </a><a aria-label=Twitter href=https://twitter.com/learn_byexample title=Twitter> <i class="fa fa-twitter"id=twitter-button></i> </a><a aria-label="Git repository"title="Git repository"href=https://github.com/learnbyexample/cli-computing> <i class="fa fa-github"id=git-repository-button></i> </a></div></div><div class=hidden id=search-wrapper><form class=searchbar-outer id=searchbar-outer><input placeholder="Search this book ..."aria-controls=searchresults-outer aria-describedby=searchresults-header id=searchbar name=searchbar type=search></form><div class="searchresults-outer hidden"id=searchresults-outer><div class=searchresults-header id=searchresults-header></div><ul id=searchresults></ul></div></div><script>document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});</script><div class=content id=content><main><div class=sidetoc><nav class=pagetoc></nav></div><h1 id=shell-scripting><a class=header href=#shell-scripting>Shell Scripting</a></h1><p>This chapter will cover the basics of shell scripting with <code>bash</code>. You'll learn about declaring variables, control structures, working with arguments passed to a script, getting user input and so on.<blockquote><p><img alt=info src=./images/info.svg> The <a href=https://github.com/learnbyexample/cli-computing/tree/master/example_files>example_files</a> directory has all the shell scripts discussed in this chapter. However, it is recommended that you type the scripts manually using your favorite text editor and refer to the <code>example_files/shell_scripting</code> directory only when necessary.</blockquote><h2 id=need-for-scripting><a class=header href=#need-for-scripting>Need for scripting</a></h2><p>From <a href=https://en.wikipedia.org/wiki/Scripting_language>wikipedia: Scripting language</a>:<blockquote><p>A scripting language or script language is a programming language for a runtime system that automates the execution of tasks that would otherwise be performed individually by a human operator. Scripting languages are usually interpreted at runtime rather than compiled.</blockquote><blockquote><p>Typical scripting languages are intended to be very fast to learn and write in, either as short source code files or interactively in a read–eval–print loop (REPL, language shell). This generally implies relatively simple syntax and semantics; typically a "script" (code written in the scripting language) is executed from start to finish, as a "script", with no explicit entry point.</blockquote><p>From <a href=https://en.wikipedia.org/wiki/Shell_script>wikipedia: Shell script</a>:<blockquote><p>A shell script is a computer program designed to be run by the Unix shell, a command-line interpreter. The various dialects of shell scripts are considered to be scripting languages. Typical operations performed by shell scripts include file manipulation, program execution, and printing text. A script which sets up the environment, runs the program, and does any necessary cleanup or logging, is called a wrapper.</blockquote><p>See also <a href=https://stackoverflow.com/q/17253545/4082052>Difference between scripting and programming languages</a>.<h2 id=executable-script><a class=header href=#executable-script>Executable script</a></h2><p>There are several ways you can execute commands from a file. This section shows an example of creating an executable script. Consider this sample script saved in a file named <code>hello.sh</code>:<pre><code class=language-bash>#!/bin/bash
echo "Hello $USER"
echo "Today is $(date -u +%A)"
echo 'Have a nice day'
</code></pre><p>The first line in the above script has two parts:<ul><li><code>/bin/bash</code> is the path of the <code>bash</code> interpreter <ul><li>you can use <code>type bash</code> to get the path on your system</ul><li><code>#!</code> is known as <a href=https://en.wikipedia.org/wiki/Shebang_(Unix)>shebang or hashbang</a> which directs the program loader to use the interpreter path provided <ul><li>see also <a href=https://stackoverflow.com/q/21612980/4082052>stackoverflow: comparison between #!/usr/bin/env and #!/bin/bash?</a><li>the <code>#</code> character starts a comment, <code>#!</code> is only special at the start of the script</ul></ul><p>Use <code>chmod</code> to add executable permission to the file and then run the script:<pre><code class=language-bash>$ chmod +x hello.sh
$ ./hello.sh
Hello learnbyexample
Today is Wednesday
Have a nice day
</code></pre><p>If you want to use just the script name to execute it, the file has to be located in one of the <code>PATH</code> folders. Otherwise, you'll have to provide the script's path (absolute or relative) in order to execute it (as shown in the above illustration).<blockquote><p><img alt=info src=images/info.svg> <code>.sh</code> is typically used as the file extension for shell scripts. It is also common to <em>not</em> have an extension at all, especially for executable scripts.</blockquote><h2 id=passing-file-argument-to-bash><a class=header href=#passing-file-argument-to-bash>Passing file argument to bash</a></h2><p>You can also just pass a regular file as an argument to the <code>bash</code> command. In this case, the shebang isn't needed (though it wouldn't cause any issues either, since it will be treated as a comment).<pre><code class=language-bash>$ cat greeting.sh
echo 'hello'
echo 'have a nice day'
$ bash greeting.sh
hello
have a nice day
</code></pre><h2 id=sourcing-script><a class=header href=#sourcing-script>Sourcing script</a></h2><p>Yet another way to execute a script is to <em>source</em> it using the <code>source</code> (or <code>.</code>) builtin command. A major difference from the previous methods is that the script is executed in the current shell environment context instead of a sub-shell. A common use case is sourcing <code>~/.bashrc</code> and alias/functions (if they are saved in a separate file).<p>Here's an example:<pre><code class=language-bash>$ cat prev_cmd.sh
prev=$(fc -ln -2 | sed 's/^\s*//; q')
echo "$prev"
# 'echo' here is just a sample command for illustration purposes
$ echo 'hello'
hello
# sourcing the script correctly gives the previous command
$ source prev_cmd.sh
echo 'hello'
$ echo 'hello'
hello
# no output when the script is executed in a sub-shell
$ bash prev_cmd.sh
</code></pre><blockquote><p><img alt=info src=./images/info.svg> <code>fc</code> is a builtin command to manipulate the history of commands you've used from the terminal. See <a href=https://www.gnu.org/software/bash/manual/bash.html#Bash-History-Builtins>bash manual: History Builtins</a> for more details.</blockquote><h2 id=comments><a class=header href=#comments>Comments</a></h2><p>Single line comments can be inserted after the <code>#</code> character, either at the start of a line or after an instruction.<pre><code class=language-bash>$ cat comments.sh
# this is a comment on its own line
echo 'hello' # and this is a comment after a command
$ bash comments.sh
hello
</code></pre><blockquote><p><img alt=info src=images/info.svg> See this <a href=https://unix.stackexchange.com/q/37411/109046>unix.stackexchange thread</a> for emulating multiline comments.</blockquote><h2 id=variables><a class=header href=#variables>Variables</a></h2><p>Here's a basic example of assigning a variable and accessing its value:<pre><code class=language-bash># note that there cannot be any space characters around the = operator
$ name='learnbyexample'
$ echo "$name"
learnbyexample
</code></pre><p>As seen above, you need to use the <code>$</code> prefix while accessing the value stored in a variable. You can use the <code>${variable}</code> syntax to distinguish between the variable and other parts of the string. Using appropriate quotes is recommended, unless otherwise necessary.<p>You can append to a variable by using the <code>+=</code> operator. Here's an example:<pre><code class=language-bash>$ colors='blue'
$ echo "$colors"
blue
$ colors+=' green'
$ echo "$colors"
blue green
</code></pre><p>You can use the <code>declare</code> builtin to add attributes to variables. For example, the <code>-i</code> option for treating the variable as an integer, <code>-r</code> option for readonly, etc. These attributes can change the behavior of operators like <code>=</code> and <code>+=</code> for those variables. See <a href=https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameters>bash manual: Shell-Parameters</a> and <a href=https://www.gnu.org/software/bash/manual/bash.html#index-declare>bash manual: declare</a> for more details.<pre><code class=language-bash>$ declare -i num=5
$ echo "$num"
5
$ num+=42
$ echo "$num"
47
$ declare -r color='brown'
$ echo "$color"
brown
$ color+=' green'
bash: color: readonly variable
</code></pre><blockquote><p><img alt=info src=./images/info.svg> <img alt=warning src=./images/warning.svg> Assigning variables is one of the most common source for errors. Unlike most programming languages, spaces are <em>not</em> allowed around the <code>=</code> sign. That is because space is a shell metacharacter. Another common issue is using quotes (or not) around the value. Here are some examples:<pre><code class=language-bash>$ num = 42
num: command not found
$ greeting=hello world
world: command not found
$ greeting='hello world'
$ echo "$greeting"
hello world
# using quotes is NOT desirable here
$ dir_path=~/reports
$ echo "$dir_path"
/home/learnbyexample/reports
$ dir_path='~/reports'
$ echo "$dir_path"
~/reports
</code></pre></blockquote><h2 id=arrays><a class=header href=#arrays>Arrays</a></h2><p>From <a href=https://www.gnu.org/software/bash/manual/bash.html#Arrays>bash manual: Arrays</a>:<blockquote><p>Bash provides one-dimensional indexed and associative array variables. Any variable may be used as an indexed array; the <code>declare</code> builtin will explicitly declare an array. There is no maximum limit on the size of an array, nor any requirement that members be indexed or assigned contiguously. Indexed arrays are referenced using integers and are zero-based; associative arrays use arbitrary strings.</blockquote><p>Here's an example of assigning an indexed array and various ways of accessing the elements:<pre><code class=language-bash>$ fruits=('apple' 'fig' 'mango')
# first element
$ echo "${fruits[0]}"
apple
# last element
$ echo "${fruits[-1]}"
mango
# all elements (example with for loop will be discussed later on)
$ echo "${fruits[@]}"
apple fig mango
$ printf '%s\n' "${fruits[@]}"
apple
fig
mango
</code></pre><h2 id=parameter-expansion><a class=header href=#parameter-expansion>Parameter Expansion</a></h2><p>Bash provides several useful ways to extract and modify the contents of parameters and variables (including arrays). Some of these features will be discussed in this section.<p><em>1)</em> Substring extraction using <code>${parameter:offset}</code> syntax to get all characters from the given index:<pre><code class=language-bash>$ city='Lucknow'
# all characters from index 4 onwards
# indexing starts from 0
$ echo "${city:4}"
now
# last two characters
# space before the negative sign is compulsory here,
# since ${parameter:-word} is a different feature
$ echo "${city: -2}"
ow
</code></pre><p>When applied to arrays, substring extraction will give you those elements:<pre><code class=language-bash>$ fruits=('apple' 'fig' 'mango')
# all elements from index 1
$ echo "${fruits[@]:1}"
fig mango
</code></pre><p><em>2)</em> Substring extraction using <code>${parameter:offset:length}</code> syntax to get specific number of characters from the given index:<pre><code class=language-bash>$ city='Lucknow'
# 4 characters starting from index 0
# can also use: echo "${city::4}"
$ echo "${city:0:4}"
Luck
# 2 characters starting from index -4 (4th character from the end)
$ echo "${city: -4:2}"
kn
# except the last 2 characters
$ echo "${city::-2}"
Luckn
</code></pre><p><em>3)</em> <code>${#parameter}</code> will give you the length of the string and <code>${#array[@]}</code> will give you the number of elements in the array:<pre><code class=language-bash>$ city='Lucknow'
$ echo "${#city}"
7
$ fruits=('apple' 'fig' 'mango')
$ echo "${#fruits[@]}"
3
</code></pre><p><em>4)</em> <code>${parameter#glob}</code> will remove the shortest match from the start of the string. You can also use extended globs if enabled via the <code>shopt</code> builtin. <code>${parameter##glob}</code> will remove the longest match from the start of the string. Here are some examples:<pre><code class=language-bash>$ s='this is his life history'
# shortest match is deleted
$ echo "${s#*is}"
is his life history
# longest match is deleted
$ echo "${s##*is}"
tory
# assuming extglob is already enabled
$ echo "${s#+([^ ])}"
his is his life history
$ echo "${s##+([^ ])}"
is his life history
# for arrays, the processing is applied to each element
$ fruits=('apple' 'fig' 'mango')
$ echo "${fruits[@]#*[aeiou]}"
pple g ngo
</code></pre><p><em>5)</em> You can use <code>${parameter%glob}</code> to remove the shortest match from the end of the string. <code>${parameter%%glob}</code> will remove the longest match from the end of the string.<pre><code class=language-bash>$ s='this is his life history'
$ echo "${s%is*}"
this is his life h
$ echo "${s%%is*}"
th
$ fruits=('apple' 'fig' 'mango')
$ echo "${fruits[@]%[aeiou]*}"
appl f mang
</code></pre><p><em>6)</em> <code>${parameter/glob/string}</code> replaces the first matching occurrence with the given replacement string and <code>${parameter//glob/string}</code> will replace all the matching occurrences. You can leave out the <code>/string</code> portion when you want to delete the matching occurrences. The <code>glob</code> will match the longest portion, similar to greedy behavior in regular expressions. Here are some examples:<pre><code class=language-bash>$ ip='this is a sample string'
# first occurrence of 'is' is replaced with '123'
$ echo "${ip/is/123}"
th123 is a sample string
# all occurrences of 'is' are replaced with '123'
$ echo "${ip//is/123}"
th123 123 a sample string
# replace all occurrences of 'am' or 'in' with '-'
$ echo "${ip//@(am|in)/-}"
this is a s-ple str-g
# matches from the first 'is' to the last 's' in the input
$ echo "${ip/is*s/ X }"
th X tring
# delete the first occurrence of 's'
$ echo "${ip/s}"
thi is a sample string
# delete all the occurrences of 's'
$ echo "${ip//s}"
thi i a ample tring
</code></pre><p><em>7)</em> You can use <code>${parameter/#glob/string}</code> to match only at the start of the string and <code>${parameter/%glob/string}</code> to match only at the end of the string.<pre><code class=language-bash>$ ip='spare'
# remove only from the start of the string
$ echo "${ip/#sp}"
are
$ echo "${ip/#par}"
spare
# example with replacement string
$ echo "${ip/#sp/fl}"
flare
# remove only from the end of the string
$ echo "${ip/%re}"
spa
$ echo "${ip/%par}"
spare
</code></pre><p><em>8)</em> <code>${parameter^glob}</code> can change only the first character to uppercase if matched by the glob. <code>${parameter^^glob}</code> changes all the matching characters to uppercase (anywhere in the input string). You should provide a glob that only matches one character in length. If the glob is omitted, entire parameter will be matched. These rules also apply to the lowercase and swap case versions discussed later.<pre><code class=language-bash>$ fruit='apple'
# uppercase the first character
$ echo "${fruit^}"
Apple
# uppercase the entire parameter
$ echo "${fruit^^}"
APPLE
# first character doesn't match the 'g-z' range, so no change
$ echo "${fruit^[g-z]}"
apple
# uppercase all letters in the 'g-z' range
$ echo "${fruit^^[g-z]}"
aPPLe
# uppercase all letters in the 'a-e' or 'j-m' ranges
$ echo "${fruit^^[a-ej-m]}"
AppLE
# this won't work since 'sky-' is not a single character
$ color='sky-rose'
$ echo "${color^^*-}"
sky-rose
</code></pre><p><em>9)</em> To change the characters to lowercase, use <code>,</code> and <code>,,</code> as shown below:<pre><code class=language-bash>$ fruit='APPLE'
$ echo "${fruit,}"
aPPLE
$ echo "${fruit,,}"
apple
$ echo "${fruit,,[G-Z]}"
ApplE
</code></pre><p><em>10)</em> To swap case, use <code>~</code> and <code>~~</code> as shown below. Note that this seems to be deprecated, since it is no longer mentioned in the <code>bash</code> manual.<pre><code class=language-bash>$ fruit='aPPle'
# swap case only the first character
$ echo "${fruit~}"
APPle
# swap case all the characters
$ echo "${fruit~~}"
AppLE
# swap case characters matching the given character set
$ echo "${fruit~~[g-zG-Z]}"
appLe
</code></pre><blockquote><p><img alt=info src=images/info.svg> See <a href=https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion>bash manual: Shell Parameter Expansion</a> for more details and other types of expansions.</blockquote><h2 id=command-line-arguments><a class=header href=#command-line-arguments>Command Line Arguments</a></h2><p>Command line arguments passed to a script (or a function) are saved in positional parameters starting with <code>1</code>, <code>2</code>, <code>3</code> etc. <code>0</code> contains the name of the shell or shell script. <code>@</code> contains all the positional parameters starting from <code>1</code>. Use <code>#</code> to get the number of positional parameters. Similar to variables, you need to use a <code>$</code> prefix to get the value stored in these parameters. If the parameter number requires more than a single digit, you have to necessarily enclose them in <code>{}</code> (for example, <code>${12}</code> to get the value of the twelfth parameter).<p>Here's an example script that accepts two arguments:<pre><code class=language-bash>$ cat command_line_arguments.sh
echo "No. of lines in '$1' is $(wc -l < "$1")"
echo "No. of lines in '$2' is $(wc -l < "$2")"
$ seq 12 > 'test file.txt'
$ bash command_line_arguments.sh hello.sh test\ file.txt
No. of lines in 'hello.sh' is 5
No. of lines in 'test file.txt' is 12
</code></pre><p><strong>Further Reading</strong><ul><li><a href=https://unix.stackexchange.com/q/131766/109046>unix.stackexchange: shell script choking on whitespace or other special characters</a><li><a href=https://www.gnu.org/software/bash/manual/bash.html#Special-Parameters>bash manual: Special Parameters</a></ul><h2 id=conditional-expressions><a class=header href=#conditional-expressions>Conditional Expressions</a></h2><p>You can test a condition within <code>[[</code> and <code>]]</code> to get a success (<code>0</code>) or failure (<code>1</code> or higher) exit status and take action accordingly. Bash provides several options and operators that you can use. Space is required after <code>[[</code> and before <code>]]</code> for this compound command to function.<blockquote><p><img alt=info src=./images/info.svg> Operators <code>;</code>, <code>&&</code> and <code>||</code> will be used in this section to keep the examples terser. <code>if-else</code> and other control structures will be discussed later.</blockquote><h3 id=options><a class=header href=#options>Options</a></h3><p>The <code>-e</code> option checks if the given path argument exists or not. Add a <code>!</code> prefix to negate the condition.<pre><code class=language-bash># change to the 'example_files/shell_scripting' directory for this section
$ [[ -e hello.sh ]] && echo 'found' || echo 'not found'
found
$ [[ -e xyz.txt ]] && echo 'found' || echo 'not found'
not found
# exit status
$ [[ -e hello.sh ]] ; echo $?
0
$ [[ -e xyz.txt ]] ; echo $?
1
$ [[ ! -e xyz.txt ]] ; echo $?
0
</code></pre><p>You can use the <code>-d</code> and <code>-f</code> options to check if the path is a valid directory and file respectively. The <code>-s</code> option checks if the file exists and its size is greater than zero. The <code>-x</code> option checks if the file exists and is executable. See <code>help test</code> and <a href=https://www.gnu.org/software/bash/manual/bash.html#Bash-Conditional-Expressions>bash manual: Conditional Expressions</a> for a complete list of such options.<h3 id=string-comparisons><a class=header href=#string-comparisons>String comparisons</a></h3><ul><li><code>s1 = s2</code> or <code>s1 == s2</code> checks if two strings are equal <ul><li>unquoted portions of <code>s2</code> will be treated as a wildcard while testing against <code>s1</code><li><code>extglob</code> would be considered as enabled for such comparisons</ul><li><code>s1 != s2</code> checks if strings are <em>not</em> equal <ul><li>unquoted portions of <code>s2</code> will be treated as a wildcard while testing against <code>s1</code><li><code>extglob</code> would be considered as enabled for such comparisons</ul><li><code>s1 < s2</code> checks if <code>s1</code> sorts before <code>s2</code> lexicographically<li><code>s1 > s2</code> checks if <code>s1</code> sorts after <code>s2</code> lexicographically<li><code>s1 =~ s2</code> checks if <code>s1</code> matches the POSIX extended regular expression provided by <code>s2</code> <ul><li>exit status will be <code>2</code> if <code>s2</code> is not a valid regexp</ul></ul><p>Here are some examples for equal and not-equal comparisons:<pre><code class=language-bash>$ fruit='apple'
$ [[ $fruit == 'apple' ]] && echo 'true' || echo 'false'
true
$ [[ $fruit == 'banana' ]] && echo 'true' || echo 'false'
false
# glob should be constructed to match the entire string
$ [[ hello == h* ]] && echo 'true' || echo 'false'
true
# don't quote the glob!
$ [[ hello == 'h*' ]] && echo 'true' || echo 'false'
false
# another example to emphasize that the glob should match the entire string
$ [[ hello == e*o ]] && echo 'true' || echo 'false'
false
$ [[ hello == *e*o ]] && echo 'true' || echo 'false'
true
$ [[ hello != *a* ]] && echo 'true' || echo 'false'
true
$ [[ hello != *e* ]] && echo 'true' || echo 'false'
false
</code></pre><p>Here are some examples for greater-than and less-than comparisons:<pre><code class=language-bash>$ [[ apple < banana ]] && echo 'true' || echo 'false'
true
$ [[ par < part ]] && echo 'true' || echo 'false'
true
$ [[ mango > banana ]] && echo 'true' || echo 'false'
true
$ [[ sun > moon && fig < papaya ]] && echo 'true' || echo 'false'
true
# don't use this to compare numbers!
$ [[ 20 > 3 ]] && echo 'true' || echo 'false'
false
# -gt and other such operators will be discussed later
$ [[ 20 -gt 3 ]] && echo 'true' || echo 'false'
true
</code></pre><p>Here are some examples for regexp comparison. You can use the special array <code>BASH_REMATCH</code> to retrieve specific portions of the string that was matched. Index <code>0</code> gives entire matched portion, <code>1</code> gives the portion matched by the first capture group and so on.<pre><code class=language-bash>$ fruit='apple'
$ [[ $fruit =~ ^a ]] && echo 'true' || echo 'false'
true
$ [[ $fruit =~ ^b ]] && echo 'true' || echo 'false'
false
# entire matched portion
$ [[ $fruit =~ a.. ]] && echo "${BASH_REMATCH[0]}"
app
# portion matched by the first capture group
$ [[ $fruit =~ a(..) ]] && echo "${BASH_REMATCH[1]}"
pp
</code></pre><h3 id=numeric-comparisons><a class=header href=#numeric-comparisons>Numeric comparisons</a></h3><ul><li><code>n1 -eq n2</code> checks if two numbers are equal<li><code>n1 -ne n2</code> checks if two numbers are <em>not</em> equal<li><code>n1 -gt n2</code> checks if <code>n1</code> is greater than <code>n2</code><li><code>n1 -ge n2</code> checks if <code>n1</code> is greater than or equal to <code>n2</code><li><code>n1 -lt n2</code> checks if <code>n1</code> is less than <code>n2</code><li><code>n1 -le n2</code> checks if <code>n1</code> is less than or equal to <code>n2</code></ul><p>These operators support only integer comparisons.<pre><code class=language-bash>$ [[ 20 -gt 3 ]] && echo 'true' || echo 'false'
true
$ n1='42'
$ n2='25'
$ [[ $n1 -gt 30 && $n2 -lt 12 ]] && echo 'true' || echo 'false'
false
</code></pre><p>Numeric arithmetic operations and comparisons can also be performed within the <code>((</code> and <code>))</code> compound command. Here are some sample comparisons:<pre><code class=language-bash>$ (( 20 > 3 )) && echo 'true' || echo 'false'
$ n1='42'
$ n2='25'
$ (( n1 > 30 && n2 < 12 )) && echo 'true' || echo 'false'
false
</code></pre><blockquote><p><img alt=info src=images/info.svg> Note that the <code>$</code> prefix was <em>not</em> used for variables in the above examples. See <a href=https://www.gnu.org/software/bash/manual/bash.html#Shell-Arithmetic>bash manual: Shell Arithmetic</a> for more details.</blockquote><h2 id=accepting-user-input-interactively><a class=header href=#accepting-user-input-interactively>Accepting user input interactively</a></h2><p>You can use the <code>read</code> builtin command to accept input from the user interactively. If multiple variables are given as arguments to the <code>read</code> command, values will be assigned based on whitespace separation by default. Any pending values will be assigned to the last variable. Here are some examples:<pre><code class=language-bash># press 'Enter' after the 'read' command
# and also after you've finished entering the input
$ read color
light green
$ echo "$color"
light green
# example with multiple variables
$ read fruit qty
apple 10
$ echo "${fruit}: ${qty}"
apple: 10
</code></pre><p>The <code>-p</code> option helps you to add a user prompt. Here is an example of getting two arguments from the user:<pre><code class=language-bash>$ cat user_input.sh
read -p 'Enter two integers separated by spaces: ' num1 num2
sum=$(( num1 + num2 ))
echo "$num1 + $num2 = $sum"
$ bash user_input.sh
Enter two integers separated by spaces: -2 42
-2 + 42 = 40
</code></pre><blockquote><p><img alt=info src=images/info.svg> You can use the <code>-a</code> option to assign an array, the <code>-d</code> option to specify a custom delimiter instead of newline for terminating user input and so on. See <code>help read</code> and <a href=https://www.gnu.org/software/bash/manual/bash.html#Bash-Builtins>bash manual: Builtins</a> for more details.</blockquote><h2 id=if-then-else><a class=header href=#if-then-else>if then else</a></h2><p>The keywords needed to construct an <code>if</code> control structure are <code>if</code>, <code>then</code>, <code>fi</code> and optionally <code>else</code> and <code>elif</code>. You can use compound commands like <code>[[</code> and <code>((</code> to provide the test condition. You can also directly use a command's exit status. Here's an example script:<pre><code class=language-bash>$ cat if_then_else.sh
if (( $# != 1 )) ; then
echo 'Error! One file argument expected.' 1>&2
exit 1
else
if [[ ! -f $1 ]] ; then
printf 'Error! %q is not a valid file\n' "$1" 1>&2
exit 1
else
echo "No. of lines in '$1' is $(wc -l < "$1")"
fi
fi
</code></pre><p><code>1>&2</code> is used in the above script to redirect error messages to the <code>stderr</code> stream. Sample script invocations are shown below:<pre><code class=language-bash>$ bash if_then_else.sh
Error! One file argument expected.
$ echo $?
1
$ bash if_then_else.sh xyz.txt
Error! xyz.txt is not a valid file
$ echo $?
1
$ bash if_then_else.sh hello.sh
No. of lines in 'hello.sh' is 5
$ echo $?
0
</code></pre><p>Sometimes you just need to know if the intended command operation was successful or not and then take an action depending on the outcome. In such cases, you can provide the command directly after the <code>if</code> keyword. Note that <code>stdout</code> and <code>stderr</code> of the command will still be active unless redirected or suppressed using appropriate options.<p>For example, the <code>grep</code> command supports the <code>-q</code> option to suppress <code>stdout</code>. Here's a script using that feature:<pre><code class=language-bash>$ cat search.sh
read -p 'Enter a search pattern: ' search
if grep -q "$search" hello.sh ; then
echo "match found"
else
echo "match not found"
fi
</code></pre><p>Sample invocations for the above script:<pre><code class=language-bash>$ bash search.sh
Enter a search pattern: echo
match found
$ bash search.sh
Enter a search pattern: xyz
match not found
</code></pre><h2 id=for-loop><a class=header href=#for-loop>for loop</a></h2><p>To construct a <code>for</code> loop, you'll need the <code>for</code>, <code>do</code> and <code>done</code> keywords. Here are some examples:<pre><code class=language-bash># iterate over numbers generated using brace expansion
$ for num in {2..4}; do echo "$num"; done
2
3
4
# iterate over files matched using wildcards
# echo is used here for dry run testing
$ for file in [gh]*.sh; do echo mv "$file" "$file.bkp"; done
mv greeting.sh greeting.sh.bkp
mv hello.sh hello.sh.bkp
</code></pre><p>As seen in the above examples, the space separated arguments provided after the <code>in</code> keyword are automatically assigned to the variable provided after the <code>for</code> keyword during each iteration.<p>Here's a modified example of the last example that accepts user provided command line arguments:<pre><code class=language-bash>$ cat for_loop.sh
for file in "$@"; do
echo mv "$file" "$file.bkp"
done
$ bash for_loop.sh [gh]*.sh
mv greeting.sh greeting.sh.bkp
mv hello.sh hello.sh.bkp
$ bash for_loop.sh report.log ip.txt fruits.txt
mv report.log report.log.bkp
mv ip.txt ip.txt.bkp
mv fruits.txt fruits.txt.bkp
</code></pre><p>Here's an example of iterating over an array:<pre><code class=language-bash>$ files=('report.log' 'pass_list.txt')
$ for f in "${files[@]}"; do echo "$f"; done
report.log
pass_list.txt
</code></pre><blockquote><p><img alt=info src=images/info.svg> You can use <code>continue</code> and <code>break</code> to alter the loop flow depending on specific conditions. See <a href=https://www.gnu.org/software/bash/manual/bash.html#Bourne-Shell-Builtins>bash manual: Bourne Shell Builtins</a> for more details.</blockquote><blockquote><p><img alt=info src=./images/info.svg> <code>for file;</code> is same as <code>for file in "$@";</code> since <code>in "$@"</code> is the default. I'd recommend using the explicit version.</blockquote><h2 id=while-loop><a class=header href=#while-loop>while loop</a></h2><p>Here's a simple <code>while</code> loop construct. You'll see a more practical example later in this chapter.<pre><code class=language-bash>$ cat while_loop.sh
i="$1"
while (( i > 0 )) ; do
echo "$i"
(( i-- ))
done
$ bash while_loop.sh 3
3
2
1
</code></pre><h2 id=reading-a-file><a class=header href=#reading-a-file>Reading a file</a></h2><p>The <code>while</code> loop in combination with the <code>read</code> builtin can be used to process the content of a file. Here's an example of reading input contents line by line:<pre><code class=language-bash>$ cat read_file_lines.sh
while IFS= read -r line; do
# do something with each line
wc -l "$line"
done < "$1"
$ printf 'hello.sh\ngreeting.sh\n' > files.txt
$ bash read_file_lines.sh files.txt
5 hello.sh
2 greeting.sh
</code></pre><p>The intention in the above script is to treat each input line literally. So, the <code>IFS</code> (input field separator) special variable is set to an empty string to prevent stripping of leading and trailing whitespaces. The <code>-r</code> option to the <code>read</code> builtin allows <code>\</code> in the input to be treated literally. Note that the input filename is accepted as the first command line argument and redirected as <code>stdin</code> to the <code>while</code> loop. You also need to make sure that the last line of the input ends with a newline character, otherwise the last line won't be processed.<p>You can change <code>IFS</code> to split the input line into different fields and specify appropriate number of variables to the <code>read</code> builtin. Here's an example:<pre><code class=language-bash>$ cat read_file_fields.sh
while IFS=' : ' read -r field1 field2; do
echo "$field2,$field1"
done < "$1"
$ bash read_file_fields.sh <(printf 'apple : 3\nfig : 100\n')
3,apple
100,fig
</code></pre><p>You can pass a number to the <code>-n</code> option for the <code>read</code> builtin to process the input that many characters at a time. Here's an example:<pre><code class=language-bash>$ while read -r -n2 ip; do echo "$ip"; done <<< '\word'
\w
or
d
</code></pre><blockquote><p><img alt=info src=./images/info.svg> The <code>xargs</code> command can also be used for some of the cases discussed above. See <a href=https://unix.stackexchange.com/q/149726/109046>unix.stackexchange: parse each line of a text file as a command argument</a> for examples.</blockquote><h2 id=functions><a class=header href=#functions>Functions</a></h2><p>From <a href=https://www.gnu.org/software/bash/manual/bash.html#Shell-Functions>bash manual: Shell Functions</a>:<blockquote><p>Shell functions are a way to group commands for later execution using a single name for the group. They are executed just like a "regular" command. When the name of a shell function is used as a simple command name, the list of commands associated with that function name is executed. Shell functions are executed in the current shell context; no new process is created to interpret them.</blockquote><p>You can use either of the syntax shown below to declare functions:<pre><code class=language-bash>fname () compound-command [ redirections ]
function fname [()] compound-command [ redirections ]
</code></pre><p>Arguments to functions are passed in the same manner as those discussed earlier for shell scripts. Here's an example:<pre><code class=language-bash>$ cat functions.sh
add_border ()
{
size='10'
color='grey'
if (( $# == 1 )) ; then
ip="$1"
elif (( $# == 2 )) ; then
if [[ $1 =~ ^[0-9]+$ ]] ; then
size="$1"
else
color="$1"
fi
ip="$2"
else
size="$1"
color="$2"
ip="$3"
fi
op="${ip%.*}_border.${ip##*.}"
echo convert -border "$size" -bordercolor "$color" "$ip" "$op"
}
add_border flower.png
add_border 5 insect.png
add_border red lake.png
add_border 20 blue sky.png
</code></pre><p>In the above example, <code>echo</code> is used to display the command that will be executed. Remove <code>echo</code> if you want this script to actually create new images with the given parameters. The function accepts one to three arguments and uses default values when some of the arguments are not passed. Here's the output:<pre><code class=language-bash>$ bash functions.sh
convert -border 10 -bordercolor grey flower.png flower_border.png
convert -border 5 -bordercolor grey insect.png insect_border.png
convert -border 10 -bordercolor red lake.png lake_border.png
convert -border 20 -bordercolor blue sky.png sky_border.png
</code></pre><blockquote><p><img alt=info src=images/info.svg> Use <code>mogrify</code> instead of <code>convert</code> if you want to modify the input image inplace instead of creating a new image. These image manipulation commands are part of the <a href=https://imagemagick.org/>ImageMagick</a> suite. As an exercise, modify the above function to generate an error if the arguments passed do not match the expected usage. You can also accept an output image name (or perhaps a different suffix) as an additional argument.</blockquote><p>The shell script and user defined functions (which in turn might call itself or another function) can both have positional arguments. In such cases, the shell takes cares of restoring positional arguments to the earlier state once a function completes its tasks.<p>Functions have exit status as well, which is based on the last executed command by default. You can use the <code>return</code> builtin to provide your own custom exit status.<h2 id=debugging><a class=header href=#debugging>Debugging</a></h2><p>You can use the following <code>bash</code> options for debugging purposes:<ul><li><code>-x</code> print commands and their arguments as they are executed<li><code>-v</code> verbose option, print shell input lines as they are read</ul><p>Here's an example with the <code>bash -x</code> option:<pre><code class=language-bash>$ bash -x search.sh
+ read -p 'Enter a search pattern: ' search
Enter a search pattern: xyz
+ grep -q xyz hello.sh
+ echo 'match not found'
match not found
</code></pre><p>The lines starting with <code>+</code> show the command being executed with expanded values if applicable (the <code>search</code> variable to <code>grep -q</code> for example). Multiple <code>+</code> will be used if there are multiple expansions. Here's how <code>bash -xv</code> would behave for the same script:<pre><code class=language-bash>$ bash -xv search.sh
read -p 'Enter a search pattern: ' search
+ read -p 'Enter a search pattern: ' search
Enter a search pattern: xyz
if grep -q "$search" hello.sh ; then
echo "match found"
else
echo "match not found"
fi
+ grep -q xyz hello.sh
+ echo 'match not found'
match not found
</code></pre><blockquote><p><img alt=info src=images/info.svg> You can also use <code>set -x</code> or <code>set -v</code> or <code>set -xv</code> from within the script to debug from a particular point onwards. You can turn off such debugging by using <code>+</code> instead of <code>-</code> as the option prefix (for example, <code>set +x</code>).</blockquote><h2 id=shellcheck><a class=header href=#shellcheck>shellcheck</a></h2><p><a href=https://www.shellcheck.net/>shellcheck</a> is a static analysis tool that gives warnings and suggestions for scripts. You can use it online or install the tool for offline use. Given the various <code>bash</code> gotchas, this tool is highly recommended for both beginners and advanced users.<p>Consider this script:<pre><code class=language-bash>$ cat bad_script.sh
#!/bin/bash
greeting = 'hello world'
echo "$greeting"
</code></pre><p>Here's how <code>shellcheck</code> reports the issue:<pre><code class=language-nohighlight>$ shellcheck bad_script.sh
In bad_script.sh line 3:
greeting = 'hello world'
^-- SC1068: Don't put spaces around the = in assignments
(or quote to make it literal).
For more information:
https://www.shellcheck.net/wiki/SC1068 -- Don't put spaces around the = in ...
</code></pre><blockquote><p><img alt=info src=images/info.svg> If the script doesn't have a shebang, you can use the <code>-s</code> option (<code>shellcheck -s bash</code> for example) to specify the shell application.</blockquote><blockquote><p><img alt=info src=images/info.svg> <img alt=warning src=./images/warning.svg> Note that <code>shellcheck</code> will not catch all types of issues. And suggestions should not be blindly accepted without understanding if it makes sense in the given context.</blockquote><h2 id=resource-lists><a class=header href=#resource-lists>Resource lists</a></h2><p>Here are some more learning resources:<p><strong>Shell Scripting</strong><ul><li><a href=https://mywiki.wooledge.org/BashGuide>Bash Guide</a> — aspires to teach good practice techniques for using Bash, and writing simple scripts<li><a href=https://ryanstutorials.net/bash-scripting-tutorial/>Bash Scripting Tutorial</a> — solid foundation in how to write Bash scripts, to get the computer to do complex, repetitive tasks for you<li><a href=https://github.com/denysdovhan/bash-handbook>bash-handbook</a> — for those who want to learn Bash without diving in too deeply<li><a href=https://freebsdfrau.gitbook.io/serious-shell-programming/>Serious Shell Programming</a> — focuses on POSIX-compliant Bourne Shell for portability</ul><p><strong>Handy tools, tips and reference</strong><ul><li><a href=https://www.shellcheck.net/>shellcheck</a> — linting tool to avoid common mistakes and improve your script<li><a href=https://devmanual.gentoo.org/tools-reference/bash/index.html>Bash reference cheatsheet</a> — nicely formatted and explained well<li><a href=https://devhints.io/bash>Bash scripting cheatsheet</a> — quick reference to getting started with Bash scripting<li>Comprehensive lists on the <code>mywiki.wooledge.org</code> website: <ul><li><a href=https://mywiki.wooledge.org/BashFAQ>Bash FAQ</a><li><a href=https://mywiki.wooledge.org/BashGuide/Practices>Bash Practices</a><li><a href=https://mywiki.wooledge.org/BashPitfalls>Bash Pitfalls</a></ul><li><a href=https://google.github.io/styleguide/shellguide.html>Google shell style guide</a><li>Reliability and robustness <ul><li><a href=https://github.com/anordal/shellharden/blob/master/how_to_do_things_safely_in_bash.md>safe ways to do things in bash</a><li><a href=https://robertmuth.blogspot.com/2012/08/better-bash-scripting-in-15-minutes.html>better scripting</a><li><a href=https://www.davidpashley.com/articles/writing-robust-shell-scripts/>robust scripting</a></ul></ul><p><strong>Specific topics</strong><ul><li>Reading files <ul><li><a href=https://mywiki.wooledge.org/BashFAQ/001>Robust way to read files for various use cases</a><li><a href=https://unix.stackexchange.com/q/82541/109046>Loop through the lines of two files in parallel</a></ul><li><a href=https://mywiki.wooledge.org/BashGuide/Arrays>arrays</a><li><a href=https://unix.stackexchange.com/q/288886/109046>nameref</a> <ul><li>also see this <a href=https://mywiki.wooledge.org/BashFAQ/006>FAQ</a></ul><li>getopts <ul><li><a href=https://web.archive.org/web/20221226035414/https://wiki.bash-hackers.org/howto/getopts_tutorial>getopts tutorial</a><li><a href=https://mywiki.wooledge.org/BashFAQ/035>handling command-line arguments</a><li><a href=https://stackoverflow.com/q/16483119/4082052>stackoverflow: getopts example</a></ul><li><a href=https://mywiki.wooledge.org/SignalTrap>Sending and Trapping Signals</a></ul><h2 id=exercises><a class=header href=#exercises>Exercises</a></h2><blockquote><p><img alt=info src=./images/info.svg> Use a temporary working directory before attempting the exercises. You can delete such practice directories afterwards.</blockquote><p><strong>1)</strong> What's wrong with the script shown below? Also, will the error go away if you use <code>bash try.sh</code> instead?<pre><code class=language-bash>$ printf ' \n!#/bin/bash\n\necho hello\n' > try.sh
$ chmod +x try.sh
$ ./try.sh
./try.sh: line 2: !#/bin/bash: No such file or directory
hello
# expected output
$ ./try.sh
hello
</code></pre><p><strong>2)</strong> Will the command shown below work? If so, what would be the output?<pre><code class=language-bash>$ echo echo hello | bash
</code></pre><p><strong>3)</strong> When would you <code>source</code> a script instead of using <code>bash</code> or creating an executable using shebang?<p><strong>4)</strong> How would you display the contents of a variable with <code>shake</code> appended?<pre><code class=language-bash>$ fruit='banana'
$ echo # ???
bananashake
</code></pre><p><strong>5)</strong> What changes would you make to the code shown below to get the expected output?<pre><code class=language-bash># default behavior
$ n=100
$ n+=100
$ echo "$n"
100100
# expected output
$ echo "$n"
200
</code></pre><p><strong>6)</strong> Is the following code valid? If so, what would be the output of the <code>echo</code> command?<pre><code class=language-bash>$ declare -a colors
$ colors[3]='green'
$ colors[1]='blue'
$ echo "${colors[@]}"
# ???
</code></pre><p><strong>7)</strong> How would you get the last three characters of a variable's contents?<pre><code class=language-bash>$ fruit='banana'
# ???
ana
</code></pre><p><strong>8)</strong> Will the second <code>echo</code> command give an error? If not, what will be the output?<pre><code class=language-bash>$ fruits=('apple' 'fig' 'mango')
$ echo "${#fruits[@]}"
3
$ echo "${#fruits}"
# ???
</code></pre><p><strong>9)</strong> For the given array, use parameter expansion to remove characters until the first/last space.<pre><code class=language-bash>$ colors=('green' 'dark brown' 'deep sky blue white')
# remove till the first space
$ printf '%s\n' # ???
green
brown
sky blue white
# remove till the last space
$ printf '%s\n' # ???
green
brown
white
</code></pre><p><strong>10)</strong> Use parameter expansion to get the expected outputs shown below.<pre><code class=language-bash>$ ip='apple:banana:cherry:dragon'
$ echo # ???
apple:banana:cherry
$ echo # ???
apple
</code></pre><p><strong>11)</strong> Is it possible to achieve the expected outputs shown below using parameter expansion? If so, how?<pre><code class=language-bash>$ ip1='apple:banana:cherry:dragon'
$ ip2='Cradle:Mistborn:Piranesi'
$ echo # ???
apple 42 dragon
$ echo # ???
Cradle 42 Piranesi
$ echo # ???
fig:banana:cherry:dragon
$ echo # ???
fig:Mistborn:Piranesi
$ echo # ???
apple:banana:cherry:end
$ echo # ???
Cradle:Mistborn:end
</code></pre><p><strong>12)</strong> For the given input, change case as per the expected outputs shown below.<pre><code class=language-bash>$ ip='This is a Sample STRING'
$ echo # ???
THIS IS A SAMPLE STRING
$ echo # ???
this is a sample string
$ echo # ???
tHIS IS A sAMPLE string
</code></pre><p><strong>13)</strong> Why does the conditional expression shown below fail?<pre><code class=language-bash>$ touch ip.txt
$ [[-f ip.txt]] && echo 'file exists'
[[-f: command not found
</code></pre><p><strong>14)</strong> What is the difference between the <code>==</code> and <code>=~</code> string comparison operators?<p><strong>15)</strong> Why does the conditional expression used below show <code>failed</code> both times? Modify the expressions such that the first one correctly says <code>matched</code> instead of <code>failed</code>.<pre><code class=language-bash>$ f1='1234.txt'
$ f2='report_2.txt'
$ [[ $f1 == '+([0-9]).txt' ]] && echo 'matched' || echo 'failed'
failed
$ [[ $f2 == '+([0-9]).txt' ]] && echo 'matched' || echo 'failed'
failed
</code></pre><p><strong>16)</strong> Extract the digits that follow a <code>:</code> character for the given variable contents.<pre><code class=language-bash>$ item='chocolate:50'
# ???
50
$ item='50 apples, fig:100, books-12'
# ???
100
</code></pre><p><strong>17)</strong> Modify the expression shown below to correctly report <code>true</code> instead of <code>false</code>.<pre><code class=language-bash>$ num=12345
$ [[ $num > 3 ]] && echo 'true' || echo 'false'
false
</code></pre><p><strong>18)</strong> Write a shell script named <code>array.sh</code> that accepts array input from the user followed by another input as index. Display the corresponding value at that index. Couple of examples are shown below.<pre><code class=language-bash>$ bash array.sh
enter array elements: apple banana cherry
enter array index: 1
element at index '1' is: banana
$ bash array.sh
enter array elements: dragon unicorn centaur
enter array index: -1
element at index '-1' is: centaur
</code></pre><p><strong>19)</strong> Write a shell script named <code>case.sh</code> that accepts exactly two command line arguments. The first argument can be <code>lower</code>, <code>upper</code> or <code>swap</code> and this should be used to transform the contents of the second argument. Examples script invocations are shown below, including what should happen if the command line arguments do not meet the script expectations.<pre><code class=language-bash>$ ./case.sh upper 'how are you?'
HOW ARE YOU?
$ ./case.sh lower PineAPPLE
pineapple
$ ./case.sh swap 'HeLlo WoRlD'
hElLO wOrLd
$ ./case.sh lower
Error! Two arguments expected.
$ echo $?
1
$ ./case.sh upper apple fig
Error! Two arguments expected.
$ ./case.sh lowercase DRAGON
Error! 'lowercase' command not recognized.
$ echo $?
1
$ ./case.sh apple lower 2> /dev/null
$ echo $?
1
</code></pre><p><strong>20)</strong> Write a shell script named <code>loop.sh</code> that displays the number of lines for each of the files passed as command line arguments.<pre><code class=language-bash>$ printf 'apple\nbanana\ncherry\n' > items_1.txt
$ printf 'dragon\nowl\nunicorn\ntroll\ncentaur\n' > items_2.txt
$ bash loop.sh items_1.txt
number of lines in 'items_1.txt' is: 3
$ bash loop.sh items_1.txt items_2.txt
number of lines in 'items_1.txt' is: 3
number of lines in 'items_2.txt' is: 5
</code></pre><p><strong>21)</strong> Write a shell script named <code>read_file.sh</code> that reads a file line by line to be passed as an argument to the <code>paste -sd,</code> command. Can you also write a solution using the <code>xargs</code> command instead of a script?<pre><code class=language-bash>$ printf 'apple\nbanana\ncherry\n' > items_1.txt
$ printf 'dragon\nowl\nunicorn\ntroll\ncentaur\n' > items_2.txt
$ printf 'items_1.txt\nitems_2.txt\n' > list.txt
$ bash read_file.sh list.txt
apple,banana,cherry
dragon,owl,unicorn,troll,centaur
$ xargs # ???
apple,banana,cherry
dragon,owl,unicorn,troll,centaur
</code></pre><p><strong>22)</strong> Write a function named <code>add_path</code> which prefixes the path of the current working directory to the arguments it receives and displays the results. Examples are shown below.<pre><code class=language-bash>$ add_path() # ???
$ cd
$ pwd
/home/learnbyexample
$ add_path ip.txt report.log
/home/learnbyexample/ip.txt /home/learnbyexample/report.log
$ cd cli-computing
$ pwd
/home/learnbyexample/cli-computing
$ add_path f1
/home/learnbyexample/cli-computing/f1
</code></pre><p><strong>23)</strong> What do the options <code>bash -x</code> and <code>bash -v</code> do?<p><strong>24)</strong> What is <code>shellcheck</code> and when would you use it?</main><nav aria-label="Page navigation"class=nav-wrapper><a aria-label="Previous chapter"class="mobile-nav-chapters previous"title="Previous chapter"aria-keyshortcuts=Left href=assorted-text-processing-tools.html rel=prev> <i class="fa fa-angle-left"></i> </a><a aria-label="Next chapter"class="mobile-nav-chapters next"title="Next chapter"aria-keyshortcuts=Right href=shell-customization.html rel=next> <i class="fa fa-angle-right"></i> </a><div style="clear: both"></div></nav></div></div><nav aria-label="Page navigation"class=nav-wide-wrapper><a aria-label="Previous chapter"class="nav-chapters previous"title="Previous chapter"aria-keyshortcuts=Left href=assorted-text-processing-tools.html rel=prev> <i class="fa fa-angle-left"></i> </a><a aria-label="Next chapter"class="nav-chapters next"title="Next chapter"aria-keyshortcuts=Right href=shell-customization.html rel=next> <i class="fa fa-angle-right"></i> </a></nav></div><script>window.playground_copyable = true;</script><script charset=utf-8 src=elasticlunr.min.js></script><script charset=utf-8 src=mark.min.js></script><script charset=utf-8 src=searcher.js></script><script charset=utf-8 src=clipboard.min.js></script><script charset=utf-8 src=highlight.js></script><script charset=utf-8 src=book.js></script><script src=sidebar.js></script>