diff src/learn_bash.html.luan @ 49:3057adc065f3

finish learn_bash
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 07 Jan 2024 20:11:00 -0700
parents 889e3c2d2699
children 0bb5fa9b94cc
line wrap: on
line diff
--- a/src/learn_bash.html.luan	Sun Jan 07 02:34:44 2024 -0700
+++ b/src/learn_bash.html.luan	Sun Jan 07 20:11:00 2024 -0700
@@ -13,9 +13,9 @@
 		title = [[Introduction]]
 		content = function()
 %>
-<p>I really don't want to write this tutorial, but all the existing <a href="bash.html">Bash</a> 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 crap.  And they don't explain core concepts.  So I have no choice but to write this damn thing for my <a href="http://localhost:8080/learn.html#bash">Learn Reactionary Programming</a> Bash lesson.</p>
+<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 crap.  And they don't explain core concepts.  So I have no choice but to write this damn thing for my <a href="http://localhost:8080/learn.html#bash">Learn Reactionary Programming</a> Bash lesson.</p>
 
-<p>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>
+<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>
 <%
 		end
 	}
@@ -71,7 +71,7 @@
 bye
 </code>
 
-<p>The <code>echo</code> command just echos 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>
+<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>
 
 <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>
 
@@ -81,6 +81,14 @@
 </code>
 
 <p><code>echo</code> just returns the arguments separated by one space.</p>
+
+<code block>
+~ $ echo one; echo two
+one
+two
+</code>
+
+<p>You can put multiple commands on one line separated by a <code>;</code>.</p>
 <%
 		end
 	}
@@ -93,7 +101,7 @@
 ~ $ man echo
 </code>
 
-<p>You should get:</p>
+<p>You should get something like:</p>
 
 <code block>
 
@@ -160,7 +168,7 @@
 ~ $
 </code>
 
-<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.<p>
+<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>
 
 <p>Continuing on my Mac:</p>
 
@@ -331,7 +339,7 @@
 ~/learn/dir1 $ cd
 ~ $ pwd
 /Users/fschmidt
-~ $  cd ~/learn
+~ $ cd ~/learn
 ~/learn $ pwd
 /Users/fschmidt/learn
 ~/learn $ echo ~
@@ -640,21 +648,6 @@
 <%
 		end
 	}
-	ctrl_c = {
-		title = [[Control+c]]
-		content = function()
-%>
-<code block>
-~/learn $ sleep 3
-~/learn $ sleep 30
-^C
-~/learn $ 
-</code>
-
-<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>
-<%
-		end
-	}
 	find = {
 		title = [[The "find" Command]]
 		content = function()
@@ -703,6 +696,25 @@
 ~/learn $ cat test.txt 
 this is another test
 another line
+~/learn $ cat &lt;test.txt 
+this is another test
+another line
+~/learn $ cat &lt;&lt;End >test.txt 
+> I am typing this
+> and this
+> End
+~/learn $ cat test.txt 
+I am typing this
+and this
+~/learn $ (echo one; echo two) >test.txt 
+~/learn $ cat test.txt 
+one
+two
+</code>
+
+<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>
+
+<code block>
 ~/learn $ ls >ls.txt
 ~/learn $ cat ls.txt
 dir1
@@ -742,7 +754,7 @@
 file3
 </code>
 
-<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 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>2>file</code> sends standard error to <code>file</code>.  <code>2>&1</code> sends standard error to standard output.  <code>|</code> sends standard output of the previous command to standard input of the following command.  We haven't used standard input before, but <code>tee file</code> reads standard input and then writes it to both standard output and to <code>file</code>.  And for completeness, <code>&lt;file</code> reads standard input from <code>file</code> even though I haven't shown an example of this.</p>
+<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>
 
 <code block>
 ~/learn $ find . -type f | wc -l
@@ -753,6 +765,35 @@
 <%
 		end
 	}
+	ctrl = {
+		title = [[Control Keys]]
+		content = function()
+%>
+<code block>
+~/learn $ sleep 3
+~/learn $ sleep 30
+^C
+~/learn $ 
+</code>
+
+<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>
+
+<code block>
+~/learn $ wc
+I am typing this
+and this
+now I will end my input with control+d
+       3      14      65
+~/learn $ wc
+this time I will use control+c to break out
+^C
+~/learn $ 
+</code>
+
+<p>Control+d means end of input.</p>
+<%
+		end
+	}
 	subst = {
 		title = [[Command Substitution]]
 		content = function()
@@ -777,11 +818,263 @@
 <%
 		end
 	}
