comparison src/computer_literacy/bash.html @ 26:fb87f762847e

start computer_literacy
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 09 Oct 2025 16:56:36 -0600
parents
children 9134d56639ae
comparison
equal deleted inserted replaced
25:4e019ee9c44d 26:fb87f762847e
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <script src="/site.js"></script>
5 <script> head() </script>
6 <title>Arkian - Bash</title>
7 <script>
8 'use strict';
9
10 let content = {
11 intro: {
12 title: 'Introduction',
13 content: `\
14 <p>I really don't want to write this tutorial, but all the existing Bash tutorials are so horrible that I have no choice. I looked at books, websites, and YouTube - all horrible. They don't start with the basics. They include all kinds of useless junk. And they don't explain core concepts. So I have no choice but to write this for my <a href="learn.html#bash">Learn Reactionary Programming</a> Bash lesson.</p>
15
16 <p><a href="bash.html">Bash</a> is a <a href="https://en.wikipedia.org/wiki/Unix_shell">shell</a>, one of many, but the one I prefer. I will focus on Mac and Windows. I don't have Linux, and I hate Linux, so I won't discuss it. Most of Bash is the same on Mac and Windows, but where they differ, I will discuss both.</p>
17 ` ,
18 },
19 access: {
20 title: 'Running Bash',
21 content: `\
22 <p>How you access Bash depends on your operating system. If you are on a Mac then you access Bash through the Mac Terminal which is found in "Applications > Utilities > Terminal.app". Be sure to <a href="https://www.howtogeek.com/444596/how-to-change-the-default-shell-to-bash-in-macos-catalina/">set the default shell to Bash</a>. If you are on Windows then install <a href="https://www.msys2.org/">MSYS2</a>. The default terminal isn't so good, so I suggest <a href="https://www.msys2.org/docs/terminals/#windows-terminal">using the Windows Terminal</a>.</p>
23 ` ,
24 },
25 start: {
26 title: 'Getting Started',
27 content: `\
28 <p>When I start Bash on my Mac I see:</p>
29
30 <code block>
31 Last login: Thu Jan 4 23:25:35 on ttys004
32
33 The default interactive shell is now zsh.
34 To update your account to use zsh, please run 'chsh -s /bin/zsh'.
35 For more details, please visit https://support.apple.com/kb/HT208050.
36 ~ $
37
38 </code>
39
40 <p>On Windows - MSYS2 I just see:</p>
41
42 <code block>
43 ~ $
44
45 </code>
46
47 <p>The line with the <b><code>$</code></b> is the command prompt. The cursor is at the end of it, and if I type, my text will go there. You may have different text before the <b><code>$</code></b> which is okay, but the line should end with <b><code>$</code></b>. If it doesn't, something is wrong.</p>
48
49 <p>Now type "qqq". When I say type "whatever", you should type return/enter at the end. Only when you type return/enter will Bash process what you typed. Now you should see:</p>
50
51 <code block>
52 ~ $ qqq
53 -bash: qqq: command not found
54 ~ $
55 </code>
56
57 <p>Bash doesn't know what "qqq" means and says so. Now try the following... Note that you type what is after the <b><code>$</code></b> and Bash should respond as shown.</p>
58
59 <code block>
60 ~ $ echo hi
61 hi
62 ~ $ echo how are you
63 how are you
64 ~ $ echo bye
65 bye
66 </code>
67
68 <p>The <code>echo</code> command just echoes what comes after. Now press the up-arrow on your keyboard. This should put the previous command where your cursor is. Up-arrow again brings the command before that. Try down-arrow and left-arrow and right-arrow. You can use this to navigate through your command history. The delete key also works for editing lines. And of course you can type. When you press return/enter then Bash will get your edited command and process it.</p>
69
70 <p>When you enter <code>echo how are you</code>, <code>echo</code> is the command. This command has 3 arguments: <code>how</code>, <code>are</code>, and <code>you</code>. Commands and arguments are separated with spaces. It doesn't matter how many spaces, so:</p>
71
72 <code block>
73 ~ $ echo how are you
74 how are you
75 </code>
76
77 <p><code>echo</code> just returns the arguments separated by one space.</p>
78
79 <code block>
80 ~ $ echo one; echo two
81 one
82 two
83 </code>
84
85 <p>You can put multiple commands on one line separated by a <code>;</code>.</p>
86 ` ,
87 },
88 man: {
89 title: 'The "man" Command',
90 content: `\
91 <p>Enter:</p>
92 <code block>
93 ~ $ man echo
94 </code>
95
96 <p>You should get something like:</p>
97
98 <code block>
99
100 ECHO(1) BSD General Commands Manual ECHO(1)
101
102 NAME
103 echo -- write arguments to the standard output
104
105 SYNOPSIS
106 echo [-n] [string ...]
107
108 DESCRIPTION
109 The echo utility writes any specified operands, separated by single blank
110 (' ') characters and followed by a newline ('\\n') character, to the stan-
111 dard output.
112
113 The following option is available:
114
115 -n Do not print the trailing newline character. This may also be
116 achieved by appending '\\c' to the end of the string, as is done by
117 iBCS2 compatible systems. Note that this option as well as the
118 effect of '\\c' are implementation-defined in IEEE Std 1003.1-2001
119 (''POSIX.1'') as amended by Cor. 1-2002. Applications aiming for
120 maximum portability are strongly encouraged to use printf(1) to
121 suppress the newline character.
122 :
123 </code>
124
125 <p>But if you are on Windows, you may not have <code>man</code> installed. In that case, do:</p>
126
127 <code block>
128 ~ $ pacman -S man-db
129 </code>
130
131 <p>to install <code>man</code> as described <a href="https://packages.msys2.org/package/man-db">here</a> and then try <code>man echo</code> again.</p>
132
133 <p>The <code>man</code> command shows documentation of commands. Unfortunately it has a silly user interface based on memorizing keys, so I will just tell you the few keys you need. Down-arrow and up-arrow move down and up by one line. The space key moves down by one page. And most importantly, typing "q" quits and takes you back to Bash. You just have to memorize this.</p>
134
135 <p>Now try entering <code>man man</code>. You don't need all the stuff shown, but you can see what a complicated man page looks like. You can use <code>man</code> to get the documentation of other commands as I discuss them.</p>
136 ` ,
137 },
138 dirs: {
139 title: 'Directories',
140 content: `\
141 <p>You should be familiar with Mac Finder or Windows File Explorer, and you should know from this that directories (also called "folders") are organized into a tree.</p>
142
143 <p>On Mac:</p>
144
145 <code block>
146 ~ $ pwd
147 /Users/fschmidt
148 ~ $ open .
149 ~ $
150 </code>
151
152 <p>On Windows:</p>
153
154 <code block>
155 ~ $ pwd
156 /home/fschmidt
157 ~ $ explorer .
158 ~ $
159 </code>
160
161 <p>When using Bash, you are always in some directory, called the current directory or the working directory. <code>pwd</code> shows you the full path to this directory. Do <code>man pwd</code> for details. <code>open .</code> should open the Mac Finder for the current directory, and <code>explorer .</code> should open the Windows File Explorer for the current directory.<p>
162
163 <p>Continuing on my Mac:</p>
164
165 <code block>
166 ~ $ mkdir learn
167 ~ $ cd learn
168 ~/learn $ pwd
169 /Users/fschmidt/learn
170 </code>
171
172 <p><code>mkdir</code> makes a directory in the current directory. You should be able to see the created directory in Mac Finder or Windows File Explorer. <code>cd</code> stands for "change directory". This changes the current directory. <code>cd</code> is a built-in command (built into Bash), and <code>man</code> isn't useful with built-in commands, so instead of <code>man cd</code>, try <code>help cd</code>. Continuing...</p>
173
174 <code block>
175 ~/learn $ pwd
176 /Users/fschmidt/learn
177 ~/learn $ ls
178 ~/learn $ touch file1
179 ~/learn $ ls
180 file1
181 ~/learn $ touch file2
182 ~/learn $ touch file3
183 ~/learn $ ls
184 file1 file2 file3
185 ~/learn $ mkdir dir1
186 ~/learn $ ls
187 dir1 file1 file2 file3
188 ~/learn $ ls -F
189 dir1/ file1 file2 file3
190 ~/learn $ ls -a
191 . .. dir1 file1 file2 file3
192 ~/learn $ ls -a -F
193 ./ ../ dir1/ file1 file2 file3
194 ~/learn $ ls -aF
195 ./ ../ dir1/ file1 file2 file3
196 </code>
197
198 <p><code>ls</code> lists files and <code>touch</code> creates an empty file. Arguments that start with "-" are options. Do <code>man ls</code> to see what the options I used do. <code>-F</code> appends a "/" to directories, and <code>-a</code> shows files starting with "." which are usually hidden. Options can be combined.</p>
199
200 <code block>
201 ~/learn $ ls file1
202 file1
203 ~/learn $ ls qqq
204 ls: qqq: No such file or directory
205 ~/learn $ ls file1 qqq file2
206 ls: qqq: No such file or directory
207 file1 file2
208 ~/learn $ ls dir1
209 ~/learn $ touch dir1/d1file
210 ~/learn $ ls dir1
211 d1file
212 ~/learn $ ls -d dir1
213 dir1
214 ~/learn $ ls file1 file2 dir1
215 file1 file2
216
217 dir1:
218 d1file
219 ~/learn $ ls -d file1 file2 dir1
220 dir1 file1 file2
221 ~/learn $ ls -dF file1 file2 dir1
222 dir1/ file1 file2
223 </code>
224
225 <p>Without file arguments, <code>ls</code> lists files in the current directory. With file arguments, it lists those files if they exist. If the file is a directory, it will list what is in the directory unless the <code>-d</code> option is used.</p>
226
227 <code block>
228 ~/learn $ ls
229 dir1 file1 file2 file3
230 ~/learn $ ls .
231 dir1 file1 file2 file3
232 ~/learn $ ls -d .
233 .
234 ~/learn $ ls -dF .
235 ./
236 ~/learn $ ls ./file1
237 ./file1
238 ~/learn $ ls dir1
239 d1file
240 ~/learn $ ls ./dir1
241 d1file
242 ~/learn $ pwd
243 /Users/fschmidt/learn
244 ~/learn $ cd .
245 ~/learn $ pwd
246 /Users/fschmidt/learn
247 </code>
248
249 <p><code>.</code> is the current directory.</p>
250
251 <code block>
252 ~/learn $ pwd
253 /Users/fschmidt/learn
254 ~/learn $ cd dir1
255 ~/learn/dir1 $ pwd
256 /Users/fschmidt/learn/dir1
257 ~/learn/dir1 $ ls .
258 d1file
259 ~/learn/dir1 $ ls ..
260 dir1 file1 file2 file3
261 ~/learn/dir1 $ cd ..
262 ~/learn $ pwd
263 /Users/fschmidt/learn
264 ~/learn $ cd dir1
265 ~/learn/dir1 $ pwd
266 /Users/fschmidt/learn/dir1
267 ~/learn/dir1 $ cd ../..
268 ~ $ pwd
269 /Users/fschmidt
270 ~ $ cd learn
271 ~/learn $ pwd
272 /Users/fschmidt/learn
273 </code>
274
275 <p><code>..</code> is the parent directory.</p>
276
277 <code block>
278 ~/learn $ echo *
279 dir1 file1 file2 file3
280 ~/learn $ echo d*
281 dir1
282 ~/learn $ echo f*
283 file1 file2 file3
284 ~/learn $ echo *1
285 dir1 file1
286 ~/learn $ echo dir1/*
287 dir1/d1file
288 ~/learn $ echo */*
289 dir1/d1file
290 ~/learn $ echo qqq*
291 qqq*
292 </code>
293
294 <p><code>*</code> does wildcard matching of files. It is important to understand that Bash does the wildcard matching and then passes the resulting arguments to the command. <code>echo</code> never sees the "*" unless there is no match.</p>
295
296 <code block>
297 ~/learn $ ls *
298 file1 file2 file3
299
300 dir1:
301 d1file
302 ~/learn $ ls -dF *
303 dir1/ file1 file2 file3
304 ~/learn $ ls -dF d*
305 dir1/
306 ~/learn $ ls -dF f*
307 file1 file2 file3
308 ~/learn $ ls -dF *1
309 dir1/ file1
310 ~/learn $ ls dir1/*
311 dir1/d1file
312 ~/learn $ ls */*
313 dir1/d1file
314 ~/learn $ ls -dF qqq*
315 ls: qqq*: No such file or directory
316 </code>
317
318 <p>Should be self-explanatory.</p>
319
320 <code block>
321 ~/learn $ pwd
322 /Users/fschmidt/learn
323 ~/learn $ cd ~
324 ~ $ pwd
325 /Users/fschmidt
326 ~ $ cd learn/dir1
327 ~/learn/dir1 $ pwd
328 /Users/fschmidt/learn/dir1
329 ~/learn/dir1 $ cd
330 ~ $ pwd
331 /Users/fschmidt
332 ~ $ cd ~/learn
333 ~/learn $ pwd
334 /Users/fschmidt/learn
335 ~/learn $ echo ~
336 /Users/fschmidt
337 ~/learn $ echo .
338 .
339 ~/learn $ echo ..
340 ..
341 </code>
342
343 <p><code>~</code> means your home directory. <code>cd</code> without arguments is the same as <code>cd ~</code>. <code>~</code> is expanded into your home directory by Bash.</p>
344
345 <code block>
346 ~/learn $ ls -ltF
347 total 0
348 drwxr-xr-x 3 fschmidt staff 96 Jan 5 02:33 dir1/
349 -rw-r--r-- 1 fschmidt staff 0 Jan 5 02:21 file3
350 -rw-r--r-- 1 fschmidt staff 0 Jan 5 02:21 file2
351 -rw-r--r-- 1 fschmidt staff 0 Jan 5 02:21 file1
352 </code>
353
354 <p><code>-l</code> gives you this ugly techy format. You get the date that the file was last modified. Before the date is the file size. <code>-t</code> sorts by date descending.</p>
355
356 <p>Lastly I will describe autocompletion. I type <code>echo d</code> without enter/return but instead then press the tab key. It autocompletes to <code>echo dir1/</code>. I press tab again and it autocompletes to <code>echo dir1/d1file</code>. Pressing tab while entering a file or directory makes Bash try to autocomplete using matching file names. If I enter <code>echo f</code> and press tab, I get <code>echo file</code>. It doesn't know which to choose next. Another tab just beeps. And another tab shows me the options like this:</p>
357
358 <code block>
359 ~/learn $ echo file
360 file1 file2 file3
361 ~/learn $ echo file
362 </code>
363
364 <p>In general, you can press tab anytime while entering a file name and see what happens. Autocompletion saves a lot of typing.</p>
365 ` ,
366 },
367 files: {
368 title: 'Working with Files',
369 content: `\
370 <code block>
371 ~/learn $ ls -F
372 dir1/ file1 file2 file3
373 ~/learn $ cp file1 copied
374 ~/learn $ ls -F
375 copied dir1/ file1 file2 file3
376 ~/learn $ mv copied moved
377 ~/learn $ ls -F
378 dir1/ file1 file2 file3 moved
379 ~/learn $ rm moved
380 ~/learn $ ls -F
381 dir1/ file1 file2 file3
382 </code>
383
384 <p><code>cp</code> copies files or directories. <code>mv</code> moves files or directories. <code>rm</code> removes files or directories. See the <code>man</code> pages of these commands for details.</p>
385
386 <code block>
387 ~/learn $ ls -F
388 dir1/ file1 file2 file3
389 ~/learn $ mkdir dir2
390 ~/learn $ touch dir2/d2file
391 ~/learn $ ls -F
392 dir1/ dir2/ file1 file2 file3
393 ~/learn $ ls dir2
394 d2file
395 ~/learn $ rm dir2
396 rm: dir2: is a directory
397 ~/learn $ rm -d dir2
398 rm: dir2: Directory not empty
399 ~/learn $ rm dir2/d2file
400 ~/learn $ rm -d dir2
401 ~/learn $ ls -F
402 dir1/ file1 file2 file3
403 </code>
404
405 <code block>
406 ~/learn $ ls -F
407 dir1/ file1 file2 file3
408 ~/learn $ mkdir dir2
409 ~/learn $ touch dir2/d2file
410 ~/learn $ ls -F
411 dir1/ dir2/ file1 file2 file3
412 ~/learn $ rm -r dir2
413 ~/learn $ ls -F
414 dir1/ file1 file2 file3
415 </code>
416
417 <code block>
418 ~/learn $ ls -F
419 dir1/ file1 file2 file3
420 ~/learn $ cp dir1 dir2
421 cp: dir1 is a directory (not copied).
422 ~/learn $ cp -r dir1 dir2
423 ~/learn $ ls -F
424 dir1/ dir2/ file1 file2 file3
425 ~/learn $ ls dir2
426 d1file
427 ~/learn $ cp f* dir2
428 ~/learn $ ls dir2
429 d1file file1 file2 file3
430 ~/learn $ rm -r dir2
431 ~/learn $ ls -F
432 dir1/ file1 file2 file3
433 </code>
434
435 <code block>
436 ~/learn $ ls -F
437 dir1/ file1 file2 file3
438 ~/learn $ mkdir dir2
439 ~/learn $ cp -r dir1 dir2
440 ~/learn $ ls -F dir2
441 dir1/
442 ~/learn $ ls -F dir2/dir1
443 d1file
444 ~/learn $ rm -r dir2
445 ~/learn $ ls -F
446 dir1/ file1 file2 file3
447 </code>
448
449 <p>I could explain all this, but I won't. You should learn to understand commands and their options using <code>man</code> and by playing with them. Don't continue until you completely understand the above.</p>
450 ` ,
451 },
452 quote: {
453 title: 'Quoting',
454 content: `\
455 <code block>
456 ~/learn $ echo a b
457 a b
458 ~/learn $ echo "a b"
459 a b
460 ~/learn $ echo 'a b'
461 a b
462 ~/learn $ echo "a b" c
463 a b c
464 </code>
465
466 <p>Bash treats text in quotes as one argument. So in <code>echo a b</code>, <code>echo</code> has two arguments: "a" and "b". In <code>echo "a b"</code>, <code>echo</code> has one argument: "<span pre>a b</span>". In <code>echo 'a b'</code>, <code>echo</code> has one argument: "<span pre>a b</span>". In <code>echo "a b" c</code>, <code>echo</code> has two arguments: "<span pre>a b</span>" and "c".</p>
467
468 <code block>
469 ~/learn $ echo a\\ \\ \\ b
470 a b
471 </code>
472
473 <p>Outside of quotes, <code>\\ </code> is not treated as a separator, but rather is treated as a space character that is part of the argument.</p>
474
475 <code block>
476 ~/learn $ echo file*
477 file1 file2 file3
478 ~/learn $ echo "file*"
479 file*
480 ~/learn $ echo 'file*'
481 file*
482 </code>
483
484 <p>Quotes prevent wildcard expansion.</p>
485 ` ,
486 },
487 vars: {
488 title: 'Variables',
489 content: `\
490 <code block>
491 ~/learn $ echo $X
492
493 ~/learn $ X="some text"
494 ~/learn $ echo $X
495 some text
496 ~/learn $ echo "X is: $X"
497 X is: some text
498 ~/learn $ echo 'X is: $X'
499 X is: $X
500 ~/learn $ X="$X and more"
501 ~/learn $ echo $X
502 some text and more
503 </code>
504
505 <p>Here <code>X</code> is a variable. You get its value with <code>$X</code>. This also works inside double-quotes but not inside single-quotes.</p>
506
507 <p>There are special variables called environment variables that are used by Bash.</p>
508
509 <code block>
510 ~/learn $ echo $PATH
511 /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/fschmidt/Dropbox/bin:/Users/fschmidt/hg/luan/scripts:/usr/local/opt/postgresql@9.5/bin
512 ~/learn $ which ls
513 /bin/ls
514 ~/learn $ cd /bin
515 /bin $ pwd
516 /bin
517 /bin $ ls
518 [ dd launchctl pwd test
519 bash df link rm unlink
520 cat echo ln rmdir wait4path
521 chmod ed ls sh zsh
522 cp expr mkdir sleep
523 csh hostname mv stty
524 dash kill pax sync
525 date ksh ps tcsh
526 /bin $ ls -F
527 [* dd* launchctl* pwd* test*
528 bash* df* link* rm* unlink*
529 cat* echo* ln* rmdir* wait4path*
530 chmod* ed* ls* sh* zsh*
531 cp* expr* mkdir* sleep*
532 csh* hostname* mv* stty*
533 dash* kill* pax* sync*
534 date* ksh* ps* tcsh*
535 /bin $ cd ~/learn
536 ~/learn $
537 </code>
538
539 <p><code>PATH</code> is an environment variable containing a list of directories separated by <code>:</code> that are searched for commands by Bash. The <code>which</code> command shows the full path to a command. <code>ls -F</code> appends a <code>*</code> to executable files.</p>
540
541 <code block>
542 ~/learn $ subl file1
543 -bash: subl: command not found
544 ~/learn $ "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" file1
545 ~/learn $ PATH="$PATH:/Applications/Sublime Text.app/Contents/SharedSupport/bin"
546 ~/learn $ echo $PATH
547 /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/fschmidt/Dropbox/bin:/Users/fschmidt/hg/luan/scripts:/usr/local/opt/postgresql@9.5/bin:/Applications/Sublime Text.app/Contents/SharedSupport/bin
548 ~/learn $ subl file1
549 ~/learn $
550 </code>
551
552 <p>Here I edit the file <code>file1</code> with <a href="learn.html#editor">Sublime Text</a>, first by using the full path, and then by adding the directory to <code>PATH</code> so that Bash can find <code>subl</code>.</p>
553
554 <p>I have Microsoft Word on Windows. From the Windows Command Prompt (not Bash):</p>
555
556 <code block>
557 C:\\Users\\fschmidt>winword
558
559 C:\\Users\\fschmidt>where winword
560 C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE
561 </code>
562
563 <p><code>winword</code> runs Microsoft Word. The Command Prompt <code>where</code> command is like the Bash <code>which</code> command. So now on MSYS2:</p>
564
565 <code block>
566 ~ $ winword
567 bash: winword: command not found
568 ~ $ echo $PATH
569 /usr/local/bin:/usr/bin:/bin:/opt/bin:/c/Windows/System32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0/:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/c/Program Files/TortoiseHg:/c/Program Files/Java/jdk1.8.0_202/bin
570 ~ $ PATH="$PATH:/c/Program Files/Microsoft Office/root/Office16"
571 ~ $ echo $PATH
572 /usr/local/bin:/usr/bin:/bin:/opt/bin:/c/Windows/System32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0/:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/c/Program Files/TortoiseHg:/c/Program Files/Java/jdk1.8.0_202/bin:/c/Program Files/Microsoft Office/root/Office16
573 ~ $ winword
574 ~ $
575 </code>
576
577 <p>Returning to the Mac, there is another way to run applications found in Finder's "Applications" simply as applications instead of as commands.</p>
578
579 <code block>
580 ~/learn $ open -a 'Sublime Text' file1
581 </code>
582
583 <p>Another useful environment variable is <code>PS1</code> which controls the command prompt. I already have this set up, but if I didn't:</p>
584
585 <code block>
586 Franklins-MacBook-Pro:learn fschmidt$ echo $PS1
587 \\h:\\W \\u\\$
588 Franklins-MacBook-Pro:learn fschmidt$ PS1="\\w $ "
589 ~/learn $ echo $PS1
590 \\w $
591 ~/learn $
592 </code>
593
594 <p>Google "bash PS1" for more info.</p>
595 ` ,
596 },
597 bash_profile: {
598 title: '.bash_profile',
599 content: `\
600 <code block>
601 ~/learn $ cd
602 ~ $ ls .bash_profile
603 .bash_profile
604 </code>
605
606 <p>If <code>.bash_profile</code> isn't found then do <code>touch .bash_profile</code> to create it. This file contains Bash commands that are run when Bash starts. If you already have this file, it is likely to contain comments that start with <code>#</code>. Comments are ignored like this:</p>
607
608 <code block>
609 ~ $ # comment line, does nothing
610 ~ $ echo whatever # end of line comment
611 whatever
612 ~ $
613 </code>
614
615 <p>To edit <code>.bash_profile</code> on a Mac, you can do:</p>
616
617 <code block>
618 ~ $ open -a 'Sublime Text' .bash_profile
619 </code>
620
621 <p>To edit <code>.bash_profile</code> on Windows, you can do:</p>
622
623 <code block>
624 ~ $ notepad .bash_profile
625 </code>
626
627 <p>Now try adding this line to <code>.bash_profile</code>:</p>
628
629 <code block>
630 echo hello there
631 </code>
632
633 <p>Now when you open a new Bash terminal, you should see "hello there". <code>.bash_profile</code> runs when Bash is started by opening a new Bash terminal.</p>
634
635 <p>I set <code>PS1</code> and <code>PATH</code> in <code>.bash_profile</code> to have the command prompt I want, and access to the commands that I want. I suggest that you make the <a href="https://www.sublimetext.com/docs/command_line.html">Sublime Text command</a> <code>subl</code> available in <code>PATH</code>.</p>
636 ` ,
637 },
638 find: {
639 title: 'The "find" Command',
640 content: `\
641 <code block>
642 ~/learn $ find .
643 .
644 ./file3
645 ./file2
646 ./file1
647 ./dir1
648 ./dir1/d1file
649 ~/learn $ find . -name 'file*'
650 ./file3
651 ./file2
652 ./file1
653 ~/learn $ find . -name '*file'
654 ./dir1/d1file
655 ~/learn $ find . -name 'd*'
656 ./dir1
657 ./dir1/d1file
658 ~/learn $ find . -name '*1' -or -name '*2'
659 ./file2
660 ./file1
661 ./dir1
662 </code>
663
664 <p><code>find</code> recursively searches for files in a directory tree. Note that in this case the <code>*</code> wildcard matching is not being done by Bash, it is being done by <code>find</code>. <code>find</code> has many options for searching for files and acting on them, see <code>man find</code>.</p>
665 ` ,
666 },
667 io: {
668 title: 'Input and Output',
669 content: `\
670 <code block>
671 ~/learn $ echo 'this is a test' >test.txt
672 ~/learn $ ls -F
673 dir1/ file1 file2 file3 test.txt
674 ~/learn $ cat test.txt
675 this is a test
676 ~/learn $ echo 'this is another test' >test.txt
677 ~/learn $ cat test.txt
678 this is another test
679 ~/learn $ echo 'another line' >>test.txt
680 ~/learn $ cat test.txt
681 this is another test
682 another line
683 ~/learn $ cat &lt;test.txt
684 this is another test
685 another line
686 ~/learn $ cat &lt;&lt;End >test.txt
687 > I am typing this
688 > and this
689 > End
690 ~/learn $ cat test.txt
691 I am typing this
692 and this
693 ~/learn $ (echo one; echo two) >test.txt
694 ~/learn $ cat test.txt
695 one
696 two
697 </code>
698
699 <p>All programs have standard input, standard output, and standard error. Programs write normal output to standard output and error messages to standard error. By default, standard input comes from the terminal, and standard output and standard error go to the terminal, but this can be changed. <code>>file</code> sends standard output to <code>file</code>. <code>>>file</code> appends standard output to <code>file</code>. <code>&lt;file</code> reads standard input from <code>file</code>. <code>&lt;&lt;whatever</code> reads standard input from the text that follows until a line with just <code>whatever</code>. Commands can be combined between <code>(</code> and <code>)</code>. Be sure to <code>man cat</code> to understand how <code>cat</code> works.</p>
700
701 <code block>
702 ~/learn $ ls >ls.txt
703 ~/learn $ cat ls.txt
704 dir1
705 file1
706 file2
707 file3
708 ls.txt
709 test.txt
710 ~/learn $ ls -d f* q* >ls.txt
711 ls: q*: No such file or directory
712 ~/learn $ cat ls.txt
713 file1
714 file2
715 file3
716 ~/learn $ ls -d f* q* 2>ls.txt
717 file1 file2 file3
718 ~/learn $ cat ls.txt
719 ls: q*: No such file or directory
720 ~/learn $ ls -d f* q* | tee ls.txt
721 ls: q*: No such file or directory
722 file1
723 file2
724 file3
725 ~/learn $ cat ls.txt
726 file1
727 file2
728 file3
729 ~/learn $ ls -d f* q* 2>&1 | tee ls.txt
730 ls: q*: No such file or directory
731 file1
732 file2
733 file3
734 ~/learn $ cat ls.txt
735 ls: q*: No such file or directory
736 file1
737 file2
738 file3
739 </code>
740
741 <p><code>2>file</code> sends standard error to <code>file</code>. <code>|</code> sends standard output of the previous command to standard input of the following command. <code>2>&1</code> sends standard error to standard output. <code>tee file</code> reads standard input and then writes it to both standard output and to <code>file</code>.</p>
742
743 <code block>
744 ~/learn $ find . -type f | wc -l
745 6
746 </code>
747
748 <p>There are 6 files in <code>learn</code>. Use <code>man</code> to figure out how this works.</p>
749 ` ,
750 },
751 ctrl: {
752 title: 'Control Keys',
753 content: `\
754 <code block>
755 ~/learn $ sleep 3
756 ~/learn $ sleep 30
757 ^C
758 ~/learn $
759 </code>
760
761 <p><code>sleep 3</code> sleeps for 3 seconds, meaning it does nothing for 3 seconds. I waited 3 seconds for this command to finish. Then I ran <code>sleep 30</code> which would sleep for 30 seconds, but I lost my patience and pressed control+c which interrupts the program and breaks out of it. You can try control+c if you ever get stuck waiting for a command to finish.</p>
762
763 <code block>
764 ~/learn $ wc
765 I am typing this
766 and this
767 now I will end my input with control+d
768 3 14 65
769 ~/learn $ wc
770 this time I will use control+c to break out
771 ^C
772 ~/learn $
773 </code>
774
775 <p>Control+d means end of input.</p>
776 ` ,
777 },
778 subst: {
779 title: 'Command Substitution',
780 content: `\
781 <code block>
782 ~/learn $ echo I am in $(pwd)
783 I am in /Users/fschmidt/learn
784 ~/learn $ echo this directory contains: $(ls)
785 this directory contains: dir1 file1 file2 file3 ls.txt test.txt
786 ~/learn $ echo this directory contains $(ls | wc -l) files
787 this directory contains 6 files
788 </code>
789
790 <p><code>cmd $(commands)</code> will use the output of <code>commands</code> as argument text for <code>cmd</code>.</p>
791
792 <code block>
793 ~/learn $ cat $(find . -type f) | wc -c
794 86
795 </code>
796
797 <p>The files in <code>learn</code> contain a total of 86 bytes. Use <code>man</code> to figure out how this works.</p>
798 ` ,
799 },
800 ampersand: {
801 title: 'Ampersand',
802 content: `\
803 <code block>
804 ~/learn $ (sleep 5; echo done) &
805 [1] 10080
806 ~/learn $ echo waiting
807 waiting
808 ~/learn $ done
809
810 [1]+ Done ( sleep 5; echo done )
811 ~/learn $
812 </code>
813
814 <p>Normally Bash waits for a command to complete before showing the command prompt and allowing input. But ending a command line with <code>&</code> tells bash not to wait, but instead to run the command in a separate process. Above in <code>~/learn $ echo waiting</code>, I typed in <code>echo waiting</code>. But in <code>~/learn $ done</code>, I did not type <code>done</code>. Instead this was produced by <code>echo done</code> after 5 seconds. <code>[1] 10080</code> tells me that a process was started and <code>[1]+ Done ( sleep 5; echo done )</code> tells me that the process finished.</p>
815
816 <p>This is useful where you do not want to wait for a command to finish. Consider this on Windows:</p>
817
818 <code block>
819 ~ $ notepad
820 </code>
821
822 <p>Here you will not get a command prompt again until you quit Notepad because Bash is waiting for this command to finish. So instead do:
823
824 <code block>
825 ~ $ notepad &
826 [1] 2010
827 ~ $
828 </code>
829
830 <p>Now Notepad will run and you can continue using Bash.</p>
831 ` ,
832 },
833 scripts: {
834 title: 'Shell Scripts',
835 content: `\
836 <p>Make a file called <code>test.sh</code> containing the following:</p>
837
838 <code block>
839 echo this is a shell script
840 </code>
841
842 <p>Now from Bash:</p>
843
844 <code block>
845 ~/learn $ cat test.sh
846 echo this is a shell script
847 ~/learn $ ./test.sh
848 -bash: ./test.sh: Permission denied
849 ~/learn $ ls -F test.sh
850 test.sh
851 ~/learn $ chmod +x test.sh
852 ~/learn $ ls -F test.sh
853 test.sh*
854 ~/learn $ ./test.sh
855 this is a shell script
856 ~/learn $
857 </code>
858
859 <p><code>chmod +x file</code> makes <code>file</code> into an executable that can be run. Now I will edit <code>test.sh</code></p>
860
861 <code block>
862 ~/learn $ # edit test.sh
863 ~/learn $ cat test.sh
864 nonsense
865 echo this is a shell script
866 ~/learn $ ./test.sh
867 ./test.sh: line 1: nonsense: command not found
868 this is a shell script
869 ~/learn $ # edit test.sh
870 ~/learn $ cat test.sh
871 set -e
872 nonsense
873 echo this is a shell script
874 ~/learn $ ./test.sh
875 ./test.sh: line 2: nonsense: command not found
876 ~/learn $
877 </code>
878
879 <p>By default, scripts continue running after an error. In longer scripts, we want the script to exit after an error. <code>set -e</code> does this, see <code>help set</code>.</p>
880
881 <code block>
882 ~/learn $ X=some
883 ~/learn $ echo $X
884 some
885 ~/learn $ echo $Xthing
886
887 ~/learn $ echo \${X}thing
888 something
889 ~/learn $ # edit test.sh
890 ~/learn $ cat test.sh
891 echo "\\$* = $*"
892 echo "\\$# = $#"
893 echo "\\$0 = $0"
894 echo "\\$1 = $1"
895 echo "\\$2 = $2"
896 echo "\\$3 = $3"
897 echo "\\$4 = $4"
898 echo "\\$14 = $14"
899 echo "\\\${14} = \$\{14}"
900 echo "\\$@ = $@"
901 ./count.sh "$*"
902 ./count.sh "$@"
903 ~/learn $ ./test.sh a b "c d"
904 $* = a b c d
905 $# = 3
906 $0 = ./test.sh
907 $1 = a
908 $2 = b
909 $3 = c d
910 $4 =
911 $14 = a4
912 \${14} =
913 $@ = a b c d
914 1
915 3
916 ~/learn $ cat count.sh
917 echo $#
918 ~/learn $
919 </code>
920
921 <p>Bash scripts have special defined variables. The difference between <code>$*</code> and <code>$@</code> is subtle, and you will usually just use <code>$*</code>. <code>$*</code> returns all arguments as one string while <code>$@</code> returns the arguments separately, but this distinction rarely makes any difference.</p>
922 ` ,
923 },
924 vars_and_scripts: {
925 title: 'Variables and Scripts',
926 content: `\
927 <code block>
928 ~/learn $ X=value
929 ~/learn $ echo $X
930 value
931 ~/learn $ # edit test.sh
932 ~/learn $ cat test.sh
933 echo "\\$X = $X"
934 ~/learn $ ./test.sh
935 $X =
936 ~/learn $ export X
937 ~/learn $ ./test.sh
938 $X = value
939 </code>
940
941 <p>Variables are defined in the current shell. Shell scripts are run in their own shell. So by default, they don't see variables defined in the terminal/parent shell. <code>export var</code> makes <code>var</code> available in descendant processes, meaning available in shell scripts. It is a good idea to do <code>export PATH</code> in <code>.bash_profile</code> so that your PATH is available to your scripts.</p>
942
943 <code block>
944 ~/learn $ X=terminal
945 ~/learn $ echo $X
946 terminal
947 ~/learn $ # edit test.sh
948 ~/learn $ cat test.sh
949 X=script
950 export X
951 ~/learn $ ./test.sh
952 ~/learn $ echo $X
953 terminal
954 ~/learn $ . test.sh
955 ~/learn $ echo $X
956 script
957 </code>
958
959 <p>You can export a variable from parent to children but not from children to parent. <code>. script</code> includes the text in the file <code>script</code> in the current shell. In this case, it is not run in a separate shell. This is the only way to have a script set variables in your terminal shell.</p>
960
961 <code block>
962 ~/learn $ pwd
963 /Users/fschmidt/learn
964 ~/learn $ # edit test.sh
965 ~/learn $ cat test.sh
966 cd ~
967 ~/learn $ ./test.sh
968 ~/learn $ pwd
969 /Users/fschmidt/learn
970 ~/learn $ . test.sh
971 ~ $ pwd
972 /Users/fschmidt
973 ~ $ cd learn
974 ~/learn $
975 </code>
976
977 <p>This illustrates the difference between <code>./script</code> and <code>. script</code>.</p>
978 ` ,
979 },
980 your_scripts: {
981 title: 'Your Scripts',
982 content: `\
983 <code block>
984 ~/learn $ echo $PATH
985 /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/fschmidt/Dropbox/bin:/Users/fschmidt/hg/luan/scripts:/usr/local/opt/postgresql@9.5/bin:/Applications/Sublime Text.app/Contents/SharedSupport/bin
986 ~/learn $ echo ~/Dropbox/bin
987 /Users/fschmidt/Dropbox/bin
988 ~/learn $ ls -F ~/Dropbox/bin/e
989 /Users/fschmidt/Dropbox/bin/e*
990 ~/learn $ cat ~/Dropbox/bin/e
991 open -a 'Sublime Text' $*
992 ~/learn $ e test.sh
993 ~/learn $
994 </code>
995
996 <p>When you write useful scripts, put them in a directory and add that directory to your PATH. I use <code>~/Dropbox/bin</code> and I have a script named <code>e</code> in that directory for editing files. So <code>e test.sh</code> lets me edit <code>test.sh</code> from the command line.</p>
997
998 <p>Note that Bash will only look in your PATH for scripts unless you give an explicit path to the script.</p>
999
1000 <code block>
1001 ~/learn $ # edit test.sh
1002 ~/learn $ cat test.sh
1003 echo this is a shell script
1004 ~/learn $ test.sh
1005 -bash: test.sh: command not found
1006 ~/learn $ ./test.sh
1007 this is a shell script
1008 ~/learn $
1009 </code>
1010
1011 <p>Calling <code>test.sh</code> by itself fails because it isn't in the PATH. But <code>./test.sh</code> works because it is an explicit path.</p>
1012 ` ,
1013 },
1014 advanced: {
1015 title: 'Advanced Scripting',
1016 content: `\
1017 <p>Here is a more advanced script called <code>undocx.sh</code> that unpacks a Word DOCX file.</p>
1018
1019 <code block>
1020 #!/bin/bash
1021
1022 set -e
1023
1024 if [ $# -ne 1 ]; then
1025 echo "usage: $0 filename"
1026 exit 1
1027 fi
1028
1029 FILE="$1"
1030 NEWDIR=$(basename $FILE .docx)
1031
1032 mkdir $NEWDIR
1033 unzip $FILE -d $NEWDIR
1034
1035 export XMLLINT_INDENT=$'\\t'
1036 for file in $(find $NEWDIR -name "*.xml" -o -name "*.rels"); do
1037 mv "$file" temp.xml
1038 xmllint --format temp.xml >"$file"
1039 done
1040 rm temp.xml
1041 </code>
1042
1043 <p>Bash is a full programming language containing all the usual features. Some commands in my script are well explained by <code>man</code>, but some are not. In particular, the documentation for <code>if</code> and <code>for</code> are poor. In cases like this, I suggest asking ChatGPT like this:
1044
1045 <code block>
1046 Please explain the Bash "if" statement.
1047 </code>
1048
1049 <code block>
1050 Please explain the Bash "for" statement.
1051 </code>
1052
1053 <p>ChatGPT knows Bash well. I trust ChatGPT to explain details but not to explain core concepts. You can also try Google, but ChatGPT is better than modern programmers.</p>
1054 ` ,
1055 },
1056 conclusion: {
1057 title: 'Conclusion',
1058 content: `\
1059 <p>At least 90% of your usage of Bash will be simple commands that you enter in the terminal. Try to use Bash as much as possible instead of using the GUI so that you get practice using it. Unless you become system administrator, you won't use advanced scripting much. But with a solid understanding of the core basics, you should be able to figure out how to read or write advanced scripts when needed.</p>
1060 ` ,
1061 },
1062 };
1063 </script>
1064 </head>
1065 <body>
1066 <script> header() </script>
1067 <div content>
1068 <h1><a href="bash.html">Bash Tutorial</a></h1>
1069 <hr>
1070 <h2>Contents</h2>
1071 <div toc>
1072 <script> showToc(content) </script>
1073 </div>
1074 <hr>
1075 <script> showContent(content,2) </script>
1076 </div>
1077 </body>
1078 </html>