# Exploring Bash Scripting ### checking the specific bash version ```bash bash --version ``` ### checking the environment variables *bash Varibles https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html* ```bash env env ${ENVIRONMENT_VARIABLES} ``` ### listing processes ```bash ps -e -f ``` *ps stands for list processes* *-e stands for every process* *-f stands for full listing* ### checking for available space ```bash df --human-readable df -h ``` ## Elements of a Bash Script *Using google standard Guide https://google.github.io/styleguide/shellguide.html* ### The shebang line ```bash #!/bin/bash ``` ### Optional Arguments and what they do ```bash #!/bin/bash -x ``` *Prints all the commands and arguments as they are present in the script, very useful for debugging* ```bash #!/bin/bash -r ``` *Creates a restricted bash shell* ```bash bash -r myScript.sh ``` *Passing an argument to the bash interpreter* ### Writing comments in bash ```bash #!/bin/bash # This is my first script ``` ### A simple script that prints Hello World! ```bash #!/bin/bash echo "Hello World!" ``` ### Debugging scripts ```bash bash -n myscript.sh ``` *This performs a dry run without executing any codes but highlights and print syntax errors on the code* ```bash bash -x myscript.sh ``` *executes the codes in realtime using verbose mode and highlights the error while running and helps to debug* ```bash #!/bin/bash set -x --snip-- set +x ``` *This performs point to point debugging, where set is like an on and off switch* ### A script that creates a directory and files within it and list the files ```bash #!/bin/bash # Create the Directory mkdir myDirectory # Create two files in the Directory touch myDirectory/file1 touch myDirectory/file2 ls -l myDirectory echo "......end of script" ``` ### Variables in Bash *The following rules govern the naming of bash variables: • They can include alphanumeric characters. • They cannot start with a number. • They can contain an underscore (_). • They cannot contain whitespace* *In bash, variables are considered type strings by default. ### Assigning and Accessing Variables ```bash # assigning the variable book="Black hat bash" # reading the value of the variable using ${variable_name} echo "The name of the book is ${book}" #reading the value of the variable using $Variable_name echo "The name of the book is $book" ``` ### Assigning output of a command to a variable ```bash root_directory=$(ls -ld /) echo "${root_directory}" ``` ### Unassigning Variables *Unassigning variables is done using the unset command* ```bash unset book # This removes the value set in the vook variable initially ``` ### Scoping Variables #### Global Variables are those variables whose values available to the entire program. #### Local variables are those whose values are available within the local scope. ```bash #!/bin/bash PUBLISHER="No Starch Press" print_name(){ local name name="Black Hat Bash" echo "${name} by ${PUBLISHER}" } print_name echo "Variable ${name} will not be printed because it is a local variable, but variable ${PUBLISHER} will be printed because it is a global variable." ``` ```bash $ ./printName.sh Black Hat Bash by No Starch Press Variable will not be printed because it is a local variable, but variable No Starch Press will be printed because it is a global variable. ``` ### Arithmetic Operators *full details on operators are available here https://tldp.org/LDP/abs/html/ops.html* <pre> Operator Description + Addition - Subtraction * Multiplication / Division % Modulo += Incrementing by a constant -= Decrementing by a constant </pre> *you can perform arithmetic operation using different methods* ### using the let Command ```bash= let result="5 * 10" echo "$result" # prints 50 ``` ### using the double parenthesis command ```bash result=$((5 * 10)) echo "${result}" # prints 5 ``` ### using the expr command ```bash complete=$(expr 20 + 40) echo "${complete}" # prints 60 ``` ### more thins to do with expr ```bash= #!/bin/bash # Get the length of a specific string expr length "string" # prints 5 # Get the substring of a string with a specific length # expr substr "string" from <start lenght> to <length after start> expr substr "My test string" 4 4 # prints test # In modern scripting, you can also do this string="my test string" # echo ${string:position:length} echo ${string:3:4} #prints test # Match a specific substring against an anchored pattern *syntax* # expr match <string/variable> <pattern> # Match using regular expression expr match "abc123xyz" '[a-z]*' # prints 3 (matching the a,b and c in the string) # Match using a substring expr match "hello world" 'hello' # prints 5 expr match "hello world" : 'hello' # prints 5 # Get the first character position from a specific set in a string *syntax* # expr index "string" "char" expr index "my name is StingRay Okafor" "o" # prints 25 as thats where the character small o was found. # find the position of any character in a set expr index "hello world" "aeiou" # prints 2, as e was first found in the second position of hello ``` ### Arrays in bash ```bash #!/bin/bash # Sets an array IP_ADDRESSES=(192.168.1.1 192.168.1.2 192.168.1.3) # Prints all elements in the array echo "Print all IP Addresses in the list" echo "${IP_ADDRESSES[*]}" # Prints only the first element in the array echo "Print only the first IP Address in the list" echo "${IP_ADDRESSES[0]}" ``` ### Deleting elements from an Array using unset ```bash unset IP_ADDRESSES[1] ``` ### Swapping array values with some other value ```bash IP_ADDRESSES[1]=192.168.1.10 ``` ### Streams *Streams are files that act as a communication channel between a program and its environment. Bash has 3 standard streams* <table> <thead> <tr> <th>Stream Name</th> <th>Description</th> <th>File Descriptor Number</th> </tr> </thead> <tbody> <tr> <td>Standard Input (stdin) </td> <td> Data coming into a program as input</td> <td>0</td> </tr> <tr> <td>Standard Output (stdout) </td> <td> Data coming out of a program as an output</td> <td>1</td> </tr> <tr> <td>Standard Error (stderr)</td> <td>Errors coming out of a program</td> <td>2</td> </tr> </tbody> </table> ### Control Operators in Bash <em>These are tokens that perform operations in bash</em> <table> <thead> <tr> <th>Operator</th> <th>Descriptions</th> </tr> </thead> <tbody> <tr> <td>&</td> <td>Sends a command to the background</td> </tr> <tr> <td>&&</td> <td>Logical AND operator</td> </tr> <tr> <td>( )</td> <td>Used to group commands together</td> </tr> <tr> <td>;</td> <td>Used as a list terminator A command following the terminator will run after the preceding command has finished, regardless of whether it evaluates to true or not</td> </tr> <tr> <td>;;</td> <td>ends a case statement</td> </tr> <tr> <td>|</td> <td>Redirects the output of a command as an input of another command</td> </tr> <tr> <td>||</td> <td>Logical OR Operator</td> </tr> </tbody> </table> ### Logical AND Operator ```bash sudo apt update && sudo apt upgrade -y ``` *The second command only executes if the first command is successful* ### Parenthesis Operator ```bash (ps; df --human-readable) ``` *This runs the processes command, then move to run the df command which prints the available disc spaces in human readable format* ### Semi-colon operator ; ```bash ls;ps;whoami ``` *This lists available files and directories in the current directory, then prints the processes running and move on to print the current user* ```bash # print the current working directory, list the contents, run the process command and print the current user pwd;ls;ps;whoami ``` ### Chaining commands with the OR operator || ```bash pwd || echo "the pwd command failed" ``` *The command above prints the working directory and ignores the other command; hence, once one of each side works, the other is ignored* ## Redirecting Operators *The three standard streams {stdin, stdout, stderr}, highlighted earlier are redirected from one program to another. <table> <thead> <tr> <th>Operator</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>></td> <td>Redirects stdout to a file</td> </tr> <tr> <td>>></td> <td>Redirects stdout to a file by appending it to an existing content</td> </tr> <tr> <td>>& or &></td> <td>Redirects stdout and stderr to a file</td> </tr> <tr> <td>&>></td> <td>Redirects stdout and stderr to a file by appending it to an existing file</td> </tr> <tr> <td> < </td> <td>Redirects input to a command</td> </tr> <tr> <td> << </td> <td>Called a here document, or heredoc, redirects multiple input lines to a command</td> </tr> <tr> <td> | </td> <td>Redirects output of a command as input to another command (Pipe)</td> </tr> </tbody> </table> ### Redirection Operators Application * > operator* ```bash echo "This is my first redirection" > output.txt # The output.txt file will contain the string above ``` * >> operator* ```bash echo "This s a second line in my redirections" >> output.txt # This prints the above string in a new line below the previous string in the output.txt file ``` * &> operator* ```bash # standard output and standard error redirection ls -l / &> stdout_and_stderr.txt cat stdout_and_stderr.txt ## to append outputs to the stdout_and_stderr.txt file we use the ampersand and double chevron ls -l / &>> stdout_and_stderr.txt ``` * 1> and 2> operators* ```bash # Sending the standard output to one file and the standard error ot another file we do this ls -l / 1> stdout.txt 2> stderr.txt # using the error 2> command to log errors and send them to a file during runtime lzl 2> error.txt # lzl is not a real linux command hence it will throw an error, which is sent to the error.txt file. $ cat error.txt # displays the error from the lzl command ``` ### standard input stream using the < operator ```bash cat < output.txt # note that the file could be in any accurate format. # this is mostly used to for a single file ``` ### standard input stream using the << operator ```bash # mostly used to redirect multiple lines to a command #!/bin/bash cat << EOF This is my first rodeo However, I am still thinking of my second And maybe third EOF # save this to a file named test_mul_red.sh # make it executable chmod u+x test_mul_red.sh # run the script ./test_mul_red.sh ``` ### Pipe redirect operator | ```bash ls -l / | grep "bin" # this command lists in the long format the root directory and searches for the folder with "bin" as part of the name and outputs them. ``` ## Positional Arguments ```bash #!/bin/bash # This script will png any address provided as an argument # ${0} is assigned by default to the script name # ${1} takes the first positional argument, which is either a domain name, or an IP Address SCRIPT_NAME="${0}" TARGET="${1}" echo "Running the script ${SCRIPT_NAME}..." echo "Pinging the target: ${TARGET}..." ping "${TARGET}" ``` *Save the script as ping_with_arg.sh ```bash chmod u+x ping_with_arg.sh ``` ```bash # Run the scrip with ./ping_with_script.sh nostarch.com ``` <pre> The next script takes an input parameter from the user and pings the domain 10 times and return the result </pre> ```bash #!/bin/bash # This script tells you what to do echo "After seeing the script name below, you will be asked to input an argument" # Assigning the first parameter to the script. SCRIPT_NAME="${0}" # Running this should print the script name echo "The script name is $SCRIPT_NAME" sleep 2 # Ask the user to input an argument read -p "Enter the target domain name you intend to ping: " TARGET # Check if an input was provided by the user if [[ -z "${TARGET}" ]]; then echo "No target domain proided. Exiting ....." exit 1 fi # Display a message and wait echo "Pinging ${TARGET}..." sleep 2 # Execute the ping command ping -c 10 "${TARGET}" # Exit the script echo "Ping completed. Exiting..." exit 0 ``` ### Detailed pinging commands <pre>- <font color="#5EBDAB">Ping host:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">ping </font>host - <font color="#5EBDAB">Ping a host only a specific number of times:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">ping -c </font>count<font color="#FEA44C"> </font>host - <font color="#5EBDAB">Ping host, specifying the interval in seconds between requests (default is 1 se</font> <font color="#5EBDAB">cond):</font> <font color="#5EBDAB"> </font><font color="#FEA44C">ping -i </font>seconds<font color="#FEA44C"> </font>host - <font color="#5EBDAB">Ping host without trying to lookup symbolic names for addresses:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">ping -n </font>host - <font color="#5EBDAB">Ping host and ring the bell when a packet is received (if your terminal support</font> <font color="#5EBDAB">s it):</font> <font color="#5EBDAB"> </font><font color="#FEA44C">ping -a </font>host - <font color="#5EBDAB">Also display a message if no response was received:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">ping -O </font>host - <font color="#5EBDAB">Ping a host with specific number of pings, timeout (-W) for each reply, and tot</font> <font color="#5EBDAB">al time limit (-w) of the entire ping run:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">ping -c </font>count<font color="#FEA44C"> -W </font>seconds<font color="#FEA44C"> -w </font>seconds<font color="#FEA44C"> </font>host </pre> ### Accessing all arguments in a script is done with $@ ```bash #!/bin/bash # This shows all the arguments passed echo "The arguments are: $@" ``` ### Accessing the total number of arguments in a script is done with $# ```bash #!/bin/bash # This tells us the total number of arguments passed echo "The total number of arguments is: $#" ``` ### Variables related to positional arguments <table> <thead> <tr> <th>Variable</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>$0</td> <td>The name of the script file</td> </tr> <tr> <td>$1, $2, $3</td> <td>Positional arguments</td> </tr> <tr> <td>$#</td> <td>Number of passed positional arguments</td> </tr> <tr> <td>$*</td> <td>All positional arguments</td> </tr> <tr> <td>$@</td> <td>All positional arguments, where each argument is individually quoted</td> </tr> </tbody> </table> ### The difference between $@ and $* in a loop <pre> Create a script named test_args.sh copy and paste the script below Chmod u+x test_args.sh # run the command bash test_args.sh "1" "2" "3" "4" "5" Observe how $@ displays on each new line once the script is run, while $* displays on one line </pre> ```bash #!/bin/bash # Change "$@" to "$*" to observe the behaviour echo "Below shows the \$@ display" for args in "$@"; do echo "${args}" done echo "Below shows the \$* display" for args in "$*"; do echo "${args}" done ``` *Output* <pre> Below shows the $@ display 1 2 3 4 5 Below shows the $* display 1 2 3 4 5 </pre> ### Input prompting <pre> A script that asks users for inputing their first name and last name, stores it as a variable using "read -r", and prints an output </pre> ```bash #!/bin/bash # Takes input from the user with "read -r", and assigns it to a variable echo "What is your first name?" read -r firstname echo "What is your last name?" read -r lastname echo "Your first name is ${firstname} and your last name is ${lastname}" ``` ### Everything you can do with the read command in bash <pre>- <font color="#5EBDAB">Store data that you type from the keyboard:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">read </font>variable - <font color="#5EBDAB">Store each of the next lines you enter as values of an array:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">read -a </font>array - <font color="#5EBDAB">Specify the number of maximum characters to be read:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">read -n </font>character_count<font color="#FEA44C"> </font>variable - <font color="#5EBDAB">Assign multiple values to multiple variables:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">read </font>_ variable1 _ variable2<font color="#FEA44C"> &lt;&lt;&lt; &quot;</font>The surname is Bond<font color="#FEA44C">&quot;</font> - <font color="#5EBDAB">Do not let backslash (\) act as an escape character:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">read -r </font>variable - <font color="#5EBDAB">Display a prompt before the input:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">read -p &quot;</font>Enter your input here: <font color="#FEA44C">&quot; </font>variable - <font color="#5EBDAB">Do not echo typed characters (silent mode):</font> <font color="#5EBDAB"> </font><font color="#FEA44C">read -s </font>variable - <font color="#5EBDAB">Read stdin and perform an action on every line:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">while read line; do </font>echo|ls|rm|...<font color="#FEA44C"> &quot;$line&quot;; done &lt; </font>/dev/stdin|path/to/file|... </pre> ### Working with dates in Bash <pre> - <font color="#5EBDAB">Display the current date using the default locale&apos;s format:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">date +%c</font> - <font color="#5EBDAB">Display the current date in UTC, using the ISO 8601 format:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">date -u +%Y-%m-%dT%H:%M:%S%Z</font> - <font color="#5EBDAB">Display the current date as a Unix timestamp (seconds since the Unix epoch):</font> <font color="#5EBDAB"> </font><font color="#FEA44C">date +%s</font> - <font color="#5EBDAB">Convert a date specified as a Unix timestamp to the default format:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">date -d @</font>1473305798 - <font color="#5EBDAB">Convert a given date to the Unix timestamp format:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">date -d &quot;</font>2018-09-01 00:00<font color="#FEA44C">&quot; +%s --utc</font> - <font color="#5EBDAB">Display the current date using the RFC-3339 format (YYYY-MM-DD hh:mm:ss TZ):</font> <font color="#5EBDAB"> </font><font color="#FEA44C">date --rfc-3339 s</font> - <font color="#5EBDAB">Set the current date using the format MMDDhhmmYYYY.ss (YYYY and .ss are optiona</font> <font color="#5EBDAB">l):</font> <font color="#5EBDAB"> </font><font color="#FEA44C">date </font>093023592021.59 - <font color="#5EBDAB">Display the current ISO week number:</font> <font color="#5EBDAB"> </font><font color="#FEA44C">date +%V</font> </pre>