Skip to content

Instantly share code, notes, and snippets.

@tlewiscpp
Created June 7, 2016 20:52
Show Gist options
  • Save tlewiscpp/c0d30715a1ece9a79e6ee566a28d77f1 to your computer and use it in GitHub Desktop.
Save tlewiscpp/c0d30715a1ece9a79e6ee566a28d77f1 to your computer and use it in GitHub Desktop.
#!/bin/bash
#Notes going forward: the "declare" statements may be
#unecessary, but they are included for completeness.
#For tests, [[ ]] is used instead of [ ] as the former allows
#complex boolean expressions (ie && and ||)
#Also, bash interprets a 0 as true and a non-zero as false
#WTF, indeed
#If the command line -d if passed when executing this shell script,
#Turn on shell script trace debugging. This is found using the getopts
#bash builtin. If SETXTRACE is null after the getopts loop, xtrace is
#not turned on. Otherwise, it gets turned on. The second argument to
#getopts are the switches it looks for. If it finds one, it sets the
#variable $OPTARG to the full switch. If a colon is used, getopts will
#look for an argument to that switch. If it does not find one, it will
#set arg=: and OPTARG to the option lacking an argument. If a colon is
#included in the front of the second argument, getopts does all of the
#standard error messaging for you, so don't exclude that. OPTIND is the
#index variable for the loop, so it gets incremented on each successive
#loop. Here, we just want to look for the -d switch.
declare -i SETXTRACE=0
while getopts :d arg; do
case $arg
#When getopts finds a -* switch, it saves it in the
#arg variable, excluding the -. The case d|D allows the
#case to be triggered with either a lowercase or uppercase
#d is entered as the switch. The ;; ends the case
d|D) SETXTRACE=1 ;;
\?) echo "-$OPTARG is not a -d switch, ignorning" >&2 ;;
esac
done
echo "SETXTRACE = $SETXTRACE"
if [[ $SETXTRACE -eq 1 ]]; then
echo "Setting xtrace"
set -o xtrace
else
echo "No -d switch found, leaving xtrace off. Include -d in command line arguments to turn xtrace on"
fi
#The trap builtin allows the bash script to trap certain keryboard
#interrupts, and execute the command enclosed in the single parentheses.
#Using semi-colons, one can chain together several commands to be
#performed when the signal is caught. The default behavior
#for trap is to exit the script, and calling trap without any commands
#resets the trapped signals to the default. Signals can either be named
#or numerical. These are the same commands that the kill utility
#accepts as arguments. For a full list of the signals that can be
#trapped and killed, use kill -l, trap -l, or man 7 trap
trap 'echo "Thanks for using this example script! Goodbye."; set +o xtrace' EXIT
#Declare arbitrary array called 'NAMES' to hold the names
#tyler, bridgette, kourtney, and karla
declare -a NAMES=(tyler bridgette kourtney karla)
#Declare an integer variable to hold the number of elements in
#the NAMES array using a bash builtin function. Bash first expands
#${#NAMES[*]} to a string representation of the number of elements
#in the NAMES array, then bash expands $(( )) to the integer
#representation of the number of elements in the NAME array
declare -i LENGTHOFNAMES=$((${#NAMES[*]}))
#Declare an integer variable to hold the length of an
#element in the NAMES array using a bash builtin function
declare -i LENGTHOFMEMBER=
#Declare an empty array that will hold the arguments
declare -a ARGS=
#Declare integer variable to hold the number
#of arguments. $# expands to the string
#representation of the number of command line
#arguments, not including the script
#name itself. Then. the $(( )) expands the string
#into an integer number, which then gets assigned
declare -i NUMOFARGS=$(($#))+1
#Declare integer variable to hold the PID of
#the parent shell. Same expansion as above
declare -i SHELLPID=$$
#Declare integer variable to hold the PID
#of the most recent background process
#Not entirely useful
declare -i BGPID=$!
#Declare integer variable to hold the exit
#status of the previously run command
declare -i LASTEXIT=$?
#A simple way of displaying specific arguments
#The $0 is expanded to the first command on the
#command line (the name of the script), the $1
#is expanded to the second command, and so on
echo First 5 arguments are $0 $1 $2 $3 $4
#For i; implies for i in $@, which expands to all command
#line arguments. The shell then expands $i to the value
#of i at that time.
for i; do
echo -n "$i "
done
echo
#Display the number of arguments. The $(( )) gets expanded
#as above, in the number of args line
echo "Number of arguments = $((NUMOFARGS))"
#Similarly, display the process ID of the parent shell.
echo "PID of parent shell = $((SHELLPID))"
#Display the process ID of the most recent background process
echo "PID of most recent background process = $((BGPID))"
#Display the exit status of the last command run
echo "Exit status returned by the last process run = $((LASTEXIT))"
#C like for loop that iterates over the number of arguments
for ((i=0; i<$((NUMOFARGS)); i++)); do
#Using the 'test' builtin ([ ] is synonymous with test),
#check two integer values with the -eq flag. For integer
#comparisons, always use -eq, -neq, etc. For string
#comparisons, use =, !=, etc.
#If the current integer value of zero is equal to zero...
if [[ $((i)) -eq 0 ]]; then
#Set the first element in the ARGS array to the first thing
#that appeared on the command line (the script name)
ARGS[0]=${0}
#Otherwise...
else
#Set the ith element of the array to the ith
#command line argument.
ARGS[$((i))]=$((${i}))
fi
echo "ARGS[$((i))] = ${ARGS[$((i))]}"
done
#An easier way to store all of the arguments in an array variable is to
#declare it using a bash builtin, like so:
declare -a ALLARGS=("${#@}")
for i in ALLARGS; do
echo ${ALLARGS[$i]}
done
#Another C like for loop that iterates over the NAME array
#and prints out the names. It also prints the length of each name
for ((i=0; i<$((LENGTHOFNAMES)); i++)); do
#LENGTHOFFMEMBER is set to the length of the ith variable in the NAMES array. The bash
#buildin first expands the $((i)) to an integer representing i, then expands ${#NAMES[i]}
#to the length of the ith element of the NAMES array
LENGTHOFMEMBER=${#NAMES[$((i))]}
echo "NAMES[$((i))] = ${NAMES[$((i))]}"
echo "Length of ${NAMES[$((i))]} = $((LENGTHOFMEMBER))"
done
#Now use a bash builtin conditional to print something to
#standard error if this variable is null. Since it has not been
#declared elsewhere, it is null. Note that this is commented out
#because it causes bash to exit with an exit code of 1
#NULLVAR = ${NULLVAR:?This script was run at $(date), and the variable NULLVAR has not been set}
#echo "NULLVAR = $NULLVAR"
#A similar command can be used as a safeguard. The following
#will cause bash to expand to the default value specified after
#the colon. However, the variable will still be null
echo ${NULLVAR:-"Hello, World!"}
echo "NULLVAR = $NULLVAR"
#The following does the same, but assignes the default value to
#NULLVAR, then bash expands it
echo ${NULLVAR:="Hello, World!"}
echo "NULLVAR = $NULLVAR"
#Now, move on to a do...until loop. This will ask for a password
#two times, then not allow the user to continue the shell script
#until the password is entered a third time. The password is echoed
#back to them each time for sake of a test, no need to lock up the terminal
#in an example
echo "Now starting do...until example"
#Bash builtin trap command traps certain keyboard interrupts so the user
#cannot send a CTRL+C or whatever to exit the script. This would be used
#for a real password program, so the user can't just bypass logging in
trap 'echo "You pressed an interrupt, but I dont care. You have to enter to password to exit! Hahaha!"' 1 2 3 18
#Read with a -p switch allows the prompt within quotes to be displayed
#to the user. Then, after the user hits return, any typed text will be
#stored into the specified variable (password1 in the first case)
#Note that this preserves spaces
read -p "Password: " password1
read -p "Password (again): " password2
#Make sure the passwords match, using a string comparison. Until
#they match, continue to echo that they do not match and ask for
#them to be input once more. The && "$password1" && "$password2"
#will expand to true if the variables are not null. Therefore, the
#logic line checks to make sure the user didn't just hit enter 3 times
#to bypass the password checking altogether
until [[ "$password1" = "$password2" && "$password1" && "$password2" ]]; do
echo "The passwords do not match."
#stty -echo can be used to not allow echo...or something
#stty -echo
read -p "Password: " password1
read -p "Password (again): " password2
done
#Password3 is initialized as a null before using it. This way,
#in the comparison to come, if the user doesn't enter anything before
#hitting return and saving null to password1 and password2, the
#first comparison doesn't evaluate to true, bypassing the password altogether
password3=
#tput clear can be used to completely clear the terminal screen,
#but it's use is omitted here because it's annoying
#tput clear
until [ "$password3" = "$password2" ]; do
read -p "Enter password $password2 to continue: " password3
done
echo "Successfully entered password!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment