Study guide

7.4 Process Shell Command Output in Scripts

Red Hat Certified System Administrator RHCSA

Track your progress as you read

Finished sections get a check mark.

1 Introduction & Exam Overview

1.1 Current Exam Facts 1.2 Lab Setup & Performance-Based Study Strategy

2 Essential Tools: Shell, Redirection & Text Analysis

2.1 Shell Prompt and Command Syntax 2.2 Input-Output Redirection and Pipes 2.3 grep and Regular Expressions for Text Analysis

3 Essential Tools: Remote Access, Users & Text Editing

3.1 Access Remote Systems Using SSH 3.2 Log In and Switch Users in Multiuser Targets 3.3 Create and Edit Text Files

4 Essential Tools: Files, Links & Archives

4.1 Create, Delete, Copy, and Move Files and Directories 4.2 Hard and Soft Links 4.3 Archive and Compress with tar, gzip, and bzip2

5 Essential Tools: Permissions & System Documentation

5.1 List, Set, and Change Standard ugo/rwx Permissions 5.2 System Documentation: man, info, and /usr/share/doc

6 Manage Software: RPM and Flatpak

6.1 Configure Access to RPM Repositories 6.2 Install and Remove RPM Software Packages 6.3 Configure Access to Flatpak Repositories 6.4 Install and Remove Flatpak Software Packages

7 Create Simple Shell Scripts

7.1 Conditional Execution: if, test, and [] 7.2 Looping Constructs for Files and Input 7.3 Process Script Inputs ($1, $2, and Beyond) 7.4 Process Shell Command Output in Scripts

8 Operate Running Systems: Boot and Recovery

8.1 Boot, Reboot, and Shut Down a System Normally 8.2 Boot Systems into Different Targets Manually 8.3 Interrupt the Boot Process to Gain Access

9 Operate Running Systems: Processes and Tuning

9.1 Identify and Kill CPU/Memory Intensive Processes 9.2 Adjust Process Scheduling 9.3 Manage Tuning Profiles

10 Operate Running Systems: Logs, Services & File Transfer

10.1 Locate and Interpret System Log Files and Journals 10.2 Preserve System Journals 10.3 Start, Stop, and Check Status of Network Services 10.4 Securely Transfer Files Between Systems

11 Configure Local Storage: Partitions and LVM

11.1 List, Create, and Delete Partitions on GPT Disks 11.2 Create and Remove Physical Volumes 11.3 Assign Physical Volumes to Volume Groups 11.4 Create and Delete Logical Volumes

12 Configure Local Storage: Mounting and Expansion

12.1 Mount File Systems at Boot by UUID or Label 12.2 Add Partitions, Logical Volumes, and Swap Non-Destructively

13 Create and Configure File Systems: Local FS and Permissions

13.1 Create, Mount, and Use VFAT, ext4, and XFS File Systems 13.2 Extend Existing Logical Volumes 13.3 Diagnose and Correct File Permission Problems

14 Create and Configure File Systems: NFS and autofs

14.1 Mount and Unmount Network File Systems Using NFS 14.2 Configure autofs

15 Deploy and Maintain: Scheduling, Services & Targets

15.1 Schedule Tasks with at, cron, and systemd Timers 15.2 Start, Stop, and Enable Services at Boot 15.3 Configure Systems to Boot into a Specific Target

16 Deploy and Maintain: Time, Software Updates & Bootloader

16.1 Configure Time Service Clients 16.2 Install and Update Software from CDN, Remote Repos, and Local Files 16.3 Modify the System Bootloader

17 Manage Basic Networking

17.1 Configure IPv4 and IPv6 Addresses 17.2 Configure Hostname Resolution 17.3 Configure Network Services to Start Automatically at Boot 17.4 Restrict Network Access Using firewalld and firewall-cmd

18 Manage Users and Groups

18.1 Create, Delete, and Modify Local User Accounts 18.2 Change Passwords and Adjust Password Aging 18.3 Create, Delete, and Modify Local Groups and Memberships 18.4 Configure Privileged Access

19 Manage Security: Firewall, umask & SSH Keys

19.1 Configure Firewall Settings with firewall-cmd and firewalld 19.2 Manage Default File Permissions 19.3 Configure Key-Based Authentication for SSH

20 Manage Security: SELinux Modes and Contexts

20.1 Set Enforcing and Permissive Modes for SELinux 20.2 List and Identify SELinux File and Process Contexts 20.3 Restore Default File Contexts

21 Manage Security: SELinux Ports and Booleans

21.1 Manage SELinux Port Labels 21.2 Use Boolean Settings to Modify SELinux Settings

7.4 Process Shell Command Output in Scripts

Key Takeaways Explain This Topic AI

Command substitution $(command) captures stdout into a string for variables, tests, and loops; legacy backticks command do the same but nest poorly—prefer $().

Combine substitution with if/test, for/while, and positional parameters to build complete simple EX200 scripts.

Watch word-splitting: quote "$var" after capture; use while read for multi-line output instead of unquoted for x in $(cmd).

