grep1-6 Exercise 1: A Report Script That Dies Without a Word ============================================================= (Run in a fresh copy of the practice folder from the chapter. Make it first with the block at the top of the chapter.) cat > report1.sh <<'EOF' set -euo pipefail errors=$(grep -c ERROR app.log) critical=$(grep -c CRITICAL app.log) echo "errors=$errors critical=$critical" EOF echo "--- the broken script:" bash report1.sh; echo "exit status: $?" cat > report2.sh <<'EOF' set -euo pipefail errors=$(grep -c ERROR app.log || true) critical=$(grep -c CRITICAL app.log || true) echo "errors=$errors critical=$critical" EOF echo "--- fixed with || true:" bash report2.sh; echo "exit status: $?" cat > report3.sh <<'EOF' set -euo pipefail count() { awk -v p="$1" '$0 ~ p { n++ } END { print n + 0 }' app.log; } echo "errors=$(count ERROR) critical=$(count CRITICAL)" EOF echo "--- fixed with awk:" bash report3.sh; echo "exit status: $?" Output: --- the broken script: exit status: 1 --- fixed with || true: errors=2 critical=0 exit status: 0 --- fixed with awk: errors=2 critical=0 exit status: 0 WHY THIS WORKS AS AN ANSWER --------------------------- The first script stops at the second grep: CRITICAL does not occur, grep -c prints 0 and exits with 1, set -e treats that as a failure inside the command substitution, and the script ends WITHOUT printing anything. The status 1 is the only sign. '|| true' runs after a failure and succeeds, so the assignment succeeds with the 0 grep had already printed. It keeps grep's own output (0) and hides its status, so it also hides a REAL failure (a missing file would now print an error but carry on). awk treats 'no matches' as a normal 0 and only fails for a real error, such as a file that cannot be read. The cost is that the pattern is an awk regular expression (extended-style), not grep's, and the line is written differently.