├── Codecademy-introduction-to-bash-scripting.md └── README.md /Codecademy-introduction-to-bash-scripting.md: -------------------------------------------------------------------------------- 1 | # Introduction to Bash Scripting 2 | 3 | [Source: Codecademy](https://www.codecademy.com/courses/learn-the-command-line) 4 | 5 | - [Introduction to Bash Scripting](#introduction-to-bash-scripting) 6 | - [Variables](#variables) 7 | - [Conditionals](#conditionals) 8 | - [Loops](#loops) 9 | - [Inputs](#inputs) 10 | - [Aliases](#aliases) 11 | - [Review](#review) 12 | - [Running Commands](#running-commands) 13 | - [Project: Build a Build Script](#project--build-a-build-script) 14 | 15 | Bash (or shell) scripting is a great way to automate repetitive tasks and can save you a ton of time as a developer. Bash scripts execute within a Bash shell interpreter terminal. Any command you can run in your terminal can be run in a Bash script. When you have a command or set of commands that you will be using frequently, consider writing a Bash script to perform it. 16 | 17 | There are some conventions to follow to ensure that your computer is able to find and execute your Bash scripts. The beginning of your script file should start with `#!/bin/bash`on its own line. This tells the computer which type of interpreter to use for the script. When saving the script file, it is good practice to place commonly used scripts in the `~/bin/`directory. 18 | 19 | The script files also need to have the "execute" permission to allow them to be run. To add this permission to a file with filename: `script.sh` use: 20 | 21 | ``` 22 | chmod +x script.sh 23 | ``` 24 | 25 | Your terminal runs a file every time it is opened to load its configuration. On Linux style shells, this is `~/.bashrc` and on OSX, this is `~/.bash_profile`. To ensure that scripts in `~/bin/` are available, you must add this directory to your `PATH` within your configuration file: 26 | 27 | - `PATH=~/bin:$PATH` 28 | 29 | Now any scripts in the `~/bin` directory can be run from anywhere by typing the filename. 30 | 31 | # Variables 32 | 33 | Within bash scripts (or the terminal for that matter), variables are declared by simply setting the variable name equal to another value. For example, to set the variable `greeting` to "Hello", you would use the following syntax: 34 | 35 | ``` 36 | greeting="Hello" 37 | ``` 38 | 39 | Note that there is no space between the variable name, the equals sign, or "Hello". 40 | 41 | To access the value of a variable, we use the variable name prepended with a dollar sign (`$`). In the previous example, if we want to print the variable value to the screen, we use the following syntax: 42 | 43 | ``` 44 | echo $greeting 45 | ``` 46 | 47 | # Conditionals 48 | 49 | When bash scripting, you can use conditionals to control which set of commands within the script run. Use `if` to start the conditional, followed by the condition in square brackets (`[ ]`). `then` begins the code that will run if the condition is met. `else` begins the code that will run if the condition is not met. Lastly, the conditional is closed with a backwards `if`, `fi`. 50 | 51 | A complete conditional in a bash script uses the following syntax: 52 | 53 | ``` 54 | if [ $index -lt 5 ] 55 | then 56 | echo $index 57 | else 58 | echo 5 59 | fi 60 | ``` 61 | 62 | Bash scripts use a specific list of operators for comparison. Here we used `-lt` which is "less than". The result of this conditional is that if `$index` is less than 5, it will print to the screen. If it is 5 or greater, "5" will be printed to the screen. 63 | 64 | Here is the list of comparison operators for numbers you can use within bash scripts: 65 | 66 | - Equal: `-eq` 67 | - Not equal: `-ne` 68 | - Less than or equal: `-le` 69 | - Less than: `-lt` 70 | - Greater than or equal: `-ge` 71 | - Greater than: `-gt` 72 | - Is null: `-z` 73 | 74 | When comparing strings, it is best practice to put the variable into quotes (`"`). This prevents errors if the variable is null or contains spaces. The common operators for comparing strings are: 75 | 76 | - Equal: `==` 77 | - Not equal: `!=` 78 | 79 | For example, to compare if the variables `foo`and `bar` contain the same string: 80 | 81 | ``` 82 | if [ "$foo" == "$bar"] 83 | ``` 84 | 85 | # Loops 86 | 87 | There are 3 different ways to loop within a bash script: `for`, `while` and `until`. 88 | 89 | A for loop is used to iterate through a list and execute an action at each step. For example, if we had a list of words stored in a variable `paragraph`, we could use the following syntax to print each one: 90 | 91 | ``` 92 | for word in $paragraph 93 | do 94 | echo $word 95 | done 96 | ``` 97 | 98 | Note that `word` is being "defined" at the top of the for loop so there is no `$` prepended. Remember that we prepend the `$` when accessing the value of the variable. So, when accessing the variable within the `do` block, we use `$word` as usual. 99 | 100 | Within bash scripting `until` and `while` are very similar. `while` loops keep looping while the provided condition is true whereas `until`loops loop until the condition is true. Conditions are established the same way as they are within an `if` block, between square brackets. If we want to print the `index` variable as long as it is less than 5, we would use the following `while` loop: 101 | 102 | ``` 103 | while [ $index -lt 5 ] 104 | do 105 | echo $index 106 | index=$((index + 1)) 107 | done 108 | ``` 109 | 110 | Note that arithmetic in bash scripting uses the `$((...))` syntax and within the brackets the variable name is not prepended with a `$`. 111 | 112 | The same loop could also be written as an `until` loop as follows: 113 | 114 | ``` 115 | until [ $index -eq 5 ] 116 | do 117 | echo $index 118 | index=$((index + 1)) 119 | done 120 | ``` 121 | 122 | # Inputs 123 | 124 | To make bash scripts more useful, we need to be able to access data external to the bash script file itself. The first way to do this is by prompting the user for input. For this, we use the `read` syntax. To ask the user for input and save it to the `number` variable, we would use the following code: 125 | 126 | ``` 127 | echo "Guess a number" 128 | read number 129 | echo "You guessed $number" 130 | ``` 131 | 132 | Another way to access external data is to have the user add input arguments when they run your script. These arguments are entered after the script name and are separated by spaces. For example: 133 | 134 | ``` 135 | saycolors red green blue 136 | ``` 137 | 138 | Within the script, these are accessed using `$1`, `$2`, etc, where `$1` is the first argument (here, "red") and so on. Note that these are 1 indexed. 139 | 140 | If your script needs to accept an indefinite number of input arguments, you can iterate over them using the `"$@"` syntax. For our `saycolors` example, we could print each color using: 141 | 142 | ``` 143 | for color in "$@" 144 | do 145 | echo color 146 | done 147 | ``` 148 | 149 | Lastly, we can access external files to our script. You can assign a set of files to a variable name using standard bash pattern matching using regular expressions. For example, to get all files in a directory, you can use the `*` character: 150 | 151 | ``` 152 | files = /some/directory/* 153 | ``` 154 | 155 | You can then iterate through each file and do something. Here, lets just print the full path and filename: 156 | 157 | ``` 158 | for file in $files 159 | do 160 | echo $file 161 | done 162 | ``` 163 | 164 | # Aliases 165 | 166 | You can set up aliases for your bash scripts within your `.bashrc` in Linux or `.bash_profile` in MacOS file to allow calling your scripts without the full filename. For example, if we have our `saycolors.sh` script, we can alias it to the word `saycolors` using the following syntax: 167 | 168 | ``` 169 | alias saycolors='./saycolors.sh' 170 | ``` 171 | 172 | You can even add standard input arguments to your alias. For example, if we always want "green" to be included as the first input to `saycolors`, we could modify our alias to: 173 | 174 | ``` 175 | alias saycolors='./saycolors.sh "green"' 176 | ``` 177 | 178 | # Review 179 | 180 | Take a minute to review what you've learned about bash scripting. 181 | 182 | - Any command that can be run in the terminal can be run in a bash script. 183 | - Variables are assigned using an equals sign with no space (`greeting="hello"`). 184 | - Variables are accessed using a dollar sign (`echo $greeting`). 185 | - Conditionals use `if`, `then`, `else`, `fi`syntax. 186 | - Three types of loops can be used: `for`, `while`, and `until`. 187 | - Bash scripts use a unique set of comparison operators: 188 | - Equal: `-eq` 189 | - Not equal: `-ne` 190 | - Less than or equal: `-le` 191 | - Less than: `-lt` 192 | - Greater than or equal: `-ge` 193 | - Greater than: `-gt` 194 | - Is null: `-z` 195 | - Input arguments can be passed to a bash script after the script name, separated by spaces (myScript.sh "hello" "how are you"). 196 | - Input can be requested from the script user with the `read` keyword. 197 | - Aliases can be created in the `.bashrc` or `.bash_profile` using the `alias` keyword. 198 | 199 | # Running Commands 200 | 201 | We can run any CLI commands inside the bash script. Example: `ls -al` 202 | 203 | In order to store the output of running a command, we can assign them to variables like this: 204 | 205 | ``` 206 | VAR_NAME=$(command) 207 | ``` 208 | Example: `listAll=$(ls -a)` 209 | 210 | # Project: Build a Build Script 211 | 212 | One common use of bash scripts is for releasing a "build" of your source code. Sometimes your private source code may contain developer resources or private information that you don't want to release in the published version. 213 | 214 | In this project, you'll create a release script to copy certain files from a **source** directory into a **build** directory. 215 | 216 | 1. Take a look at the **build** and **source** folders. The objective of our script is to files from **source** to **build**, with a couple of exceptions and modifications. 217 | 218 | ``` 219 | $ ls 220 | build script.sh source 221 | ``` 222 | 223 | Get started on the script by adding a header to **script.sh**, identifying the type of script. 224 | 225 | ```bash 226 | #!/bin/bash 227 | ``` 228 | 229 | 2. Let's welcome the user to the script. Feel free to use your own style here. Make sure to save your script. Test your script in the terminal using `./script.sh`. 230 | 231 | ```bash 232 | #!/bin/bash 233 | echo "Welcome User" 234 | ``` 235 | 236 | ``` 237 | $ ./script.sh 238 | Welcome User 239 | ``` 240 | 241 | 3. Since we are creating a new build, let's verify with the user that they have updated **changelog.md** with the current release version. 242 | 243 | The first line of the file contains a version number with markdown formatting like so: 244 | 245 | ``` 246 | ## 1.1.1 247 | ``` 248 | 249 | Read the first line of this file into a variable `firstline`. You can use the linux command [head](http://www.linfo.org/head.html) for this purpose. 250 | 251 | ``` 252 | $ ls source/ 253 | bar.js foo1.html secretinfo.md 254 | buzz.css foo2.html 255 | changelog.md foo3.html 256 | ``` 257 | 258 | ```bash 259 | #!/bin/bash 260 | echo "Welcome User" 261 | release=$(head -n1 ./source/changelog.md) 262 | echo $release 263 | ``` 264 | 265 | ``` 266 | $ ./script.sh 267 | Welcome User 268 | ## 11.2.3 269 | ``` 270 | 271 | 4. We want just the version number without the markdown formatting. The command [read](http://linuxcommand.org/lc3_man_pages/readh.html) can be used to split a string into an array using the `-a` argument. 272 | 273 | Split the string `firstline` into the array `splitfirstline`. 274 | 275 | The syntax for splitting a string `foo`into an array `bar` is: 276 | 277 | ```bash 278 | read -a bar <<< $foo 279 | ``` 280 | 281 | ```bash 282 | #!/bin/bash 283 | echo "Welcome User" 284 | firstline=$(head -n1 ./source/changelog.md) 285 | read -a splitfirstline <<< $firstline 286 | echo $splitfirstline 287 | ``` 288 | 289 | ``` 290 | $ ./script.sh 291 | Welcome User 292 | ## 293 | ``` 294 | 295 | 5. Now we are ready to set the value of the version of the script. It is located in index `1` of the array `splitfirstline`. 296 | 297 | The syntax for accessing the value at `index` of an array `foo` is: 298 | 299 | ``` 300 | ${foo[index]} 301 | ``` 302 | 303 | Save the version to a variable, `version`. 304 | 305 | Print a statement to the terminal notifying the user of the version they are building. 306 | 307 | ```bash 308 | #!/bin/bash 309 | echo "Welcome User" 310 | firstline=$(head -n1 ./source/changelog.md) 311 | read -a splitfirstline <<< $firstline 312 | version=${splitfirstline[1]} 313 | echo $version 314 | ``` 315 | 316 | ``` 317 | $ ./script.sh 318 | Welcome User 319 | 11.2.3 320 | ``` 321 | 322 | 6. Let's give the user a chance to exit the script if they need to make a change to the version. 323 | 324 | Ask the user to enter "1" (for yes) to continue and "0" (for no) to exit. 325 | 326 | Assign their response to the variable `versioncontinue`. 327 | 328 | ```bash 329 | #!/bin/bash 330 | echo "Welcome User" 331 | firstline=$(head -n1 ./source/changelog.md) 332 | read -a splitfirstline <<< $firstline 333 | version=${splitfirstline[1]} 334 | echo $version 335 | echo "Enter 1 to continue, 0 to exit" 336 | read versioncontinue 337 | ``` 338 | 339 | ``` 340 | $ ./script.sh 341 | Welcome User 342 | 11.2.3 343 | Enter 1 to continue, 0 to exit 344 | 1 345 | ``` 346 | 347 | 7. Add a conditional. If the user said "1" to the continue question, we will execute the rest of our script. For now, respond "OK". 348 | 349 | If the user did not, tell them "Please come back when you are ready". 350 | 351 | ```bash 352 | #!/bin/bash 353 | echo "Welcome User" 354 | firstline=$(head -n1 ./source/changelog.md) 355 | read -a splitfirstline <<< $firstline 356 | version=${splitfirstline[1]} 357 | echo $version 358 | echo "Enter 1 to continue, 0 to exit" 359 | read versioncontinue 360 | if [ $versioncontinue -eq 1 ] 361 | then 362 | echo 'OK' 363 | else 364 | echo 'Please come back when you are ready' 365 | fi 366 | ``` 367 | 368 | ``` 369 | $ ./script.sh 370 | Welcome User 371 | 11.2.3 372 | Enter 1 to continue, 0 to exit 373 | 0 374 | Please come back when you are ready 375 | ``` 376 | 377 | 8. Now we want to copy every file from **source** to **build** except for **secretinfo.md**. 378 | 379 | Within the positive conditional (where we told the user "OK"), start by iterating over all the files in the **source ** directory and printing their names to the terminal. 380 | 381 | ```bash 382 | #!/bin/bash 383 | echo "Welcome User" 384 | firstline=$(head -n1 ./source/changelog.md) 385 | read -a splitfirstline <<< $firstline 386 | version=${splitfirstline[1]} 387 | echo $version 388 | echo "Enter 1 to continue, 0 to exit" 389 | read versioncontinue 390 | if [ $versioncontinue -eq 1 ] 391 | then 392 | files=./source/* 393 | for file in $files 394 | do 395 | echo $file 396 | done 397 | else 398 | echo 'Please come back when you are ready' 399 | fi 400 | ``` 401 | 402 | ``` 403 | $ ./script.sh 404 | Welcome User 405 | 11.2.3 406 | Enter 1 to continue, 0 to exit 407 | 1 408 | ./source/bar.js 409 | ./source/buzz.css 410 | ./source/changelog.md 411 | ./source/foo1.html 412 | ./source/foo2.html 413 | ./source/foo3.html 414 | ./source/secretinfo.md 415 | ``` 416 | 417 | 9. Check if the `filename` is "source/secretinfo.md". If it is, inform the user that it is not being copied. 418 | 419 | Otherwise, inform the user that it is being copied. 420 | 421 | Make sure to use spaces in your string conditional. 422 | 423 | ```bash 424 | #!/bin/bash 425 | echo "Welcome User" 426 | firstline=$(head -n1 ./source/changelog.md) 427 | read -a splitfirstline <<< $firstline 428 | version=${splitfirstline[1]} 429 | echo $version 430 | echo "Enter 1 to continue, 0 to exit" 431 | read versioncontinue 432 | if [ $versioncontinue -eq 1 ] 433 | then 434 | files=source/* 435 | for file in $files 436 | do 437 | if [ $file == "source/secretinfo.md" ] 438 | then 439 | echo "$file Not copied" 440 | else 441 | echo "$file being copied" 442 | fi 443 | done 444 | else 445 | echo 'Please come back when you are ready' 446 | fi 447 | ``` 448 | 449 | ``` 450 | $ ./script.sh 451 | Welcome User 452 | 11.2.3 453 | Enter 1 to continue, 0 to exit 454 | 1 455 | source/bar.js being copied 456 | source/buzz.css being copied 457 | source/changelog.md being copied 458 | source/foo1.html being copied 459 | source/foo2.html being copied 460 | source/foo3.html being copied 461 | source/secretinfo.md Not copied 462 | ``` 463 | 464 | 10. Now we can actually copy the files. After informing the user the file is being copied, copy the file into the `build` directory. 465 | 466 | You can use the terminal to make sure the right files have been copied: 467 | 468 | ``` 469 | ls build/ 470 | ``` 471 | 472 | ```bash 473 | #!/bin/bash 474 | echo "Welcome User" 475 | firstline=$(head -n1 ./source/changelog.md) 476 | read -a splitfirstline <<< $firstline 477 | version=${splitfirstline[1]} 478 | echo $version 479 | echo "Enter 1 to continue, 0 to exit" 480 | read versioncontinue 481 | if [ $versioncontinue -eq 1 ] 482 | then 483 | files=source/* 484 | for file in $files 485 | do 486 | if [ $file == "source/secretinfo.md" ] 487 | then 488 | echo "$file Not copied" 489 | else 490 | echo "$file being copied" 491 | cp $file build/ 492 | fi 493 | done 494 | else 495 | echo 'Please come back when you are ready' 496 | fi 497 | ``` 498 | 499 | ``` 500 | $ ./script.sh 501 | Welcome User 502 | 11.2.3 503 | Enter 1 to continue, 0 to exit 504 | 1 505 | source/bar.js being copied 506 | source/buzz.css being copied 507 | source/changelog.md being copied 508 | source/foo1.html being copied 509 | source/foo2.html being copied 510 | source/foo3.html being copied 511 | source/secretinfo.md Not copied 512 | $ ls build/ 513 | bar.js changelog.md foo2.html 514 | buzz.css foo1.html foo3.html 515 | ``` 516 | 517 | 11. The final thing we want to do is list the files in the **build** directory for the user. 518 | 519 | Outside of the loop over the filenames in the directory, use the script to change the directory to the build directory. So that we don't forget, also add the command to change back to the directory with the script. 520 | 521 | 12. Add code to notify the user what files are currently in the build directory. 522 | 523 | Be sure to reference the version in your message. 524 | 525 | ```bash 526 | #!/bin/bash 527 | echo "Welcome User" 528 | firstline=$(head -n1 ./source/changelog.md) 529 | read -a splitfirstline <<< $firstline 530 | version=${splitfirstline[1]} 531 | echo $version 532 | echo "Enter 1 to continue, 0 to exit" 533 | read versioncontinue 534 | if [ $versioncontinue -eq 1 ] 535 | then 536 | files=source/* 537 | for file in $files 538 | do 539 | if [ $file == "source/secretinfo.md" ] 540 | then 541 | echo "$file Not copied" 542 | else 543 | echo "$file being copied" 544 | cp $file build/ 545 | fi 546 | done 547 | cd build/ 548 | echo "Build version $version contains:" 549 | ls 550 | cd .. 551 | else 552 | echo 'Please come back when you are ready' 553 | fi 554 | ``` 555 | 556 | ``` 557 | $ ./script.sh 558 | Welcome User 559 | 11.2.3 560 | Enter 1 to continue, 0 to exit 561 | 1 562 | source/bar.js being copied 563 | source/buzz.css being copied 564 | source/changelog.md being copied 565 | source/foo1.html being copied 566 | source/foo2.html being copied 567 | source/foo3.html being copied 568 | source/secretinfo.md Not copied 569 | Build version 11.2.3 contains: 570 | bar.js changelog.md foo2.html 571 | buzz.css foo1.html foo3.html 572 | ``` 573 | 574 | 13. You now have a build script for this repository. Feel free to play around with making it more robust. Some ideas: 575 | 576 | - Copy **secretinfo.md** but replace "42" with "XX". 577 | - Zip the resulting **build** directory. 578 | - Give the script more character with emojis. 579 | - If you are familiar with `git`, commit the changes in the build directory. 580 | 581 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bash Shell Scripting Notes 2 | Shell Scripting Short Notes & Reference 3 | 4 | - [Bash Shell Scripting Notes](#bash-shell-scripting-notes) 5 | * [Introduction](#introduction) 6 | + [Executing a Shell Script:](#executing-a-shell-script-) 7 | + [First Line of a shell script:](#first-line-of-a-shell-script-) 8 | + [Printing/Displaying:](#printing-displaying-) 9 | + [Comments:](#comments-) 10 | + [Variables:](#variables-) 11 | + [Variable Names:](#variable-names-) 12 | + [Using Variables:](#using-variables-) 13 | + [Storing Commands in variables:](#storing-commands-in-variables-) 14 | + [Tests:](#tests-) 15 | - [File Operator Tests:](#file-operator-tests-) 16 | - [String Operator Tests:](#string-operator-tests-) 17 | * [General Test Syntax: `[ -flag STRING]`](#general-test-syntax------flag-string--) 18 | - [Arithmetic Operator Tests:](#arithmetic-operator-tests-) 19 | + [The `if` Condition: (Uses Tests in its conditional)](#the--if--condition---uses-tests-in-its-conditional-) 20 | + [The `if-else` Condition: (Uses Tests in its conditional)](#the--if-else--condition---uses-tests-in-its-conditional-) 21 | + [The `if-elif-else` Condition: (Uses Tests in its conditional)](#the--if-elif-else--condition---uses-tests-in-its-conditional-) 22 | + [The `for` loop:](#the--for--loop-) 23 | + [Positional Parameters:](#positional-parameters-) 24 | - [The `$@`:](#the------) 25 | + [Accepting User Input (STDIN):](#accepting-user-input--stdin--) 26 | + [Exit Status of Commands:](#exit-status-of-commands-) 27 | - [The `$?`: (Return code/exit status)](#the--------return-code-exit-status-) 28 | - [Storing return code/exit status in a variable:](#storing-return-code-exit-status-in-a-variable-) 29 | + [Chaining Commands:](#chaining-commands-) 30 | + [Exit Statuses of Shell scripts:](#exit-statuses-of-shell-scripts-) 31 | + [Functions:](#functions-) 32 | - [Calling a function:](#calling-a-function-) 33 | - [Parameters to functions:](#parameters-to-functions-) 34 | - [Variable Scope:](#variable-scope-) 35 | - [Local Variables:](#local-variables-) 36 | - [Exit Status/Return Code of functions:](#exit-status-return-code-of-functions-) 37 | + [Shell Script CheckList:](#shell-script-checklist-) 38 | + [WILDCARDS:](#wildcards-) 39 | - [Using Wildcards in Shell Scripts:](#using-wildcards-in-shell-scripts-) 40 | + [Switch-Case Statements:](#switch-case-statements-) 41 | + [Logging:](#logging-) 42 | - [Log files locations are CONFIGURABLE:](#log-files-locations-are-configurable-) 43 | - [Logging with `logger`:](#logging-with--logger--) 44 | + [`while` loops:](#-while--loops-) 45 | - [Infinite While loops:](#infinite-while-loops-) 46 | - [Reading a file LINE-BY-LINE:](#reading-a-file-line-by-line-) 47 | - [Exiting a loop before it's normal end:](#exiting-a-loop-before-it-s-normal-end-) 48 | - [Skipping iteration and executing next iteration on a loop:](#skipping-iteration-and-executing-next-iteration-on-a-loop-) 49 | + [Arithmetic Operations:](#arithmetic-operations-) 50 | + [Debugging Shell Scripts:](#debugging-shell-scripts-) 51 | - [`-x` option:](#--x--option-) 52 | - [Exit on Error:](#exit-on-error-) 53 | - [Print Shell Input Lines (As they are being read):](#print-shell-input-lines--as-they-are-being-read--) 54 | + [Manual Debugging:](#manual-debugging-) 55 | + [Syntax Highlighting:](#syntax-highlighting-) 56 | - [PS4:](#ps4-) 57 | + [File Types (DOS/WINDOWS vs UNIX/LINUX):](#file-types--dos-windows-vs-unix-linux--) 58 | - [Knowing what type of file: (To which shell does the script belong to?)](#knowing-what-type-of-file---to-which-shell-does-the-script-belong-to--) 59 | - [Converting DOS text scripts to UNIX scripts:](#converting-dos-text-scripts-to-unix-scripts-) 60 | - [Converting UNIX text scripts to DOS scripts:](#converting-unix-text-scripts-to-dos-scripts-) 61 | 62 | 63 | ## Introduction 64 | 65 | Any series of terminal commands can be put into a script. The shell, which is an interpreter, reads the script and executes those commands. Shell Scripting is useful to automate tasks, performing complex execution, etc. 66 | 67 | BASH script = **B**ourne **A**gain **SH**ell script 68 | 69 | ### Executing a Shell Script: 70 | 71 | Give execute permission to your script. Ex: `chmod +x /path/to/yourscript.sh` 72 | 73 | And to run your script. Ex: `/path/to/yourscript.sh` 74 | 75 | We can also use the relative path if it is known: Ex: `./yourscript.sh` (`./` is necessary if directory is not in $PATH!) 76 | 77 | ### First Line of a shell script: 78 | 79 | When we create a shell script and wish to run it, we need to make sure it has the execute permission. 80 | 81 | Use `chmod` command to add it, if execute permission does not exist. 82 | 83 | Every script STARTS with a line like this: 84 | 85 | `#!/path/to/interpreter` 86 | 87 | The #(sharp) and !(bang/exclamation) are collectively known as the 'SHEBANG'. 88 | 89 | The first line tells us which interpreter(Shell) is supposed to execute this script!. 90 | Examples: 91 | - `#!/bin/csh` = c shell, 92 | - `#!/bin/ksh` = k shell, 93 | - `#!/bin/zsh` = z shell, 94 | - `#!/bin/bash` = bash shell 95 | 96 | Basically, when the script runs: It is the specified shell that is running as a process and the file that it executes is the script's path. 97 | 98 | You don't have to use a shell as an interpreter: Ex: `#!/usr/bin/python` => Uses the python interpreter (Hence, a python script) 99 | 100 | Note: If we don't specify the interpreter, the current shell executes the script but this is tricky. 101 | If the script contains commands not understood by the current shell, it could cause errors (Don't do this!) 102 | 103 | ### Printing/Displaying: 104 | 105 | `echo` command = It prints the supplied argument/string. Every echo statement prints on a NEW LINE. 106 | 107 | Ex: `echo "Hello World!"` => Output: `Hello World!` 108 | 109 | ### Comments: 110 | 111 | Every line other than the first line (#! /bin/..) that STARTS with a '#'(pound/sharp/hash) marks a comment, example: 112 | 113 | ``` 114 | #!/bin/bash 115 | 116 | # Let's print something => A comment 117 | 118 | echo "Hello There" 119 | 120 | # End of printing => A comment 121 | ``` 122 | 123 | - If '#' STARTS on the line, the WHOLE LINE is IGNORED. 124 | - If '#' appears in the MIDDLE of a line, Anything to the RIGHT of a '#' is IGNORED. 125 | 126 | ### Variables: 127 | 128 | Storage locations that have a name. They are 'name-value' pairs. 129 | 130 | Syntax: `VARIABLE_NAME="Value"` 131 | 132 | **NOTE: NO SPACES before or after the '='** 133 | 134 | - The variable names are CASE-SENSITIVE!. 135 | - And, by "convention", they are usually in UPPERCASE. 136 | 137 | ### Variable Names: 138 | 139 | Variable names can contain: 140 | - LETTERS (a-z, A-Z), 141 | - DIGITS (0-9), & 142 | - UNDERSCORES (_) [ONLY!] 143 | 144 | VARIABLE NAMES **CANNOT** START WITH A DIGIT! 145 | 146 | ### Using Variables: 147 | 148 | Precede the variable name with a '$' sign. Ex: 149 | ``` 150 | MY_SHELL="bash" 151 | echo "This script uses the $MY_SHELL shell" 152 | ``` 153 | Output: This script uses the bash shell. 154 | 155 | **Alternatively: (Optional)** 156 | 157 | Enclose the variable in curly brackets '{}' and preced it with a '$', Ex: 158 | ``` 159 | MY_SHELL="bash" 160 | echo "I am ${MY_SHELL}ing shell" 161 | ``` 162 | Output: I am bashing on my keyboard 163 | 164 | Hint: It's a best practice to use the ${VARIABLE} syntax if there is text or characters that directly precede or follow the variable. 165 | 166 | If a specified variable does NOT exist, Nothing is printed in its place in the echo statement. 167 | 168 | ### Storing Commands in variables: 169 | 170 | `VAR_NAME=$(command)` 171 | 172 | (or) 173 | 174 | ``` 175 | VAR_NAME=`command` 176 | ``` 177 | 178 | (Command enclosed with tilde sign: Older Scripts) 179 | 180 | ### Tests: 181 | 182 | Syntax: `[ condition-to-test-for ]` 183 | 184 | Returns true if test passes, else false. Ex: 185 | 186 | `[ -e /etc/passwd]` = tests if the '/etc/passwd' file exists. 187 | 188 | #### File Operator Tests: 189 | 190 | General Test Syntax: `[ -flag fileOrDirPath]` 191 | Flags: 192 | - `-d` = True if file is a directory 193 | - `-e` = True if file exists 194 | - `-f` = True if file exists and is a regular file 195 | - `-s` = True if file exists and is NOT empty 196 | - `-r` = True if file is readable by you 197 | - `-w` = True if file is writable by you 198 | - `-x` = True if file is executable by you 199 | 200 | #### String Operator Tests: 201 | 202 | ##### General Test Syntax: `[ -flag STRING]` 203 | 204 | Flags: 205 | - `-z` = True if STRING is empty 206 | - `-n` = True if STRING is NOT empty 207 | 208 | Equality Tests: 209 | - `STRING1 = STRING2` = True if strings are equal 210 | - `STRING1 != STRING2` = True if strings are NOT equal 211 | 212 | NOTE: For testing Variable string in the conditions, enclose it in quote(""). Ex: 213 | 214 | `"$MY_SHELL" = "bash"` 215 | 216 | #### Arithmetic Operator Tests: 217 | 218 | General Test Syntax: `[ arg1 -flag arg2]` 219 | 220 | Flags: 221 | - `-eq` = True if arg1 equals to arg2 222 | - `-ne` = True if arg1 is NOT equal to arg2 223 | - `-lt` = True if arg1 is LESS THAN arg2 224 | - `-le` = True if arg1 is LESS THAN OR EQUAL TO arg2 225 | - `-gt` = True if arg1 is GREATER THAN arg2 226 | - `-ge` = True if arg1 GREATER THAN OR EQUAL TO arg2 227 | 228 | 229 | (`man test` => Help on tests.) 230 | 231 | ### The `if` Condition: (Uses Tests in its conditional) 232 | 233 | ``` 234 | if [ condition-is-true ] 235 | then 236 | command 1 237 | command 2 238 | ... 239 | command N 240 | fi 241 | ``` 242 | 243 | ### The `if-else` Condition: (Uses Tests in its conditional) 244 | 245 | ``` 246 | if [ condition-is-true ] 247 | then 248 | commands 1 ... N 249 | else 250 | commands 1 ... N 251 | fi 252 | ``` 253 | 254 | ### The `if-elif-else` Condition: (Uses Tests in its conditional) 255 | 256 | ``` 257 | if [ condition-is-true ] 258 | then 259 | commands 1 ... N 260 | elif [ condition-is-true ] 261 | then 262 | commands 1 ... N 263 | else 264 | commands 1 ... N 265 | fi 266 | ``` 267 | 268 | ### The `for` loop: 269 | 270 | ``` 271 | for VARIABLE_NAME in ITEM_1 ... ITEM_N 272 | do 273 | command 1 274 | command 2 275 | ... 276 | command N 277 | done 278 | ``` 279 | 280 | First item in the block is assigned to variable and commands are executed, next item in in the block is assigned to variable and commands are executed again, and so on ... 281 | 282 | VARIABLE_NAME need NOT be declared earlier. 283 | 284 | **Ex 1:** 285 | 286 | ``` 287 | for COLOR in red green blue 288 | do 289 | echo "COLOR: $COLOR" 290 | done 291 | ``` 292 | 293 | Output: 294 | ``` 295 | COLOR: red 296 | COLOR: green 297 | COLOR: blue 298 | ``` 299 | 300 | **We can also store the items in a variable, separated by spaces!** 301 | 302 | **Ex 2:** 303 | 304 | ``` 305 | COLORS="red green blue" 306 | for COLOR in $COLORS 307 | do 308 | echo "COLOR: $COLOR" 309 | done 310 | ``` 311 | 312 | Output: 313 | ``` 314 | COLOR: red 315 | COLOR: green 316 | COLOR: blue 317 | ``` 318 | 319 | Note:: Bash scripting also contains `while` loops. 320 | 321 | ### Positional Parameters: 322 | 323 | Syntax (On the CLI): `$ script.sh parameter1 parameter2 parameter3` 324 | 325 | - `$0` = "script.sh" (The script itself) 326 | - `$1` = "parameter1" 327 | - `$2` = "parameter2" 328 | - `$3` = "parameter3" 329 | 330 | We can Assign Positional Parameters to Variables. Ex: 331 | 332 | `USER=$1` 333 | 334 | #### The `$@`: 335 | 336 | `$@` contains all the parameters starting from Parameter 1 to the last parameter. It can be used to loop over parameters. Ex: 337 | ``` 338 | for USER in $@ 339 | do 340 | echo "Archiving user : $USER" 341 | ... 342 | ... 343 | done 344 | ``` 345 | 346 | ### Accepting User Input (STDIN): 347 | 348 | `read` command. 349 | 350 | Syntax: `read -p "PROMPT" VARIABLE` 351 | 352 | Note: The input could also come from pipelined(|) or redirected output/input(<, >) if STDIN is changed to those. 353 | 354 | Ex: 355 | ``` 356 | read -p "Enter a UserName: " USER 357 | echo "ARCHIVING $USER" 358 | ``` 359 | 360 | ### Exit Status of Commands: 361 | 362 | Every command returns an exit status. Range of the status is from `0 - 255`. It is used for Error checking. 363 | 364 | - `0` = Success 365 | - Other than `0` = Error condition 366 | 367 | **Use `man` on the command to find out what exit status means what (for the command).** 368 | 369 | #### The `$?`: (Return code/exit status) 370 | 371 | `$?` contains the return code(exit status) of the 'previously executed' command. 372 | 373 | Ex: 374 | ``` 375 | ls /not/here 376 | echo "$?" 377 | ``` 378 | 379 | Output: `2` is echoed, which is the return command. 380 | 381 | Ex: 382 | ``` 383 | HOST="google.com" 384 | ping -c 1 $HOST => -c tells command to send only 1 packet to test connection 385 | if [ "$?" -eq "0" ] 386 | then 387 | echo "$HOST reachable." 388 | else 389 | echo "$HOST unreachable." 390 | fi 391 | ``` 392 | 393 | #### Storing return code/exit status in a variable: 394 | 395 | Ex: 396 | ``` 397 | ping -c 1 "google.com" 398 | RETURN_CODE=$? => Now, we can use the variable RETURN_CODE anywhere in the script. 399 | ``` 400 | 401 | ### Chaining Commands: 402 | 403 | - `&&` => AND => Executes commands one after the other UNTIL one of them FAILS. (in that case -> short-circuiting) 404 | 405 | (It executes commands as long as they are returning exit statuses `0`. STOPS as soons as a command does NOT return 0) 406 | 407 | Syntax: `cmd1 && cmd2 && ...` 408 | 409 | - `||` => OR => Executes commands one after the other UNTIL one of them SUCCEEDS. (in that case -> short-circuiting) 410 | 411 | (It executes commands as long as they are returning exit statuses NOT `0`. STOPS as soons as a command returns 0) 412 | 413 | Syntax: `cmd1 || cmd2 || ...` 414 | 415 | - `;` => Semicolon => Executes commands ONE AFTER ANOTHER without checking the exit statuses/return codes. 416 | 417 | (It it same as/equivalent to executing each of the commands on a separate line) 418 | 419 | Syntax: `cmd1 ; cmd2 ; ...` 420 | 421 | ### Exit Statuses of Shell scripts: 422 | 423 | Shell scripts too can have exit statuses. 424 | 425 | `exit` command needs to be used. 426 | 427 | - `exit` => Exits the script with exit status equal to that of the previously executed command within the script. 428 | - `exit X` => Exits with exit status X. X is a number between 0 & 255. (0 = Success, !0 = Error) 429 | 430 | (No exit status => This also equals the exit status of the previously executed command within the script) 431 | 432 | ### Functions: 433 | 434 | Reduces script length, **"DRY"(Dont Repeat Yourself) - concept of functions**. 435 | 436 | A function: 437 | - Is a block of reusable code. 438 | - Must be defined before use. 439 | - Has parameter support. 440 | - It can return an "exit status/return code". 441 | 442 | **Method 1:** 443 | ``` 444 | function function-name() { 445 | # code goes here 446 | } 447 | ``` 448 | 449 | **Method 2:** 450 | ``` 451 | function-name() { 452 | # code goes here 453 | } 454 | ``` 455 | 456 | #### Calling a function: 457 | 458 | `function-name` 459 | 460 | We do **NOT** use parentheses () in the function like in other programming languages. Functions need to be defined before they are used. (In Top Down order of parsing) (That is, Function Definition must have been scanned(Top->Down parsing) before the call to the function) 461 | 462 | **Functions can call other functions** 463 | 464 | #### Parameters to functions: 465 | 466 | `function-name parameter1 parameter2 ...` 467 | 468 | - `$1`, `$2`, ... = Parameter1, Parameter2, ... 469 | - `$@` = Array of all the parameters 470 | 471 | NOTE: `$0` REFERS TO THE SHELL SCRIPT ITSELF (NOT THE FUNCTION!) 472 | 473 | Ex: 474 | ``` 475 | function hello() { 476 | echo "Hello, $1" 477 | } 478 | hello Jason 479 | ``` 480 | 481 | Output: `"Hello, Jason"` 482 | 483 | #### Variable Scope: 484 | 485 | - ALL Variables are GLOBAL by Default! 486 | - Variables have to be DEFINED before Use. 487 | 488 | Therefore, All variables defined 'before' a 'function call' can be accessed within it. Those that are defined 'after' the 'function call' **cannot** be accessed. 489 | 490 | - Accessing a variable that has NOT been defined before the function call : Nothing/null/No Value for Variable. 491 | - Accessing a variable that has been defined before the function call : Correct value. 492 | 493 | #### Local Variables: 494 | 495 | Variables that can be accessed only within the function that it is declared. Use the keyword `local`. 496 | 497 | Syntax: `local LOCAL_VAR=someValue` 498 | 499 | Only functions can have local variables! (Try to keep variables local inside a function) 500 | 501 | #### Exit Status/Return Code of functions: 502 | 503 | - Explicity: `return ` 504 | 505 | Note: For the WHOLE SCRIPT, we used the `exit ` command. For functions it is `return` keyword. 506 | 507 | - Implicitly: The exit status of the last command executed within the function. 508 | 509 | Exit status range: `0 to 255` 510 | 511 | - `0` = Success, 512 | - All other values = errors of some kind, 513 | 514 | - `$?` = gets the exit status of last executed command(after execution of cmd)/function(after the call)/script(terminal)) 515 | 516 | Tip: write function to backup files, returning 0 exit status if successful. 517 | 518 | NOTE: 519 | - `basename fileOrDirPath` => returns just the filename/Directory name after stripping off the path to the file. 520 | - `dirname fileOrDirPath` => Getting the directory of the file/directory. 521 | 522 | ### Shell Script CheckList: 523 | 524 | HOW to write your scripts (PATTERN!) 525 | 526 | 1. Does your script start with a shebang? 527 | 2. Does your script include a comment describing the purpose of the script? 528 | 3. Are the global variables declared at the top of your script, following the initial comment(s)? 529 | 4. Have you grouped all of your functions together following the global variables? 530 | 5. Do your functions use local variables? 531 | 6. Does the main body of your shell script follow the functions? 532 | 7. Does your script exit with an explicit exit status? 533 | 8. At the various exit points, are exit statuses explicitly used? 534 | 535 | **EXAMPLE** 536 | ``` 537 | #!/bin/bash 538 | # 539 | # 540 | 541 | GLOBAL_VAR1="one" 542 | GLOBAL_VAR2="two" 543 | 544 | function function_one() { 545 | local LOCAL_VAR1="one" 546 | # 547 | } 548 | 549 | # Main body of the shell script starts here. 550 | # 551 | # 552 | 553 | # Exit with an explicit exit status. Ex: exit 0 554 | ``` 555 | 556 | ### WILDCARDS: 557 | 558 | **(Read in detail in: 'Command Line Basics Course/pdf/cheatsheet')** 559 | 560 | Wildcards are Character or Strings used for pattern matching. Also referred to as **'Globbing'**: Commonly used to match file or directory paths. Wilcards can be used with **MOST** commands. 561 | 562 | - * - matches 0 or more characters (Ex: *.txt, a*, a*.txt, *a*.txt) 563 | - ? - matches exactly 1 character (Ex: ?.txt, a?, a?.txt, ?a.txt, a???.txt) 564 | - [] - a character class(Mathces any of the characters inside bracket, exactly one match.) (Ex: [aeiou], ca[nt]* => can cant candy ... etc) 565 | - [!] - matches any of the characters NOT included inside the bracket. Exactly one match. (Ex: [!aeiou]* => First character should not be a vowel) 566 | - [x-y] - creating a range of values to match. Exactly one match. (Ex: [a-g]* => start with any letter between a and g, [3-6]* => start with any number between 3 and 6) 567 | 568 | - [[:alpha:]] => Matches upper and lower case letters. Exactly One match. 569 | - [[:alnum:]] => Matches upper and lower case letters or any decimal digits (0-9). Exactly One match. 570 | - [[:digit:]] => Matches decimal digits (0-9). Exactly One match. 571 | - [[:lower:]] => Matches lower case letters. Exactly One match. 572 | - [[:space:]] => Matches White Space. Exactly One match. 573 | - [[:upper:]] => Matches upper case letters. Exactly One match. 574 | 575 | - \ - Match a wildcard character (Escape) (Ex: *\? => matches anything followed by a question mark [Ex: 'done?']) 576 | 577 | TIP:: good practice to **not** include wildcards as filenames/directory names. 578 | 579 | Examples: 580 | - `ls *.txt` => list all files ending with '.txt' 581 | - `ls a*` => list all files starting with 'a' 582 | - `ls *aA` => list all files containing 'aA' 583 | - `ls ?` => list all files containing one character 584 | - `ls ??` => list all files containing two characters 585 | - `ls ?.txt` => list all files containing one character and ending with '.txt' 586 | - `ls fil?` => list all files such as 'file', 'filk', 'fild' ... (starting with 'fil' followed by any one character) 587 | - `ls [a-d]*` => list all files starting with any lowercase letter in between 'a' and 'd' 588 | - `ls *mp[[:digit:]]` => lists all files ending with 'mp' and a digit(0-9). [Ex: ending with 'mp3', 'mp4', ...] 589 | 590 | #### Using Wildcards in Shell Scripts: 591 | 592 | Wildcards are great for working with groups of files of directories. 593 | 594 | We can use it just like in regualar commands. Ex1: 595 | ``` 596 | #!/bin/bash 597 | cd /var/www 598 | cp *.html /var/www-just-html 599 | ``` 600 | 601 | Ex2: 602 | ``` 603 | for FILE in /var/www/*.html 604 | do 605 | echo "Copying $FILE" 606 | cp $FILE /var/www-just-html 607 | done 608 | ``` 609 | 610 | (NOTE: In the above example, the wildcard matches in the for loop expand as a List/Array that can be Iterated.) 611 | 612 | ### Switch-Case Statements: 613 | 614 | (Similar to `Switch-Case`) => Testing for multiple values. Use in-place of many 'if-elif-elif-elif...' scenarios! 615 | 616 | Syntax: 617 | ``` 618 | case "$VAR" in 619 | pattern_1) 620 | #commands go here 621 | ;; 622 | pattern_N) 623 | #commands go here 624 | ;; 625 | esac 626 | ``` 627 | 628 | Patterns are case-sensitive!. Once a pattern has been matched, the commands of a pattern are executed until `;;` is reached! `;;` is like a break statement. 629 | 630 | Examples. Ex1: 631 | ``` 632 | case "$1" in 633 | start) 634 | /usr/sbin/sshd # Executes the script at the specified path! 635 | ;; 636 | stop) 637 | kill $(cat /var/run/sshd.pid) 638 | ;; 639 | esac 640 | ``` 641 | 642 | Note: Wildcards can be used as patterns: Ex: `*)` matches anything. 643 | 644 | The pipe(|) maybe used as an 'OR' in the patterns. Ex2: 645 | ``` 646 | case "$1" in 647 | start|START) 648 | /usr/sbin/sshd 649 | ;; 650 | stop|STOP) 651 | kill $(cat /var/run/sshd.pid) 652 | ;; 653 | *) 654 | echo "Usage: $0 start|stop" ; exit 1 655 | ;; 656 | esac 657 | ``` 658 | 659 | The above example matches either upper or lower case 'start', else either upper or lower case 'stop', else it 660 | matches anything that did not match one of the first two cases. 661 | 662 | We can use other wildcards in the patterns as well. Ex3: 663 | ``` 664 | case "$1" in 665 | [yY]|[yY][eE][sS]) 666 | echo "You answered yes." 667 | ;; 668 | [nN]|[nN][oO]) 669 | echo "You answered no." 670 | ;; 671 | *) 672 | echo "Invalid Answer." 673 | ;; 674 | esac 675 | ``` 676 | Above matches y, yes (case-insensitive) or n, no(case-insensitive). 677 | 678 | NOTE: All the rules of wildcards are valid for patterns of case statements. 679 | 680 | Case Statements: 681 | - In place of if statements, 682 | - Wildcards maybe used for patterns. 683 | - Multiple patterns can be matched with the help of a pipe(|) which acts as an 'OR' in the pattern. 684 | 685 | ### Logging: 686 | 687 | `Syslog` => Uses standard facilities and severities to categorize messages. 688 | 689 | - `Facilities`: `kern`, `user`, `mail`, `daemon`, `auth`, `local0`, `local7`, etc. 690 | - `Severities`: `emerg`, `alert`, `crit`, `err`, `warning`, `notice`, `info`, `debug`. 691 | 692 | Ex: if your script is using mail, you could use the 'mail' facility for logging. 693 | 694 | #### Log files locations are CONFIGURABLE: 695 | 696 | 1. '/var/log/messages' 697 | 698 | (or) 699 | 700 | 2. '/var/log/syslog' 701 | 702 | (Location depends on system) 703 | 704 | #### Logging with `logger`: 705 | 706 | `logger` is a command line utility - used for logging 'syslog' messages. By default, it creates `user.notice` messages. 707 | 708 | 1. Basic logging message: `logger "Message"` 709 | 710 | Example output: 'Aug 2 01:02:44 linuxsvr: Message'. 711 | 712 | 2. Logging message with facility and severity: Syntax: `logger -p facility.severity "Message"` 713 | 714 | Ex: `logger -p local0.info "Message"` 715 | 716 | 3. Tagging the log message: Use the `-t` option followed by tagName 717 | 718 | Usually you want to use the script's name as tag in order to easily identify the source of the log message. `logger -t myscript -p local0.info "Message"` 719 | 720 | Example output: 'Aug 2 01:02:44 linuxsvr myscript: Message' 721 | 722 | 4. Include PID(Process ID in the log message): use `-i` option: `logger -i -t myscript "Message"` 723 | 724 | Ex output: 'Aug 2 01:02:44 linuxsvr myscript[12986]: Message' 725 | 726 | 5. Additionally display log message on screen (apart from already logging it to the log file): use `-s` option 727 | 728 | Ex: `logger -s -p local0.info "Message"` 729 | 730 | NOTE: Different facilities and severities could cause the system logger to route the log messages to a different locaton/log file. 731 | 732 | **NOTE:** 733 | 734 | `$RANDOM` generates a random number. Ex: 735 | 736 | `echo "$RANDOM"` => 29133 737 | 738 | Using `shift`: `shift` shifts the command line arguments to the left (The script name is deleted). 739 | 740 | The original param1 becomes $0, original param2 becomes $1, and so on ... 741 | 742 | ### `while` loops: 743 | 744 | Loop control (Alternative to `for`) 745 | 746 | Syntax: 747 | ``` 748 | while [ CONDITION_IS_TRUE ] 749 | do 750 | command 1 751 | command 2 752 | ... 753 | command N 754 | done 755 | ``` 756 | 757 | While the command in the command keeps returning 0(success) exit status, the while loop keeps looping/executing. Usually commands inside the while loop change the condition for the next iteration's check. 758 | 759 | #### Infinite While loops: 760 | 761 | Condition is always true, keeps looping forever (Use `CTRL-C` to exit the script - when executed in the terminal). You may need to use the `kill` command to kill the process - when run as an application outside terminal. 762 | 763 | You may want to run some background processes infinitely: Ex: Running a daemon process in the background: 764 | ``` 765 | while true 766 | do 767 | command 1 ... N 768 | sleep 1 769 | done 770 | ``` 771 | 772 | The `sleep` command: Used to 'PAUSE' the execution of the code for a given number of seconds (as argument). 773 | 774 | Examples of while loops: Ex1: 775 | ``` 776 | INDEX=1 777 | while [ $INDEX -lt 6 ] 778 | do 779 | echo "Creating project-${INDEX}" 780 | mkdir /usr/local/project-${INDEX} 781 | ((INDEX++)) # Arithmetic operations are enclosed within '((...))' 782 | done 783 | ``` 784 | 785 | Ex2: 786 | ``` 787 | while [ "$CORRECT" != "y" ] 788 | do 789 | read -p "Enter your name: " NAME 790 | read -p "Is $NAME correct?: " CORRECT 791 | done 792 | ``` 793 | 794 | Ex3: 795 | ``` 796 | while ping -c 1 app1 > /dev/null # ping must succeed & redirect o/p to /dev/null as we don't want to see msg 797 | do 798 | echo "app1 still up ... " 799 | sleep 5 # Pause execution for 5 seconds 800 | done 801 | echo "app1 is down! ... " 802 | ``` 803 | 804 | #### Reading a file LINE-BY-LINE: 805 | 806 | - Not possible in `for` loop since it reads word by word. 807 | 808 | While loop example: 809 | ``` 810 | LINE_NUM=1 811 | while read LINE 812 | do 813 | echo "${LINE_NUM}: ${LINE}" 814 | ((LINE_NUM++)) 815 | done < /etc/fstab 816 | ``` 817 | 818 | - The /etc/fstab is taken as input to the whole while loop. 819 | - `read LINE` reads the current line of the file 820 | - Since it is a condition of the while, while stops when lines of the files are over 821 | 822 | Note: input to read command need not only be lines from a file, we can use pipelining to the while to read from commands, etc. 823 | 824 | Note: read command supports splitting of data that it reads into multiple variables! 825 | 826 | #### Exiting a loop before it's normal end: 827 | 828 | - Use `break` statement. 829 | ``` 830 | while [ CONDITION_IS_TRUE ] 831 | do 832 | ... 833 | break 834 | ... 835 | done 836 | ``` 837 | 838 | #### Skipping iteration and executing next iteration on a loop: 839 | 840 | - Use `continue` statement. 841 | ``` 842 | while [ CONDITION_IS_TRUE ] 843 | do 844 | ... 845 | continue 846 | ... 847 | done 848 | ``` 849 | 850 | ### Arithmetic Operations: 851 | 852 | We can execute arithmetic operations within double parentheses'((...))'. Ex: 853 | 854 | `((INDEX++))` = increments INDEX value by 1 (++ operator). Useful within loops. 855 | 856 | 857 | ### Debugging Shell Scripts: 858 | 859 | 'bug' => Means error 860 | 861 | - Examine inner workings of your script 862 | - Get to the Source/Root of the problem 863 | - Fix Bugs(errors) 864 | 865 | #### `-x` option: 866 | 867 | Debug Whole script: 868 | 869 | - `#!/bin/bash -x` = prints commands and their arguments as they execute. That is: Values of variables, values of wildcard matches. 870 | 871 | Ex: `VAR1="HI"` (Call an x-trace) 872 | 873 | - `PS4` controls what is shown before a line while using '-x' command (default is '+') (LATER..) 874 | 875 | - Debug from command line: 876 | - `set -x` = Start Debugging 877 | - `set +x` = Stop Debugging 878 | 879 | Ex: 880 | ``` 881 | $ set -x 882 | $ ./scriptName.sh 883 | 884 | $set +x 885 | ``` 886 | 887 | - Debug only a portion of the code: 888 | Ex: 889 | ``` 890 | #!/bin/bash 891 | ... 892 | set -x 893 | echo $VAR_NAME 894 | set +x 895 | ... 896 | ``` 897 | 898 | Note: For every command that is debugged, a '+' sign appears to it's left. The outputs of commands (Ex: output of echo command) DON'T have a '+' next to them. 899 | 900 | Note: Using ONLY THE `-x` flag executes subsequent lines of code (commands) even if a previous command was erroneous! 901 | 902 | #### Exit on Error: 903 | 904 | - `-e` flag. (Exit immediately if a command exists with a non-zero status.) 905 | 906 | Syntax: `#!/bin/bash -e` 907 | 908 | Ex: It can be combined with the trace (-x) option. 909 | - `#!/bin/bash -e -x` 910 | - `#!/bin/bash -ex` 911 | - `#!/bin/bash -xe` 912 | - `#!/bin/bash -x -e` 913 | 914 | #### Print Shell Input Lines (As they are being read): 915 | 916 | - `-v` flag. (Can also be combined with -x and -e.) 917 | 918 | Prints input lines before (without) any substitutions and expansions are performed. Therefore, All the lines of the shell script are printed as they are, and the outputs of printing commands(like echo) are also displayed. 919 | 920 | - `#!/bin/bash -vx` = Useful, because we can see trace (substituted input) lines as well as shell script lines! 921 | 922 | More Info: `help set` or `help set | less` 923 | 924 | ### Manual Debugging: 925 | 926 | You can create your own debugging code. Ex: Use a sepcial variable like DEBUG. (DEBUG=true, DEBUG=false) 927 | 928 | - Boolean `true` : exit status 0 (success) 929 | - Boolean `false` : exit status non-zero (failure) 930 | 931 | Ex 1: 932 | ``` 933 | #!/bin/bash 934 | DEBUG=true 935 | $DEBUG && echo "debug mode on!" 936 | ``` 937 | 938 | ``` 939 | #!/bin/bash 940 | DEBUG=false 941 | $DEBUG || echo "debug mode off!" 942 | ``` 943 | 944 | Ex 2: (When you want to echo lines in debug mode) 945 | ``` 946 | #!/bin/bash 947 | DEBUG="echo" 948 | $DEBUG ls 949 | ``` 950 | 951 | Prints ls output to screen since $DEBUG is nothing but 'echo' 952 | 953 | Ex 3: 954 | ``` 955 | #!/bin/bash 956 | function debug() { 957 | echo "Executing $@" 958 | $@ 959 | } 960 | debug ls 961 | ``` 962 | 963 | ### Syntax Highlighting: 964 | 965 | Syntax errors are common. Use a text editor and enable syntax highlighting to identify syntax errors. (Ex: vim, emacs) Helps us catch syntax errors. 966 | 967 | #### PS4: 968 | 969 | Controls what is displayed before a line while using the `-x` option (during debugging). Default value is '+' 970 | 971 | Bash Variables: 972 | - BASH_SOURCE, (name of the script) 973 | - LINENO, (line number of the script) 974 | - FUNCNAME (function name) 975 | - etc ... 976 | 977 | We can explicitly set the PS4 variable. 978 | 979 | Ex: 980 | ``` 981 | #!/bin/bash -x 982 | ... 983 | PS4='+ $BASH_SOURCE : $LINENO : ${FUNCNAME[0] : ' 984 | ... 985 | ``` 986 | 987 | Example Output: '+ ./test.sh : 3 : debug() : TEST_VAR=test' 988 | 989 | ### File Types (DOS/WINDOWS vs UNIX/LINUX): 990 | 991 | Control character is used to represent end of line in both DOS and Unix text files. 992 | 993 | Control Character: 994 | 1. Unix/Linux: Line Feed 995 | 2. DOS: Carriage return & a Line Feed (2 characters) 996 | 997 | - `cat -v script.sh` = View the file along with the carriage returns (^M) 998 | 999 | When opening Linux/Unix text files on a DOS/Windows system: We may see one long line without new lines. 1000 | 1001 | And, in the opposite: We may see additional characters on Unix/Linux (`^M`). => Might run into errors while executing the files. 1002 | 1003 | Therefore, need to remove the carriage returns in order to run the file. The shebang `#!` is very important. 1004 | 1005 | #### Knowing what type of file: (To which shell does the script belong to?) 1006 | 1007 | `file script.sh` 1008 | 1009 | Example Output: 1010 | - `script.sh: Bourne-Again shell script, ASCII text executable` => UNIX script, 1011 | - `script.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators` => DOS script, 1012 | 1013 | #### Converting DOS text scripts to UNIX scripts: 1014 | 1015 | - `dos2unix script.sh` => Removes incompatible characters(ex: DOS carriage returns) in DOS to match with UNIX text scripts. (So that we can run it on UNIX/LINUX) 1016 | 1017 | Confirm the removal of incompatible characters by running `file script.sh` again to see what type of shell the script runs in. (Should be one of the unix shells that you are using.) 1018 | 1019 | #### Converting UNIX text scripts to DOS scripts: 1020 | 1021 | - `unix2dos script.sh` => Does the opposite of dos2unix. (So that we can run it on DOS/WINDOWS) 1022 | 1023 | How does all this happen? => When editing file in one OS and operating and using it another, Copying from one OS and pasting in another (via net, etc), Copying from web browsers into the system ... many ways!] 1024 | 1025 | 1026 | ************* 1027 | - USE SHELL SCRIPTS TO AUTOMATE TASKS - REPETITIVE WORK 1028 | - SHELL SCRIPTS CAN BE SHORTCUTS - DON'T HAVE TO REMEMBER EVERYTHING (LIKE EVERY COMMAND) 1029 | - HAPPY SCRIPTING! 1030 | ************* 1031 | 1032 | **THE END** 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | --------------------------------------------------------------------------------