Separate stdout from stderr: 2>/dev/null or 2>&1 when appropriate; check exit status of commands with $? or if cmd directly.

Exam scripts should capture package queries, path checks, counts, and filtered lists, then act—leaving results and exit codes the grader can verify.

Last updated: August 2026

7.4 Process Shell Command Output in Scripts

Quick Answer: Capture a command’s stdout with $(command) (preferred) or legacy `command` , store it in a variable or use it inline, then branch or loop on that data. Always consider exit status separately from text output, quote expansions, and prefer while read for multi-line results.

The missing piece of simple scripts

You can write if and for without talking to other programs, but EX200 tasks expect scripts that use the system :

  • Is package X installed? ( rpm -q )
  • How many lines? ( wc -l )
  • What is today’s date stamp? ( date +%F )
  • Which files match? ( find , ls , grep -l )

Command substitution is how bash turns command output into script data.

  • $(...) modern form HOST=$(hostname)
  • NOW=$(date +%F)
  • COUNT=$(wc -l < /etc/passwd)
  • echo "host=$HOST date=$NOW users_file_lines=$COUNT"

Rules:

The shell runs the command in a subshell.

Stdout is captured (trailing newlines are stripped from the overall substitution in typical use).

Stderr still goes to the script’s stderr unless redirected.

The substitution’s text is inserted on the command line— quote it when it must stay one word. FILE=$(ls /etc/hosts) # fine for single path without spaces

echo "file is $FILE"

# Quote when used as a single argument later:

  • TARGET="$(pwd)/data"
  • mkdir -p "$TARGET"
  • Inline use if [ "$(id -u)" -eq 0 ]; then
  • echo "running as root"
  • else
  • echo "not root" >&2
  • exit 1
  • fi for pkg in $(rpm -qa 'kernel*'); do
  • echo "kernel package: $pkg"
  • done

Package NVRs usually lack spaces, so this for is acceptable. For arbitrary filenames, do not use unquoted $(find ...) .

Backticks: legacy form HOST=`hostname`

