Test Yourself!

These ten questions run the length of the chapter, and each turns on behavior that catches working Linux administrators out. Every snippet ran on Ubuntu 26.04 225 LTS as the user dev, with sudo-rs, GNU awk and GNU sed. Treat each numbered block as separate, run nothing, and write down exactly what it prints and why. The answers, with the reasoning and the section each comes from, are in Appendix H.

Questions

Questions 1-3: what does each snippet print?Shell
# 1. dev's only group is dev; carol belongs to group web. What do stat, both cats and rm print?
umask 027; echo hi > a.txt; mkdir d; stat -c '%a %n' a.txt d
umask 000; touch b.txt; stat -c '%a %n' b.txt
sudo chown root:web a.txt && sudo chmod 604 a.txt
sudo -u carol cat a.txt; sudo -u nobody cat a.txt && echo "nobody read it"
sudo touch d/root.txt && rm -f d/root.txt && echo "dev deleted root's file"
# 2. Both drop-ins pass visudo -cf and are installed root:root, mode 0440:
#      /etc/sudoers.d/carol.conf   carol ALL=(root) NOPASSWD: /usr/bin/id
#      /etc/sudoers.d/carol-web    carol ALL=(root) NOPASSWD: /usr/bin/systemctl status *
sudo -u carol sudo -n /usr/bin/id -un
sudo -u carol sudo -n /usr/bin/systemctl status --no-pager cron ssh | grep -c Active
export APP_ENV=prod; sudo sh -c 'echo "[$APP_ENV]"'; sudo -E true; echo "exit $?"
# 3. The directory holds "a b.txt" (one name), c.txt and .env.
f='a b.txt'
ls $f; ls "$f"
for x in *.txt; do echo "[$x]"; done
echo *.md '$HOME' "~" ~root; set -- *.md; echo $#
shopt -s nullglob; set -- *.md; echo $#; echo {1..3}{a,b}
Questions 4-6: what does each snippet print, and what ends up on disk?Shell
# 4. names.txt holds pear, apple and fig. What reaches the terminal, and what is in the files?
ls /nope names.txt > one.txt 2>&1; ls /nope names.txt 2>&1 > two.txt | tr a-z A-Z
echo hi > x.txt > y.txt; wc -c x.txt y.txt
sort names.txt > names.txt; wc -l names.txt
{ echo out; echo err >&2; } 2>/dev/null | wc -l
cat one.txt two.txt
# 5. Four units, each a [Service] section holding only these two settings:
#      quit.service    Restart=on-failure   ExecStart=/bin/sh -c 'sleep 1; exit 0'
#      crash.service   Restart=on-failure   ExecStart=/bin/sh -c 'exit 1'
#      term.service    Restart=on-failure   ExecStart=/bin/sleep 600
#      always.service  Restart=always       ExecStart=/bin/sleep 600
#    What do ActiveState, Result and NRestarts show for each?
sudo systemctl start quit crash term always; sleep 1
sudo kill -TERM "$(systemctl show -p MainPID --value term)"
sudo systemctl stop always; sleep 5
systemctl show quit crash term always -p ActiveState -p Result -p NRestarts
# 6. The crontab of carol, whose login shell is bash; greet in ~/bin prints "hello from greet".
#    Two root-owned, mode 644 files sit beside it in /etc/cron.d. Which of the six logs
#    exist two minutes later, and what do they hold?
* * * * * greet >> /tmp/q6/a.log 2>&1
* * * * * echo "day $(date +%d)" >> /tmp/q6/b.log 2>&1
* * * * * cd /tmp/q6 && echo "$PWD $SHELL $HOME" > c.log
* * * * * $HOME/bin/greet > /tmp/q6/d.log
# /etc/cron.d/site.backup:  * * * * * root echo ran > /tmp/q6/e.log
# /etc/cron.d/site-backup:  * * * * * root echo ran > /tmp/q6/f.log
Question 7: a deny rule on a fresh firewallShell
# 7. Nothing else is configured. Can 198.51.100.7 and 198.51.100.9 fetch the page on port 80?
#    The "fix" on the fifth line is typed next: what does it print, and is .9 locked out now?
sudo ufw default deny incoming
sudo ufw allow 80/tcp
sudo ufw deny from 198.51.100.9
sudo ufw --force enable
sudo ufw insert 1 deny from 198.51.100.9
sudo ufw status numbered
Question 8: the ~/.ssh/config that the ssh -G commands below readShell
Host *
  User admin
  ServerAliveInterval 30
Host web1
  HostName 192.0.2.10
  User deploy
  Port 2222
Host *.example.com !legacy.example.com
  Port 2200
Host legacy.example.com
  User root
Questions 8-10: SSH settings, text tools, and when Certbot really renewsShell
# 8. Which user, hostname and port does ssh resolve for each command?
ssh -G web1 | grep -E '^(user|hostname|port) '
ssh -G -p 22 -l ops web1 | grep -E '^(user|port) '
ssh -G app.example.com | grep -E '^(user|port) '
ssh -G legacy.example.com | grep -E '^(user|port) '
ssh -G deploy@legacy.example.com | grep -E '^(user|port) '
# 9. Each of these seven lines prints something a first reading gets wrong.
printf 'a,b,,d\n' | awk -F, '{print NF, "[" $3 "]"}'
echo '  x   y  ' | awk '{print NF; $1 = $1; print "[" $0 "]"}'
printf '' | awk '{s += $1} END {print "sum=" s}'
echo '3 10' | awk '{print ($1 > $2), ("3" > "10")}'
echo abc | sed 's/x*/-/g'; echo 'a.b.c' | sed 's/./X/'
printf 'one\ntwo\nthree\n' | sed -n '/two/,/end/p'
# 10. The timer runs this twice a day, and the CA's renewal-information (ARI) request fails.
#     a and b are 90-day classic certificates with 31 and 29 days left; c and d are 160-hour
#     shortlived certificates with 81 and 79 hours left. Which does Certbot try to renew, and
#     why does a run started by the timer sit silent for minutes before it does anything?
sudo certbot renew