comparison 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
comparison
equal deleted inserted replaced
48:889e3c2d2699 49:3057adc065f3
11 local content = { 11 local content = {
12 intro = { 12 intro = {
13 title = [[Introduction]] 13 title = [[Introduction]]
14 content = function() 14 content = function()
15 %> 15 %>
16 <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> 16 <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>
17 17
18 <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> 18 <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>
19 <% 19 <%
20 end 20 end
21 } 21 }
22 access = { 22 access = {
23 title = [[Running Bash]] 23 title = [[Running Bash]]
69 how are you 69 how are you
70 ~ $ echo bye 70 ~ $ echo bye
71 bye 71 bye
72 </code> 72 </code>
73 73
74 <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> 74 <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>
75 75
76 <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> 76 <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>
77 77
78 <code block> 78 <code block>
79 ~ $ echo how are you 79 ~ $ echo how are you
80 how are you 80 how are you
81 </code> 81 </code>
82 82
83 <p><code>echo</code> just returns the arguments separated by one space.</p> 83 <p><code>echo</code> just returns the arguments separated by one space.</p>
84
85 <code block>
86 ~ $ echo one; echo two
87 one
88 two
89 </code>
90
91 <p>You can put multiple commands on one line separated by a <code>;</code>.</p>
84 <% 92 <%
85 end 93 end
86 } 94 }
87 man = { 95 man = {
88 title = [[The "man" Command]] 96 title = [[The "man" Command]]
91 <p>Enter:</p> 99 <p>Enter:</p>
92 <code block> 100 <code block>
93 ~ $ man echo 101 ~ $ man echo
94 </code> 102 </code>
95 103
96 <p>You should get:</p> 104 <p>You should get something like:</p>
97 105
98 <code block> 106 <code block>
99 107
100 ECHO(1) BSD General Commands Manual ECHO(1) 108 ECHO(1) BSD General Commands Manual ECHO(1)
101 109
158 /home/fschmidt 166 /home/fschmidt
159 ~ $ explorer . 167 ~ $ explorer .
160 ~ $ 168 ~ $
161 </code> 169 </code>
162 170
163 <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> 171 <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>
164 172
165 <p>Continuing on my Mac:</p> 173 <p>Continuing on my Mac:</p>
166 174
167 <code block> 175 <code block>
168 ~ $ mkdir learn 176 ~ $ mkdir learn
329 ~/learn/dir1 $ pwd 337 ~/learn/dir1 $ pwd
330 /Users/fschmidt/learn/dir1 338 /Users/fschmidt/learn/dir1
331 ~/learn/dir1 $ cd 339 ~/learn/dir1 $ cd
332 ~ $ pwd 340 ~ $ pwd
333 /Users/fschmidt 341 /Users/fschmidt
334 ~ $ cd ~/learn 342 ~ $ cd ~/learn
335 ~/learn $ pwd 343 ~/learn $ pwd
336 /Users/fschmidt/learn 344 /Users/fschmidt/learn
337 ~/learn $ echo ~ 345 ~/learn $ echo ~
338 /Users/fschmidt 346 /Users/fschmidt
339 ~/learn $ echo . 347 ~/learn $ echo .
638 <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> 646 <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>
639 647
640 <% 648 <%
641 end 649 end
642 } 650 }
643 ctrl_c = {
644 title = [[Control+c]]
645 content = function()
646 %>
647 <code block>
648 ~/learn $ sleep 3
649 ~/learn $ sleep 30
650 ^C
651 ~/learn $
652 </code>
653
654 <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>
655 <%
656 end
657 }
658 find = { 651 find = {
659 title = [[The "find" Command]] 652 title = [[The "find" Command]]
660 content = function() 653 content = function()
661 %> 654 %>
662 <code block> 655 <code block>
701 this is another test 694 this is another test
702 ~/learn $ echo 'another line' >>test.txt 695 ~/learn $ echo 'another line' >>test.txt
703 ~/learn $ cat test.txt 696 ~/learn $ cat test.txt
704 this is another test 697 this is another test
705 another line 698 another line
699 ~/learn $ cat &lt;test.txt
700 this is another test
701 another line
702 ~/learn $ cat &lt;&lt;End >test.txt
703 > I am typing this
704 > and this
705 > End
706 ~/learn $ cat test.txt
707 I am typing this
708 and this
709 ~/learn $ (echo one; echo two) >test.txt
710 ~/learn $ cat test.txt
711 one
712 two
713 </code>
714
715 <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>
716
717 <code block>
706 ~/learn $ ls >ls.txt 718 ~/learn $ ls >ls.txt
707 ~/learn $ cat ls.txt 719 ~/learn $ cat ls.txt
708 dir1 720 dir1
709 file1 721 file1
710 file2 722 file2
740 file1 752 file1
741 file2 753 file2
742 file3 754 file3
743 </code> 755 </code>
744 756
745 <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> 757 <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>
746 758
747 <code block> 759 <code block>
748 ~/learn $ find . -type f | wc -l 760 ~/learn $ find . -type f | wc -l
749 6 761 6
750 </code> 762 </code>
751 763
752 <p>There are 6 files in <code>learn</code>. Use <code>man</code> to figure out how this works.</p> 764 <p>There are 6 files in <code>learn</code>. Use <code>man</code> to figure out how this works.</p>
765 <%
766 end
767 }
768 ctrl = {
769 title = [[Control Keys]]
770 content = function()
771 %>
772 <code block>
773 ~/learn $ sleep 3
774 ~/learn $ sleep 30
775 ^C
776 ~/learn $
777 </code>
778
779 <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>
780
781 <code block>
782 ~/learn $ wc
783 I am typing this
784 and this
785 now I will end my input with control+d
786 3 14 65
787 ~/learn $ wc
788 this time I will use control+c to break out
789 ^C
790 ~/learn $
791 </code>
792
793 <p>Control+d means end of input.</p>
753 <% 794 <%
754 end 795 end
755 } 796 }
756 subst = { 797 subst = {
757 title = [[Command Substitution]] 798 title = [[Command Substitution]]
775 816
776 <p>The files in <code>learn</code> contain a total of 86 bytes. Use <code>man</code> to figure out how this works.</p> 817 <p>The files in <code>learn</code> contain a total of 86 bytes. Use <code>man</code> to figure out how this works.</p>
777 <% 818 <%
778 end 819 end
779 } 820 }
780 later = { 821 ampersand = {
781 title = [[placeholder]] 822 title = [[Ampersand]]
782 content = function() 823 content = function()
783 %> 824 %>
784 <p>later</p> 825 <code block>
826 ~/learn $ (sleep 5; echo done) &
827 [1] 10080
828 ~/learn $ echo waiting
829 waiting
830 ~/learn $ done
831
832 [1]+ Done ( sleep 5; echo done )
833 ~/learn $
834 </code>
835
836 <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>
837
838 <p>This is useful where you do not want to wait for a command to finish. Consider this on Windows:</p>
839
840 <code block>
841 ~ $ notepad
842 </code>
843
844 <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:
845
846 <code block>
847 ~ $ notepad &
848 [1] 2010
849 ~ $
850 </code>
851
852 <p>Now Notepad will run and you can continue using Bash.</p>
853 <%
854 end
855 }
856 scripts = {
857 title = [[Shell Scripts]]
858 content = function()
859 %>
860 <p>Make a file called <code>test.sh</code> containing the following:</p>
861
862 <code block>
863 echo this is a shell script
864 </code>
865
866 <p>Now from Bash:</p>
867
868 <code block>
869 ~/learn $ cat test.sh
870 echo this is a shell script
871 ~/learn $ ./test.sh
872 -bash: ./test.sh: Permission denied
873 ~/learn $ ls -F test.sh
874 test.sh
875 ~/learn $ chmod +x test.sh
876 ~/learn $ ls -F test.sh
877 test.sh*
878 ~/learn $ ./test.sh
879 this is a shell script
880 ~/learn $
881 </code>
882
883 <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>
884
885 <code block>
886 ~/learn $ # edit test.sh
887 ~/learn $ cat test.sh
888 nonsense
889 echo this is a shell script
890 ~/learn $ ./test.sh
891 ./test.sh: line 1: nonsense: command not found
892 this is a shell script
893 ~/learn $ # edit test.sh
894 ~/learn $ cat test.sh
895 set -e
896 nonsense
897 echo this is a shell script
898 ~/learn $ ./test.sh
899 ./test.sh: line 2: nonsense: command not found
900 ~/learn $
901 </code>
902
903 <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>
904
905 <code block>
906 ~/learn $ X=some
907 ~/learn $ echo $X
908 some
909 ~/learn $ echo $Xthing
910
911 ~/learn $ echo ${X}thing
912 something
913 ~/learn $ # edit test.sh
914 ~/learn $ cat test.sh
915 echo "\$* = $*"
916 echo "\$# = $#"
917 echo "\$0 = $0"
918 echo "\$1 = $1"
919 echo "\$2 = $2"
920 echo "\$3 = $3"
921 echo "\$4 = $4"
922 echo "\$14 = $14"
923 echo "\${14} = ${14}"
924 echo "\$@ = $@"
925 ./count.sh "$*"
926 ./count.sh "$@"
927 ~/learn $ ./test.sh a b "c d"
928 $* = a b c d
929 $# = 3
930 $0 = ./test.sh
931 $1 = a
932 $2 = b
933 $3 = c d
934 $4 =
935 $14 = a4
936 ${14} =
937 $@ = a b c d
938 1
939 3
940 ~/learn $ cat count.sh
941 echo $#
942 ~/learn $
943 </code>
944
945 <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>
946 <%
947 end
948 }
949 vars_and_scripts = {
950 title = [[Variables and Scripts]]
951 content = function()
952 %>
953 <code block>
954 ~/learn $ X=value
955 ~/learn $ echo $X
956 value
957 ~/learn $ # edit test.sh
958 ~/learn $ cat test.sh
959 echo "\$X = $X"
960 ~/learn $ ./test.sh
961 $X =
962 ~/learn $ export X
963 ~/learn $ ./test.sh
964 $X = value
965 </code>
966
967 <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>
968
969 <code block>
970 ~/learn $ X=terminal
971 ~/learn $ echo $X
972 terminal
973 ~/learn $ # edit test.sh
974 ~/learn $ cat test.sh
975 X=script
976 export X
977 ~/learn $ ./test.sh
978 ~/learn $ echo $X
979 terminal
980 ~/learn $ . test.sh
981 ~/learn $ echo $X
982 script
983 </code>
984
985 <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>
986
987 <code block>
988 ~/learn $ pwd
989 /Users/fschmidt/learn
990 ~/learn $ # edit test.sh
991 ~/learn $ cat test.sh
992 cd ~
993 ~/learn $ ./test.sh
994 ~/learn $ pwd
995 /Users/fschmidt/learn
996 ~/learn $ . test.sh
997 ~ $ pwd
998 /Users/fschmidt
999 ~ $ cd learn
1000 ~/learn $
1001 </code>
1002
1003 <p>This illustrates the difference between <code>./script</code> and <code>. script</code>.</p>
1004
1005 <%
1006 end
1007 }
1008 your_scripts = {
1009 title = [[Your Scripts]]
1010 content = function()
1011 %>
1012 <code block>
1013 ~/learn $ echo $PATH
1014 /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
1015 ~/learn $ echo ~/Dropbox/bin
1016 /Users/fschmidt/Dropbox/bin
1017 ~/learn $ ls -F ~/Dropbox/bin/e
1018 /Users/fschmidt/Dropbox/bin/e*
1019 ~/learn $ cat ~/Dropbox/bin/e
1020 open -a 'Sublime Text' $*
1021 ~/learn $ e test.sh
1022 ~/learn $
1023 </code>
1024
1025 <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>
1026 <%
1027 end
1028 }
1029 advanced = {
1030 title = [[Advanced Scripting]]
1031 content = function()
1032 %>
1033 <p>Here is a more advanced script called <code>undocx.sh</code> that unpacks a Word DOCX file.</p>
1034
1035 <code block>
1036 #!/bin/bash
1037
1038 set -e
1039
1040 if [ $# -ne 1 ]; then
1041 echo "usage: $0 filename"
1042 exit 1
1043 fi
1044
1045 FILE="$1"
1046 NEWDIR=$(basename $FILE .docx)
1047
1048 mkdir $NEWDIR
1049 unzip $FILE -d $NEWDIR
1050
1051 export XMLLINT_INDENT=$'\t'
1052 for file in $(find $NEWDIR -name "*.xml" -o -name "*.rels"); do
1053 mv "$file" temp.xml
1054 xmllint --format temp.xml >"$file"
1055 done
1056 rm temp.xml
1057 </code>
1058
1059 <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:
1060
1061 <code block>
1062 Please explain the Bash "if" statement.
1063 </code>
1064
1065 <code block>
1066 Please explain the Bash "for" statement.
1067 </code>
1068
1069 <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>
1070 <%
1071 end
1072 }
1073 conclusion = {
1074 title = [[Conclusion]]
1075 content = function()
1076 %>
1077 <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>
785 <% 1078 <%
786 end 1079 end
787 } 1080 }
788 } 1081 }
789 1082