├── foo ├── files │ ├── bar.jpg │ ├── bar.txt │ ├── baz.jpg │ ├── baz.txt │ ├── foo.jpg │ └── foo.txt ├── done │ ├── file.txt │ ├── arithmetic-expression │ ├── loop │ ├── read-input │ ├── parameter-expansion │ ├── braces-numeric │ ├── for-loops │ ├── 256-colors │ ├── say-bye │ ├── complex.csv │ ├── conditionals │ ├── curly-brace-expansion │ ├── simple.csv │ ├── check-terminal │ ├── char-by-char │ ├── hello │ ├── indexed-arrays │ ├── curly-vs-parens │ ├── say-hi │ ├── command-substitution │ ├── process-substitution │ ├── case │ ├── associative-arrays │ ├── greeter │ ├── move-cursor │ └── return-codes ├── lib │ └── greetings └── bashrc ├── .gitignore ├── tools ├── .gitignore ├── assets │ └── logo.png ├── fonts │ ├── FiraMono-Bold.ttf │ ├── FiraMono-Medium.ttf │ ├── FiraMono-Regular.ttf │ └── OFL.txt ├── make-all-metadata-images ├── finalcut-xml-to-chapters └── make-metadata-image ├── notes ├── 12-00-test-vs-bracket │ ├── foo.jpg │ ├── test.jpg │ ├── 01-simple-[ │ ├── 00-simple-test │ ├── 02-simple-[[ │ ├── 03-patterns │ ├── 04-pattern-fail │ ├── 06-numbers │ ├── 08-number-fix-2 │ ├── 05-disable-pattern │ ├── 07-number-fix-1 │ └── readme.md ├── 16-00-pitfall-ls-list │ ├── files │ │ ├── a │ │ ├── b │ │ ├── c │ │ ├── f g │ │ └── d e │ ├── bad │ ├── good │ └── readme.md ├── 11-02-regex │ ├── readme.md │ ├── images │ │ ├── fun family party - 2022-10-31.jpg │ │ └── not fun enemy party - 2022-11-27.jpg │ ├── want.txt │ ├── 01-cringe │ ├── 01-fine │ ├── 00-simple │ ├── 02-based │ └── 02-good ├── 03-06-what-we-have │ ├── readme.md │ ├── 01-case │ └── 00-script ├── 04-07-what-we-have │ ├── readme.md │ ├── 00-cmds-and-math │ └── 01-arrays ├── 07-03-what-we-have │ ├── readme.md │ ├── 00-simple │ └── 01-better ├── 17-00-forkbomb │ ├── bomb.txt │ ├── diagram.txt │ └── readme.md ├── 05-00-cut-tr │ ├── readme.md │ ├── complex.csv │ └── simple.csv ├── 04-00-case-statements │ ├── readme.md │ ├── 01-simple │ ├── 02-always-fallthrough │ ├── 03-try-again │ └── 00-case ├── 11-03-mapfile-readarray │ ├── data.txt │ ├── readme.md │ ├── 01-mapfile │ ├── 02-mapfile-trim │ ├── 03-mapfile-limit │ ├── 00-while-read │ └── 04-mapfile-callback ├── 06-00-bash-arguments │ ├── 01-syntax-error │ ├── 02-undefined │ ├── 00-simple-script │ └── readme.md ├── 13-00-trap-signals │ ├── readme.md │ ├── 00-simple-exit │ ├── 04-trap-info │ ├── 01-exit-code │ ├── 03-trap-debug │ └── 02-exit-override ├── 13-01-named-pipes │ ├── 00-basic-reader │ ├── 02-client-writer │ ├── 01-forever-read │ ├── 03-all-in-one │ └── readme.md ├── 01-02-hidden-files │ └── readme.md ├── 14-01-cursor-commands │ ├── 00-move-cursor │ └── readme.md ├── 06-02-timing │ └── readme.md ├── 16-02-ansi-length │ └── readme.md ├── 14-02-isatty │ └── readme.md ├── 12-01-special-strings │ └── readme.md ├── 06-01-pipestatus │ └── readme.md ├── 15-02-readline-and-such │ └── readme.md ├── 16-01-aliases-with-arguments │ └── readme.md ├── 02-02-more-variables │ └── readme.md ├── new-section ├── 09-00-basic-globbing │ └── readme.md ├── format-titles ├── 01-04-pager │ └── readme.md ├── 02-01-programs-and-commands │ └── readme.md ├── 02-04-file-permissions │ └── readme.md ├── 04-03-IFS-variable │ └── readme.md ├── 07-00-sourcing │ └── readme.md ├── concat-readmes ├── 10-01-braces-and-globbing │ └── readme.md ├── 01-00-terminal-and-finder │ └── readme.md ├── 01-03-searching-in-files │ └── readme.md ├── 11-01-date-formatting │ └── readme.md ├── 03-04-for-loops │ └── readme.md ├── 05-02-find-cmd │ └── readme.md ├── 02-03-vim-crash-course │ └── readme.md ├── 07-01-curlies-vs-parens │ └── readme.md ├── 09-01-extended-globbing │ └── readme.md ├── 10-00-brace-string-expansion │ └── readme.md ├── 00-00-intro │ └── readme.md ├── 03-05-io │ └── readme.md ├── 02-00-man-pages │ └── readme.md ├── 04-05-arithmetic-expression │ └── readme.md ├── 03-00-actually-scripting │ └── readme.md ├── 03-01-taking-input │ └── readme.md ├── 04-02-associative-arrays │ └── readme.md ├── 10-02-braces-numeric │ └── readme.md ├── 04-06-process-substitution │ └── readme.md ├── 05-01-sed-awk-grep │ └── readme.md ├── 07-02-return-vs-output │ └── readme.md ├── 15-01-customize-bashrc │ └── readme.md ├── 01-01-file-manipulation │ └── readme.md ├── 03-02-functions │ └── readme.md ├── 04-04-command-substitution │ └── readme.md ├── 14-00-color │ └── readme.md ├── 03-03-conditionals │ └── readme.md ├── 04-01-indexed-arrays │ └── readme.md ├── 08-01-array-expansion-chars │ └── readme.md ├── 09-02-glob-options │ └── readme.md ├── 11-00-printf │ └── readme.md ├── 15-00-interactive-ps1 │ └── readme.md └── 08-00-parameter-expansion │ └── readme.md ├── website ├── .gitignore ├── Makefile ├── make-curl-website ├── download.html ├── style.css └── index.html ├── .github └── FUNDING.yml └── README.md /foo/files/bar.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /foo/files/bar.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /foo/files/baz.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /foo/files/baz.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /foo/files/foo.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /foo/files/foo.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/foo.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/test.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notes/16-00-pitfall-ls-list/files/a: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notes/16-00-pitfall-ls-list/files/b: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notes/16-00-pitfall-ls-list/files/c: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notes/16-00-pitfall-ls-list/files/f g: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /foo/done/file.txt: -------------------------------------------------------------------------------- 1 | foo 2 | bar 3 | baz 4 | -------------------------------------------------------------------------------- /notes/16-00-pitfall-ls-list/files/d e: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | favicon.ico 2 | index.txt 3 | -------------------------------------------------------------------------------- /notes/11-02-regex/readme.md: -------------------------------------------------------------------------------- 1 | # Regular Expressions 2 | -------------------------------------------------------------------------------- /notes/11-02-regex/images/fun family party - 2022-10-31.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notes/11-02-regex/images/not fun enemy party - 2022-11-27.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notes/03-06-what-we-have/readme.md: -------------------------------------------------------------------------------- 1 | # Chapter 3 Recap 2 | 3 | (nothing) 4 | -------------------------------------------------------------------------------- /notes/04-07-what-we-have/readme.md: -------------------------------------------------------------------------------- 1 | # Chapter 4 Recap 2 | 3 | (nothing) 4 | -------------------------------------------------------------------------------- /notes/07-03-what-we-have/readme.md: -------------------------------------------------------------------------------- 1 | # Chapter 7 Recap 2 | 3 | (nothing) 4 | -------------------------------------------------------------------------------- /notes/17-00-forkbomb/bomb.txt: -------------------------------------------------------------------------------- 1 | ## DON'T RUN THIS 2 | 3 | :(){ :|:& };: 4 | -------------------------------------------------------------------------------- /notes/05-00-cut-tr/readme.md: -------------------------------------------------------------------------------- 1 | # cut and tr 2 | 3 | use cut tr 4 | use head and tail 5 | -------------------------------------------------------------------------------- /notes/04-00-case-statements/readme.md: -------------------------------------------------------------------------------- 1 | # Case Statements 2 | 3 | condense 00 the 2 cases 4 | -------------------------------------------------------------------------------- /notes/11-02-regex/want.txt: -------------------------------------------------------------------------------- 1 | 2022-10-31: fun family party 2 | 2022-11-27: not fun enemy party 3 | -------------------------------------------------------------------------------- /tools/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahamas10/bash-course/HEAD/tools/assets/logo.png -------------------------------------------------------------------------------- /foo/done/arithmetic-expression: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | a=08 4 | echo "$a" 5 | echo $(( 10#$a )) 6 | -------------------------------------------------------------------------------- /foo/done/loop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for thing in "$@"; do 4 | echo "thing is $thing" 5 | done 6 | -------------------------------------------------------------------------------- /foo/done/read-input: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while read -r line; do 4 | echo "we read line: $line" 5 | done 6 | -------------------------------------------------------------------------------- /notes/11-03-mapfile-readarray/data.txt: -------------------------------------------------------------------------------- 1 | you what's up everyone 2 | my name's dave 3 | and you suck at programming 4 | -------------------------------------------------------------------------------- /tools/fonts/FiraMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahamas10/bash-course/HEAD/tools/fonts/FiraMono-Bold.ttf -------------------------------------------------------------------------------- /foo/done/parameter-expansion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | name=${1?} 4 | 5 | echo "hello $name!" 6 | 7 | echo 'done' 8 | -------------------------------------------------------------------------------- /notes/16-00-pitfall-ls-list/bad: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for f in `ls files`; do 4 | echo "file is $f" 5 | done 6 | -------------------------------------------------------------------------------- /notes/16-00-pitfall-ls-list/good: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for f in ./files/*; do 4 | echo "file is $f" 5 | done 6 | -------------------------------------------------------------------------------- /tools/fonts/FiraMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahamas10/bash-course/HEAD/tools/fonts/FiraMono-Medium.ttf -------------------------------------------------------------------------------- /tools/fonts/FiraMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahamas10/bash-course/HEAD/tools/fonts/FiraMono-Regular.ttf -------------------------------------------------------------------------------- /foo/done/braces-numeric: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | max=4 4 | for ((i = 0; i <= max; i += 2)); do 5 | echo "$i" 6 | done 7 | -------------------------------------------------------------------------------- /foo/done/for-loops: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | max=5 4 | for ((i = 0; i < max; i++)); do 5 | echo "thing is $i" 6 | done 7 | -------------------------------------------------------------------------------- /notes/07-03-what-we-have/00-simple: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # get shell 4 | cat /etc/passwd | grep dave | cut -d : -f 7 5 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/01-simple-[: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -f foo.jpg ]; then 4 | echo foo.jpg exists 5 | fi 6 | -------------------------------------------------------------------------------- /notes/11-03-mapfile-readarray/readme.md: -------------------------------------------------------------------------------- 1 | # Using mapfile 2 | 3 | mapfile AND readarray 4 | 5 | "good news - they are the same" 6 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/00-simple-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if test -f foo.jpg; then 4 | echo foo.jpg exists 5 | fi 6 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/02-simple-[[: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -f foo.jpg ]]; then 4 | echo foo.jpg exists 5 | fi 6 | -------------------------------------------------------------------------------- /foo/done/256-colors: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for i in {0..255}; do 4 | printf '\e[38;5;%dmColor %d\e[0m\n' "$i" "$i" 5 | done 6 | -------------------------------------------------------------------------------- /foo/done/say-bye: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | . ./lib/greetings || exit 1 4 | 5 | goodbye dave 6 | goodbye john 7 | goodbye buddy 8 | -------------------------------------------------------------------------------- /notes/06-00-bash-arguments/01-syntax-error: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo hello 4 | 5 | if true; then 6 | echo goodbye 7 | fii 8 | -------------------------------------------------------------------------------- /notes/13-00-trap-signals/readme.md: -------------------------------------------------------------------------------- 1 | # Trap Signals 2 | 3 | ``` 4 | help trap 5 | trap -l 6 | ``` 7 | 8 | 03 can be done with bash -x 9 | -------------------------------------------------------------------------------- /notes/16-00-pitfall-ls-list/readme.md: -------------------------------------------------------------------------------- 1 | # Pitfall: ls 2 | 3 | mention it was my first video 4 | 5 | 6 | at the end explain "isatty" 7 | -------------------------------------------------------------------------------- /notes/13-01-named-pipes/00-basic-reader: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while read -r line; do 4 | echo "[read line] $line" 5 | done < pipe 6 | -------------------------------------------------------------------------------- /notes/01-02-hidden-files/readme.md: -------------------------------------------------------------------------------- 1 | # Hidden Files 2 | 3 | - `touch .lesson.txt` 4 | - `ls` 5 | - don't show up in finder 6 | - `ls -a` 7 | - `ls -l` 8 | -------------------------------------------------------------------------------- /foo/done/complex.csv: -------------------------------------------------------------------------------- 1 | name,age,location 2 | Alice,30,"New York, NY" 3 | Bob,25,"Chicago, IL" 4 | Charlie,35,"Boston, MA" 5 | Dana,28,"San Francisco, CA" 6 | -------------------------------------------------------------------------------- /notes/11-03-mapfile-readarray/01-mapfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mapfile array < data.txt 4 | 5 | # print array 6 | printf '<%s>\n' "${array[@]}" 7 | -------------------------------------------------------------------------------- /foo/done/conditionals: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if apt-get update; then 4 | echo "if statement was true" 5 | else 6 | echo "if statement was false" 7 | fi 8 | -------------------------------------------------------------------------------- /foo/done/curly-brace-expansion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | arr=(/etc/{foo,bar}/{1,2,3}.txt) 4 | 5 | for item in "${arr[@]}"; do 6 | echo "$item" 7 | done 8 | -------------------------------------------------------------------------------- /foo/done/simple.csv: -------------------------------------------------------------------------------- 1 | name,age,city,favorite_color 2 | Alice,30,New York,Blue 3 | Bob,25,Chicago,Green 4 | Charlie,35,Boston,Red 5 | Dana,28,San Francisco,Purple 6 | -------------------------------------------------------------------------------- /notes/06-00-bash-arguments/02-undefined: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | foo=1 4 | bar=2 5 | 6 | echo "foo is $foo" 7 | echo "bar is $bar" 8 | echo "baz is $baz" 9 | -------------------------------------------------------------------------------- /notes/11-03-mapfile-readarray/02-mapfile-trim: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mapfile -t array < data.txt 4 | 5 | # print array 6 | printf '<%s>\n' "${array[@]}" 7 | -------------------------------------------------------------------------------- /foo/done/check-terminal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -t 1 ]]; then 4 | red=$'\e[31m' 5 | else 6 | red='' 7 | fi 8 | 9 | echo "$red It worked" 10 | -------------------------------------------------------------------------------- /notes/05-00-cut-tr/complex.csv: -------------------------------------------------------------------------------- 1 | name,age,location 2 | Alice,30,"New York, NY" 3 | Bob,25,"Chicago, IL" 4 | Charlie,35,"Bosto, MA" 5 | Dana,28,"San Francisco, CA" 6 | -------------------------------------------------------------------------------- /notes/11-03-mapfile-readarray/03-mapfile-limit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mapfile -t -n 2 foo < data.txt 4 | 5 | # print array 6 | printf '<%s>\n' "${foo[@]}" 7 | -------------------------------------------------------------------------------- /notes/13-01-named-pipes/02-client-writer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | name=$1 4 | 5 | while true; do 6 | sleep 3 7 | echo "[client $name] hello" > pipe 8 | done 9 | -------------------------------------------------------------------------------- /foo/done/char-by-char: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | s='dave eddy' 4 | len=${#s} 5 | 6 | for ((i = 0; i < len; i++)); do 7 | c=${s:i:1} 8 | echo "$c" 9 | done 10 | -------------------------------------------------------------------------------- /foo/done/hello: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -n $1 ]]; then 4 | name=$1 5 | else 6 | read -p 'Enter your name: ' name 7 | fi 8 | 9 | echo "hello $name" 10 | -------------------------------------------------------------------------------- /notes/05-00-cut-tr/simple.csv: -------------------------------------------------------------------------------- 1 | name,age,city,favorite_color 2 | Alice,30,New York,Blue 3 | Bob,25,Chicago,Green 4 | Charlie,35,Boston,Red 5 | Dana,28,San Francisco,Purple 6 | -------------------------------------------------------------------------------- /notes/13-01-named-pipes/01-forever-read: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while true; do 4 | while read -r line; do 5 | echo "[read line] $line" 6 | done < pipe 7 | done 8 | -------------------------------------------------------------------------------- /foo/done/indexed-arrays: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | array=( 4 | foo 5 | bar 6 | baz 7 | 'hello world' 8 | ) 9 | 10 | IFS=, 11 | echo "array is: ${!array[*]}" 12 | -------------------------------------------------------------------------------- /notes/04-07-what-we-have/00-cmds-and-math: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # edit these 4 | name=$(whoami) 5 | number=$((2 + 2)) 6 | 7 | echo "name is $name, number is $number" 8 | -------------------------------------------------------------------------------- /notes/14-01-cursor-commands/00-move-cursor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #printf '\e[2J' 4 | #printf '\e[H' 5 | printf '\e7' 6 | printf '\e[20;20H' 7 | printf 'hello world' 8 | printf '\e8' 9 | -------------------------------------------------------------------------------- /foo/done/curly-vs-parens: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | . ./lib/greetings || exit 1 4 | 5 | name='buddy' 6 | 7 | echo "before name = $name" 8 | greet dave 9 | echo "after name = $name" 10 | -------------------------------------------------------------------------------- /foo/done/say-hi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my-source() { 4 | source -p ./lib "$1" 5 | } 6 | 7 | my-source greetings || exit 1 8 | 9 | greet dave 10 | greet john 11 | greet buddy 12 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/03-patterns: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | f='test.jpg' 4 | if [[ $f == *.jpg ]]; then 5 | echo "$f is a jpg file" 6 | else 7 | echo "$f is not a jpg file" 8 | fi 9 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/04-pattern-fail: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | f='test.jpg' 4 | if [ $f == *.jpg ]; then 5 | echo "$f is a jpg file" 6 | else 7 | echo "$f is not a jpg file" 8 | fi 9 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/06-numbers: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | a=9 4 | b=10 5 | 6 | if [[ $b > $a ]]; then 7 | echo "$b is greater than $a" 8 | else 9 | echo "uh oh" 10 | fi 11 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/08-number-fix-2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | a=9 4 | b=10 5 | 6 | if ((b > a)); then 7 | echo "$b is greater than $a" 8 | else 9 | echo "uh oh" 10 | fi 11 | -------------------------------------------------------------------------------- /foo/done/command-substitution: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | i=5 4 | 5 | my-func() { 6 | i=6 7 | echo hi 8 | } 9 | 10 | thing=${ my-func; } 11 | echo "thing is $thing" 12 | echo "i is $i" 13 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/05-disable-pattern: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | f='test.jpg' 4 | if [[ $f == '*.jpg' ]]; then 5 | echo "$f is *.jpg" 6 | else 7 | echo "$f is not LITERALLY *.jpg" 8 | fi 9 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/07-number-fix-1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | a=9 4 | b=10 5 | 6 | if [[ $b -gt $a ]]; then 7 | echo "$b is greater than $a" 8 | else 9 | echo "uh oh" 10 | fi 11 | -------------------------------------------------------------------------------- /foo/done/process-substitution: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | i=0 4 | while read -r word; do 5 | echo "$word" 6 | ((i++)) 7 | done < <(grep d /usr/share/dict/words) 8 | 9 | echo "found $i words" 10 | -------------------------------------------------------------------------------- /notes/06-02-timing/readme.md: -------------------------------------------------------------------------------- 1 | # Timing Commands 2 | 3 | time ls 4 | 5 | explain the 3 diff kinds 6 | show htop 7 | 8 | --- 9 | 10 | type -a time 11 | 12 | time sudo whoami 13 | sudo time whoami 14 | -------------------------------------------------------------------------------- /foo/done/case: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | s=$1 4 | 5 | case "$s" in 6 | dave) echo hi dave;; 7 | buddy) echo ohhh there he is;; 8 | guy) echo uh oh here comes trouble;; 9 | *) echo idk who you are;; 10 | esac 11 | -------------------------------------------------------------------------------- /notes/12-00-test-vs-bracket/readme.md: -------------------------------------------------------------------------------- 1 | # Brackets vs. Test 2 | help '[[' 3 | help '[' 4 | help test 5 | 6 | > run 00 7 | > run 01 8 | 9 | [ -f foo.txt ] ; echo $? 10 | which '[' 11 | /bin/[ -f foo.txt ] ; echo $? 12 | -------------------------------------------------------------------------------- /notes/13-00-trap-signals/00-simple-exit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cleanup() { 4 | echo cleanup function running 5 | } 6 | 7 | trap cleanup exit 8 | 9 | echo script starting 10 | echo ... 11 | echo script done 12 | -------------------------------------------------------------------------------- /notes/13-00-trap-signals/04-trap-info: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my-func() { 4 | echo "hello world" 5 | } 6 | 7 | trap my-func SIGINFO || exit 1 8 | 9 | while true; do 10 | sleep 1 11 | echo running 12 | done 13 | -------------------------------------------------------------------------------- /foo/done/associative-arrays: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare -A arr 4 | 5 | arr[foo]=1 6 | arr[bar]=2 7 | arr[baz]=3 8 | 9 | for key in "${!arr[@]}"; do 10 | value=${arr[$key]} 11 | echo "got $key=$value" 12 | done 13 | -------------------------------------------------------------------------------- /notes/13-00-trap-signals/01-exit-code: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cleanup() { 4 | echo cleanup function running 5 | } 6 | 7 | trap cleanup exit 8 | 9 | echo script starting 10 | echo ... 11 | echo script done 12 | exit 1 13 | -------------------------------------------------------------------------------- /foo/done/greeter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | greet() { 4 | local name=$1 5 | echo "hello $name (from func)" 6 | return 5 7 | } 8 | 9 | for name in "$@"; do 10 | greet "$name" 11 | done 12 | 13 | greet dave 14 | echo $? 15 | -------------------------------------------------------------------------------- /notes/13-00-trap-signals/03-trap-debug: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my-func() { 4 | echo "[DEBUG] running: $BASH_COMMAND" 5 | } 6 | 7 | trap my-func debug 8 | 9 | echo script starting 10 | echo ... 11 | echo script done 12 | exit 1 13 | -------------------------------------------------------------------------------- /notes/11-03-mapfile-readarray/00-while-read: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | array=() 4 | 5 | # read file into array 6 | while read -r line; do 7 | array+=("$line") 8 | done < data.txt 9 | 10 | # print array 11 | printf '<%s>\n' "${array[@]}" 12 | -------------------------------------------------------------------------------- /notes/13-00-trap-signals/02-exit-override: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cleanup() { 4 | echo cleanup function running 5 | exit 2 6 | } 7 | 8 | trap cleanup exit 9 | 10 | echo script starting 11 | echo ... 12 | echo script done 13 | exit 1 14 | -------------------------------------------------------------------------------- /foo/done/move-cursor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # save the cursor position 4 | printf '\e7' 5 | 6 | # jump somewhere 7 | printf '\e[20;20H' 8 | 9 | # print "hello world" 10 | printf 'Hello World' 11 | 12 | # restore the cursor 13 | printf '\e8' 14 | -------------------------------------------------------------------------------- /foo/done/return-codes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my-func() { 4 | echo 'this goes to stdout' >&1 5 | echo 'this goes to stderr' >&2 6 | return 0 7 | } 8 | 9 | var=$(my-func 2>&1 >/dev/null) 10 | code=$? 11 | 12 | echo "output=$var, code=$code" 13 | -------------------------------------------------------------------------------- /notes/14-01-cursor-commands/readme.md: -------------------------------------------------------------------------------- 1 | # Cursor Commands 2 | 3 | ``` 4 | tput setaf 1 > file.txt 5 | 6 | clear 7 | clear > file.txt 8 | 9 | # make script 10 | printf '\e7' 11 | printf '\e[20;20H' 12 | echo hello world 13 | printf '\e8' 14 | ``` 15 | -------------------------------------------------------------------------------- /notes/04-00-case-statements/01-simple: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | s='dave' 4 | 5 | case "$s" in 6 | d*) echo 'matched d*';; 7 | dave) echo 'matched dave';; 8 | f*) echo 'matched f*';; 9 | foo) echo 'matched foo';; 10 | *) echo 'matched *';; 11 | esac 12 | -------------------------------------------------------------------------------- /notes/04-00-case-statements/02-always-fallthrough: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | s='far' 4 | 5 | case "$s" in 6 | d*) echo 'matched d*';& 7 | dave) echo 'matched dave';& 8 | f*) echo 'matched f*';& 9 | foo) echo 'matched foo';& 10 | *) echo 'matched *';& 11 | esac 12 | -------------------------------------------------------------------------------- /notes/04-00-case-statements/03-try-again: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | s='dave' 4 | 5 | case "$s" in 6 | d*) echo 'matched d*';;& 7 | dave) echo 'matched dave';;& 8 | f*) echo 'matched f*';;& 9 | foo) echo 'matched foo';;& 10 | *) echo 'matched *';;& 11 | esac 12 | -------------------------------------------------------------------------------- /notes/11-02-regex/01-cringe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for f in ./images/*; do 4 | bname=$(basename "$f") 5 | 6 | name=$(echo "$bname" | cut -d - -f 1) 7 | date=$(echo "$bname" | cut -d - -f 2- | cut -d . -f 1 | xargs echo) 8 | 9 | echo "$date: $name" 10 | done 11 | -------------------------------------------------------------------------------- /notes/11-02-regex/01-fine: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for f in ./images/*; do 4 | bname=$(basename "$f") 5 | 6 | name=$(echo "$bname" | cut -d - -f 1) 7 | date=$(echo "$bname" | cut -d - -f 2- | cut -d . -f 1 | xargs echo) 8 | 9 | echo "$date: $name" 10 | done 11 | -------------------------------------------------------------------------------- /notes/11-03-mapfile-readarray/04-mapfile-callback: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my_func() { 4 | local line=$1 5 | echo "(mapfile) called with line: $line" 6 | } 7 | 8 | mapfile -t -C my_func -c 1 foo < data.txt 9 | 10 | # print array 11 | printf '<%s>\n' "${foo[@]}" 12 | -------------------------------------------------------------------------------- /notes/06-00-bash-arguments/00-simple-script: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | first_name='john' 4 | last_name='eddy' 5 | 6 | full_name="$first_name $last_name" 7 | 8 | if [[ $first_name == 'dave' ]]; then 9 | echo "oh hey it's dave" 10 | fi 11 | 12 | echo "Hello $full_name!" 13 | -------------------------------------------------------------------------------- /foo/lib/greetings: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | greet() { 4 | name=$1 5 | echo "hello $name" 6 | } 7 | 8 | goodbye() { 9 | name=$1 10 | echo "goodbye $name" 11 | } 12 | 13 | if ! (return 2>/dev/null); then 14 | # we are being called directly 15 | greet dave 16 | goodbye john 17 | fi 18 | -------------------------------------------------------------------------------- /notes/11-02-regex/00-simple: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | re='^(d|j).*$' 4 | input='dave' 5 | 6 | if [[ $input =~ $re ]]; then 7 | echo match 8 | 9 | printf '%s\n' "${BASH_REMATCH[@]}" 10 | full_string=${BASH_REMATCH[0]} 11 | name=${BASH_REMATCH[1]} 12 | else 13 | echo no match 14 | fi 15 | -------------------------------------------------------------------------------- /notes/16-02-ansi-length/readme.md: -------------------------------------------------------------------------------- 1 | # Pitfall: String Length 2 | 3 | ``` 4 | echo $"\e[33mHello World\e[0m" 5 | echo $'\e[33mHello World\e[0m' 6 | 7 | s=... 8 | 9 | echo "${#s}" 10 | 11 | echo "$s" 12 | echo "$s" | wc -c 13 | echo -n 'Hello World' | wc -c 14 | ``` 15 | 16 | mention strip ansi 17 | -------------------------------------------------------------------------------- /notes/14-02-isatty/readme.md: -------------------------------------------------------------------------------- 1 | # Is a TTY 2 | 3 | ``` 4 | ls 5 | ls | wc -l 6 | ``` 7 | 8 | ``` 9 | #!/usr/bin/env bash 10 | if [[ -t 1 ]]; then 11 | echo stdout is a terminal 12 | else 13 | echo stdout is NOT a terminal 14 | fi 15 | ``` 16 | 17 | ./check-terminal 18 | ./check-terminal | cat 19 | ``` 20 | -------------------------------------------------------------------------------- /notes/12-01-special-strings/readme.md: -------------------------------------------------------------------------------- 1 | # Special Strings 2 | 3 | ``` 4 | echo 'hello\nworld' 5 | 6 | echo -e 'hello\nworld' 7 | 8 | msg='hello\nworld' 9 | echo "$msg" 10 | msg=$'hello\nworld' 11 | echo "$msg" 12 | 13 | echo $'foo\nbar' 14 | echo $'foo\tbar' 15 | echo $'foo\vbar' 16 | echo $'foo\b\b\bbar' 17 | ``` 18 | -------------------------------------------------------------------------------- /notes/06-01-pipestatus/readme.md: -------------------------------------------------------------------------------- 1 | # Pipe Status 2 | echo hi 3 | echo $? 4 | 5 | echo hi | grep hi 6 | echo $? 7 | 8 | echo hi | grep bye 9 | echo $? 10 | 11 | $? the last command in a pipeline 12 | 13 | echo "${PIPESTATUS[*]}" 14 | 15 | true|false|true|false|true 16 | 17 | (exit 4) | (exit 7) | (exit 100) | (exit 0) 18 | -------------------------------------------------------------------------------- /notes/11-02-regex/02-based: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | regex='^.*\/(.*) - ([0-9]{4}-[0-9]{2}-[0-9]{2})\..*$' 4 | for f in ./images/*; do 5 | if ! [[ $f =~ $regex ]]; then 6 | echo "$f didn't match pattern" 7 | continue 8 | fi 9 | 10 | name=${BASH_REMATCH[1]} 11 | date=${BASH_REMATCH[2]} 12 | echo "$date: $name" 13 | done 14 | -------------------------------------------------------------------------------- /notes/11-02-regex/02-good: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | regex='^.*\/(.*) - ([0-9]{4}-[0-9]{2}-[0-9]{2})\..*$' 4 | for f in ./images/*; do 5 | if ! [[ $f =~ $regex ]]; then 6 | echo "$f didn't match pattern" 7 | continue 8 | fi 9 | 10 | name=${BASH_REMATCH[1]} 11 | date=${BASH_REMATCH[2]} 12 | echo "$date: $name" 13 | done 14 | -------------------------------------------------------------------------------- /notes/17-00-forkbomb/diagram.txt: -------------------------------------------------------------------------------- 1 | -> func -> ... 2 | / 3 | -> func -> 4 | / \ 5 | func -> -> func -> ... 6 | \ -> func -> ... 7 | \ / 8 | -> func -> 9 | \ 10 | -> func -> ... 11 | -------------------------------------------------------------------------------- /notes/15-02-readline-and-such/readme.md: -------------------------------------------------------------------------------- 1 | # Readline Shortcuts 2 | 3 | alt-f - forward word 4 | alt-b - back word 5 | alt-t - swap words 6 | ctrl-f - forward char 7 | ctrl-b - back char 8 | ctrl-t - swap char 9 | ctrl-w - delete one word back 10 | ctrl-u - delete entire line to left 11 | 12 | ctrl-r - reverse search 13 | 14 | history 15 | -------------------------------------------------------------------------------- /notes/16-01-aliases-with-arguments/readme.md: -------------------------------------------------------------------------------- 1 | # Aliases with Arguments 2 | 3 | ``` 4 | # mkdir and cd 5 | alias mkdc='mkdir $1 && cd $1' 6 | 7 | alias foo='echo $1 $2' 8 | 9 | foo 10 | foo hello world 11 | 12 | set -- a b c 13 | foo hello world 14 | 15 | mkdc() { 16 | mkdir $1 && cd $1 17 | } 18 | 19 | then clean it up 20 | ``` 21 | -------------------------------------------------------------------------------- /notes/02-02-more-variables/readme.md: -------------------------------------------------------------------------------- 1 | # Basic Variables 2 | 3 | - `echo $PATH` 4 | - `echo $PATH | tr ':' '\n'` 5 | - `echo $PWD` 6 | - `echo $USER` 7 | - `echo $SHELL` 8 | - `echo $HOSTNAME` 9 | - name=dave 10 | - echo $name 11 | - (show basics) 12 | - unset name 13 | - what if we want more? 14 | - thing=`uname`, thing=$(uname), echo $thing 15 | -------------------------------------------------------------------------------- /notes/new-section: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Helper to make a new section directory 4 | # 5 | # Author: Dave Eddy 6 | # Date: October 13, 2025 7 | # License: MIT 8 | 9 | section=${1-?} 10 | mkdir -p "$section" 11 | touch "$section/readme.md" 12 | echo "$section/readme.md" 13 | exec ${EDITOR:-vi} "$section/readme.md" 14 | -------------------------------------------------------------------------------- /notes/13-01-named-pipes/03-all-in-one: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | exec {fd}<>pipe 4 | 5 | client() { 6 | local name=$1 7 | 8 | while true; do 9 | sleep 3 10 | echo "[client $name] hello" >&$fd 11 | done 12 | } 13 | 14 | client foo & 15 | client bar & 16 | client baz & 17 | 18 | while read -r -u "$fd" line; do 19 | echo "[read line] $line" 20 | done 21 | -------------------------------------------------------------------------------- /notes/07-03-what-we-have/01-better: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # show finger command 4 | 5 | declare -A shells 6 | while IFS=: read -r name pass uid gid gecos home shell; do 7 | # skip comments 8 | if [[ $name == '#'* ]]; then 9 | continue 10 | fi 11 | 12 | shells[$name]=$shell 13 | done < /etc/passwd 14 | 15 | echo "dave has the shell: ${shells[nobody]}" 16 | -------------------------------------------------------------------------------- /notes/09-00-basic-globbing/readme.md: -------------------------------------------------------------------------------- 1 | # Basic Globbing 2 | 3 | ``` 4 | ls files/ 5 | bar.jpg bar.txt baz.jpg baz.txt foo.jpg foo.txt 6 | 7 | echo files/* 8 | printf '%s\n' files/* 9 | 10 | ls -1 files/* 11 | 12 | ls -1 files/*.txt 13 | ls -1 files/*.jpg 14 | 15 | ls -1 files/foo.* 16 | ls -1 files/bar.* 17 | 18 | ls -1 files/ba?.txt 19 | 20 | ls -1 files/ba[rz].txt 21 | ``` 22 | -------------------------------------------------------------------------------- /notes/format-titles: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Format the titles somewhat nicely 4 | # 5 | # Author: Dave Eddy 6 | # Date: December 03, 2025 7 | # License: MIT 8 | 9 | for f in */readme.md; do 10 | IFS=- read -r chapter section _ <<< "$f" 11 | title=$(head -1 < "$f") 12 | title=${title#\# } 13 | 14 | echo "Chapter $chapter Section $section: $title" 15 | done 16 | -------------------------------------------------------------------------------- /notes/03-06-what-we-have/01-case: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | hello() { 4 | local s=$1 5 | echo "hello $s!" 6 | } 7 | 8 | goodbye() { 9 | local s=$1 10 | echo "goodbye $s!" 11 | } 12 | 13 | if (($# == 0)); then 14 | echo 'name required!' >&2 15 | exit 1 16 | fi 17 | 18 | for name in "$@"; do 19 | case "$name" in 20 | d* | b*) hello "$name";; 21 | *) goodbye "$name";; 22 | esac 23 | done 24 | -------------------------------------------------------------------------------- /notes/01-04-pager/readme.md: -------------------------------------------------------------------------------- 1 | # Paging Files 2 | 3 | - `cat /usr/share/dict/words` 4 | - it's a big file and annoying let's do this 5 | - `less /usr/share/dict/words` 6 | - teach less 7 | - `cat /usr... | less` 8 | - `cat ... | grep | less` 9 | - `man less` 10 | - search with / 11 | - also show `more` 12 | - ls -1i /usr/bin/more /usr/bin/less (less is more) 13 | - `less file.txt` 14 | - `cat file.txt | less` 15 | -------------------------------------------------------------------------------- /notes/02-01-programs-and-commands/readme.md: -------------------------------------------------------------------------------- 1 | # Programs and Commands 2 | 3 | - `which ls`, `which chmod` 4 | - `which history` - this fails 5 | - `type history` - this works 6 | - `file file.txt` 7 | - `file /usr/bin/ls` 8 | - the `file` command tells us what type of file something is 9 | - there's all sorts of programs/commands for us! how do we see them all? 10 | - `echo $PATH` 11 | - `echo $PATH | tr ':' '\n'` 12 | -------------------------------------------------------------------------------- /notes/13-01-named-pipes/readme.md: -------------------------------------------------------------------------------- 1 | # Named Pipes 2 | 3 | mkfifo pipe 4 | 5 | open tmux, split horizontally 6 | 7 | A: cat pipe (hangs) 8 | B: echo hi > pipe 9 | 10 | next 11 | 12 | A: echo hi > pipe (hangs) 13 | B: cat pipe 14 | 15 | 00 basic reader 16 | 17 | 01 forever 18 | 19 | show multiple background clients 02 20 | 21 | CLOSE TMUX 22 | 23 | show 03 24 | 25 | close out linking to video about nightfall TUI 26 | -------------------------------------------------------------------------------- /notes/04-00-case-statements/00-case: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | hello() { 4 | local s=$1 5 | echo "hello $s!" 6 | } 7 | 8 | goodbye() { 9 | local s=$1 10 | echo "goodbye $s!" 11 | } 12 | 13 | if (($# == 0)); then 14 | echo 'name required!' >&2 15 | exit 1 16 | fi 17 | 18 | for name in "$@"; do 19 | case "$name" in 20 | d*) hello "$name";; 21 | b*) hello "$name";; 22 | *) goodbye "$name";; 23 | esac 24 | done 25 | -------------------------------------------------------------------------------- /tools/make-all-metadata-images: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p out 4 | 5 | for f in ../notes/notes/*/readme.md; do 6 | IFS=/ read -r _ _ _ dir _ <<< "$f" 7 | IFS=- read -r chapter section _ <<< "$dir" 8 | title=$(head -1 < "$f") 9 | title=${title#\# } 10 | 11 | newf=out/$dir.png 12 | 13 | echo "$newf" 14 | ./make-metadata-image -s "$section" -c "$chapter" -o "$newf" -t "$title" || exit 1 15 | done 16 | -------------------------------------------------------------------------------- /notes/02-04-file-permissions/readme.md: -------------------------------------------------------------------------------- 1 | # File Permissions 2 | 3 | - do we need to run `bash`? 4 | - compare to how we run `users` or `ls` directly 5 | - can we just run `script.sh` 6 | - try `./script.sh` 7 | - `chmod +x script.sh` 8 | - `./script.sh` # why does this work?? 9 | - mention shebangs, link to video 10 | - `stat script.sh` 11 | - `stat /usr/bin/ls` 12 | - `ls -lha .` 13 | - explain users, group, world 14 | - `chown`, `chmod`, `chgrp` 15 | -------------------------------------------------------------------------------- /website/Makefile: -------------------------------------------------------------------------------- 1 | favicon.ico: 2 | curl -o favicon.ico https://ysap.sh/favicon.ico 3 | 4 | .PHONY: index 5 | index: 6 | ./make-curl-website > index.txt 7 | 8 | .PHONY: serve 9 | serve: favicon.ico 10 | python3 -mhttp.server 11 | 12 | # deploy the website 13 | .PHONY: deploy 14 | deploy: favicon.ico index 15 | rsync -avh --delete --progress \ 16 | ./{index.txt,index.html,style.css,favicon.ico,download.html} \ 17 | web:/var/www/course.ysap.sh/ 18 | -------------------------------------------------------------------------------- /notes/03-06-what-we-have/00-script: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | hello() { 4 | local s=$1 5 | echo "hello $s!" 6 | } 7 | 8 | goodbye() { 9 | local s=$1 10 | echo "goodbye $s!" 11 | } 12 | 13 | if (($# == 0)); then 14 | echo 'name required!' >&2 15 | exit 1 16 | fi 17 | 18 | for name in "$@"; do 19 | if [[ $name == d* ]]; then 20 | hello "$name" 21 | elif [[ $name == b* ]]; then 22 | hello "$name" 23 | else 24 | goodbye "$name" 25 | fi 26 | done 27 | -------------------------------------------------------------------------------- /notes/04-03-IFS-variable/readme.md: -------------------------------------------------------------------------------- 1 | # IFS Variable 2 | 3 | talk about a small section of IFS today 4 | 5 | ``` 6 | array=(foo bar baz) 7 | echo "${array[*]}" 8 | 9 | IFS=, 10 | echo "${array[*]}" 11 | echo ${array[*]} 12 | 13 | IFS=_ 14 | echo "${array[*]}" 15 | 16 | IFS=abcd 17 | echo "${array[*]}" 18 | 19 | 20 | unset array 21 | declare -A array=([foo]=1 [bar]=2) 22 | echo "${array[*]}" 23 | 24 | IFS=, 25 | echo "${array[*]}" 26 | echo "${!array[*]}" 27 | ``` 28 | -------------------------------------------------------------------------------- /notes/07-00-sourcing/readme.md: -------------------------------------------------------------------------------- 1 | # Sourcing Code 2 | 3 | greet() { 4 | } 5 | 6 | greet dave 7 | greet john 8 | 9 | --- 10 | 11 | move the greet function into the lib/ dir 12 | 13 | source it with `source` or `.` 14 | 15 | source ./lib/greet 16 | source -p ./lib greet 17 | 18 | --- 19 | 20 | make lib have an example run 21 | 22 | ``` 23 | if ! (return 2>/dev/null); then 24 | # we are being called directly - execute the main function 25 | main "$@" 26 | fi 27 | ``` 28 | -------------------------------------------------------------------------------- /notes/concat-readmes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # concat all the readmes so I can easily look through them during recording. 4 | # 5 | # Author: Dave Eddy 6 | # Date: November 17, 2025 7 | # License: MIT 8 | 9 | for d in */; do 10 | bname=${d%/} 11 | readme=${d}readme.md 12 | 13 | echo ======================================== 14 | echo = $bname 15 | echo ======================================== 16 | echo 17 | cat "$readme" 18 | echo 19 | echo 20 | done 21 | -------------------------------------------------------------------------------- /notes/10-01-braces-and-globbing/readme.md: -------------------------------------------------------------------------------- 1 | # Braces and Globbing 2 | 3 | ``` 4 | mkdir files 5 | echo files/{foo,bar,baz}.{jpg,txt} 6 | touch files/{foo,bar,baz}.{jpg,txt} 7 | 8 | printf '<%s>\n' files/*.txt 9 | 10 | # explain that it doesn't have to exist - compare to ext glob 11 | printf '<%s>\n' files/{foo,bar}.txt 12 | printf '<%s>\n' files/@(foo|bar).txt 13 | 14 | ls files/*.{txt,jpg} 15 | 16 | 17 | mkdir empty 18 | ls empty/*.{txt,jpg} 19 | printf '<%s>\n' empty/*.{txt,jpg} 20 | ``` 21 | -------------------------------------------------------------------------------- /notes/01-00-terminal-and-finder/readme.md: -------------------------------------------------------------------------------- 1 | # Terminal and Finder 2 | 3 | - Before we can talk about scripts we have to talk about the shell in general 4 | - Open terminal, we are in a shell 5 | - basic REPL - read-eval-print-loop 6 | - unix philosophy with output - or lack thereof 7 | - `cd ~/Desktop; mkdir foo; open foo` 8 | - Open finder, move to the same directory 9 | - directories ARE folders 10 | - create some files, touch file.txt 11 | - vim it 12 | - cat it 13 | - echo hi > it 14 | - rm it 15 | -------------------------------------------------------------------------------- /notes/06-00-bash-arguments/readme.md: -------------------------------------------------------------------------------- 1 | # Bash Arguments 2 | 3 | ./00-simple-script 4 | 5 | bash ./00-simple-script 6 | bash -x ./00-simple-script 7 | 8 | ... edit script, add set -x ... 9 | 10 | set +x add as well 11 | 12 | PS4='>>>>' bash -x 00-simple-script 13 | 14 | --- 15 | 16 | ./01-syntax-error 17 | bash -n ./01-syntax-error 18 | 19 | explain why no output 20 | 21 | bash -n 00-simple-script 22 | 23 | --- 24 | 25 | ./02-undef 26 | 27 | bash -u ... 28 | 29 | --- 30 | 31 | show shellcheck 32 | -------------------------------------------------------------------------------- /notes/01-03-searching-in-files/readme.md: -------------------------------------------------------------------------------- 1 | # Searching in Files 2 | 3 | - show `/usr/share/dict/words` 4 | - grep it for dave 5 | - let's make our own files 6 | - `touch file.txt` 7 | - `echo hello` 8 | - `echo hello > file.txt` 9 | - `echo a >> file.txt`, b, c 10 | - `grep b file.txt` 11 | - `grep -A1 b file.txt`, -B, -C 12 | - open it with a text editor and add random words 13 | - `grep -i ...` 14 | - `grep -o ...`, `grep -v ...` 15 | - `cat file.txt | grep -v ... | grep -o ...` # subtle intro of pipelines 16 | -------------------------------------------------------------------------------- /notes/11-01-date-formatting/readme.md: -------------------------------------------------------------------------------- 1 | # Date Formatting 2 | 3 | ``` 4 | datefmt='%m/%d/%Y %H:%M:%S' 5 | date "+$datefmt" 6 | 7 | printf "%($datefmt)T\n" -1 8 | date +'the time is %H:%M:%S' 9 | 10 | 11 | printf '%T\n' 12 | printf '%()T\n' 13 | printf '%(%H:%M:%S)T\n' -1 14 | 15 | printf '%(%H:%M:%S)T\n' -2 16 | 17 | printf '%(%H:%M:%S)T\n' 0 18 | printf '%(%H:%M:%S)T\n' 5000 19 | 20 | date +%s 21 | printf '%(%s)T\n' -1 22 | printf -v var '%(%s)T\n' -1 23 | echo $EPOCHSECONDS 24 | echo $EPOCHREALTIME 25 | ``` 26 | -------------------------------------------------------------------------------- /notes/03-04-for-loops/readme.md: -------------------------------------------------------------------------------- 1 | # For Loops 2 | 3 | ``` bash 4 | for name in foo bar baz; do 5 | echo "name is $name" 6 | done 7 | ``` 8 | 9 | ``` bash 10 | for c in {a..f}; do 11 | echo "c is $c" 12 | done 13 | ``` 14 | 15 | ``` bash 16 | for i in {1..5}; do 17 | echo "i is $i" 18 | done 19 | ``` 20 | 21 | ``` bash 22 | max=5 23 | for i in {1..$max}; do 24 | echo "i is $i" 25 | done 26 | ``` 27 | 28 | ``` bash 29 | max=5 30 | for ((i = 0; i < max; i++)); do 31 | echo "i is $i" 32 | done 33 | ``` 34 | -------------------------------------------------------------------------------- /notes/05-02-find-cmd/readme.md: -------------------------------------------------------------------------------- 1 | # Find Command 2 | 3 | ``` bash 4 | find . 5 | 6 | find /var/empty 7 | find /var/empty -type f 8 | 9 | find /usr/share -type f | wc -l 10 | find /usr/share -type d | wc -l 11 | find /usr/share -type l | wc -l 12 | # add them up 13 | 14 | 15 | find /usr/share/ -type f -name '*.plist' -iname 'info*' 16 | find /usr/share/ -type f -name 'Info.plist' -exec echo the file is {} \; 17 | 18 | echo /usr/share/* 19 | for f in ...; do 20 | ``` 21 | 22 | we'll talk more about that in the globbing section 23 | -------------------------------------------------------------------------------- /notes/02-03-vim-crash-course/readme.md: -------------------------------------------------------------------------------- 1 | # vim Crash Course 2 | 3 | - We're gonna use `vim` - don't be scared! 4 | - `vim file.txt` 5 | - press `i` - basic stuff, esc-:wq 6 | - reinforce `less`, `grep`, to show it's still just a file 7 | - talk about modes - make video game analogy (game play vs. menu) 8 | - `vim script.sh` 9 | - explain `.sh` and talk about extensions in general 10 | - (ignore shebang for now) 11 | - basic script like `echo hello $USER` 12 | - `bash script.sh` 13 | - mess around with the script a bit 14 | - `date` 15 | - `now=$(date)` 16 | -------------------------------------------------------------------------------- /notes/07-01-curlies-vs-parens/readme.md: -------------------------------------------------------------------------------- 1 | # Curlies vs. Parens 2 | 3 | ``` bash 4 | greet() { 5 | name=$1 6 | echo "hello $name!" 7 | } 8 | 9 | echo "before: $name" 10 | greet dave 11 | echo "aftire: $name" 12 | ``` 13 | 14 | 1. run it 15 | 2. swap { to ( 16 | 3. explain local 17 | 4. remove local, force with `( greet dave )` 18 | 19 | --- 20 | 21 | output=$(greet dave) 22 | echo "output=$output, name=$name" 23 | 24 | output=${ uname; } 25 | echo "output=$output, name=$name" 26 | 27 | { echo hi; whoami; id; } | nl 28 | ( echo hi; whoami; id ) | nl 29 | -------------------------------------------------------------------------------- /notes/09-01-extended-globbing/readme.md: -------------------------------------------------------------------------------- 1 | # Extended Globbing 2 | 3 | ``` 4 | set -H 5 | shopt -s extglob 6 | 7 | ls -1 files/*.txt 8 | ls -1 files/!(*.txt) 9 | 10 | ls -1 files/+(foo|bar).txt 11 | 12 | +(thing) => /(thing)+/ 13 | ?(thing) => /(thing)?/ 14 | *(thing) => /(thing)*/ 15 | 16 | --- 17 | 18 | @(thing) 19 | 20 | ls files/+(foo).* 21 | ls files/@(foo).* 22 | 23 | touch files/foofoo.txt 24 | 25 | ls files/@(foo).* 26 | 27 | ls files/foo.* 28 | 29 | touch files/barbar.txt 30 | ls files/@(foo|bar).* 31 | ls files/@(foo|ba?).* 32 | 33 | ``` 34 | -------------------------------------------------------------------------------- /notes/10-00-brace-string-expansion/readme.md: -------------------------------------------------------------------------------- 1 | # Brace Expansion 2 | 3 | ``` 4 | echo {hello,world} 5 | 6 | echo "{hello,world}" 7 | 8 | 9 | 10 | echo my-file.{txt,jpg,mov} 11 | 12 | filename='my-file' 13 | echo "$filename."{txt,jpg,mov} 14 | 15 | 16 | # make script 17 | filename='my-file' 18 | array=("$filename."{txt,jpg,mov}) 19 | 20 | for item in "${array[@]}"; do 21 | echo "$item" 22 | done 23 | 24 | # make script 25 | 26 | array=(/etc/{foo,bar,baz}/{1,2,3}.txt) 27 | echo "${#array[@]} items" 28 | for item in "${array[@]}"; do 29 | echo "$item" 30 | done 31 | ``` 32 | -------------------------------------------------------------------------------- /notes/00-00-intro/readme.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | - This course will teach you how to be comfortable on the shell writing 4 | scripts. 5 | - We will be targeting bash but try to be OS-agnostic. 6 | - Linux vs. Mac vs. illumos - just to show where Bash stops and the 7 | distribution starts. 8 | - Course is free 9 | - it's on youtube and has a dedicated website 10 | - We expect you to know super basic programming concepts like variable 11 | assignment 12 | - website: course.ysap.sh 13 | - mention patreon, talk about how monthly payments suck, show one-shot for those 14 | that want to pay 15 | -------------------------------------------------------------------------------- /notes/03-05-io/readme.md: -------------------------------------------------------------------------------- 1 | # Input / Output 2 | 3 | Bash can take input from the user 4 | 5 | ``` bash 6 | read -r foo 7 | echo "you said: $foo" 8 | ``` 9 | 10 | ./script 11 | echo hi | ./script 12 | 13 | cat /usr/share/dict/words | ./script 14 | 15 | ``` bash 16 | while read -r line; do 17 | echo "got line: $line" 18 | done 19 | ``` 20 | 21 | talk about how the read command is in the condition of the while loop 22 | 23 | --- 24 | 25 | # talk about how these are the same 26 | cat /usr/share/dict/words | ./script 27 | < /usr/share/dict/words ./script 28 | ./script < /usr/share/dict/words 29 | -------------------------------------------------------------------------------- /notes/02-00-man-pages/readme.md: -------------------------------------------------------------------------------- 1 | # Man Pages 2 | 3 | - show basic examples 4 | - `man man` - this is a fun one 5 | - talk about sections 6 | - when you run without a section name it assumes 1 (and works its way up) 7 | - commands exist on your system 8 | - `man cp`, `man mkdir`, etc. 9 | - `man history` doesn't quit work? 10 | - some commands are builtin to the shell (bash) 11 | - bash has `help` 12 | - `help history` 13 | - `help help` 14 | - `compgen -b` will list all builtins 15 | - `compgen -b | grep | less` # all of this stuff is the same!! 16 | - `compgen -b > builtins.txt` 17 | - `history | ... | ...` # because history is builtin! 18 | -------------------------------------------------------------------------------- /notes/04-05-arithmetic-expression/readme.md: -------------------------------------------------------------------------------- 1 | # Arithmetic Expression 2 | 3 | ``` bash 4 | echo $((2 + 2)) 5 | echo $((18)) 6 | 7 | a=2 8 | b=3 9 | 10 | echo $(($a + $b)) 11 | echo $((a + b)) 12 | 13 | a='hello' 14 | b='goodbye' 15 | echo $((a + b)) 16 | 17 | 18 | i=0 19 | ((i++)) 20 | echo "$i" 21 | 22 | ((i = i + 1)) 23 | ((i = 57)) 24 | 25 | ((2 + 2)) 26 | echo $? 27 | 28 | ((2 - 2)) 29 | echo $? 30 | 31 | ((a = 5, b = 7)) 32 | ``` 33 | 34 | --- 35 | 36 | ``` bash 37 | a=06 38 | echo $a 39 | echo $(( a )) 40 | 41 | a=07 42 | echo $(( a )) 43 | 44 | a=08 45 | echo $(( a )) 46 | echo $(( 10#a )) 47 | echo $(( 10#$a )) 48 | ``` 49 | -------------------------------------------------------------------------------- /notes/03-00-actually-scripting/readme.md: -------------------------------------------------------------------------------- 1 | # Finally Scripting 2 | 3 | - Basic scripts 4 | 5 | `hello` - say hello 6 | 7 | ```bash 8 | echo hello 9 | ``` 10 | 11 | `greet` - say hello to a user 12 | 13 | ``` bash 14 | name='dave' 15 | echo "hello $name" 16 | ``` 17 | 18 | `num-items` 19 | 20 | ``` bash 21 | name='dave' 22 | items=5 23 | 24 | echo "user $name has $items items" 25 | ``` 26 | 27 | `loop` - Loop over a set of items 28 | 29 | ``` bash 30 | for thing in foo bar baz bat; do 31 | echo "thing is $thing" 32 | done 33 | ``` 34 | 35 | --- 36 | 37 | Using `bash script` 38 | Using chmod +x 39 | Using `bash -n` to syntax check 40 | -------------------------------------------------------------------------------- /notes/03-01-taking-input/readme.md: -------------------------------------------------------------------------------- 1 | # User Input 2 | 3 | Modify the above 2 scripts to take from stdin 4 | 5 | ``` bash 6 | read -p 'enter your name: ' name 7 | echo hello $name 8 | ``` 9 | 10 | ... 11 | 12 | Then modify to take as an argument 13 | 14 | ``` bash 15 | name=$1 16 | echo "hello $name" 17 | ``` 18 | 19 | Show what happens without an argument: 20 | 21 | ``` bash 22 | if [[ -n $1 ]]; then 23 | name=$1 24 | else 25 | read -p 'enter your name: ' name 26 | fi 27 | ... 28 | ``` 29 | 30 | Show `help [[` and `help test` 31 | 32 | - Arguments 33 | 34 | ``` bash 35 | for thing in "$@"; do 36 | echo thing is $thing 37 | done 38 | ``` 39 | -------------------------------------------------------------------------------- /notes/04-02-associative-arrays/readme.md: -------------------------------------------------------------------------------- 1 | # Associative Arrays 2 | 3 | ``` bash 4 | declare -A arr 5 | echo $? 6 | ``` 7 | 8 | ``` bash 9 | declare -A array || exit 10 | 11 | if ! declare -A array; then 12 | echo uh oh 13 | fi 14 | ``` 15 | 16 | ``` bash 17 | declare -A array=() 18 | 19 | array[foo]=1 20 | array[bar]=2 21 | array[baz]=3 22 | 23 | echo "$array" # nothing 24 | echo "${array[foo]}" 25 | echo "${array[bar]}" 26 | echo "${array[fake]}" 27 | 28 | echo "${array[*]}" 29 | echo "${array[@]}" 30 | 31 | echo "${!array[*]}" 32 | echo "${!array[@]}" 33 | 34 | for key in "${!array[@]}"; do 35 | value=${array[$key]} # $ is needed 36 | echo "$key is $value" 37 | done 38 | ``` 39 | -------------------------------------------------------------------------------- /notes/17-00-forkbomb/readme.md: -------------------------------------------------------------------------------- 1 | # Forkbomb 2 | 3 | show bomb.txt 4 | 5 | unminify it 6 | 7 | show the diagram 8 | 9 | explain why it's a problem and isn't specific to bash 10 | 11 | --- 12 | 13 | ``` 14 | #!/usr/bin/env bash 15 | 16 | bomb() { 17 | local num=$1 18 | local name=$2 19 | printf '%-2d %-5s %s\n' "$num" "$name" 'running' >&2 20 | 21 | if ((num == 6)); then 22 | return 0 23 | fi 24 | 25 | ((num++)) 26 | bomb "$num" 'left' | bomb "$num" 'right' & 27 | } 28 | 29 | bomb 0 'none' 30 | ``` 31 | 32 | ./step-bomb 2>&1 | sort | awk '{print $1}' | uniq -c 33 | 34 | bc 35 | 2^5 36 | 2^6 37 | 38 | --- 39 | 40 | RUN IT 41 | -------------------------------------------------------------------------------- /notes/10-02-braces-numeric/readme.md: -------------------------------------------------------------------------------- 1 | # Numeric Brace Expansion 2 | 3 | ``` 4 | echo {1,2} 5 | echo {1..2} 6 | echo {1..20} 7 | echo {5..10} 8 | echo {10..5} 9 | echo {1..100..5} 10 | 11 | seq 1 10 12 | seq 1 20 13 | seq 5 14 | seq 1 20 5 # wrong 15 | seq 1 5 20 # wrong 16 | 17 | for i in $(seq 1 10); do 18 | echo "$i" 19 | done 20 | 21 | for i in {1..10}; do 22 | echo "$i" 23 | done 24 | 25 | --- 26 | 27 | for i in foo {1..4} {8..10} bar; do 28 | echo "$i" 29 | done 30 | 31 | for i in {1..10..3}; do 32 | echo "$i" 33 | done 34 | 35 | 36 | max=4 37 | for i in {1..$max}; do 38 | echo "$i" 39 | done 40 | 41 | max=4 42 | for i in $(seq 1 "$max"); do 43 | echo "$i" 44 | done 45 | 46 | max=4 47 | for ((i = 1; i <= max; i++)); do 48 | echo "$i" 49 | done 50 | ``` 51 | -------------------------------------------------------------------------------- /notes/04-06-process-substitution/readme.md: -------------------------------------------------------------------------------- 1 | # Process Substitution 2 | 3 | ``` 4 | words=$(grep d /usr/share/dict/words) 5 | 6 | i=0 7 | for word in $words; do 8 | echo "$word" 9 | ((i++)) 10 | done 11 | 12 | echo "found $i words" 13 | ``` 14 | 15 | ``` 16 | words=$(grep d /usr/share/dict/words) 17 | 18 | i=0 19 | while read -r word; do 20 | echo "$word" 21 | ((i++)) 22 | done <<< "$words" 23 | 24 | echo "found $i words" 25 | ``` 26 | 27 | ``` 28 | i=0 29 | grep d /usr/share/dict/words | while read -r word; do 30 | echo "$word" 31 | ((i++)) 32 | done 33 | 34 | echo "found $i words" 35 | ``` 36 | 37 | ``` 38 | i=0 39 | while read -r word; do 40 | echo "$word" 41 | ((i++)) 42 | done < <(grep d /usr/share/dict/words) 43 | 44 | echo "found $i words" 45 | ``` 46 | -------------------------------------------------------------------------------- /notes/05-01-sed-awk-grep/readme.md: -------------------------------------------------------------------------------- 1 | # sed, awk, and grep 2 | 3 | cat /etc/passwd 4 | man 5 passwd 5 | 6 | cut for names 7 | remove comments 8 | find dave with grep 9 | 10 | use sed to replace shell 11 | 12 | cat /etc/passwd | grep nobody | cut -d : -f 7 | sed 's|/usr/bin/|/tmp/sbin/|' 13 | 14 | 15 | cat /etc/passwd | awk -F : '/^[^#]/ { printf("the shell is %s\n", $7); }' 16 | 17 | 18 | cat /etc/passwd | awk -F : '/^[^#]/ { print $7 }' | sort | uniq -c | sort -n 19 | 20 | 21 | cat /usr/share/dict/words | grep dave | wc -l 22 | cat /usr/share/dict/words | grep -c dave 23 | grep -c dave /usr/share/dict/words 24 | 25 | 26 | man ls 27 | man ls | grep dave 28 | man ls | grep file 29 | man ls | grep file | wc -w 30 | man ls | grep file | wc -l 31 | man ls | grep file | wc 32 | -------------------------------------------------------------------------------- /notes/07-02-return-vs-output/readme.md: -------------------------------------------------------------------------------- 1 | # Return vs. Output 2 | 3 | ``` 4 | uname 5 | echo $? 6 | ``` 7 | 8 | - show what happens 9 | - explain output vs return code 10 | - capture output in a variable 11 | - capture exit code in a variable 12 | - replace with my-func 13 | 14 | ``` 15 | my-func() { 16 | echo hello 17 | return 0 18 | } 19 | 20 | my-func 21 | echo $? 22 | ``` 23 | 24 | - show what happens 25 | - explain it's like a mini program 26 | 27 | ``` 28 | my-func() { 29 | echo this goes to stdout >&1 30 | echo this goes to stderr >&2 31 | return 0 32 | } 33 | 34 | output=$(my-func) 35 | output=$(my-func 1>/dev/null) 36 | output=$(my-func 2>/dev/null) 37 | output=$(my-func 2>&1) 38 | 39 | output=$(my-func >/dev/null 2>&1) 40 | output=$(my-func 2>&1 >/dev/null) 41 | ``` 42 | -------------------------------------------------------------------------------- /notes/15-01-customize-bashrc/readme.md: -------------------------------------------------------------------------------- 1 | # Customizing Bash 2 | 3 | ``` 4 | 1. shopt -s cdspell 5 | # show examples of types 6 | 7 | 2. shopt -s autocd 8 | # show examples of autocd 9 | 10 | 3. talk about `..` 11 | 12 | 4. mention that it's interactive only 13 | ``` 14 | 15 | ``` 16 | # Aliases (if applicable) 17 | grep --color=auto < /dev/null &>/dev/null && 18 | alias grep='grep --color=auto' 19 | xdg-open --version &>/dev/null && 20 | alias open='xdg-open' 21 | command -v system_profiler &>/dev/null && 22 | alias wattage='system_profiler SPPowerDataType | grep Wattage' 23 | 24 | # Enable color support of ls 25 | if ls --color=auto &>/dev/null; then 26 | alias ls='ls -p --color=auto' 27 | else 28 | alias ls='ls -p -G' 29 | fi 30 | ``` 31 | 32 | make our own bashrc 33 | -------------------------------------------------------------------------------- /notes/01-01-file-manipulation/readme.md: -------------------------------------------------------------------------------- 1 | # Basic File Manipulation 2 | 3 | - also, it can be easy to get lost, here's `pwd` 4 | - explain CWD (show gui as an analog) 5 | - `touch lesson-1.txt` 6 | - edit 7 | - `cp lesson-1.txt lesson-2.txt` 8 | - cp vs mv 9 | - `rm lesson*.txt` (gloss over globbing) 10 | - we don't have a trash, we could implement it... and that's the lesson about 11 | the shell and linux and unix in general - you always have the option to make 12 | your own lol. 13 | - `rm -i` seems useful now, can we make it the default? 14 | - `man rm` 15 | - `alias rm='rm -i'` 16 | 17 | - take inventory - we've done a lot of commands - 2 useful tricks 18 | 1. Press `up` on your keyboard to go through old commands 19 | 2. run `history` to see the commands you run 20 | 3. Tab complete! 21 | -------------------------------------------------------------------------------- /notes/03-02-functions/readme.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | ## simple 4 | 5 | ``` bash 6 | for name in foo bar baz; do 7 | ./greet "$name" 8 | done 9 | ``` 10 | 11 | ## using funcs 12 | 13 | we can also use functions 14 | 15 | ``` bash 16 | my-func() { 17 | echo hello from my function 18 | echo arg1 is $1 19 | } 20 | 21 | my-func 22 | my-func hello! 23 | my-func $1 $2 $3 24 | my-func "$@" 25 | ``` 26 | 27 | ### using arguments (local) 28 | 29 | --- 30 | ``` bash 31 | my-func() { 32 | local arg1=$1 33 | echo hello from my function 34 | echo arg1 is $arg1 35 | } 36 | 37 | my-func 38 | my-func hello! 39 | my-func $1 $2 $3 40 | my-func "$@" 41 | ``` 42 | --- 43 | 44 | ## return codes 45 | 46 | ``` bash 47 | my-func() { 48 | echo hi 49 | return 1 50 | } 51 | 52 | my-func 53 | echo $? 54 | ``` 55 | -------------------------------------------------------------------------------- /tools/finalcut-xml-to-chapters: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # im so sorry i don't know XML or xpath stuff. 4 | # this only needs to work once. 5 | # 6 | # Author: Dave Eddy 7 | # Date: December 02, 2025 8 | # License: MIT 9 | 10 | prevline= 11 | while read -r line; do 12 | if [[ $line == *''* ]]; then 13 | [[ -n $prevline ]] || exit 1 14 | 15 | name=${line//} 16 | name=${name//<\/note>} 17 | 18 | offset=${prevline#*offset=\"} 19 | offset=${offset%%s\"*} 20 | 21 | IFS=/ read -r offset num2 <<< "$offset" 22 | 23 | if [[ -n $num2 ]]; then 24 | ((offset /= num2)) 25 | fi 26 | 27 | TZ=UTC printf -v offset '%(%H:%M:%S)T' "$offset" 28 | 29 | chapter=${name:1:2} 30 | section=${name:4:2} 31 | name=${name:7} 32 | 33 | echo "$offset Chapter $chapter Section $section: $name" 34 | fi 35 | prevline=$line 36 | done 37 | -------------------------------------------------------------------------------- /notes/04-04-command-substitution/readme.md: -------------------------------------------------------------------------------- 1 | # Command Substitution 2 | 3 | ``` bash 4 | thing='whatever' 5 | 6 | echo "thing is $thing" 7 | ``` 8 | 9 | ``` 10 | thing=`whoami` 11 | thing=`ls` 12 | 13 | thing=$(whoami) 14 | 15 | # contrived example, use 16 | thing=$USER 17 | 18 | # we prefer $(...) because nesting weirdness) 19 | 20 | whoami 21 | echo `whoami` 22 | echo `echo \`whoami\`` 23 | echo `echo \`echo \\\.... 24 | echo "`echo \`" 25 | 26 | whoami 27 | echo "$(whoami)" 28 | echo "$(echo "$(whoami)")" 29 | echo "$(echo "$(echo "$(whoami)")")" 30 | 31 | # don't let me catch you writing code like this 32 | 33 | ``` 34 | 35 | ``` 36 | my-func() { 37 | echo hi 38 | } 39 | 40 | thing=$(my-func) 41 | 42 | echo "thing is $thing" 43 | ``` 44 | 45 | ``` 46 | i=5 47 | my-func() { 48 | i=6 49 | echo hi 50 | } 51 | 52 | thing=$(my-func) 53 | 54 | echo "thing is $thing" 55 | ``` 56 | -------------------------------------------------------------------------------- /notes/14-00-color/readme.md: -------------------------------------------------------------------------------- 1 | # Color Output 2 | 3 | ``` 4 | echo 'hello' 5 | echo -e '\e[1mhello' 6 | 7 | # break it down 8 | 9 | echo $'\e[1mhello' 10 | echo $'\033[1mhello' 11 | echo $'\x1b[1mhello' 12 | 13 | x1b == 033 == 27 decimal 14 | https://www.ascii-code.com 15 | 16 | ansi_escape=$'\e' 17 | echo "${ansi_escape}[1mHello World" 18 | 19 | tput bold; echo hi 20 | 21 | tput bold; echo hi; tput sgr0 22 | echo -e '\e[1mhello\e[0m' 23 | 24 | 25 | tput setaf 1; echo hi 26 | tput setaf 2; echo hi 27 | tput setaf 3; echo hi 28 | 29 | echo -e '\e[31mhi' 30 | 31 | printf '\e[38;5;nmHello' 32 | printf '\e[38;5;128mHello\n' 33 | 34 | reference https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 35 | 36 | colors () { 37 | local i 38 | for i in {0..255}; do 39 | printf '\e[38;5;%dmcolor %d\n' "$i" "$i"; 40 | done 41 | printf '\e[0m' 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /notes/03-03-conditionals/readme.md: -------------------------------------------------------------------------------- 1 | # Conditionals 2 | 3 | we can check things internal to bash 4 | 5 | ``` bash 6 | a=2 7 | b=2 8 | if [[ $a == $b ]]; then 9 | echo they are the same 10 | fi 11 | ``` 12 | 13 | we can check the outside world 14 | 15 | ``` bash 16 | if [[ -f /etc/passwd ]]; then 17 | echo /etc/passwd is a file 18 | fi 19 | ``` 20 | 21 | we also have `while` and `until` 22 | 23 | ``` bash 24 | while [[ -f flag.txt ]]; do 25 | rm flag.txt 26 | done 27 | echo done 28 | ``` 29 | 30 | ``` bash 31 | until [[ -f flag.txt ]]; do 32 | touch flag.txt 33 | done 34 | echo done 35 | ``` 36 | 37 | 38 | --- 39 | 40 | these are just COMMANDS 41 | 42 | ``` bash 43 | if true; then 44 | echo hi 45 | fi 46 | ``` 47 | 48 | ``` 49 | while ls; then 50 | sleep 1 51 | fi 52 | ``` 53 | 54 | ``` 55 | [[ -f /etc/passwd ]] 56 | echo $? 57 | [[ -f /etc/passwd ]] && echo it exists 58 | ``` 59 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: YouSuckatProgramming 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /notes/04-01-indexed-arrays/readme.md: -------------------------------------------------------------------------------- 1 | # Indexed Arrays 2 | 3 | ``` bash 4 | array=(foo bar baz) 5 | 6 | echo "$array" 7 | ``` 8 | 9 | ``` bash 10 | array=(foo bar baz) 11 | 12 | echo "${array[0]}" 13 | echo "${array[1]}" 14 | echo "${array[2]}" 15 | 16 | echo "${array[3]}" 17 | 18 | echo "${array[-1]}" 19 | 20 | item=2 21 | echo "${array[item]}" 22 | echo "${array[$item]}" 23 | 24 | echo "${array[*]}" 25 | echo "${array[@]}" 26 | ``` 27 | 28 | ``` bash 29 | array=(foo bar baz) 30 | 31 | for item in "${array[*]}"; do 32 | echo "item is: $item" 33 | done 34 | 35 | for item in "${array[@]}"; do 36 | echo "item is: $item" 37 | done 38 | ``` 39 | 40 | ``` 41 | declare -a array=(...) 42 | ``` 43 | 44 | ``` 45 | # array copy 46 | newarray=( "${array[@]}" ) 47 | newarray=( "${array[@]:1}" ) 48 | newarray=( "${array[@]:0:2}" ) 49 | ``` 50 | 51 | ``` 52 | # array push 53 | array=("${array[@]}" bat) 54 | array+=(bat) 55 | ``` 56 | 57 | declare -p array < on the CLI 58 | -------------------------------------------------------------------------------- /notes/08-01-array-expansion-chars/readme.md: -------------------------------------------------------------------------------- 1 | # Array Expansion 2 | 3 | ``` 4 | for arg in $*; do 5 | echo "<$arg>" 6 | done 7 | 8 | for arg in "$*"; do 9 | echo "<$arg>" 10 | done 11 | 12 | for arg in $@; do 13 | echo "<$arg>" 14 | done 15 | 16 | for arg in "$@"; do 17 | echo "<$arg>" 18 | done 19 | ``` 20 | 21 | arr=(foo bar baz) 22 | 23 | ``` 24 | for arg in ${arr[*]}; do 25 | echo "<$arg>" 26 | done 27 | 28 | for arg in "${arr[*]}"; do 29 | echo "<$arg>" 30 | done 31 | 32 | for arg in ${arr[@]}; do 33 | echo "<$arg>" 34 | done 35 | 36 | for arg in "${arr[@]}"; do 37 | echo "<$arg>" 38 | done 39 | ``` 40 | 41 | 42 | If you find that it works without quotes - you're doing it wrong 43 | 44 | 45 | 46 | - $* - join items together as a string (separated by $IFS) 47 | - "$*" - same as above with no word-splitting on expansion 48 | 49 | - $@ - deconstruct array in the exact form of the array 50 | - "$@" - same as above with no word-splitting on expansion 51 | -------------------------------------------------------------------------------- /notes/09-02-glob-options/readme.md: -------------------------------------------------------------------------------- 1 | # Glob Shell Options 2 | 3 | ``` 4 | mkdir empty 5 | ls empty 6 | ls empty/* 7 | 8 | printf '<%s>\n' empty/* 9 | 10 | touch empty/foo 11 | 12 | printf '<%s>\n' empty/* 13 | 14 | rm empty/foo 15 | 16 | printf '<%s>\n' empty/* 17 | 18 | shopt -s nullglob 19 | 20 | printf '<%s>\n' empty/* 21 | arr=(empty/*) 22 | 23 | echo "${arr[*]}" 24 | 25 | --- 26 | 27 | ls empty/* 28 | 29 | shopt -u nullglob 30 | ``` 31 | 32 | --- 33 | 34 | 35 | ``` 36 | touch empty/.hidden-file 37 | 38 | ls empty 39 | ls -a empty 40 | 41 | echo empty/* 42 | 43 | shopt -s dotglob 44 | 45 | echo empty/* 46 | 47 | arr=(empty/*) 48 | echo "${arr[*]}" 49 | 50 | shopt -u dotglob 51 | 52 | arr=(empty/* empty/.*) 53 | echo "${arr[*]}" 54 | 55 | shopt -s dotglob 56 | echo empty/* empty/.* 57 | ``` 58 | 59 | --- 60 | 61 | ``` 62 | cd ~/dev/ysap 63 | 64 | find . 65 | echo ** 66 | 67 | shopt -s globstar 68 | 69 | echo ** 70 | printf '%s\n' ** 71 | 72 | shopt -s dotglob 73 | 74 | txt=( ./**/*.txt ) 75 | 76 | count it, show loop 77 | ``` 78 | -------------------------------------------------------------------------------- /notes/04-07-what-we-have/01-arrays: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | process-indexed() { 4 | local arguments=("$@") 5 | echo "found: ${#arguments[@]} arguments" 6 | 7 | local IFS=, 8 | echo "${arguments[*]}" 9 | } 10 | 11 | process-associative() { 12 | local arguments=("$@") 13 | echo "found: ${#arguments[@]} total arguments" 14 | 15 | declare -A unique 16 | local item 17 | for item in "${arguments[@]}"; do 18 | unique[$item]=1 19 | done 20 | 21 | echo "found: ${#unique[@]} unique arguments" 22 | } 23 | 24 | process-commands() { 25 | local arguments=("$@") 26 | echo "found: ${#arguments[@]} total arguments" 27 | 28 | local item 29 | for item; do 30 | echo "running command: $item" 31 | "$item" # hmmm 32 | done 33 | } 34 | 35 | cmd=$1 36 | echo "script called with arguments: $*" 37 | shift 38 | echo "script shifted to arguments: $*" 39 | 40 | case "$cmd" in 41 | indexed) 42 | process-indexed "$@" 43 | ;; 44 | associative) 45 | process-associative "$@" 46 | ;; 47 | commands) 48 | process-commands "$@" 49 | ;; 50 | *) 51 | echo "unknown cmd: $cmd" >&2 52 | exit 1 53 | ;; 54 | esac 55 | -------------------------------------------------------------------------------- /notes/11-00-printf/readme.md: -------------------------------------------------------------------------------- 1 | # Understanding printf 2 | ``` 3 | s='hello world' 4 | 5 | echo "$s" 6 | 7 | printf "$s" # wrong 8 | printf '%s\n' "$s" # right 9 | 10 | printf '>>> the string is: %s!! <<' "$s" 11 | 12 | $ printf '%5s %5s\n' hello world 13 | hello world 14 | $ printf '%5s %5s\n' a b 15 | a b 16 | $ printf '%-5s %-5s\n' a b 17 | a b 18 | $ printf '>>%*s<<\n' 20 dave 19 | >> dave<< 20 | 21 | 22 | $ printf '%d\n' 55 23 | 55 24 | $ printf '%5d\n' 55 25 | 55 26 | $ printf '%-5d\n' 55 27 | 55 28 | $ printf '%05d\n' 55 29 | 00055 30 | 31 | $ printf '%d\n' hello 32 | -bash: printf: hello: invalid number 33 | 0 34 | $ printf '%d\n' 05 35 | 5 36 | $ printf '%d\n' 06 37 | 6 38 | $ printf '%d\n' 07 39 | 7 40 | $ printf '%d\n' 08 41 | -bash: printf: 08: invalid octal number 42 | 0 43 | # check exit code 44 | 45 | $ printf '%d\n' 09 46 | -bash: printf: 09: invalid octal number 47 | 0 48 | $ printf '%d\n' 010 49 | 8 50 | $ printf '%d\n' 011 51 | 9 52 | $ printf '%d\n' 0xff 53 | 255 54 | 55 | 56 | $ printf '%q\n' 'foo$bar' 57 | foo\$bar 58 | 59 | $ printf 'hello%bworld\n' '\x0a' 60 | hello 61 | world 62 | ``` 63 | -------------------------------------------------------------------------------- /notes/15-00-interactive-ps1/readme.md: -------------------------------------------------------------------------------- 1 | # PS1 Variable 2 | 3 | show bashrc i've been using this whole time 4 | 5 | 6 | reconstruct PS1 prompt as part of the video 7 | 8 | PS1='$ ' 9 | 10 | ``` 11 | \d The date, in "Weekday Month Date" format (e.g., "Tue May 26"). 12 | 13 | \h The hostname, up to the first . (e.g. deckard) 14 | \H The hostname. (e.g. deckard.SS64.com) 15 | 16 | \j The number of jobs currently managed by the shell. 17 | 18 | \l The basename of the shell's terminal device name. 19 | 20 | \s The name of the shell, the basename of $0 (the portion following the final slash). 21 | 22 | \t The time, in 24-hour HH:MM:SS format. 23 | \T The time, in 12-hour HH:MM:SS format. 24 | \@ The time, in 12-hour am/pm format. 25 | 26 | \u The username of the current user. 27 | 28 | \v The version of Bash (e.g., 2.00) 29 | 30 | \V The release of Bash, version + patchlevel (e.g., 2.00.0) 31 | 32 | \w The current working directory. 33 | \W The basename of $PWD. 34 | 35 | \! The history number of this command. 36 | \# The command number of this command. 37 | 38 | \$ If you are not root, inserts a "$"; if you are root, you get a "#" (root uid = 0) 39 | ``` 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The Complete Bash Scripting Course 2 | =========== 3 | 4 | Bash scripting course and guide created by [Dave 5 | Eddy](https://www.daveeddy.com) of [ysap.sh](https://ysap.sh). 6 | 7 | [![YouTube Video](http://img.youtube.com/vi/Sx9zG7wa4FA/hqdefault.jpg)]( 8 | http://www.youtube.com/watch?v=Sx9zG7wa4FA "Complete Bash Scripting Guide - Full Bash Course by Dave Eddy") 9 | 10 | - Watch on [YouTube](http://www.youtube.com/watch?v=Sx9zG7wa4FA) 11 | 12 | Learn the Bash Shell and master beginner all the way up to advanced Bash 13 | scripting techniques. 14 | 15 | --- 16 | 17 | The course is completely free - find out more at the website: 18 | 19 | https://course.ysap.sh 20 | 21 | Repository 22 | ---------- 23 | 24 | This repository is for anyone looking for the code written during the course as 25 | well as my own personal notes while recording it. The repo is broken into the 26 | following directories. 27 | 28 | - [foo/](foo/) - The same `foo` directory that was made during the course. 29 | - [notes/](notes/) - Notes and code for each section of the course. 30 | - [tools/](tools/) - Tools used during the course creation. 31 | - [website/](website/) - Scripts used for creating the [course.ysap.sh](https://course.ysap.sh) site. 32 | 33 | License 34 | ------- 35 | 36 | All code licensed under the MIT License 37 | -------------------------------------------------------------------------------- /tools/make-metadata-image: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | chapter= 4 | section= 5 | title= 6 | outfile= 7 | while getopts 'c:s:t:o:' opt; do 8 | case "$opt" in 9 | c) chapter=$OPTARG;; 10 | s) section=$OPTARG;; 11 | t) title=$OPTARG;; 12 | o) outfile=$OPTARG;; 13 | *) exit 1;; 14 | esac 15 | done 16 | 17 | if [[ -z $chapter || -z $section || -z $title || -z $outfile ]]; then 18 | echo 'Usage: make-metadata-image -c -s
-t -o <outfile>' >&2 19 | exit 1 20 | fi 21 | 22 | FONT='./fonts/FiraMono-Bold.ttf' 23 | LOGO='./assets/logo.png' 24 | WIDTH=666 25 | HEIGHT=848 26 | 27 | TITLE_W=$((WIDTH - 20 * 2)) 28 | TITLE_H=100 29 | 30 | magick -size "${WIDTH}x${HEIGHT}" xc:black \ 31 | -font "$FONT" -fill '#bbb' \ 32 | -kerning -2 \ 33 | \ 34 | -gravity north -pointsize 88 \ 35 | -annotate +0+13 "Chapter $chapter" \ 36 | -gravity north -pointsize 88 \ 37 | -annotate +0+110 "Section $section" \ 38 | \( \ 39 | -background none -fill '#bbb' \ 40 | -font "$FONT" -gravity north \ 41 | +pointsize \ 42 | -size "${TITLE_W}x${TITLE_H}" \ 43 | caption:"$title" \ 44 | \) -gravity north -geometry +0+220 -composite \ 45 | \ 46 | \( \ 47 | "$LOGO" \ 48 | -filter point -resize 500x500 \ 49 | -alpha on -channel A -evaluate set 7% +channel \ 50 | \) -gravity north -geometry +0+370 -composite \ 51 | \ 52 | -font "$FONT" -fill '#666' \ 53 | -gravity south -pointsize 36 \ 54 | -annotate +0+90 'author: Dave Eddy' \ 55 | -gravity south -pointsize 36 \ 56 | -annotate +0+50 'channel: @yousuckatprogramming' \ 57 | -gravity south -pointsize 36 \ 58 | -annotate +0+10 'website: https://course.ysap.sh' \ 59 | \ 60 | "$outfile" 61 | -------------------------------------------------------------------------------- /website/make-curl-website: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # make the curl-able version of the course 4 | # 5 | # Author: Dave Eddy <dave@daveeddy.com> 6 | # Date: December 13, 2025 7 | # License: MIT 8 | 9 | 10 | LINE_COLORS=( 11 | $'\e[48;5;160m' 12 | $'\e[48;5;202m' 13 | $'\e[48;5;214m' 14 | ) 15 | 16 | TEXT_COLOR=$'\e[38;5;223m' 17 | GREEN_COLOR=$'\e[38;5;120m' 18 | RST=$'\e[0m' 19 | 20 | C1=$TEXT_COLOR 21 | C2=$GREEN_COLOR 22 | 23 | WIDTH=80 24 | 25 | cat <<-EOF 26 | 27 | 28 | 29 | $C1 ┏┳┓┓ ┏┓ ┓ $C2 ┳┓ ┓ ┏┓ • • $C1 ┏┓ 30 | $C1 ┃ ┣┓┏┓ ┃ ┏┓┏┳┓┏┓┃┏┓╋┏┓ $C2 ┣┫┏┓┏┣┓ ┗┓┏┏┓┓┏┓╋┓┏┓┏┓ $C1 ┃ ┏┓┓┏┏┓┏┏┓ 31 | $C1 ┻ ┛┗┗ ┗┛┗┛┛┗┗┣┛┗┗ ┗┗ $C2 ┻┛┗┻┛┛┗ ┗┛┗┛ ┗┣┛┗┗┛┗┗┫ $C1 ┗┛┗┛┗┻┛ ┛┗ 32 | $C1 ┛ $C2 ┛ ┛ $C1 33 | 34 | 35 | EOF 36 | echo -n "$C1" 37 | 38 | items=( 39 | '7+ Hours' 40 | '60+ Sections' 41 | 'Created by Dave Eddy' 42 | 'https://course.ysap.sh' 43 | ) 44 | # calculate padding to center justify it first (look for longest line) 45 | longest=-1 46 | for item in "${items[@]}"; do 47 | # center justify it 48 | len=${#item} 49 | if ((len > longest)); then 50 | longest=$len 51 | fi 52 | done 53 | pad=$(((WIDTH - longest) / 2)) 54 | for item in "${items[@]}"; do 55 | len=${#item} 56 | i=$(( pad + (longest - len) / 2)) 57 | ((i -= 2)) # i eyeballed this 58 | printf '%*s %s\n' "$i" ' ' "$item" 59 | done 60 | 61 | printf '\n\n' 62 | for color in "${LINE_COLORS[@]}"; do 63 | printf '%s%s%*s%s\n' "$RST" "$color" "$WIDTH" ' ' "$RST" 64 | done 65 | 66 | echo 67 | echo -n "$TEXT_COLOR" 68 | printf ' %-61s %s\n' 'Bash Course' 'course.ysap.sh' 69 | echo -n "$RST" 70 | echo 71 | -------------------------------------------------------------------------------- /notes/08-00-parameter-expansion/readme.md: -------------------------------------------------------------------------------- 1 | # Parameter Expansion 2 | 3 | == casing == 4 | 5 | ``` bash 6 | s='dave eddy' 7 | ${s^} 8 | ${s^^} 9 | ${s^^d} 10 | ${s^d} 11 | ${s^^da} 12 | ${s^^[da]} 13 | ${s,,} 14 | s='DAVE EDDY' 15 | ${s,,} 16 | ${s,} 17 | ${s,,D} 18 | ${s,D} 19 | ${s,,DA} 20 | ${s,,[DA]} 21 | ``` 22 | 23 | == default values == 24 | 25 | ``` bash 26 | name=$1 27 | 28 | echo "hello $name!" 29 | 30 | echo 'done' 31 | ``` 32 | 33 | ``` 34 | name=${1:-dave} 35 | name=${1:-$USER} 36 | 37 | name=${1?} 38 | name=${1?You must supply a name} 39 | name=${1:?} 40 | name=${1:?You must supply a name} 41 | ``` 42 | 43 | == replacements == 44 | 45 | ``` 46 | path=/home/dave/abc.txt 47 | 48 | echo "$path" | tr a o 49 | echo "${path/a/o} 50 | echo "${path//a/o} 51 | 52 | basename "$path" 53 | dirname "$path" 54 | 55 | echo "${path}" 56 | echo "${path#*/}" 57 | echo "${path##*/}" 58 | 59 | echo "${path}" 60 | echo "${path%/*}" 61 | ``` 62 | 63 | ``` 64 | echo "${path//[abc]/5}" 65 | ``` 66 | 67 | == substrings == 68 | 69 | s='dave eddy' 70 | 71 | echo "${s:0}" 72 | echo "${s:1}" 73 | echo "${s:2}" 74 | echo "${s:3}" 75 | 76 | echo "${s:-1}" 77 | echo "${s: -1}" 78 | echo "${s: -2}" 79 | 80 | echo "${s:0:1}" 81 | echo "${s:1:2}" 82 | echo "${s:2}" 83 | echo "${s:3:-1}" 84 | 85 | 86 | Loop character by character 87 | 88 | s='dave eddy' 89 | len=${#s} 90 | 91 | for ((i = 0; i < len; i++)); do 92 | c=${s:i:1} 93 | echo "$c" 94 | done 95 | 96 | 97 | 98 | == arrays == 99 | 100 | ``` 101 | arr=(foo bar baz) 102 | 103 | printf '<%s>\n' "${arr[@]}" 104 | printf '<%s>\n' "${arr[@]:1:1}" 105 | 106 | $ printf '<%s>\n' "${arr[@]/a/1}" 107 | $ printf '<%s>\n' "${arr[@]#b}" 108 | $ printf '<%s>\n' "${arr[@]%z}" 109 | -------------------------------------------------------------------------------- /website/download.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8"> 5 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 | <title>The Complete Bash Scripting Course - download 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | ← home
19 |

The Complete Bash Scripting Course

20 | 21 | Bash scripting course and guide created by Dave Eddy of 23 | ysap.sh. 24 |

25 | 26 | Below is DRM-free mp4 27 | file of the video. You're free to make as 28 | many personal copies as you'd like and play it on any of 29 | your devices. Please do not share or distribute this 30 | video (don't re-upload anywhere) - if you have friends 31 | that may be interested in the course please just link 32 | them the YouTube video or this website instead. 33 | 34 |

35 | 36 |

Download

37 | This advanced download system uses the honor system - so please make sure 39 | you've paid before downloading the file. 40 |

41 | 44 |
45 | Or download on your terminal: 46 |

47 | wget https://course.ysap.sh/ysap-bash-course-2025.mp4
49 | 50 | 54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /website/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Undercurrents 3 | * 4 | * Author: Dave Eddy 5 | * Date: May 04, 2025 6 | * License: MIT 7 | */ 8 | 9 | /* terminal theme (from theme bash script) */ 10 | :root { 11 | --color1: #5fffff; 12 | --color2: #ff87af; 13 | --color3: #87ff87; 14 | --color4: #666; 15 | --color5: #ffd7af; 16 | } 17 | 18 | /* reset */ 19 | * { 20 | margin: 0; 21 | padding: 0; 22 | } 23 | 24 | /* helper */ 25 | .center { 26 | text-align: center; 27 | } 28 | 29 | .title { 30 | color: var(--color3); 31 | } 32 | 33 | .highlight { 34 | color: var(--color3); 35 | } 36 | 37 | .hidden { 38 | display: none; 39 | } 40 | 41 | .float-right { 42 | float: right; 43 | } 44 | 45 | .shadow { 46 | border: 1px solid #87ff8755; /* color3 */ 47 | box-shadow: 0 0 80px #87ff8774; 48 | } 49 | 50 | @media (max-width: 650px) { 51 | .hide-mobile{ 52 | display: none; 53 | } 54 | } 55 | 56 | /* layout and design */ 57 | body { 58 | background-color: #000; 59 | color: var(--color5); 60 | line-height: 18px; 61 | font-size: 13px; 62 | -webkit-text-size-adjust: 100%; 63 | 64 | } 65 | 66 | ul > li { 67 | font-size: 13px !important; 68 | } 69 | 70 | body, code { 71 | font-family: "Fira Mono", monospace; 72 | font-weight: 400; 73 | } 74 | 75 | b, strong, h1, h2, h3, h4, h5, h6 { 76 | font-weight: 900; 77 | } 78 | 79 | #container { 80 | position: relative; 81 | margin-right: auto; 82 | margin-left: auto; 83 | margin-top: 10px; 84 | max-width: 565px; 85 | padding: 0 6px; 86 | text-align: center; 87 | } 88 | 89 | a, a:visited { 90 | color: var(--color1); 91 | text-decoration: none; 92 | } 93 | 94 | .self-portrait { 95 | border-radius: 50%; 96 | } 97 | 98 | h1 { 99 | margin-bottom: 21px; 100 | margin-top: 18px; 101 | line-height: 30px; 102 | } 103 | 104 | .footer { 105 | text-align: center; 106 | font-size: 12px; 107 | } 108 | 109 | ul.controls, ul.more, pre.terminal { 110 | text-align: left; 111 | max-width: 580px; 112 | margin-right: auto; 113 | margin-left: auto; 114 | } 115 | 116 | pre.terminal { 117 | padding: 30px; 118 | } 119 | 120 | div.notes { 121 | width: 380px; 122 | margin-right: auto; 123 | margin-left: auto; 124 | text-align: left; 125 | font-size: 12px; 126 | } 127 | 128 | .screenshot { 129 | width: 100%; 130 | } 131 | 132 | ul { 133 | list-style-position: inside; 134 | } 135 | 136 | #not-yet-released { 137 | display: none; 138 | } 139 | 140 | .yt-container { 141 | display: flex; 142 | justify-content: center; 143 | } 144 | 145 | iframe { 146 | aspect-ratio: 16 / 9; 147 | width: 100% !important; 148 | } 149 | -------------------------------------------------------------------------------- /foo/bashrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # If not running interactively, don't do anything 4 | [[ -n $PS1 ]] || return 5 | 6 | # Set environment 7 | export EDITOR='vim' 8 | export GREP_COLOR='1;36' 9 | export LSCOLORS='ExGxbEaECxxEhEhBaDaCaD' 10 | export PAGER='less' 11 | export TZ='America/New_York' 12 | export VISUAL='vim' 13 | export PATH=$PATH:/opt/homebrew/bin 14 | 15 | # Support colors in less 16 | export LESS_TERMCAP_mb=$(tput bold; tput setaf 1) 17 | export LESS_TERMCAP_md=$(tput bold; tput setaf 1) 18 | export LESS_TERMCAP_me=$(tput sgr0) 19 | export LESS_TERMCAP_se=$(tput sgr0) 20 | export LESS_TERMCAP_so=$(tput bold; tput setaf 3; tput setab 4) 21 | export LESS_TERMCAP_ue=$(tput sgr0) 22 | export LESS_TERMCAP_us=$(tput smul; tput bold; tput setaf 2) 23 | export LESS_TERMCAP_mr=$(tput rev) 24 | export LESS_TERMCAP_mh=$(tput dim) 25 | export LESS_TERMCAP_ZN=$(tput ssubm) 26 | export LESS_TERMCAP_ZV=$(tput rsubm) 27 | export LESS_TERMCAP_ZO=$(tput ssupm) 28 | export LESS_TERMCAP_ZW=$(tput rsupm) 29 | 30 | # Shell Options 31 | shopt -s cdspell 32 | shopt -s checkwinsize 33 | shopt -s extglob 34 | 35 | # Bash Version >= 4 36 | shopt -s autocd 2>/dev/null || true 37 | shopt -s dirspell 2>/dev/null || true 38 | 39 | # Aliases 40 | alias ..='echo "cd .."; cd ..' 41 | alias ag='rg' # sorry silver searcher 42 | alias chomd='chmod' 43 | alias externalip='curl -sS https://ysap.sh/ip' 44 | alias gerp='grep' 45 | alias hl='rg --passthru' 46 | alias l='ls' 47 | alias ll='ls -lha' 48 | alias suod='sudo' 49 | 50 | # Aliases (if applicable) 51 | grep --color=auto < /dev/null &>/dev/null && 52 | alias grep='grep --color=auto' 53 | 54 | xdg-open --version &>/dev/null && 55 | alias open='xdg-open' 56 | 57 | # Enable color support of ls 58 | if ls --color=auto /dev/null &>/dev/null; then 59 | alias ls='ls -p --color=auto' 60 | else 61 | alias ls='ls -p -G' 62 | fi 63 | 64 | make-prompt() { 65 | local rst=$'\e[0m' 66 | local bold=$'\e[1m' 67 | local col1=$'\e[38;5;24m' 68 | local col2=$'\e[38;5;54m' 69 | local col3=$'\e[38;5;114m' 70 | local col4=$'\e[38;5;84m' 71 | 72 | # make prompt: "dave@ysap $ " 73 | 74 | # username 75 | PS1='\['$col1'\]dave\['$rst'\]' 76 | 77 | # @ 78 | PS1+='\['$bold'\]\['$col2'\]@\['$rst'\]' 79 | 80 | # hostname 81 | PS1+='\['$col3'\]ysap ' 82 | 83 | # prompt character 84 | PS1+='\['$col4'\]\$\['$rst'\] ' 85 | } 86 | make-prompt 87 | 88 | # Prompt command 89 | _prompt_command() { 90 | local user=$USER 91 | local host=${HOSTNAME%%.*} 92 | local pwd=${PWD/#$HOME/\~} 93 | local ssh= 94 | [[ -n $SSH_CLIENT ]] && ssh='[ssh] ' 95 | printf "\033]0;%s%s@%s:%s\007" "$ssh" "$user" "$host" "$pwd" 96 | } 97 | PROMPT_COMMAND=_prompt_command 98 | 99 | # print a colorized diff 100 | colordiff() { 101 | local red=$(tput setaf 1 2>/dev/null) 102 | local green=$(tput setaf 2 2>/dev/null) 103 | local cyan=$(tput setaf 6 2>/dev/null) 104 | local reset=$(tput sgr0 2>/dev/null) 105 | 106 | diff -u "$@" | awk " 107 | /^\-/ { 108 | printf(\"%s\", \"$red\"); 109 | } 110 | /^\+/ { 111 | printf(\"%s\", \"$green\"); 112 | } 113 | /^@/ { 114 | printf(\"%s\", \"$cyan\"); 115 | } 116 | 117 | { 118 | print \$0 \"$reset\"; 119 | }" 120 | 121 | return "${PIPESTATUS[0]}" 122 | } 123 | 124 | # Print all 256 colors 125 | colors() { 126 | local i 127 | for i in {0..255}; do 128 | printf "\x1b[38;5;${i}mcolor %d\n" "$i" 129 | done 130 | tput sgr0 131 | } 132 | 133 | # Copy stdin to the clipboard 134 | copy() { 135 | pbcopy 2>/dev/null || 136 | xsel 2>/dev/null || 137 | clip.exe 138 | 139 | } 140 | 141 | # print a rainbow if truecolor is available to the terminal 142 | truecolor-rainbow() { 143 | local i r g b 144 | for ((i = 0; i < 77; i++)); do 145 | r=$((255 - (i * 255 / 76))) 146 | g=$((i * 510 / 76)) 147 | b=$((i * 255 / 76)) 148 | ((g > 255)) && g=$((510 - g)) 149 | printf '\033[48;2;%d;%d;%dm ' "$r" "$g" "$b" 150 | done 151 | tput sgr0 152 | echo 153 | } 154 | 155 | # load completion 156 | . /etc/bash/bash_completion 2>/dev/null || 157 | . ~/.bash_completion 2>/dev/null 158 | 159 | true 160 | -------------------------------------------------------------------------------- /tools/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013, The Mozilla Corporation and Telefonica S.A. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://openfontlicense.org 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Complete Bash Scripting Course - ysap.sh 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

The Complete Bash Scripting Course

19 | 20 | Bash scripting course and guide created by Dave Eddy of 22 | ysap.sh. 23 | 24 |

25 |
26 |
27 |

Course Coming Soon!

28 |
29 | Watch the premier on YouTube: youtu.be/Sx9zG7wa4FA 30 |
31 |
32 | 33 | releases soon 34 |
35 |
36 |
37 |
38 |
39 |
40 | 48 |
49 |

50 | Watch on YouTube. 51 |

52 | Learn the Bash Shell and master beginner all the way up 53 | to advanced Bash scripting techniques. 54 |

55 | 56 |

Purchase

57 | This course is completely 58 | free and available as a YouTube video. 59 | If you would like to support the content please 60 | consider: 61 |

62 | 72 | [all prices USD] 73 |

74 | You can buy the course if you would like to receive a 75 | DRM-free mp4 file of the video. You're free to make as 76 | many personal copies as you'd like and play it on any of 77 | your devices. Please do not share or distribute this 78 | video (don't re-upload anywhere) - if you have friends 79 | that may be interested in the course please just link 80 | them the YouTube video or this website instead. 81 |

82 | If you've already paid you can get your download link here. 83 |

84 | 85 |

Source Code

86 | All source code written and used during the course can be found in the GitHub repo: 87 |

88 | https://github.com/bahamas10/bash-course 89 | 90 |

91 | 92 |

Support

93 | 94 | Join my discord 95 | server to talk to other members in the 96 | community! Check out the #bash-course channel 98 | specifically for discussing and asking questions related 99 | to this course. 100 | 101 |

102 | 103 |

References

104 | Websites and videos referenced during the course: 105 |

106 | 129 |
130 | 131 |

Chapter Timestamps

132 | Chapters and section timestamps for the course: 133 |

134 | 200 |
201 |

Rights

202 | If you are a teacher or professor and would like to play 203 | parts or all of this course to your students you have my 204 | permission. 205 |

206 | If you are a content-creator and would like 207 | to react or follow along while streaming (including 208 | uploading the vod to YouTube) you have my permission. 209 |

210 |
211 | 212 | 216 |
217 | 268 | 269 | 270 | --------------------------------------------------------------------------------