Backticks work on RHEL bash but:

  • Nesting requires ugly escaping: `cmd \`inner\ ``
  • $() nests cleanly: $(cmd $(inner))
  • Easy to misread with quotes

Prefer $() in all new EX200 scripts. Recognize backticks when reading older examples.

Exit status vs captured output

Substitution captures text; it does not put the exit code into the variable. Check status carefully: #!/bin/bash

OUT=$(rpm -q httpd 2>/dev/null)

status=$?

  • if [ "$status" -eq 0 ]; then
  • echo "installed: $OUT"
  • else
  • echo "httpd not installed"
  • exit 1
  • fi
  • Alternatively, test the command in if without caring about the text: if rpm -q httpd &>/dev/null; then
  • echo "httpd present"
  • fi
  • Goal Pattern
  • Need text var=$(cmd) then use "$var"

Need only success/fail if cmd; then or cmd && ...

Need both capture, then inspect $? immediately

Trap: var=$(false) sets var empty-ish and $? is non-zero—check $? right away before running other commands that overwrite it.

  • Redirecting inside substitution # Discard errors from the inner command
  • USERS=$(getent passwd 2>/dev/null | wc -l)
  • # Merge stderr into capture (usually avoid unless intentional)
  • MIXED=$(ls /no/such 2>&1)

Silencing stderr can hide useful diagnostics; on exams, hide noise when grepping optional data, but show errors when failure must be obvious.

  • Trimming and normalizing output # Avoid accidental newlines/spaces issues
  • NAME=$(echo -n "$NAME" | tr -d '\r')
  • # First field only
  • KERN=$(uname -r)
  • # Lowercase (bash)
  • MODE=$(echo "$1" | tr '[:upper:]' '[:lower:]')

wc -l < file avoids embedding the filename in wc output (which wc -l file would include).

  • Multi-line output: do it right
  • Bad (word-splitting) for line in $(cat /root/list.txt); do # splits on any IFS whitespace
  • echo "$line"
  • done
  • Good (line-oriented) while IFS= read -r line; do
  • [ -z "$line" ] && continue
  • echo "got $line"
  • done < /root/list.txt
  • From a command pipeline rpm -qa | sort | while IFS= read -r pkg; do
  • case "$pkg" in
  • bash-*|coreutils-*) echo "core: $pkg" ;;
  • esac
  • done

mapfile / readarray (bash convenience) mapfile -t lines < <(systemctl list-unit-files --type=service --no-pager --no-legend)

  • for line in "${lines[@]}"; do
  • echo "$line"
  • done

Process substitution < <(cmd) feeds mapfile without a subshell pipeline issue. Nice to know; not mandatory if while read is clearer under pressure.

  • Feeding substitution into tests and arithmetic LOAD=$(cut -d. -f1 /proc/loadavg)
  • if [ "$LOAD" -ge 8 ]; then
  • echo "high load $LOAD" >&2
  • exit 1
  • fi
  • FILES=$(find /var/log -type f -name '*.log' 2>/dev/null | wc -l)
  • echo "log files: $FILES"

Ensure numeric variables really are numeric before -ge —empty strings break integer tests. if [ -z "$LOAD" ]; then

  • echo "could not read load" >&2
  • exit 1
  • fi
  • Complete exam-style scripts
  • A. Report free space for a path argument #!/bin/bash
  • if [ $# -ne 1 ]; then
  • echo "Usage: $0 mountpoint" >&2
  • exit 2
  • fi
  • if [ ! -d "$1" ]; then
  • echo "Not a directory: $1" >&2
  • exit 1
  • fi
  • # df output processing: print available KB (field varies; use df -P for portability)
  • avail=$(df -P "$1" | awk 'NR==2 {print $4}')
  • echo "$1 available_kb=$avail"
  • B. Ensure a package is installed; capture NVR #!/bin/bash
  • PKG="${1:-httpd}"
  • if rpm -q "$PKG" &>/dev/null; then
  • nvr=$(rpm -q "$PKG")
  • echo "OK $nvr"
  • exit 0
  • else
  • echo "MISSING $PKG" >&2
  • exit 1
  • fi
  • C. Build a timestamped backup name from command output #!/bin/bash
  • SRC="$1"
  • if [ ! -f "$SRC" ]; then
  • echo "Usage: $0 existing-file" >&2
  • exit 2
  • fi
  • ts=$(date +%Y%m%d%H%M%S)
  • dest="${SRC}.bak.${ts}"
  • cp -a "$SRC" "$dest" || exit 1
  • echo "backed up to $dest"
  • D. Loop over users with UID ≥ 1000 from command output #!/bin/bash
  • while IFS=: read -r name _ uid _rest; do
  • if [ "$uid" -ge 1000 ] 2>/dev/null; then
  • echo "human-ish user: $name uid=$uid"
  • fi
  • done < /etc/passwd

Here the “command output” is effectively the file content; the same pattern applies to getent passwd . getent passwd | while IFS=: read -r name _ uid _; do

  • [ "$uid" -ge 1000 ] 2>/dev/null && echo "$name"
  • done
  • E. Conditional on grep match count #!/bin/bash
  • file="/var/log/messages"
  • pattern="${1:-error}"
  • if [ ! -r "$file" ]; then
  • echo "cannot read $file" >&2
  • exit 1
  • fi
  • matches=$(grep -c -i -- "$pattern" "$file" || true)
  • echo "matches=$matches"
  • if [ "$matches" -gt 0 ]; then
  • exit 0
  • else
  • exit 1
  • fi

|| true keeps set -e (if used) from aborting when grep finds nothing (exit 1). Many exam scripts omit set -e ; still know grep’s exit codes.

  • set -e and substitutions (awareness) set -e
  • var=$(false) # may exit the script depending on context/version/options

For EX200, explicit if checks are clearer than relying on set -e edge cases. If you enable set -e , test thoroughly with bash -x .

  • Putting all four skills together
  • A single graded script often combines this whole chapter: #!/bin/bash
  • # inventory.sh DIR
  • # Lists regular files, counts them, fails if DIR missing
  • if [ $# -ne 1 ]; then
  • echo "Usage: $0 directory" >&2
  • exit 2
  • fi
  • dir="$1"
  • if [ ! -d "$dir" ]; then
  • echo "Not a directory: $dir" >&2
  • exit 1
  • fi
  • count=0
  • for f in "$dir"/*; do
  • [ -f "$f" ] || continue
  • size=$(stat -c%s "$f" 2>/dev/null || echo 0)
  • echo "$f $size"
  • count=$((count + 1))
  • done
  • echo "TOTAL_FILES=$count"
  • if [ "$count" -eq 0 ]; then
  • exit 1
  • fi
  • exit 0

Features used:

  • Shebang + (you still chmod +x )
  • Input validation ( $# , $1 )
  • for loop over glob
  • if / test guards
  • $(stat ...) command substitution
  • Meaningful exit codes
  • Debugging command substitution bash -x ./script.sh /data

# Temporarily:

  • result=$(find /data -type f | wc -l)
  • printf 'DEBUG result=%q status=%s\n' "$result" "$?" >&2

If result is empty, check whether the command wrote only to stderr or failed.

Common traps

Using backticks nested clumsily — switch to $() .

for x in $(find ...) on arbitrary names — breaks on spaces; use find -print0 + read -d '' only if you must; for exams, constrain to safe names or use find -exec .

Forgetting quotes around "$var" after capture.

Checking $? too late — another command already replaced it.

Assuming capture includes stderr — it does not unless 2>&1 .

Parsing ls output — prefer globs, find , or stat .

Leaving the script non-executable after a perfect body.

Section checkpoint

You should capture stdout with $(...) , recognize backticks, quote results, combine output with if / for / while and positional parameters, handle multi-line data with read , check exit status separately when needed, and deliver complete simple scripts that process real RHEL command output for EX200 tasks.