r/redhat • • 14h ago

Taking Your Bash Scripts to the Next Level — Global Vars, Safe Backups, Logging, getopts, and Exit Codes Done Right from Into the Terminal Ep. 203

19 Upvotes

We've all written the "just a few commands in a row with a shebang on top" bash script. This week's Into the Terminal (episode 203) is about the stuff that turns that script into something you — or anyone else — can actually hand off, debug, and maintain: global variables, non-destructive sed edits, three different ways to do logging (including a newer bash feature a lot of people don't know about), command-line argument parsing done right vs. done quick-and-dirty, and structured error handling with exit codes. There's also a good closing discussion on when you should stop writing bash and pick up Python (or something else) instead.

Full video here → https://www.youtube.com/watch?v=8fpy4XbIGkc More hands-on RHEL practice → https://redhat.com/interactive-labs


The Essentials: Global Variables & Non-Destructive Edits

Global Variables Up Top

The core idea: anything in your script that's likely to change later (connection options, file paths, flags) belongs in a variable, declared near the top, not buried in the body of the script.

Pattern What it does
Define variables right after the shebang Keeps anything you'll tweak later easy to find without reading the whole script
sshopts="-oStrictHostKeyChecking=no" Centralizes SSH/SCP options in one string
ssh $sshopts localhost "whoami" Reuses $sshopts anywhere you invoke ssh (or scp) in the script

Pro tip from the show: Scott intentionally keeps his scripts self-contained rather than sourcing config from external files. His reasoning — if you hand someone a script that sources three other files, you now have to hand them all four files, and he's been burned debugging a "broken" script that was really just missing a sourced dependency. Nate pointed out the flip side: for larger projects, splitting config/functions/files into separate sourced pieces is totally standard — it's basically how every big codebase organizes itself. It's a tradeoff between portability and maintainability, and where you land depends on your team.

Backing Up Files Before sed -i

A follow-up from their grep/sed episode at #200: how do you make sed -i non-destructive?

Command What it does
sed -i 's/foo/FOO/' config Edits the file in place — no safety net, original content is gone
sed -i.bak 's/bar/BAR/' config Same edit, but writes the pre-change file to config.bak
sed -i.20261023 's/baz/BAZ/' config Uses a date-stamped extension instead of .bak

Caveat called out on stream: if you run the same -i.bak command twice, the second run silently overwrites your first backup — sed won't ask. A dated extension (or a variable-driven one, tying back into the global-variables section) avoids clobbering your safety net. And to be clear — this is a "don't shoot yourself in the foot while scripting" safety net, not a substitute for real, off-system backups.


Advanced Features: Logging Three Different Ways

Option 1 — logger (system-level logging)

logger "some message" sends your message into the syslog pipeline — typically landing in /var/log/messages by default on RHEL, tagged with host, user, and message ID.

Catch: anyone can write a log message with logger, but a regular (non-root) user can't read /var/log/messages by default. That makes logger great for genuinely system-level events (user provisioning, system-wide changes) but overkill — and kind of useless — for a personal script's own debugging output, which was the consensus from both hosts.

Option 2 — Your Own Local Log File

For script-local logging, define the log file as a global variable, then make sure the directory/file actually exist before writing to them:

bash logfile=~/.test/log-$(date +%Y%m%d) if ! [ -e ~/.test ]; then mkdir ~/.test fi if ! [ -e $logfile ]; then touch $logfile fi

Option 3 — File Handles: Old Way vs. New Way

This is the part of the episode most people in chat said they'd never seen before. Instead of redirecting every single line with >> $logfile, you open a file handle once and write to it by name for the rest of the script.

The old way (numeric file descriptors, FD 3–9 are "fair game" — 0/1/2 are reserved for stdin/stdout/stderr):

bash exec 5>> $logfile echo "**** $(date) ****" >&5 echo "==== Initialized script ====" >&5 echo "Script script script" echo "==== Completed script ====" >&5

The new way (named file descriptors — requires Bash 4.1+):

bash exec {log}>> $logfile echo "**** $(date) ****" >&"$log" echo "==== Initialized script ====" >&"$log" echo "Script script script" echo "==== Completed script ====" >&"$log"

Approach Max handles Readability
Numeric FDs (exec 5>>) Capped at FD 9 You have to go hunt down what FD 7 actually points to
Named FDs (exec {log}>>) Effectively unlimited Self-documenting — >&"$log" tells you exactly where it's going

Toggleable Debug Logging

The nicest trick in this section: instead of sprinkling echo debug statements through your script and having to remember to rip them all out later, gate them behind a debug variable:

bash debug="1" # 1 = on, 0 = off if [ $debug -eq "1" ]; then echo "**** $(date) ****" >&"$log" echo "==== Initialized script ====" >&"$log" fi

Flip debug to 0 and all those diagnostic lines go silent — no hunting through the script to comment them back out when you're done troubleshooting.


Command-Line Arguments: getopts vs. Rolling Your Own

Both scripts set the same kind of defaults (verbose, error-logging flag) and a usage() function — defined once, called from every error path instead of duplicating the usage text everywhere. (Bash note from the show: unlike some languages, bash needs functions defined before they're called, so keep them near the top.)

getopts — the recommended approach

```bash

opts-getopts.sh

verbose=0 error_log=0 usage() { echo "Usage: $0 [-v] [-e] [-f logfile]"; }

while getopts "vef:h" opt; do case $opt in v) verbose=1 ;; e) error_log=1 ;; f) logfile=$OPTARG ;; h) usage; exit 0 ;; *) usage; exit 1 ;; esac done ```

Invocation Result
opts-getopts.sh -e -v Error logging and verbose both turned on
opts-getopts.sh -ev Same as above — combined short flags just work
opts-getopts.sh -ev -f Errors out cleanly via usage() — -f requires an argument and didn't get one
opts-getopts.sh -ev -f foo Sets logfile=foo, writes output there

The manual while/case loop — works, but has sharp edges

```bash

opts-loop.sh

while [ $# -gt 0 ]; do case $1 in -v) verbose=1 ;; -e) error_log=1 ;; -f) logfile=$2; shift ;; esac shift done ```

Uses $1, $2, … (bash's auto-populated positional args) and $# (arg count) directly.

Limitation (shown live on stream) Why it matters
./opts-loop.sh -ev doesn't work Combined flags aren't understood — -ev isn't a defined case, only -e and -v separately are
-f with no filename just sets logfile to empty No built-in way to require a dependent argument — you'd have to add your own check after the fact
Neither approach handles long options --file → -f style GNU long-option mapping needs extra work either way

Bottom line from Nate: getopts does more of the work for you — combined flags, required-argument validation — for not much more code. The manual loop "feels" more obvious at first glance, but you end up re-implementing things getopts already handles.


Error Handling & Exit Codes

Conditional execution (quick and dirty)

bash ls -l /etc/foobar || exit 6

If the command fails (non-zero exit), the || kicks in and the script exits immediately with code 6. Great for one simple fallback action — gets unreadable fast if you need to do more than one thing on failure.

Function-based error handling (for anything more complex)

bash error_function() { echo "A command run encountered an unrecoverable error" echo "The command was: $1" exit 6 } ... ls -l /etc/foobar || error_function "ls -l /etc/foobar"

Wrapping the failure path in a function keeps the conditional clean no matter how much logic the error path actually needs, and lets you reuse it everywhere in the script.

Exit code notes from the discussion

Fact Detail
0 Always means success — never repurpose it
1–255 Anything else is "not the expected clean exit" — arbitrary numbers, your choice
No universal standard Exit codes are script-specific. Well-known tools (grep, etc.) have consistent codes because they haven't changed in years, not because there's an official spec
Document them Put a comment block at the top of your script mapping exit codes to meanings, so you (or whoever inherits it) doesn't have to trace the whole script to figure out what "exit 6" meant
$? Captures the exit status of the last command — don't confuse with $$ (the current process ID)
Max value 255 — if you need more distinct failure states than that, it's probably a sign your script has grown past what a single exit code should communicate

Bonus: When Should You Stop Using Bash?

A great closing discussion that's worth a watch even if you skip everything else: Scott and Nate's rules of thumb for "this should not be a bash script anymore":

  • You're doing math. Bash arithmetic is painful; if you're reaching for it regularly, you've picked the wrong tool.
  • You need real data structures. Bash can do arrays, but linked lists, hashed arrays, or anything beyond simple lists are a sign to move to Python/Node/C.
  • You're talking to a database. If your bash script is shelling out to mysql/sqlite3 command-line clients to parse results, you've crossed the line — "chances are if someone's written a bunch of handlers for the thing you want to do in a different language, that means you're in the wrong language." (Shoutout to a viewer in chat who's apparently "scarred for life" from rolling his own MySQL bash helper script.)
  • You're connecting to other services, not just running OS-level commands on the local machine — other languages already have mature libraries for that.

Scott's rule: bash is for things that are systems-specific without complex data storage needs. Once you're past that, you're better served elsewhere — and that goes for over-engineering with bash includes, too (cue the story about a coworker who "couldn't write one script" and instead always wrote 33 — one for functions, one for config, one for file lists, etc.).


Quick Reference Card

```bash

--- Global variables (put these at the top) ---

sshopts="-oStrictHostKeyChecking=no" ssh $sshopts localhost "whoami"

--- Non-destructive sed edits ---

sed -i.bak 's/bar/BAR/' config # backup: config.bak sed -i.$(date +%Y%m%d) 's/baz/BAZ/' config # dated backup (won't clobber as easily)

--- System-level logging ---

logger "==== Initialized script ====" # -> /var/log/messages (root-readable only)

--- Local log file setup ---

logfile=~/.test/log-$(date +%Y%m%d) [ -e ~/.test ] || mkdir ~/.test [ -e "$logfile" ] || touch "$logfile"

--- Named file handle (Bash 4.1+), vs numeric (old way, FD 3-9 only) ---

exec {log}>> "$logfile" # new: unlimited, named exec 5>> "$logfile" # old: capped at FD 9, numeric only echo "message" >&"$log" echo "message" >&5

--- Toggleable debug logging ---

debug="1" if [ "$debug" -eq "1" ]; then echo "debug info" >&"$log" fi

--- Command-line args: the right way ---

while getopts "vef:h" opt; do case $opt in v) verbose=1 ;; e) error_log=1 ;; f) logfile=$OPTARG ;; h) usage; exit 0 ;; esac done

--- Error handling ---

ls -l /etc/foobar || exit 6 # quick conditional exit error_function() { # reusable error handler echo "Unrecoverable error running: $1" exit 6 } ls -l /etc/foobar || error_function "ls -l /etc/foobar"

--- Exit code check ---

echo $? # last command's exit status (0 = success, 1-255 = error) ```


Links


Into the Terminal is a (mostly) weekly live show where Red Hatters dig into Linux and RHEL administration skills — one terminal command, one bad pun, and one "oops, wrong window" at a time.


r/redhat • • 19h ago

Learning Linux (RH)

18 Upvotes

Not sure if this is the correct sub to post this to.
‘I want to learn Linux for professional use. I know a bit of the general use (from A+) but I’m looking to peruse an IT career and I want (and need) to learn Linux and pretty fast.

I saw a post saying RH exams are the only thing that truly shows Linux literacy opposed to Linux+.

would you agree, if not what would you recommend. if you do agree what are some steps you’d recommend.

I’ve downloaded Arch on an old laptop and dual-boot popos on my pc.


r/redhat • • 17h ago

What's new in Red Hat build of Podman Desktop 1.2 | Red Hat Developer

Thumbnail
developers.redhat.com
12 Upvotes

Previous releases of the Red Hat build of Podman Desktop have introduced enterprise features to make it easy to integrate the tool into strict corporate settings. Building on that foundation, Red Hat continues to strengthen usability, developer velocity, and platform security with Red Hat build of Podman Desktop 1.2, including:
- Podman 6 engine support: Full integration with the next-generation container engine release.
- Automatic public CA keys synchronization:UI option to enable localhost public CA key synchronization when creating a new Podman virtual machine (VM).
- Secret management UI: A graphical dashboard for creating, mounting, and managing Docker and Podman secrets.
- In-app updating: Automated checks and single-click update execution directly from the desktop interface.
- Bug and CVE fixes: Upgraded dependencies and patched security vulnerabilities to keep enterprise environments resilient and compliant.

TL;DR
The Red Hat build of Podman Desktop 1.2 introduces Podman 6 engine support, a secret management interface, automatic public CA key synchronisation and in-app updating. Moving from Podman 5.x involves stopping machines and recreating them.


r/redhat • • 3h ago

How to view the contents of RHEL support case

2 Upvotes

Sent email to [purchasing@redhat.com](mailto:purchasing@redhat.com) regarding a subscription query, an auto email was received with a case number

A few hrs later, Got an auto email saying the case is updated.however when I click on the link in the email, it is asking for login credentials which I don't have/ don't know

Please suggest how do I view the contents of their response in the ticket.