1. Automated Logging with exec
Logging every line of a script manually is tedious. Using exec redirects the entire script's output stream.
The Problem Without It
- The Error: Without global redirection, you have to append >> logfile.txt 2>&1 to every single command. If you forget one, that output is lost to the terminal and never recorded.
- The "Read" Conflict: Once you redirect stdin (standard input), the read command starts looking at your log file or pipe for input instead of your keyboard. The script will either hang or crash because it can't find the "input" it needs.
The Solution
exec > >(tee -a "script.log") 2>&1
- How it works: It uses Process Substitution. It sends stdout (1) and stderr (2) into a pipe that tee reads. tee then writes to the file and back to the terminal.
- The Thought Process: To fix the read issue, you must explicitly tell the script to look at the physical terminal for input:
read -p "Enter Name: " username < /dev/tty
Why it helps: It ensures 100% of errors are caught in the log without sacrificing the ability to have an interactive UI.
2. Processing Files: while read vs. for loop
The Problem Without It
- The Error: Using for line in $(cat file.txt) breaks if a line contains a space. The for loop treats spaces as delimiters, so a line like "John Doe" becomes two separate iterations.
- The Thought Process: We need a tool that respects the Newline character \n specifically.
The Solution
while IFS= read -r line; do
echo "Processing: $line"
done < file.txt
How it helps:
while read processes the file line-by-line. The -r flag prevents backslashes from being interpreted as escape characters, preserving the data exactly as it exists in the file.
3. Non-Interactive Password Updates
The Problem Without It
- The Error: The standard passwd command is interactive. If you run it in a script, it pauses and waits for a human to type the password twice. This hangs an automated deployment script.
- The Thought Process: We need a way to "pipe" the credentials directly into the system's authentication database.
The Solution
echo "$username:$password" | chpasswd
- How it helps: chpasswd is designed specifically for batch processing. It accepts user:pass strings from stdin, making it perfect for loops and automation.
4. Clean Archiving
The Error: If you run
tar -cvf backup.tar /home/user/data/file.txt
when you extract it, tar will recreate the entire folder structure /home/user/data/.
The Solution:
tar -C /home/user/data/ -cvf backup.tar file.txt
How it helps: The -C (change directory) flag tells tar to "step into" that folder before arching, so the metadata only contains the filename.
Using basename
- The Thought Process: If you have a full path like /var/logs/app/error.log, but you only want the string error.log for a backup name.
-
The Command:
FILE_NAME=$(basename "/var/logs/app/error.log"). - How it helps: It strips the directory path, allowing you to create clean, dynamic labels for your backups.
5. Reliability over $?
The Problem with $?
The Error: $? (the exit status) only captures the result of the last command executed.
The Risk: If you call a function that checks a service, but the function has a small echo or cleanup command at the very end, $? will return the status of the echo, not the service check. You might think the service is running when it actually failed.
The Solution: Direct Boolean Logic
Instead of checking the number, use the command directly in an if statement.
Avoid: systemctl is-active service; status=$?; if [ $status -eq 0 ]...
Better: if systemctl is-active --quiet service; then ...
- How it works: In Bash, an if statement evaluates the exit code of the command following it.
- Why it helps: It eliminates the "middleman" variable. It is cleaner, less prone to being overwritten by an accidental command in between, and more readable.
6. Short-Circuiting
In Bash, && represents AND. For an "AND" statement to be true, both sides must be true. If the first part (the condition) fails, Bash doesn't even bother looking at the second part—it just stops.
The Problem with the if Block
The standard if statement is visually heavy for simple tasks:
if [[ $USER == "root" ]]; then
echo "Access granted."
else
echo "Access denied."
exit 1
fi
The Issue: It takes 6 lines to perform one simple check. In a long script, this creates "visual noise" that makes it harder to spot the actual logic.
The Solution: Grouped Commands
To run multiple commands (like an echo followed by an exit) on a single line, you must group them.
Option A: Using Parentheses ( )
This runs the commands in a subshell.
[[ $USER != "root" ]] && (echo "Error: Must be root"; exit 1)
The Catch: Because it’s a subshell, the exit command will exit the subshell, not your main script. Avoid this if you actually want the script to stop.
Option B: Using Curly Braces { } (Recommended)
This runs the commands in the current shell context.
[[ $USER != "root" ]] && { echo "Error: Must be root"; exit 1; }
Crucial Syntax:
- There must be a space after the opening {.
- Each command inside must end with a semicolon ;.
- There must be a space before the closing }.
Handling the "Else" (The Ternary Style)
If you want to emulate an if/else on one line, you can chain && and ||.
[[ -f "config.txt" ]] && echo "Found it" || { echo "Missing file"; exit 1; }
The "Thought Process" & Common Errors
- The "Unexpected Exit" Error: If you write [[ condition ]] && echo "Success" || exit 1, and for some reason the echo fails (e.g., the disk is full or stdout is closed), the exit 1 will trigger even if the condition was true.
- The Fix: Using the grouped { } after the && ensures that the "Success" logic and the "Failure" logic stay strictly separated.
So that wraps up the errors I faced and concepts I used to troubleshoot them. All the scripts are currently in my Github account
(https://github.com/sahil0907/BashScripting)
Feel free to share your insights and if there are any inputs I would love to hear over my LinkedIn
(https://www.linkedin.com/in/sahil0907/).
Have a nice day and Keep Learning.
Top comments (0)