-	later = {
-		title = [[placeholder]]
+	ampersand = {
+		title = [[Ampersand]]
+		content = function()
+%>
+<code block>
+~/learn $ (sleep 5; echo done) &
+[1] 10080
+~/learn $ echo waiting
+waiting
+~/learn $ done
+
+[1]+  Done                    ( sleep 5; echo done )
+~/learn $ 
+</code>
+
+<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>
+
+<p>This is useful where you do not want to wait for a command to finish.  Consider this on Windows:</p>
+
+<code block>
+~ $ notepad
+</code>
+
+<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:
+
+<code block>
+~ $ notepad &
+[1] 2010
+~ $
+</code>
+
+<p>Now Notepad will run and you can continue using Bash.</p>
+<%
+		end
+	}
+	scripts = {
+		title = [[Shell Scripts]]
 		content = function()
 %>
-<p>later</p>
+<p>Make a file called <code>test.sh</code> containing the following:</p>
+
+<code block>
+echo this is a shell script
+</code>
+
+<p>Now from Bash:</p>
+
+<code block>
+~/learn $ cat test.sh 
+echo this is a shell script
+~/learn $ ./test.sh
+-bash: ./test.sh: Permission denied
+~/learn $ ls -F test.sh 
+test.sh
+~/learn $ chmod +x test.sh 
+~/learn $ ls -F test.sh 
+test.sh*
+~/learn $ ./test.sh 
+this is a shell script
+~/learn $ 
+</code>
+
+<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>
+
+<code block>
+~/learn $ # edit test.sh
+~/learn $ cat test.sh 
+nonsense
+echo this is a shell script
+~/learn $ ./test.sh 
+./test.sh: line 1: nonsense: command not found
+this is a shell script
+~/learn $ # edit test.sh
+~/learn $ cat test.sh 
+set -e
+nonsense
+echo this is a shell script
+~/learn $ ./test.sh 
+./test.sh: line 2: nonsense: command not found
+~/learn $ 
+</code>
+
+<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>
+
+<code block>
+~/learn $ X=some
+~/learn $ echo $X
+some
+~/learn $ echo $Xthing
+
+~/learn $ echo ${X}thing
+something
+~/learn $ # edit test.sh
+~/learn $ cat test.sh
+echo "\$* = $*"
+echo "\$# = $#"
+echo "\$0 = $0"
+echo "\$1 = $1"
+echo "\$2 = $2"
+echo "\$3 = $3"
+echo "\$4 = $4"
+echo "\$14 = $14"
+echo "\${14} = ${14}"
+echo "\$@ = $@"
+./count.sh "$*"
+./count.sh "$@"
+~/learn $ ./test.sh a b "c d"
+$* = a b c d
+$# = 3
+$0 = ./test.sh
+$1 = a
+$2 = b
+$3 = c d
+$4 = 
+$14 = a4
+${14} = 
+$@ = a b c d
+1
+3
+~/learn $ cat count.sh 
+echo $#
+~/learn $ 
+</code>
+
+<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>
+<%
+		end
+	}
+	vars_and_scripts = {
+		title = [[Variables and Scripts]]
+		content = function()
+%>
+<code block>
+~/learn $ X=value
+~/learn $ echo $X
+value
+~/learn $ # edit test.sh
+~/learn $ cat test.sh 
+echo "\$X = $X"
+~/learn $ ./test.sh 
+$X = 
+~/learn $ export X
+~/learn $ ./test.sh 
+$X = value
+</code>
+
+<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>
+
+<code block>
+~/learn $ X=terminal
+~/learn $ echo $X
+terminal
+~/learn $ # edit test.sh
+~/learn $ cat test.sh 
+X=script
+export X
+~/learn $ ./test.sh 
+~/learn $ echo $X
+terminal
+~/learn $ . test.sh 
+~/learn $ echo $X
+script
+</code>
+
+<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>
+
+<code block>
+~/learn $ pwd
+/Users/fschmidt/learn
+~/learn $ # edit test.sh
+~/learn $ cat test.sh 
+cd ~
+~/learn $ ./test.sh 
+~/learn $ pwd
+/Users/fschmidt/learn
+~/learn $ . test.sh 
+~ $ pwd
+/Users/fschmidt
+~ $ cd learn
+~/learn $ 
+</code>
+
+<p>This illustrates the difference between <code>./script</code> and <code>. script</code>.</p>
+
+<%
+		end
+	}
+	your_scripts = {
+		title = [[Your Scripts]]
+		content = function()
+%>
+<code block>
+~/learn $ echo $PATH
+/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
+~/learn $ echo ~/Dropbox/bin
+/Users/fschmidt/Dropbox/bin
+~/learn $ ls -F ~/Dropbox/bin/e 
+/Users/fschmidt/Dropbox/bin/e*
+~/learn $ cat ~/Dropbox/bin/e 
+open -a 'Sublime Text' $*
+~/learn $ e test.sh 
+~/learn $ 
+</code>
+
+<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 file.  So <code>e test.sh</code> lets me edit <code>test.sh</code> from the command line.</p>
+<%
+		end
+	}
+	advanced = {
+		title = [[Advanced Scripting]]
+		content = function()
+%>
+<p>Here is a more advanced script called <code>undocx.sh</code> that unpacks a Word DOCX file.</p>
+
+<code block>
+#!/bin/bash
+
+set -e
+
+if [ $# -ne 1 ]; then
+	echo "usage: $0 filename"
+	exit 1
+fi
+
+FILE="$1"
+NEWDIR=$(basename $FILE .docx)
+
+mkdir $NEWDIR
+unzip $FILE -d $NEWDIR
+
+export XMLLINT_INDENT=$'\t'
+for file in $(find $NEWDIR -name "*.xml" -o -name "*.rels"); do
+	mv "$file" temp.xml
+	xmllint --format temp.xml >"$file"
+done
+rm temp.xml
+</code>
+
+<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:
+
+<code block>
+Please explain the Bash "if" statement.
+</code>
+
+<code block>
+Please explain the Bash "for" statement.
+</code>
+
+<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>
+<%
+		end
+	}
+	conclusion = {
+		title = [[Conclusion]]
+		content = function()
+%>
+<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>
 <%
 		end
 	}