commit cf38bcd2d35fb15a90731289adc232826ef41a1f
Author: David Voznyarskiy <31452046-davidvoz@users.noreply.gitlab.com>
Date: Sat, 8 Nov 2025 11:39:56 -0800
added rest
Diffstat:
66 files changed, 1746 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
diff --git a/README.md b/README.md
@@ -0,0 +1,14 @@
+# shelllings
+
+## about
+A practical way of learning shell through exercises.
+This has a focus on a linux environment.
+This is also mainly a way for me to learn shell as well so bugs and unoptimized code should be expected.
+
+## installing and running
+
+```
+git clone https://gitlab.com/davidvoz/shelllings.git
+cd shelllings
+sh run_me.sh
+```
diff --git a/exercises/01_echo.sh b/exercises/01_echo.sh
@@ -0,0 +1,15 @@
+
+# To write the first shell command, see the other files that end with
+# .sh and see what they have as their first line at the very top. Do
+# the same here.
+#
+# The first command you will learn is 'echo'. This is the same as printf
+# or print or cout in other languages. To print a message, type echo,
+# and then surround the message with double quotation marks.
+#
+# $ echo "Welcome to shelllings"
+#
+# The '$' symbol above is not needed, it is used to show you what is a
+# command. As is custom, write your first Hello World program below
+
+echo
diff --git a/exercises/02_variables.sh b/exercises/02_variables.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Assigning variables in shell go like this
+#
+# $ project="shellling"
+# $ echo "Welcome to $project" # output: Welcome to shellling
+#
+# Notice that there are no spaces around the '=', and the '$' is being
+# used in the echo command.
+#
+# Fix the command below
+
+name = Bob
+echo "Hello name" # this is a comment too, the program will ignore this
+ # because of the pound sign
diff --git a/exercises/03_listing.sh b/exercises/03_listing.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# In the folder you are in, you can list the folder contents by typing
+# 'ls' and seeing what is there. Some shell configs will have the
+# folders bold and different colored to help with navigation.
+#
+# $ ls # lists contents
+# $ ls -a # lists hidden contents as well as unhidden
+# $ ls -A # same as -a but without . and ..
+# Also, not entirely POSIX compliant but many modern shells
+# run it fine
+# $ ls -l # shows linkages in files, like how we did with
+# ls -l /bin/sh
+# $ ls -R # recursively goes into folders listing contents out
+# $ ls -AR # separate arguments can be combined to execute both
+#
+# For your exercise, a random file will be created and hidden. Find
+# the file and delete it.
+
+rm file_name # rm means to remove, do not change that part
diff --git a/exercises/04_navigation.sh b/exercises/04_navigation.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# When you installed this program, it was written to 'cd' into this
+# directory (folder). cd stands for change directory, when you cd'd
+# into shelllings/ you changed into that directory.
+#
+# $ cd foo/ # takes you into the foo folder
+# $ cd .. # takes you out of 1 upper folder
+# $ cd ../.. # takes you out of 2 upper folders
+# $ cd ~ # navigates to your user's home directory, may not
+# work with all shells
+# $ pwd # shows you which folder path you are in
+#
+# The exercise will begin when you run 'sh shelllings.sh 3' and you
+# will have to find a random folder. Navigate using the commands above
+# to find a folder with a password in it. You may want to use the 'ls'
+# command from the previous lesson.
+#
+# When you have found the file, use this command. Replace the file_name
+# with the actual file's name if you cannot open it
+#
+# $ cat file_name
+#
+# Don't worry about deleting the newly created folders, it should be
+# automatically deleted.
+
+password=""
+echo "$password"
diff --git a/exercises/05_creating.sh b/exercises/05_creating.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# To create a folder, use the touch command
+#
+# $ touch file
+# $ touch file1 file2
+#
+# To create folder, use the make directory command
+#
+# $ mkdir folder
+# $ mkdir folder1 folder2
+#
+# You can also create more folders at a single time
+#
+# $ mkdir -p dir1/dir2 # enables the parent option because without
+# it you cannot create more than a layer
+# deep
+# $ touch dir1/dir2/file # lets you touch a file deeper
+#
+# For your exercise, fix the code that is meant to create a folder that
+# is at least 3 directories deep with a file named 'file'
+
+mkdir
+touch file
+
+# do not remove the newly created folder just yet
diff --git a/exercises/06_removing.sh b/exercises/06_removing.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# The previous exercises have created a mess in this folder. We can
+# clean it up using the 'rm' command.
+#
+# $ rm file # removes file
+# $ rm file1 file2 # removes files
+# $ rm -r dir/ # recursively removes files and folders
+# $ rm -rf dir/ # forces the previous command
+# $ rm * # removes all the files within the current folder,
+# saves time on not having to input every file
+# $ rmdir dir/ # removes an empty directory
+#
+# The astrisk is a very important symbol in shell programming. Take the
+# example below
+#
+# $ rm folder/*.txt
+#
+# This command will rm all the files within folder/ that end with .txt
+# meaning anyfile that ends with .c, .sh, or has no dot endings will
+# not be deleted.
+#
+# $ rm a*z
+#
+# The above command will remove any combination of files that start with
+# 'a' and end with 'z'. Including files directly named 'az' with nothing
+# inbetween.
+#
+# For your next exercise, remove the custom folder you created earlier
+# using the commands above directly in the terminal. An extra folder
+# will be created and try to remove everything in the most efficient
+# way possible when typing. Don't change anything in the code snippet
+# below
+rm -rf 06_folder
+mkdir 06_folder
+for i in $(seq 1 100); do
+ touch 06_folder/file$i
+done
+
+# in one line below, find a way to delete all the 100 files created
+# from above without deleting the folder. Test out different ways,
+# maybe combine arguments to accomplish this. Do not add more or
+# less than 44 lines of code in this file.
+
diff --git a/exercises/07_wordcount.sh b/exercises/07_wordcount.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# wc, word count, is a program that measures and counts bytes,
+# characters, and lines
+#
+# $ wc -l file # counts the amount of lines in a file
+# $ wc -w file # counts the amount of words in a file
+# $ wc -m file # characters
+# $ wc -c file # bytes
+# $ wc file # gives out the lines, words, and bytes of a file
+#
+file="LICENSE"
+word_count=$(wc -w $file)
+line_count= # fix this
+byte_count= # fix this
+
+echo "$line_count"
+echo "$word_count"
+echo "$byte_count"
+
+# One more thing you should get used to is standard input. Run the wc
+# command without an argument and it will take you to a new line. Type
+# whatever you want and end it with a ctrl d. You can run wc -w into
+# this environment as well to see the length of your sentence.
diff --git a/exercises/08_cat.sh b/exercises/08_cat.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# We briefly mentioned the cat command earlier. It prints out all the
+# data of a file into the terminal. And like the animal, 2 more commands
+# are used. Head to print out the first lines of a file and tail to
+# print out the last lines of a file.
+#
+# $ cat file # prints out the entiretly of a file
+# $ head file # prints out the first 10 lines of a file
+# $ tail file # prints out the last 10 lines of a file
+# $ head -n 1 # prints out only the first line of a file
+# $ tail -n 18 # prints out only the last 18 lines of a file
+# $ head -n -4 # prints out all lines except the last 4 lines
+# $ tail -n +9 # prints out from line 9, onwards
+#
+# Fix the command below to do a word count on the last 50 lines of the
+# LICENSE file in this repository. Don't change the new file name.
+
+head 50 LICENSE > 08_cat # the '>' sign is discussed later. For now
+# know that the command should be outputting
+# the last 50 lines into a file called 08_cat
+wc -l 08_cat
diff --git a/exercises/09_direction.sh b/exercises/09_direction.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# You can direct data to go where you want with these commands.
+#
+# $ echo "message" > file # 'message' is the only data in the file
+# if there is extra data, it is erased
+# $ echo "message" >> file # 'message' is ADDED ONTO the end of
+# file with the previous data present
+# $ command 2> file # only direct message errors into file
+# $ command 2>> file # appends only error messages into file
+# $ command > file 2>&1 # directs standard output (>) and error
+# messages (2>) into file
+# $ command >> file 2>&1 # same as before but appends
+#
+# Let's say for whatever reason you want to add all the contents of the
+# exercises/ folder and the license into one file. Fix the commands
+# below
+
+cat exercises >> 09_file
+cat LICENSE > 09_file
diff --git a/exercises/10_piping.sh b/exercises/10_piping.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+# We used this as an example for data direction and up until this point
+# we had to run separate commands.
+#
+# $ head -n 50 LICENSE > 09_file
+# $ wc -w 09_file
+#
+# But now we can combine this using pipes. The '|' symbol is used to
+# basically accomplish what was shown before
+#
+# $ head -n 50 LICENSE | wc -w
+#
+# Notice that we did not have to add a file onto wc -l or even enter
+# a standard input environment.
+#
+# As for the actual exercise, change all of these commands into pipes.
+# NOTE the testing of this case is very sensitive, try being concise
+# and leave unnecessary whitespace out of your answers. Leave the white
+# space needed for human readability.
+
+cat LICENSE > file
+wc -w file
+
+ls -A "$HOME" > file
+wc -l file
+
+# As a fun exercise, run that command above and see how cluttered your
+# home directory is. In fact, in many modern shells, "$HOME" isn't
+# needed, '~' is enough. 'ls -A ~' will list out your home directory
diff --git a/exercises/11_userinput.sh b/exercises/11_userinput.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# We can go on ahead and read user input easily
+#
+# $ echo "Enter your name below"
+# $ read user_input
+# $ echo "$user_input"
+#
+# Unfortunately echo will take the user input into a new line. To avoid
+# that you can use printf (like in C) or, less posix compliant, use
+# 'echo -n' to avoid that new line
+#
+# $ printf "Enter your name: " # equivalent as the next line
+# $ echo -n "Enter your name: "
+#
+# Fix this program that is meant to input the user's input into a file
+
+printf "Enter your sentence: "
+read user_input > 11_file
diff --git a/exercises/12_arguments.sh b/exercises/12_arguments.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# When you run a shell, you can input arguments into the .sh file to be
+# used inside of it.
+#
+# $ sh test.sh file_name
+#
+# And then within test.sh, we can have code like this
+#
+# $ wc -l $1
+#
+# The arguments that can be into a script are read as follows
+#
+# $0 # the script name
+# $1 # the first argument
+# $2 # the second argument
+# $n # the nth argument
+# $@ # all arguments as separate words
+# $* # all arguments as a single word
+# $# # number of arguments passed
+#
+# Fix below if the arguments passed were: ugly Jayce smelly tofu
+
+echo "One day, [insult] [name] left a [adjective] [food] in his pocket."
+echo "A tiger smelt him and ate him."
+echo "The words you used were: $"
+echo "The number of words you used were: $"
diff --git a/exercises/13_copying_and_moving.sh b/exercises/13_copying_and_moving.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# You can copy files and move them from one location to another. When
+# you "move" a file, you are changing its location. When you copy a
+# file, within the same filesystem, this is just renaming the file path.
+# When you copy a file, you create a new, separate copy of the file's
+# contents in another location.
+#
+# $ cp file1 file2 # this copies the contents, permissions,
+# and metadata
+# $ cp file dir/ # copies the file into a directory
+#
+# $ mv file /to/new/path/ # moves the file to a new location
+# $ mv old_name new_name
+#
+# Often if you really don't want the metadata, and just want to have
+# the contents copied, you can use the cat command
+#
+# $ cat file_withmetadata > new_file
+#
+# Fix this program that is built off from the previous exercise by
+# having the file be renamed into a file named 11_user_input
+printf "Enter your sentence: "
+read user_input
+echo "$user_input" > 11_file
+
diff --git a/exercises/14_conditionals.sh b/exercises/14_conditionals.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+# As with other programming languages, we can evaluate if conditions or
+# statements are true or false. In this exercise we will be dealing with
+# strings and basic if/else structure. Below are 3 examples of this.
+#
+# $ option="yes"
+# $ if [ "$option" = "yes" ]; then
+# $ echo "option enabled"
+# $ fi
+#
+# $ password="shellling123"
+# $ if [ "$name" = "shellling123" ]; then
+# $ echo "access granted"
+# $ else
+# $ echo "access denied"
+# $ fi
+#
+# $ printf "Enter name: "
+# $ read name
+# $ if [ "$name" = "shellling" ]; then
+# $ echo "Welcome!"
+# $ elif [ "$name" = "shelllings" ]; then
+# $ echo "Welcome all!"
+# $ else
+# $ echo "You are not welcome here."
+# $ fi
+#
+# Notice that there is spaces between the square brackets and that, for
+# strings, there are quotation marks around the variables. There are
+# more options than just '='
+#
+# = # strict string comparison of equals
+# != # strict string comparison of does not equal
+# -z # checks if string is empty
+# -n # checks if string is not empty
+#
+# Fix the incomplete script below, leave all the echo lines unchanged
+
+if # check if user input is or is not empty here
+ if [ "$1" = "password123" ] then
+ echo "Welcome"
+ else
+ echo "Access not granted"
+else
+ echo "Password not entered"
diff --git a/exercises/15_conditionals2.sh b/exercises/15_conditionals2.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+# The previous if-conditionals were string comparisons. We will now take
+# a look into using number comparisons.
+#
+# -eq # equal to
+# -ne # not equal to
+# -gt # greater than
+# -ge # greater than or equal to
+# -lt # less than
+# -le # less than or equal to
+#
+# Also, when you input a number from the 'read' command, it
+# automatically becomes a integer. Shell cannot use float or doubles.
+# To use decimal numbers, you must delegate it to other tools that will
+# be taught much later
+#
+# $ read user_input
+# $ if [ $1 -eq 1412 ]; then
+# $ echo "Correct pin"
+# $ fi
+#
+# You can also do operations but these must be surrounded by double
+# parenthesis.
+#
+# $ $((10 + 2)) # 12 addition
+# $ $((10 - 2)) # 8 subtraction
+# $ $((10 * 2)) # 20 multiplication
+# $ $((10 / 2)) # 5 division
+# $ $((-10 % 3)) # -1 modulo, finds out the remainder
+#
+# Write a script that tells you if an inputted number is even or odd.
+
+num=$1
+
+ echo "odd"
+ echo "even"
diff --git a/exercises/16_andand_oror.sh b/exercises/16_andand_oror.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# We used the if/else case but that is often not needed in shell. We can
+# instead use && and || for many cases. Take a look at the snippet below
+#
+# $ sh foo.sh && sh bar.sh
+#
+# The snippet above means that if 'sh foo.sh' is ran successfully,
+# without error, then 'sh bas.sh' will be ran.
+#
+# $ sh foo.sh || sh bar.sh
+#
+# If 'sh foo.sh' is ran and an error does occur, then 'sh bar.sh' is
+# ran.
+#
+# Some conditions require square brackets, these are test expressions.
+# Examples of conditions that require square brackets are usually string
+# and string comparisons, existence of files, and empty or not files.
+#
+# $ mv file1 file2 || echo "Could not be moved"
+# $ [ -z "$1" ] || echo "Nothing was entered"
+#
+# You can also extend it
+#
+# $ [ "$1" = "password123" ] || { echo "power off"; sh shutdown.sh; }
+#
+# The semi colon is used the same way as other programming languages
+# where you can combine multiple lines into a single line. Note that
+# it should still be easily readable to humans to debug and understand.
+# If you go back to exercise 14, you can see them being used to condense
+# the 'do' word
+#
+# Fix the code below so the echo commands make sense with the pin 1412
+# Leave the two && in place
+
+[] && echo "incorrect pin"
+[] && echo "correct pin"
+
+# You can also combine it into 1 line but it doesn't always comply with
+# many shells
+#
+# $ [] && echo "good" || echo "bad"
diff --git a/exercises/17_exit.sh b/exercises/17_exit.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Behind the scenes for all your 'sh shelllings.sh', exit codes are
+# being used to evaluate if your exercises were successful. Like earlier
+# when we were using && and ||, we can manually say if a code has been
+# unsuccessful or successful.
+#
+# $ exit 1 # error
+# $ exit 0 # no error
+#
+# The keyword 'exit' can be used to stop a program from continuing if
+# it has
+#
+# Write a script below where if someone has 1412 as the argument when
+# calling the script, it will be successful
diff --git a/exercises/18_quiz.sh b/exercises/18_quiz.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+# Quiz 1 Notes program
+#
+# This program is meant to store notes but has some issues
+
+NOTES_FILE=notes.txt
+
+# a bug always has '-p', when selected, added to notes.txt
+if [ "$*" = "-p"]; then
+ cat notes.txt
+fi
+
+
+if [ "$#" -ge 1] then
+ echo "[$(date)]" >> "$NOTES_FILE" # this line is without fault
+ echo "" >> "$NOTES_FILE"
+else
+ echo "Nothing was added"
+fi
+
+# When you are done, change the NOTES_FILE location and consider putting
+# this into your .local/bin with this part added (yes, you have to find
+# a way to have this inserted into the code above).
+#
+# $ nvim +startinsert /tmp/note_temp
+# $ if [ -s /tmp/note_temp ]; then
+# $ echo "[$(date)]" >> "$NOTES_FILE"
+# $ cat /tmp/note_temp >> "$NOTES_FILE"
+# $ echo "" >> "$NOTES_FILE"
+# $ rm /tmp/note_temp
+# $ fi
+#
+# Don't forget to have that directory path exported as a path in your
+# shell config. Usually, like this
+#
+# export PATH="$HOME/.local/bin:$PATH"
diff --git a/exercises/19_functions.sh b/exercises/19_functions.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+# There are many times where you would need to repeat large sections of
+# code. Or need to simplify the code for readability and organization.
+# Functions would be use in this case, this is a simple example of how
+# to use a function.
+#
+# $ greet() {
+# $ name="$1"
+# $ echo "Hello, $name."
+# $ }
+# $
+# $ greet Alice # Hello, Alice.
+# $ greet Bob # Hello, Bob.
+#
+# Pay attention to the syntax to use this function.
+#
+# Fix this program
+
+display_entropy() {
+ echo "Entropy available: $(cat /proc/sys/kernel/random/entropy_avail)"
+ echo "Entropy pool size: $(cat /proc/sys/kernel/random/poolsize)"
+ echo "Wakeup threshold: $(cat /proc/sys/kernel/random/write_wakeup_threshold)"
+ echo "Minimum reseeding in seconds: $(cat /proc/sys/kernel/random/urandom_min_reseed_secs)"
+}
+
+help() {
+ echo "Usage: sh 19_functions.sh [OPTION]"
+ echo "-en displays the entropy information of your system"
+ echo "-h, --help displays this help message"
+}
+
+# Write a program where if something runs the program with the option
+# '-de' at the end (sh 19_functions.sh -de) it would call the
+# display_entropy function. Anything else written, would have the help
+# function ran.
+
diff --git a/exercises/20_whileloop.sh b/exercises/20_whileloop.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# A while loop is a way to run a command over and over again while a
+# condition is true. The condition can be an evaluation or simply the
+# word 'true'. Take these examples
+#
+# $ i=10
+# $ while [ "$i" -ge 1 ]; do
+# $ echo "$i"
+# $ i=$((i - 1))
+# $ done
+#
+# The snippet above lists out all the integers from 10 until i equals
+# 0. You can also use 'break' to stop that while loop
+#
+# $ evil_number=36
+# $ count=1
+# $ while true; do
+# $ [ ! $count -eq $evil_number ] && echo "Not evil number"
+# $ [ $count -eq $evil_number ] && break
+# $ done
+# $ echo "Evil number found"
+#
+# For your challenge, complete this game for the user. Do not change
+# the correct number.
+
+magic_number=2987133075769857928
+echo "I'm thinking of a number between 1 and four hundred quintillion."
+
+while True;
+ printf "Take a guess: "
+ read user_input
+ [ $magic_number -eq $user_input ] && break
+ echo "Nope, wrong."
+
+echo "Got it, cheater"
diff --git a/exercises/21_forloop.sh b/exercises/21_forloop.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# For loops follow a logic where it does an operation for every instance
+#
+# $ for var in list; do
+# $ operation
+# $ done
+#
+# From the semi-colon used, you can see that the snippet above can be
+# 4 lines long, but it is generally condensed using the semi-colon.
+#
+# Let's say you want to wipe the metadata from a set of photos in a
+# folder you are in, it follows
+#
+# $ for pic in *.jpg; do
+# $ exiftool -all= "$pic"
+# $ done
+#
+# Fix the program below. If you mess up the test/ folder, you can git
+# clone the project and move it in. Hopefully the annoyance will cause
+# you to be more careful when running these commands.
+#
+# You want to add a line at the very end of every file, but you also
+# want to delete it. Don't worry about deleting the '# bloat message'
+# from the tests/ folder, as the program should delete all lines like
+# that
+
+for file in tests/*; do
+ echo "# bloat message" >> "$test_file"
+done
+
+for file in tests/*; do
+ tail -n -1 "$test_file" > "$test_file".tmp
+ mv -f "$test_file".tmp "$test_file" # this line is valid
+done
diff --git a/exercises/22_file_checks.sh b/exercises/22_file_checks.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# When writing scripts and into files, there are times where you have
+# to verify that the file exists, the file is writable, or you need to
+# know if a folder fulfills the same requirements. These commands below
+# can do that
+#
+# -e returns true if the file exists
+# -d true if it's a directory
+# -f true if the file exists and is a regular file
+# -r true if the file is readable
+# -w true if the file is writable
+# -x true if the file is an executable
+# -s true if the file exists and is not empty
+#
+# $ [ -e LICENSE ] && echo 'exists'
+#
+# Find me files that can make the statement true so then echo works.
+# Don't edit the echo messages
+
+touch 22_file
+
+[ ] && echo "Exists"
+[ ] && echo "Directory exists"
+[ ] || echo "empty" # have 22_file be here somehow
+[ ] || echo "Not an executable"
+
+rm 22_file
diff --git a/exercises/23_man_and_tips.sh b/exercises/23_man_and_tips.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# At this point, you have learned many commands that are used and you
+# won't be able to remember them all at the top of your head. Instead of
+# going into your notes on into this folder, use the '--help'
+# (sometimes '-h') to see the basic usage or the 'man' command to get
+# a more detailed documentation
+#
+# $ cat --help # displays a basic help message
+# $ man echo # enters you into a more detailed portal
+#
+# You should try out those commands right in the terminal.
+#
+# If there are more specific use cases or you only need a few commands
+# that you keep forgetting, consider using the 'ffr' command located at
+# https://github.com/davidvoz/toyotacorolla/blob/main/ffr
+# For the linux users here, copy that code into .local/bin/ffr and add
+# the line below to your .bashrc or .zshrc. Follow the instructions on
+# the script and try it out.
+#
+# $ export PATH="$HOME/.local/bin:$PATH"
+#
+# To keep this exercise simple, figure out how many lines the man page
+# for cat uses, and the amount of words in ls's help command
+
+echo "$amount_cat" # leave this unchanged
+echo "$amount_ls" # leave this unchanged
diff --git a/exercises/24_grep.sh b/exercises/24_grep.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+# grep is a command that searches for text in files or input.
+#
+# $ grep "the" LICENSE
+#
+# It will show you all the times the letters 't', 'h', and 'e' are used
+# next to each other. If you want to only find that word we can do this
+#
+# $ grep -w -i "the" LICENSE # -w for the word, -i to ignore the
+# # the case spellings
+#
+# The common useful options are listed here
+#
+# -w matches whole words
+# -x matches whole lines
+# -i ignores case spellings
+# -v invert match findings
+# -n show line numbers
+# -c count matches per line
+# -l prints file names with matches
+# -q quiet, supress the output, used often for true or false findings
+# -H show the filename before a match when matching many files
+# -o prints only the matching part, not whole line
+#
+# Help me figure out a way where I can find the amount of times the word
+# 'the' (in all of its strange uppercases and lowercases) is used in
+# the LICENSE file. It seems that I can't get quite an accurate reading.
+
+grep 'the' LICENSE | wc
+
+echo "$word_count" # leave be
diff --git a/exercises/25_globbing.sh b/exercises/25_globbing.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+# Take a look at what globbing is in unix
+#
+# $ cat * # cats out everything immediate in your folder
+# $ ls 1?_* # the ? matches everything to a single character,
+# # while the * matches it to anything. Run this
+# # command in the exercises/ or tests/ folder
+# $ ls file[abc] # matches only the characters in the brackets
+# # so filea fileb and filec will show
+# $ ls file[!AB] # matches files without A or B, so fileC and fileD
+#
+# Find a way to get a list of all the files in tests/ that are numbered
+# between 10 and 16 with just one line of code
+
+ls tests/[]*
diff --git a/exercises/26_regex.sh b/exercises/26_regex.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+# Regular expression (regex) is also pattern searching through file
+# names but also through text or any string.
+#
+# . matches any single character except newline.
+# $ ls tests/ | grep 2. # if you want to see it tested
+#
+#
+# ^ matches the start of a line
+# $ grep ^"Long, long" file # same as below
+# $ grep "^Long, long" file
+#
+#
+# $ matches the end of a line
+# $ grep end$ file
+#
+# [-] gives a range within the brackets, a-z A-Z 0-9 can be used
+# $ ls 1[0-6]* # an easier way of completing the previous
+# # exercise without globbing
+#
+# + matches one or more of the previous character
+# $ ls tests/ | grep -E 'o+' # for regex expressions like this you
+# # may have to have the -E option
+#
+# ? matches zero or more of the previous character
+# $ ls tests/ | grep -E 'o+*' # not a helpful example
+#
+# You want to verify if someone has inputted a valid file from tests/
+# Your job is to match the file to a file if the user either inputs a
+# number associated with that file, or the name between the number and
+# file extension. Meaning inputs with .sh should return an error.
+
+verify() {
+ # duplicate more or remove lines below if needed
+ ls tests/ | grep -E && exit 0
+ ls tests/ | grep -E && exit 0
+}
+
+verify $1
+exit 1
diff --git a/exercises/27_regex2.sh b/exercises/27_regex2.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# Continuing off of regex
+#
+# [] matches with any one character from inside the brackets
+# [^] matches with any that are not in the brackets
+#
+# () groups parts of a pattern
+#
+# {} repeats what was shown before a certain amount of times
+# [a-zA-Z]{2,} All alphabet characters (lower and uppercase)
+# should be used 2 or more times
+# [a-zA-Z0-9]{5,15} All characters in square brackets should be used
+# a min of 5 times and max of 15
+#
+# `` same as $(...), but $() is preferred
+# \ backslash for the literal character
+#
+
+# We will verify valid email addresses below. We will keep it simple to
+# addresses with one word beginnings with numbers and letters, @,
+# and a website domain.
+# billybob@bob1.com, David2@test.xyz, etc
+
+verify() {
+ # don't forget the + symbol in some of these regex
+
+ # verify that the beginning has valid characters
+ echo "$1" | grep -Eq || exit 1
+
+ # verify that an @ symbol and a second level domain is present with
+ # only letters and numbers
+ echo "$1" | grep -Eq || exit 1
+
+ # verify a dot is present
+ echo "$1" | grep -Eq || exit 1
+
+ # verify the ending is a top level domain with 2 or more lowercase
+ # characters
+ echo "$1" | grep -Eq || exit 1
+}
+
+verify $1
+exit 0
diff --git a/exercises/28_case.sh b/exercises/28_case.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+# Instead of using if, elif, elif, elif, else, there's a more concise
+# (and often faster) method you can use; case.
+#
+# $ echo "Enter a letter: "
+# $ read user_input
+# $
+# $ case "$user_input" in
+# $ [a-z])
+# $ echo "Entered: lowercase"
+# $ ;;
+# $ [A-Z])
+# $ echo "Entered: uppercase"
+# $ ;;
+# $ [0-9])
+# $ echo "I said letter, not number"
+# $ ;;
+# $ *) # for everything else
+# $ echo "What?"
+# $ ;;
+# $ esac
+#
+# The snippet above should be self explanatory. Now, for other cases, it
+# can look like this
+#
+# $ case "$item" in
+# $ apple|banana|orange) # apple or banana or orange
+# $ echo "Food item"
+# $ ;;
+# $ book)
+# $ echo "leisure item"
+# $ ;;
+# $ *)
+# $ echo "unknown"
+# $ ;;
+# $ esac
+#
+# It's better to use 'case' instead of 'if' when you are pattern
+# matching, which means you can avoid using grep. It can be better to
+# do 'if' instead of 'case' when there's arithmetic, like playing a
+# hotter/colder game by having a user try to find a number.
+#
+# Below is the unfinished work of a rock-paper-scissors game between
+# a player and the computer
+
+choices="rock paper scissors"
+computer_choice=$(echo "$choices" | tr ' ' '\n' | shuf -n 1)
+printf "Choose rock paper or scissors: "
+read player_choice
+
+# The code snippet above needs no changing.
+# Have all the echo statements be just those 3 words exactly
+case "$player_choice" in
+ rock)
+ case "$computer_choice" in
+ rock) echo "tie" ;; # this is a valid way of condensing code
+ paper) echo "lost" ;;
+ scissors) echo "won" ;;
+ *)
+ echo "ERROR invalid option"
+ ;;
+esac
diff --git a/exercises/29_gzip.sh b/exercises/29_gzip.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# gzip (gunzip) is a GNU file compression and expansion tool that comes
+# with most linux distrobutions. Use the --help option to know how to
+# compress the LICENSE file into a new file LICENSE.gz, leaving LICENSE
+# unchanged. If successful, don't worry about having to remove the .gz
+# file, shelllings.sh will take care of that.
+
+
diff --git a/exercises/30_tar.sh b/exercises/30_tar.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# tar is another GNU compression tool that will be more likely used.
+# It's more often used in compression of multiple files and folders.
+# Same as before, using the --help option, try to find the problem with
+# the command below. Don't worry about deleting the .tar.gz file.
+
+tar -czf exercises/ tests/ shelllings.sh shelllings.tar.gz
diff --git a/exercises/31_trap.sh b/exercises/31_trap.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+# 'trap' is a very useful tool that I've been using on the exercises
+# where I said you didn't have to worry about deleting the created file.
+#
+# $ trap command SIGNAL
+#
+# For example, in your tests/ files, many will have trap statements
+#
+# $ cleanup() {
+# $ [ -f "file" ] && rm file
+# $ }
+# $ trap cleanup EXIT
+#
+# exit is the signal for trap to call cleanup(). Below are the different
+# signals that trap detects
+#
+# $ trap command EXIT # catches both exit 1 and exit 0
+# $ trap command INT # catches ctrl+c kills
+# $ trap command TERM # catches a signal terminate, like 'kill'
+# $ trap command INT EXIT # catches different instances
+#
+# You don't have to call functions when running trap, you can run
+# commands in the same line
+#
+# $ trap 'echo "Exiting.."; exit' TERM
+#
+# Below is a simple creation of a file, write a trap line that prints
+# out "Removing..." and removes the file
+
+touch file.txt
+echo "kill me" > file.txt
diff --git a/run_me.sh b/run_me.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+BLUE="\033[34m"
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+pause() {
+ printf "$1"
+ read user_input
+ echo ""
+}
+
+pause "Welcome to Shelllings [press ENTER]"
+
+echo "This program is meant to help you understand most posix shells,"
+echo "like bash or zsh. You already ran the command 'sh run_shell.sh'"
+echo "but the command 'sh' doesn't really exist on most systems."
+echo "Run 'ls -l /bin/sh' to see if there is a link to another program."
+pause "Let's test it out [press ENTER for ls -l /bin/sh]"
+
+printf "${BLUE}$(ls -l /bin/sh)${RESET}"
+echo ""
+
+USER_SHELL=$(ls -l /bin/sh | awk '{print $NF}')
+
+echo ""
+if [ "$USER_SHELL" = "sh" ]; then
+ echo "Looks like you are actually using sh, not everyone has it"
+ printf "like this. Many shells are changed. "
+else
+ echo "From the command above, we can see that you are actually"
+ printf "running ${BLUE}$USER_SHELL${RESET} under the hood. "
+fi
+
+echo "Bash is switched to a "
+echo "POSIX-compatible mode, dash also only implements POSIX features."
+echo "There are many different shells but we will be sticking with"
+echo "POSIX compliant programs and commands. Meaning just about"
+echo "every shell you use will be able to understand and run these"
+echo "commands. This will not always be the case, however."
+pause "[press ENTER]"
+
+printf "The LICENSE file is going to be used often later, ${RED}don't\n"
+printf "remove the LICENSE file${RESET}. Thanks."
+pause ""
+
+echo "To use the program shelllings, let's run the command"
+echo "'sh shelllings.sh test' to see if it's able to be run"
+pause "[sh shelllings.sh test]"
+
+if sh shelllings.sh test; then
+ printf "${GREEN}Passed${RESET}"
+ echo ""
+else
+ printf "${RED}ERROR failed${RESET}"
+ echo ""
+ exit 1
+fi
+
+echo ""
+echo "No error message meant that it has not failed. Lets continue on"
+pause "and see what exercises there are right now [ls exercises]"
+
+all_ex=$(ls exercises)
+printf "${BLUE}$all_ex${RESET}"
+echo ""
+
+echo ""
+pause "[press ENTER]"
+
+amount=$(ls exercises | wc -l)
+
+echo "Looks like there are $amount exercises. You should know that"
+echo "you can skip through the ones you already know or come back to"
+echo "ones you already did. You can run 'sh shelllings.sh n' where n"
+echo "is the numbered exercise. You don't need to type the 0's in"
+echo "front. Let's get started with the first exercise"
+pause "[sh shelllings.sh 1]"
+
+sh shelllings.sh 1 || {
+ echo "Looks like it failed. Go on over to the exercises/ folder and"
+ printf "fix it then run ${GREEN}sh shelllings 1${RESET} again.\n"
+}
diff --git a/shelllings.sh b/shelllings.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+EX_DIR="exercises"
+TEST_DIR="tests"
+
+list_prev() {
+ echo "Listing all challenges"
+
+ for file in "$TEST_DIR"/*; do
+ name=$(basename "$file")
+
+ case "$name" in
+ 11_userinput.sh|13_copying_and_moving.sh)
+ echo "$name: skipped"
+ continue
+ ;;
+ esac
+
+ sh "$file" > /dev/null 2>&1 || echo "$name: failed"
+ sh "$file" > /dev/null 2>&1 && echo "$name"
+ done
+
+ rm -rf blueberry 11_file .youfoundme
+}
+
+
+run_ex() {
+ num="$1"
+ ex_file=$(ls "$EX_DIR"/*.sh | sort | sed -n "${num}p")
+ test_file=$(ls "$TEST_DIR"/*.sh | sort | sed -n "${num}p")
+
+ if [ ! -f "$ex_file" ]; then
+ echo "ERROR exercise file not found"
+ exit 1
+ fi
+
+ if [ ! -f "$test_file" ]; then
+ echo "ERROR test file is not found"
+ exit 1
+ fi
+
+ echo "Running exercise: $(basename "$ex_file")"
+ echo "Testing with: tests/$(basename "$test_file")"
+ echo ""
+
+ sh "$test_file"
+}
+
+usage() {
+ echo "Usage"
+ echo " sh shelllings.sh INT run the INTth exercise"
+ echo " sh shelllings.sh list list the exercises you have completed"
+ echo " sh shelllings.sh test test the program if runnable"
+ exit 0
+}
+
+case "$1" in
+ test)
+ echo "shelllings.sh is working (for now)"
+ ;;
+ list)
+ list_prev
+ ;;
+ ''|*[!0-9]*)
+ usage
+ ;;
+ *)
+ run_ex "$1"
+ ;;
+esac
diff --git a/tests/01_echo.sh b/tests/01_echo.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+output="$(sh exercises/01_echo.sh)"
+
+[ -z "$output" ] && failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/02_variables.sh b/tests/02_variables.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+[ "$(sh exercises/02_variables.sh)" = "Hello Bob" ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/03_listing.sh b/tests/03_listing.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+[ -f ".youfoundme" ] || touch .youfoundme
+
+cleanup() {
+ [ -e .youfoundme ] && rm -f .youfoundme
+}
+trap cleanup EXIT
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+sh exercises/03_listing.sh || failed
+
+[ -f ".youfoundme" ] && failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/04_navigation.sh b/tests/04_navigation.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+cleanup() {
+ [ -d "blueberry" ] && rm -rf blueberry
+}
+trap cleanup EXIT
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+mkdir -p blueberry/strawberry/grape/lemon
+mkdir -p blueberry/strawberry/cherry/
+
+touch blueberry/strawberry/grape/lemon/.you_found_me.txt
+echo "The password is grannyApples" > blueberry/strawberry/grape/lemon/.you_found_me.txt
+
+echo "Find the grape folder, the file with the password might"
+echo "be hard to find, but it is there."
+
+if [ "$(sh exercises/04_navigation.sh)" = "grannyApples" ]; then
+ printf "${GREEN}Passed${RESET}\n"
+else
+ failed
+fi
diff --git a/tests/05_creating.sh b/tests/05_creating.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+sh exercises/05_creating.sh
+
+if find . -mindepth 4 -type f -name file | grep -q .; then
+ printf "${GREEN}Passed${RESET}\n"
+else
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+fi
diff --git a/tests/06_removing.sh b/tests/06_removing.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+cleanup() {
+ [ -d 06_folder ] && rm -rf 06_folder
+}
+trap cleanup EXIT
+
+
+sh exercises/06_removing.sh
+
+[ "$(wc -l < exercises/06_removing.sh)" -eq 44 ] || {
+ failed
+ echo "not 27 lines"
+}
+
+[ -d "06_folder" ] || failed
+
+[ "$(ls -A 06_folder | wc -l)" -eq 0 ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/07_wordcount.sh b/tests/07_wordcount.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+output=$(sh exercises/07_wordcount.sh)
+answer=$(wc -l LICENSE; wc -w LICENSE; wc -c LICENSE)
+
+[ "$output" = "$answer" ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/08_cat.sh b/tests/08_cat.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+cleanup() {
+ [ -f "08_cat" ] && rm 08_cat
+}
+trap cleanup EXIT
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+output=$(sh exercises/08_cat.sh)
+answer=$(tail -n 50 LICENSE | wc -w)
+
+[ $(wc -w < 08_cat) -eq $answer ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/09_direction.sh b/tests/09_direction.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+cleanup(){
+ [ -f "09_file" ] && rm 09_file
+}
+trap cleanup EXIT
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+sh exercises/09_direction.sh
+output=$(wc -l < 09_file)
+answer=$(cat LICENSE exercises/* | wc -l)
+
+[ $output -eq $answer ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/10_piping.sh b/tests/10_piping.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+cleanup(){
+ [ -f "10_piping" ] && rm 09_piping
+ [ -f "file" ] && rm file
+}
+trap cleanup EXIT
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+[ $(grep "cat LICENSE | wc -w" exercises/10_piping.sh | wc -l) -eq 0 ] && failed
+
+[ $(grep "ls -A \"\$HOME\" | wc -l" exercises/10_piping.sh | wc -l) -eq 1 ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/11_userinput.sh b/tests/11_userinput.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+cleanup(){
+ [ -f "11_file" ] && rm 11_file
+}
+trap cleanup EXIT
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+sh exercises/11_userinput.sh
+
+[ -s 11_file ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/12_arguments.sh b/tests/12_arguments.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+output=$(sh exercises/12_arguments.sh ugly Jayce big tofu)
+first_line=$(sh exercises/12_arguments.sh ugly Jayce big tofu | head -n 1)
+correct_first_line="One day, ugly Jayce left a big tofu in his pocket."
+
+[ "$first_line" != "$correct_first_line" ] && failed
+[ "$(echo "$output" | grep 4)" != "The number of words you used were: 4" ] && failed
+[ "$(echo "$output" | grep "The words")" != "The words you used were: ugly Jayce big tofu" ] && failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/13_copying_and_moving.sh b/tests/13_copying_and_moving.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ [ -f "11_file" ] && rm 11_file
+ [ -f "11_user_input" ] && rm 11_user_input
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+trap failed EXIT
+
+sh exercises/13_copying_and_moving.sh
+
+[ -f "11_file" ] && failed
+
+[ -s "11_user_input" ] && printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/14_conditionals.sh b/tests/14_conditionals.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+[ "$(sh exercises/14_conditionals.sh)" != "Password not entered" ] && failed # no password entered
+[ "$(sh exercises/14_conditionals.sh password123)" != "Welcome" ] && failed # correct password
+[ "$(sh exercises/14_conditionals.sh password)" != "Access not granted" ] && failed # incorrectpassword
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/15_conditionals2.sh b/tests/15_conditionals2.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+[ "$(sh exercises/15_conditionals2.sh 10)" = "even" ] || failed
+[ "$(sh exercises/15_conditionals2.sh -1)" = "odd" ] || failed
+[ "$(sh exercises/15_conditionals2.sh -9)" = "odd" ] || failed
+[ "$(sh exercises/15_conditionals2.sh 1000000)" = "even" ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/16_andand_oror.sh b/tests/16_andand_oror.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+[ "$(sh exercises/16_andand_oror.sh 1412)" = "correct pin" ] || failed
+[ "$(sh exercises/16_andand_oror.sh 9999)" = "incorrect pin" ] || failed
+[ $(grep "&&" exercises/16_andand_oror.sh | wc -l) -eq 6 ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/17_exit.sh b/tests/17_exit.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+$(sh exercises/17_exit.sh 9999) && failed
+$(sh exercises/17_exit.sh 1412) || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/18_quiz.sh b/tests/18_quiz.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+cleanup() {
+ [ -f "notes.txt" ] && rm notes.txt
+}
+trap cleanup EXIT
+
+sh exercises/18_quiz.sh get oreos for protein shake > /dev/null || failed
+sh exercises/18_quiz.sh -p this line should appear > /dev/null || failed
+sh exercises/18_quiz.sh -p > /dev/null || failed
+sh exercises/18_quiz.sh > /dev/null || failed
+
+[ ! $(grep "get oreos" notes.txt | wc -l) -eq 1 ] && failed
+[ ! $(grep "\-p" notes.txt | wc -l) -eq 1 ] && failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/19_functions.sh b/tests/19_functions.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+sh exercises/19_functions.sh -de | grep -q "Minimum reseeding in seconds:" || failed
+sh exercises/19_functions.sh -de | grep -q "displays the entropy" && failed
+sh exercises/19_functions.sh | grep -q "displays the entropy" || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/20_whileloop.sh b/tests/20_whileloop.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+printf "123\n456\n2987133075769857928\n" | sh exercises/20_whileloop.sh | grep -q "Nope" || failed
+printf "123\n456\n2987133075769857928\n" | sh exercises/20_whileloop.sh | grep -q "cheater" || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/21_forloop.sh b/tests/21_forloop.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+
+ for file in tests/*; do
+ sed -i '/^# bloat message$/d' $file
+ done
+
+ [ -e .tmp ] && rm .tmp
+
+ exit 1
+}
+
+sh exercises/21_forloop.sh || failed
+
+for file in tests/*; do
+ name=$(basename "$file")
+
+ case "$name" in
+ 21_forloop.sh)
+ continue
+ ;;
+ esac
+
+ [ "$(grep "bloat message" "$file" | wc -l)" -ne 0 ] && failed
+
+done
+
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/22_file_checks.sh b/tests/22_file_checks.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+for keyword in Exists Directory empty Not; do
+ sh exercises/22_file_checks.sh | grep -q "$keyword" || failed
+done
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/23_man_and_tips.sh b/tests/23_man_and_tips.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+check() {
+ read amount_cat
+ read amount_ls
+
+ [ "$amount_cat" -eq 73 ] || failed
+ [ "$amount_ls" -eq 867 ] || failed
+}
+
+sh exercises/23_man_and_tips.sh | check
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/24_grep.sh b/tests/24_grep.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+[ "$(sh exercises/24_grep.sh)" -eq 13 ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/25_globbing.sh b/tests/25_globbing.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+[ "$(sh exercises/25_globbing.sh)" = "$(ls tests/1[0123456]*)" ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/26_regex.sh b/tests/26_regex.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+sh exercises/26_regex.sh 1 > /dev/null 2>&1 || failed
+sh exercises/26_regex.sh 09 > /dev/null 2>&1 || failed
+sh exercises/26_regex.sh 10 > /dev/null 2>&1 || failed
+sh exercises/26_regex.sh 99999 > /dev/null 2>&1 && failed
+sh exercises/26_regex.sh loop > /dev/null 2>&1 || failed
+sh exercises/26_regex.sh quiz > /dev/null 2>&1 || failed
+sh exercises/26_regex.sh quiz.sh > /dev/null 2>&1 && failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/27_regex2.sh b/tests/27_regex2.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+sh exercises/27_regex2.sh billybob@bob1.com || failed
+sh exercises/27_regex2.sh David2@test.xyz || failed
+sh exercises/27_regex2.sh 1@t.co || failed
+
+sh exercises/27_regex2.sh @gmail.com && failed
+sh exercises/27_regex2.sh billybob@bob2.c2 && failed
+sh exercises/27_regex2.sh billybob@.c2 && failed
+sh exercises/27_regex2.sh billybob@mail.COM && failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/28_case.sh b/tests/28_case.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+printf "scissors\n" | sh exercises/28_case.sh | grep -Eq "tie|lost|won" || failed
+printf "scissor\n" | sh exercises/28_case.sh | grep -Eq "tie|lost|won" && failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/29_gzip.sh b/tests/29_gzip.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+cleanup() {
+ [ -f "LICENSE.gz" ] && rm LICENSE.gz
+}
+trap cleanup EXIT
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+sh exercises/29_gzip.sh || failed
+
+[ -s LICENSE.gz ] || failed
+[ -s LICENSE ] || failed
+
+leis_size=$(stat -c%s LICENSE)
+gz_size=$(stat -c%s LICENSE.gz)
+
+[ $leis_size -gt $gz_size ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/30_tar.sh b/tests/30_tar.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+cleanup() {
+ [ -f "shelllings.tar.gz" ] && rm shelllings.tar.gz
+}
+trap cleanup EXIT
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+sh exercises/30_tar.sh || failed
+
+[ -s shelllings.tar.gz ] || failed
+
+file_size=$(du -s shelllings.tar.gz | awk '{print $1}')
+
+[ $file_size -lt 20 ] || failed
+[ $file_size -gt 10 ] || failed
+
+printf "${GREEN}Passed${RESET}\n"
diff --git a/tests/31_trap.sh b/tests/31_trap.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+set -eu
+RED="\033[31m"
+GREEN="\033[32m"
+RESET="\033[0m"
+
+cleanup() {
+ [ -f "file.txt" ] && rm -f file.txt
+}
+trap cleanup EXIT
+
+failed() {
+ printf "${RED}Failed${RESET}\n"
+ exit 1
+}
+
+sh exercises/31_trap.sh | grep -Eq "Removing..." || failed
+
+grep 'trap' exercises/31_trap.sh | grep "rm file\.txt" | grep 'Removing...' | grep -q 'EXIT' || failed
+
+[ -f "file.txt" ] && failed
+
+printf "${GREEN}Passed${RESET}\n"