In Part 6 of the Linux Bash Course, we shift our focus from typing ad-hoc commands on the terminal to writing automated scripts. You will learn the shebang convention, shell execution boundaries, exit codes, variable naming rules, local vs. global scopes, special parameters, and how to read user inputs securely.
For more technical background on Bash internals, refer to the GNU Bash Reference Manual.
Chapter 16: Writing Your First Script
16.1 The Shebang Line (#!/bin/bash)
The shebang (#!) is a special two-character sequence at the absolute beginning of a script file. It tells the Linux kernel program loader which interpreter binary to use to execute the script’s instructions.
-
Kernel Handling: When you run a script (e.g.,
./myscript), the kernel reads the first line. If it finds#!, it runs the interpreter path specified after the shebang and passes the script file as an argument. -
Alternative Paths: While
#!/bin/bashis standard,#!/usr/bin/env bashis more portable because it searches the user’s system$PATHenvironment variable for thebashexecutable. -
Syntax:
#!/path/to/interpreter -
Example Code:
#!/bin/bash echo "Running in Bash"
16.2 Creating a Script File
A shell script is a plain text file containing a sequence of terminal commands. By convention, shell scripts end with the .sh file extension, although Linux determines executable status by file permissions, not by extensions.
To create a script, we use file creation tools like touch, nano, or vim.
- Syntax:
touch script_name.sh - Example Command:
touch my_backup.sh ls -l my_backup.sh - Expected Output:
-rw-r--r-- 1 suresh suresh 0 Jun 12 13:35 my_backup.sh - Flag & Command Breakdown:
touch: Creates an empty file or updates access/modification times.ls -l: Performs a long listing, displaying permissions, owner, size, and date.
16.3 Making a Script Executable: chmod +x
By default, new files created in Linux do not have execution permissions. Running ./script.sh on a non-executable file yields a Permission denied error. We modify permissions using the chmod command.
- Syntax:
chmod +x file_name.sh - Example Command:
chmod +x my_backup.sh ls -l my_backup.sh - Expected Output:
-rwxr-xr-x 1 suresh suresh 0 Jun 12 13:35 my_backup.sh - Flag & Command Breakdown:
chmod: Change mode (permissions).+x: Add execute permission for owner, group, and others.
16.4 Running a Script (Three Methods)
There are three standard ways to invoke a Bash script, and each handles subprocesses differently:
- Relative/Absolute Path Execution (
./script.shor/path/to/script.sh): Runs the script in a new subshell (subprocess). Requires execute permissions. - Explicit Interpreter Execution (
bash script.sh): Runs the script in a new subshell using the specifiedbashbinary. Does not require execute permissions. - Sourcing (
source script.shor. script.sh): Runs the script directly in the current shell process. Variables defined inside the script persist in your terminal environment.
- Syntax:
# Method 1 ./script.sh # Method 2 bash script.sh # Method 3 source script.sh - Example Command:
echo 'TEST_VAR="Active"' > test_run.sh bash test_run.sh echo "Current Shell: $TEST_VAR" source test_run.sh echo "Current Shell: $TEST_VAR" - Expected Output:
Current Shell: Current Shell: Active - Flag & Command Breakdown:
bash test_run.sh: Launches a subshell.TEST_VARis set and destroyed inside that subshell.source test_run.sh: Runs commands inside the primary shell, modifying your active environment.
16.5 Script Exit Codes (0 = Success)
Every command and script in Unix returns an exit code (exit status) between 0 and 255.
0(Success): Indicates the script ran to completion without encountering errors.1to255(Failure): Indicates various error conditions. For instance,1represents generic errors,126means file is not executable, and127represents command not found.
16.6 Checking Exit Codes: echo $?
To retrieve the exit code of the last executed command, we query the special shell parameter $?.
- Syntax:
echo $? - Example Command:
ls /invalid_directory_path echo $? - Expected Output:
ls: cannot access '/invalid_directory_path': No such file or directory 2 - Flag & Command Breakdown:
echo: Prints text or variables.$?: The special shell variable representing the exit code of the immediately preceding command.
16.7 Using exit in Scripts
The exit command terminates the script execution immediately and returns a specific exit status to the calling parent shell. If no status is specified, the script returns the exit code of the last run command.
- Syntax:
exit [exit_code] - Example Command:
# Create script cat << 'EOF' > exit_test.sh #!/bin/bash echo "Starting operations..." exit 42 echo "This line will never print." EOF chmod +x exit_test.sh ./exit_test.sh echo "Script exited with: $?" - Expected Output:
Starting operations... Script exited with: 42 - Flag & Command Breakdown:
exit 42: Stops script execution and sends return code42.
16.8 Commenting Your Code (#)
Comments are notes written inside the script to explain code logic. The shell interpreter ignores comments.
- Single Line: Prefixed with the
#symbol. - Block Comments Trick: Use a heredoc block with a colon (
:is the null operator in Bash):: ' This is a multi-line comment. The interpreter executes the null operator on this string. '
16.9 Hello World Script
Let us construct a clean, standard Hello World script applying comments, the shebang line, and proper exits.
- Example Code (
hello_world.sh):#!/bin/bash # Author: Suresh # Purpose: Basic system hello response script echo "Hello, World!" exit 0
16.10 Debugging with set -x
Bash offers trace debugging capabilities that print each command along with its expanded arguments before executing them.
-
set -x(xtrace): Turns on debugging. -
set +x: Turns off debugging. -
Syntax:
set -x # code to debug set +x -
Example Command:
cat << 'EOF' > debug_demo.sh #!/bin/bash set -x VAR_NAME="TechBlog" echo "Hello $VAR_NAME" set +x echo "Debugging off." EOF chmod +x debug_demo.sh ./debug_demo.sh -
Expected Output:
+ VAR_NAME=TechBlog + echo 'Hello TechBlog' Hello TechBlog + set +x Debugging off. -
Flag & Command Breakdown:
set -x: Instructs Bash to print input lines prefixed by a+symbol as they are evaluated.
Chapter 17: Variables in Bash
17.1 Defining User Variables
User variables store temporary data.
-
Rules: Variable names are case-sensitive (usually capitalized by convention) and must begin with a letter or underscore.
-
Important: You must not put spaces around the assignment operator (
=).NAME = Valueis parsed as running a command namedNAMEwith arguments=andValue. -
Syntax:
VARIABLE_NAME=value -
Example Command:
WEBSITE="freetechlearner.com" echo $WEBSITE -
Expected Output:
freetechlearner.com
17.2 Reading Variables: $VAR vs ${VAR}
To retrieve the value of a variable, prepend the variable name with a dollar sign ($).
-
$VAR: Basic variable extraction. -
${VAR}(Parameter Expansion): Encloses the variable name in curly braces. This is required when the variable name merges with adjacent characters to prevent naming ambiguity (e.g.${VAR}_log). -
Syntax:
$VAR_NAME ${VAR_NAME} -
Example Command:
COURSE="linux-bash" echo "Running course-$COURSE-docs" echo "Running course-${COURSE}-docs" -
Expected Output:
Running course- Running course-linux-bash-docs -
Flag & Command Breakdown:
$COURSE-docsfailed because Bash searched for a variable namedCOURSE-docswhich did not exist.${COURSE}-docsseparated the parameter token from the surrounding text.
17.3 Read-Only Variables: readonly
Read-only variables are constants. Once declared, their values cannot be reassigned, and they cannot be unset.
- Syntax:
readonly VARIABLE_NAME=value - Example Command:
readonly PORT=8080 PORT=9090 - Expected Output:
bash: PORT: readonly variable
17.4 Unsetting Variables: unset
The unset command deletes a variable from the shell memory, freeing up system environment resources.
- Syntax:
unset VARIABLE_NAME - Example Command:
TEMP_KEY="xyz123" echo "Before: $TEMP_KEY" unset TEMP_KEY echo "After: $TEMP_KEY" - Expected Output:
Before: xyz123 After:
17.5 Local vs Global Variables in Functions
Variables declared in Bash scripts are global by default, meaning they can be modified from inside functions. To confine a variable to a function’s scope, declare it with the local keyword.
- Syntax:
local variable_name=value - Example Command:
# Create test script cat << 'EOF' > scope_test.sh #!/bin/bash GLOBAL_VAR="Global Value" test_scope() { local LOCAL_VAR="Local Value" GLOBAL_VAR="Modified Globally" echo "Inside function: LOCAL=$LOCAL_VAR, GLOBAL=$GLOBAL_VAR" } test_scope echo "Outside function: LOCAL=$LOCAL_VAR, GLOBAL=$GLOBAL_VAR" EOF bash scope_test.sh - Expected Output:
Inside function: LOCAL=Local Value, GLOBAL=Modified Globally Outside function: LOCAL=, GLOBAL=Modified Globally
17.6 Special Variables: $0, $1, $#
Bash reserves special variables to capture execution metadata:
-
$0: Holds the name of the script command as invoked. -
$1to$9: Positional arguments passed to the script. For arguments above 9, use braces (e.g.${10}). -
$#: The total number of command-line arguments passed to the script. -
Syntax:
# Inside a script echo $0 echo $1 echo $# -
Example Command:
cat << 'EOF' > arg_test.sh #!/bin/bash echo "Script Name: $0" echo "First Arg: $1" echo "Args Count: $#" EOF bash arg_test.sh "Ubuntu-Install" "Azure" -
Expected Output:
Script Name: arg_test.sh First Arg: Ubuntu-Install Args Count: 2
17.7 All Arguments: $@ vs $*
Both variables capture all positional arguments passed to the script. However, their behaviors differ when enclosed in double quotes:
-
"$*": Concatenates all parameters into a single string, separated by the first character of theIFSvariable (usually space). -
"$@": Preserves each parameter as a distinct, independent word. Always use"$@"when passing arguments to other commands to prevent breaking arguments containing spaces. -
Syntax:
"$*" "$@" -
Example Command:
cat << 'EOF' > print_args.sh #!/bin/bash echo "Using \$* :" for arg in "$*"; do echo "Arg: $arg" done echo "Using \$@ :" for arg in "$@"; do echo "Arg: $arg" done EOF bash print_args.sh "first param" "second param" -
Expected Output:
Using $* : Arg: first param second param Using $@ : Arg: first param Arg: second param
17.8 The Process ID: $$
The $$ special variable returns the Process ID (PID) of the current shell shell process. This is useful for generating unique filenames for temp files.
- Syntax:
$$ - Example Command:
echo "Current Process PID: $$" - Expected Output:
Current Process PID: 28492
17.9 Last Background PID: $!
The $! variable yields the Process ID of the most recently executed background job or process.
- Syntax:
command & echo $! - Example Command:
sleep 30 & echo "Sleep Background PID: $!" kill $! - Expected Output:
Sleep Background PID: 28501 - Flag & Command Breakdown:
&: Appended to a command to run it asynchronously in the background.kill: Terminates a process using its PID.
17.10 Last Argument: $_
The $_ variable stores the last argument of the previously executed command.
- Syntax:
command argument echo $_ - Example Command:
touch config.txt mv config.txt $_.bak ls -l config.txt.bak - Expected Output:
-rw-r--r-- 1 suresh suresh 0 Jun 12 13:35 config.txt.bak
Chapter 18: User Input & Read
18.1 Basic read Command
The read command is a shell built-in that reads a single line of input from standard input (stdin) and assigns it to a variable. If no variable is specified, the input is stored in the default variable $REPLY.
- Syntax:
read variable_name - Example Command:
echo "Type your name:" # In interactive test we simulate input using standard input streams echo "Suresh" | (read USERNAME; echo "Variable contains: $USERNAME") - Expected Output:
Variable contains: Suresh
18.2 Reading Multiple Variables
If you provide multiple variable names to read, it splits the input line using the characters in the Internal Field Separator (IFS) variable. The first word is assigned to the first variable, the second to the second, and any remaining words are assigned to the last variable.
- Syntax:
read var1 var2 var3 - Example Command:
echo "A B C D" | (read x y z; echo "x=$x; y=$y; z=$z") - Expected Output:
x=A; y=B; z=C D
18.3 Prompting with -p
The -p flag prints a prompt string directly on standard error without a trailing newline, allowing you to prompt for input on the same line.
- Syntax:
read -p "Prompt message: " variable_name - Example Command:
echo "test" | (read -p "Enter code: " INPUT_CODE; echo "Code: $INPUT_CODE") - Expected Output:
Code: test - Flag & Command Breakdown:
-p: Specifies the prompt string that is displayed before reading the input.
18.4 Silent Input (Passwords): read -s
The -s flag turns off terminal echoing, hiding input characters as they are typed. This is essential for secure credential inputs.
- Syntax:
read -s -p "Enter Password: " PASSWORD_VAR - Example Command:
echo "Secret123" | (read -s -p "Enter key: " SEC_KEY; echo ""; echo "Key was: $SEC_KEY") - Expected Output:
Key was: Secret123 - Flag & Command Breakdown:
-s: Disables echo mode on the terminal.
18.5 Timeout Input: read -t
The -t flag sets a timeout limit in seconds. If the user does not press enter before the timeout, read exits with a non-zero status code and terminates the input process.
- Syntax:
read -t [seconds] variable_name - Example Command:
# Test read timeout (we use 1 second timeout) if read -t 1 -p "Waiting for quick input: " QUICK_IN; then echo "Read input: $QUICK_IN" else echo "Timeout reached." fi - Expected Output:
Timeout reached. - Flag & Command Breakdown:
-t 1: Exits read command if no line of input is completed within 1 second.
18.6 Reading N Characters: read -n
The -n flag tells read to return immediately after reading a specified number of characters, rather than waiting for a full line terminated by a newline.
- Syntax:
read -n [char_count] variable_name - Example Command:
echo "Yes" | (read -n 1 RESPONSE; echo ""; echo "Captured char: $RESPONSE") - Expected Output:
Captured char: Y - Flag & Command Breakdown:
-n 1: Triggers termination of the read input loop after exactly 1 character is entered.
18.7 Reading from Files with read
The read command can process files line-by-line by redirecting standard input from a file. This is typically implemented inside a while loop.
- Syntax:
while read -r line; do # process line done < filename.txt - Example Command:
# Create test text file printf "Line A\nLine B\n" > lines.txt while read -r line; do echo "Processing: $line" done < lines.txt - Expected Output:
Processing: Line A Processing: Line B - Flag & Command Breakdown:
-r: Disables backslash escapes, preserving all literal backslash characters.< lines.txt: Redirects standard input of the loop from the file.
18.8 Using IFS to Split Input
The IFS (Internal Field Separator) environment variable defines the characters used as field delimiters. By altering IFS temporarily before running read, you can parse CSV or customized delimited inputs directly.
- Syntax:
IFS="delimiter" read -r var1 var2 ... - Example Command:
CSV_LINE="suresh,admin,/bin/bash" IFS="," read -r USER ROLE SHELL <<< "$CSV_LINE" echo "User $USER has role $ROLE" - Expected Output:
User suresh has role admin - Flag & Command Breakdown:
IFS=",": Temporarily overrides space and tab separators with a comma.<<<: Enters a “here-string”, redirecting the variable string value directly into standard input.
18.9 Validating User Input
Input validation protects scripts from running with invalid or malicious parameters. We validate variables using conditional brackets.
- Example Script:
#!/bin/bash read -p "Enter age: " USER_AGE # Validate that input is digits only if [[ "$USER_AGE" =~ ^[0-9]+$ ]]; then echo "Valid age: $USER_AGE" else echo "Error: Invalid numeric input." exit 1 fi
18.10 Reading Command-Line Arguments
While read collects input dynamically during script runtime, positional parameters (such as $1, $2) capture arguments provided at execution time.
We parse structured command arguments using loops and the shift command.
- Syntax:
# Loop over arguments for arg in "$@"; do echo "Arg: $arg" done - Example Script:
#!/bin/bash while [[ $# -gt 0 ]]; do case "$1" in --env) ENV="$2" shift 2 ;; *) echo "Unknown argument: $1" exit 1 ;; esac done echo "Configured environment: $ENV"