Files
SSW590-DevOps/chapters/devopsLinuxCommands.tex

580 lines
26 KiB
TeX

\chapter{Linux Commands}
\ChapterLabel{LinuxCommands}
\index{Linux}
\index{Linux!command line}
\index{Linux!working directory}
\index{bash}
\index{symbolic link}
\index{file timestamps}
\section{Purpose}
This chapter records a set of Linux shell commands run in the authors' working
user directories, and the solutions to the accompanying problem set.
The setup figures reproduce Payton's terminal session. Section~\ref{Section:LinuxEvidence}
adds Victor's actual problem-set execution screenshots as \gls{evidence}.
The command answers in Section~\ref{Section:LinuxSolutions} describe solutions;
where Victor used a different command, the difference is identified rather than
presented as an execution of the exact answer shown.
\section{Environment}
\index{Linux!environment}
The setup figures were recorded by Payton as user \texttt{payton} on
Ubuntu~24.04 under \gls{wsl}, in a \texttt{bash} shell.
Victor independently performed the setup and problem set on September 27, 2026,
as user \texttt{victor} on host \texttt{Victori7}, running Ubuntu~24.04.3 LTS
under WSL~2. His working directory was \texttt{/home/victor/lx-test}.
The session was recorded with \texttt{script}; its original transcript is retained
with the evidence files. Normal commands ran without elevated privileges;
\texttt{sudo} was used for the append-only attribute in Q20.
\section{Working Directory Setup}
\index{Linux!working directory setup}
Figure~\ref{Figure:LxTestSetup} shows the executed setup commands in the order given by
the assignment. Figure~{\ref{Figure:LxTestDirectories}} shows the resulting directory structure,
and Figure~{\ref{Figure:LxTestFiles}} shows the resulting file contents.
\subsection{What the Commands Do}
\index{Linux!command explanations}
\begin{sloppypar}
\begin{itemize}[leftmargin=*]
\item \texttt{mkdir -p \textasciitilde/lx-test \&\& cd \textasciitilde/lx-test}
creates the directory, along with any missing parent directories, and does
not fail if it already exists. The \texttt{\&\&} operator runs \texttt{cd}
only if \texttt{mkdir} succeeds.
\item \texttt{printf "\ldots" > \textit{file}} writes the string to a file,
turning each \texttt{\textbackslash n} into a newline. The \texttt{>}
redirection creates the file or overwrites it if it already exists.
\texttt{printf} is used instead of \texttt{echo} for predictable formatting of these strings and newlines, avoiding
shell-dependent behavior of \texttt{echo}.
\item \texttt{dd} with \texttt{if=/dev/zero}, \texttt{of=blob.bin},
\texttt{bs=1K}, and \texttt{count=48} copies $48$ blocks of
$1\,\mathrm{KiB}$ from \texttt{/dev/zero}, producing a file of
$48 \times 1024 = 49152$ zero bytes.
\item \texttt{mkdir -p src/lib tmp archive} creates three directories in one
command; \texttt{-p} creates \texttt{src} as the parent of \texttt{lib}.
\item \texttt{ln -s src/file1.txt link-to-file1} creates a symbolic link. The
stored target is the relative path \texttt{src/file1.txt}, which is
resolved relative to the directory that contains the link.
\item \texttt{touch -t 202401020304 old.txt} creates \texttt{old.txt} with a
modification time of 2024-01-02 03:04 in local time.
\item \texttt{ls -lA} lists each named directory in long format, including
hidden entries but omitting \texttt{.} and \texttt{..}.
\texttt{head} prints the first ten lines of each file under a
file name header; every file here is shorter than ten
lines, so each is shown in full.
\end{itemize}
\end{sloppypar}
\subsection{Interpretation of Results}
\index{Linux!result interpretation}
Figure~\ref{Figure:LxTestDirectories} confirms each step of the setup:
\begin{itemize}[leftmargin=*]
\item \textbf{File sizes:} \texttt{blob.bin} is $49152$ bytes, as computed
above. Each text file's size equals its characters plus one byte per newline.
\item \textbf{Symbolic link:} \texttt{link-to-file1} has type \texttt{l} and
points to \texttt{src/file1.txt}.
\item \textbf{Timestamp:} \texttt{old.txt} is empty and shows the modified timestamp (\texttt{Jan~2~2024}).
\item \textbf{Directories:} \texttt{archive}, \texttt{tmp}, and
\texttt{src/lib} show no entries in the \texttt{ls -lA} listing, confirming they are empty
(\texttt{total 0} alone would not prove this).
\item \textbf{Permissions:} New files are \texttt{rw-r--r--} ($644$) and new
directories are \texttt{rwxr-xr-x} ($755$).
\end{itemize}
\begin{figure}[htbp]
\begin{lstlisting}[numbers=none, columns=fullflexible, keepspaces=true]
payton@paytonpc:~$ mkdir -p ~/lx-test && cd ~/lx-test
payton@paytonpc:~/lx-test$ printf "alpha\nbeta\nGamma\ngamma\nbeta\n" > words.txt
payton@paytonpc:~/lx-test$ printf "id,name,dept\n1,Ada,EE\n2,Linus,CS\n3,Grace,EE\n4,Dennis,CS\n" > people.csv
payton@paytonpc:~/lx-test$ printf "INFO boot ok\nWARN disk low\nERROR fan fail\nINFO shutdown\n" > sys.log
payton@paytonpc:~/lx-test$ dd if=/dev/zero of=blob.bin bs=1K count=48 status=none
payton@paytonpc:~/lx-test$ mkdir -p src/lib tmp archive
payton@paytonpc:~/lx-test$ printf "one two three four\n" > src/file1.txt
payton@paytonpc:~/lx-test$ printf "two three four five\n" > src/file2.txt
payton@paytonpc:~/lx-test$ ln -s src/file1.txt link-to-file1
payton@paytonpc:~/lx-test$ touch -t 202401020304 old.txt
\end{lstlisting}
\caption{Terminal input for setting up the \texttt{lx-test} working directory.}
\label{Figure:LxTestSetup}
\end{figure}
\begin{figure}[htbp]
\begin{lstlisting}[numbers=none, columns=fullflexible, keepspaces=true]
payton@paytonpc:~/lx-test$ ls -lA . src src/lib archive tmp
.:
total 72
drwxr-xr-x 2 payton payton 4096 Sep 26 15:39 archive
-rw-r--r-- 1 payton payton 49152 Sep 26 15:39 blob.bin
lrwxrwxrwx 1 payton payton 13 Sep 26 15:39 link-to-file1 -> src/file1.txt
-rw-r--r-- 1 payton payton 0 Jan 2 2024 old.txt
-rw-r--r-- 1 payton payton 56 Sep 26 15:39 people.csv
drwxr-xr-x 3 payton payton 4096 Sep 26 15:39 src
-rw-r--r-- 1 payton payton 56 Sep 26 15:39 sys.log
drwxr-xr-x 2 payton payton 4096 Sep 26 15:39 tmp
-rw-r--r-- 1 payton payton 28 Sep 26 15:39 words.txt
archive:
total 0
src:
total 12
-rw-r--r-- 1 payton payton 19 Sep 26 15:39 file1.txt
-rw-r--r-- 1 payton payton 20 Sep 26 15:39 file2.txt
drwxr-xr-x 2 payton payton 4096 Sep 26 15:39 lib
src/lib:
total 0
tmp:
total 0
\end{lstlisting}
\caption{Terminal output showing the resulting directories after \texttt{lx-test} setup.}
\label{Figure:LxTestDirectories}
\end{figure}
\begin{figure}[htbp]
\begin{lstlisting}[numbers=none, columns=fullflexible, keepspaces=true]
payton@paytonpc:~/lx-test$ head words.txt people.csv sys.log src/file1.txt src/file2.txt
==> words.txt <==
alpha
beta
Gamma
gamma
beta
==> people.csv <==
id,name,dept
1,Ada,EE
2,Linus,CS
3,Grace,EE
4,Dennis,CS
==> sys.log <==
INFO boot ok
WARN disk low
ERROR fan fail
INFO shutdown
==> src/file1.txt <==
one two three four
==> src/file2.txt <==
two three four five
\end{lstlisting}
\caption{Terminal output showing the resulting file contents after \texttt{lx-test} setup.}
\label{Figure:LxTestFiles}
\end{figure}
\clearpage
\section{Problem Set Solutions}
\label{Section:LinuxSolutions}
\index{Linux!problem set}
\subsection{Navigation and File Operations}
\begin{enumerate}[label=\arabic*., leftmargin=*]
\item Show your present working directory path only.
\begin{lstlisting}[style=cmd]
$ pwd
\end{lstlisting}
\nopagebreak[4]
\texttt{pwd} prints only the current directory; Victor's result was \texttt{/home/victor/lx-test}.
\item List all entries in the current directory, one per line, including dotfiles.
\begin{lstlisting}[style=cmd]
$ ls -1a
\end{lstlisting}
\nopagebreak[4]
\texttt{-1} places each entry on a separate line; \texttt{-a} includes dotfiles and the \texttt{.} and \texttt{..} entries.
\item Copy \texttt{src/file1.txt} to \texttt{tmp/} only if \texttt{tmp} exists; do it verbosely.
\begin{lstlisting}[style=cmd]
$ [ -d tmp ] && cp -v src/file1.txt tmp/
\end{lstlisting}
\nopagebreak[4]
The directory test prevents copying when \texttt{tmp} is absent; \texttt{-v} reports the source and destination. Victor's output confirms the copy.
\item Move \texttt{old.txt} into \texttt{archive/} and keep its original timestamp.
\begin{lstlisting}[style=cmd]
$ mv old.txt archive/
\end{lstlisting}
\nopagebreak[4]
Moving within this working tree preserves the file's modification time. Victor verified that the timestamp remained January 2, 2024 at 03:04 local time.
\item Create a new empty file \texttt{notes.md} only if it doesn't already exist.
\begin{lstlisting}[style=cmd]
$ [ -e notes.md ] || touch notes.md
\end{lstlisting}
\nopagebreak[4]
The existence test avoids touching an existing file. Victor used the equivalent conditional \texttt{if} form and verified that the newly created file was zero bytes.
\item Show disk usage (human-readable) for the \texttt{src} directory only (not total FS).
\begin{lstlisting}[style=cmd]
$ du -sh src
\end{lstlisting}
\nopagebreak[4]
\texttt{-s} reports one directory total and \texttt{-h} uses readable units. Victor observed \texttt{16K}; allocated disk usage includes directory storage and is not simply text length.
\end{enumerate}
\subsection{Viewing and Searching}
\begin{enumerate}[label=\arabic*., leftmargin=*, resume]
\item Print line numbers while displaying \texttt{sys.log}.
\begin{lstlisting}[style=cmd]
$ cat -n sys.log
\end{lstlisting}
\nopagebreak[4]
\texttt{cat -n} numbers displayed lines. Victor used \texttt{nl -ba}, which also numbered all four lines in this file.
\item Show only the lines in \texttt{sys.log} that contain \texttt{ERROR} (case-sensitive).
\begin{lstlisting}[style=cmd]
$ grep 'ERROR' sys.log
\end{lstlisting}
\nopagebreak[4]
Without \texttt{-i}, matching is case-sensitive. The result was \texttt{ERROR fan fail}.
\item Count how many distinct words appear in \texttt{words.txt} (case-insensitive).
\begin{lstlisting}[style=cmd]
$ tr '[:upper:]' '[:lower:]' < words.txt | sort -u | wc -l
\end{lstlisting}
\nopagebreak[4]
Each input line contains one word. Lowercasing before sorting and deduplication treats \texttt{Gamma} and \texttt{gamma} as one value; the resulting count was three.
\item From \texttt{words.txt}, show lines that start with \texttt{g} or \texttt{G}.
\begin{lstlisting}[style=cmd]
$ grep '^[gG]' words.txt
\end{lstlisting}
\nopagebreak[4]
The anchor \texttt{\^{}} restricts the match to the start of the line, and \texttt{[gG]} accepts either case. The two matches were \texttt{Gamma} and \texttt{gamma}.
\item Display the first 2 lines of \texttt{people.csv} without using an editor.
\begin{lstlisting}[style=cmd]
$ head -n 2 people.csv
\end{lstlisting}
\nopagebreak[4]
The first two lines are the header and the first data row: \texttt{id,name,dept} and \texttt{1,Ada,EE}.
\item Show the last 3 lines of \texttt{sys.log} and keep following if the file grows.
\begin{lstlisting}[style=cmd]
$ tail -n 3 -f sys.log
\end{lstlisting}
\nopagebreak[4]
\texttt{-n 3} selects the last three lines; \texttt{-f} keeps following. Victor captured the initial three lines, then stopped following with Ctrl+C. No later appended-line test is claimed.
\end{enumerate}
\subsection{Text Processing}
\begin{enumerate}[label=\arabic*., leftmargin=*, resume]
\item From \texttt{people.csv}, print only the name column (2nd), excluding the header.
\begin{lstlisting}[style=cmd]
$ tail -n +2 people.csv | cut -d, -f2
\end{lstlisting}
\nopagebreak[4]
\texttt{tail} skips the header and \texttt{cut} selects the comma-separated second field. Victor's equivalent \texttt{awk} command returned Ada, Linus, Grace, and Dennis.
\item Sort \texttt{words.txt} case-insensitively and remove duplicates.
\begin{lstlisting}[style=cmd]
$ sort -fu words.txt
\end{lstlisting}
\nopagebreak[4]
\texttt{-f} ignores case during comparison and \texttt{-u} removes duplicates. Victor's output was alpha, beta, and Gamma; the retained spelling need not be lowercased.
\item Replace every \texttt{three} with \texttt{3} in all files under \texttt{src/} in-place, creating \texttt{.bak} backups.
\begin{lstlisting}[style=cmd]
$ find src/ -type f ! -name '*.bak' -exec sed -i.bak 's/three/3/g' {} +
\end{lstlisting}
\nopagebreak[4]
\texttt{find} visits regular files recursively, excluding existing backups; \texttt{sed}'s \texttt{g} replaces every occurrence on each line. The original files are saved with the \texttt{.bak} suffix. Victor verified both edited content and original backups. Run once to avoid replacing the original backups on a later run.
\item Print the number of lines, words, and bytes for every \texttt{*.txt} file in \texttt{src/}.
\begin{lstlisting}[style=cmd]
$ wc src/*.txt
\end{lstlisting}
\nopagebreak[4]
By default, \texttt{wc} reports lines, words, and bytes. After Q15, Victor observed \texttt{1 4 15} and \texttt{1 4 16}, totaling \texttt{2 8 31}. Backup filenames do not match \texttt{*.txt}.
\end{enumerate}
\subsection{Permissions and Ownership}
\begin{enumerate}[label=\arabic*., leftmargin=*, resume]
\item Make \texttt{tmp/} readable, writable, and searchable only by the owner.
\begin{lstlisting}[style=cmd]
$ chmod 700 tmp/
\end{lstlisting}
\nopagebreak[4]
Mode \texttt{700} gives the owner read, write, and directory-search permissions, with none for group or others. Victor verified \texttt{drwx------}.
\item Give group execute permission to \texttt{src/lib} recursively without touching others/owner bits.
\begin{lstlisting}[style=cmd]
$ chmod -R g+x src/lib
\end{lstlisting}
\nopagebreak[4]
\texttt{g+x} adds only group execute; \texttt{-R} applies recursively. Victor's before/after value remained \texttt{755} because group execute was already enabled.
\item Show the numeric (octal) permissions of \texttt{src/file2.txt}.
\begin{lstlisting}[style=cmd]
$ stat -c '%a' src/file2.txt
\end{lstlisting}
\nopagebreak[4]
\texttt{\%a} requests octal permissions. Victor observed \texttt{644}: owner read/write, group and others read-only.
\item Make \texttt{notes.md} append-only for the owner via file attributes (if supported).
\begin{lstlisting}[style=cmd]
$ chmod 600 notes.md
$ sudo chattr +a notes.md
\end{lstlisting}
\nopagebreak[4]
Mode \texttt{600} limits ordinary access to the owner. The \texttt{a} attribute separately makes the file append-only; it is a file attribute, not an owner-specific permission bit. Victor's filesystem supported it: \texttt{lsattr} showed \texttt{a}, and \texttt{stat} showed \texttt{600}.
\end{enumerate}
\subsection{Links and Find}
\begin{enumerate}[label=\arabic*., leftmargin=*, resume]
\item Verify whether \texttt{link-to-file1} is a symlink and show its target path.
\begin{lstlisting}[style=cmd]
$ [ -L link-to-file1 ] && readlink link-to-file1
\end{lstlisting}
\nopagebreak[4]
\texttt{-L} checks the link itself and \texttt{readlink} prints its stored target. Victor confirmed \texttt{src/file1.txt}.
\item Find all regular files under the current tree larger than 40~KiB.
\begin{lstlisting}[style=cmd]
$ find . -type f -size +40k
\end{lstlisting}
\nopagebreak[4]
The size test selects regular files larger than 40 KiB. Victor used the equivalent byte threshold \texttt{+40960c}, printing \texttt{./blob.bin} with 49,152 bytes.
\item Find files modified in the last 10 minutes under \texttt{tmp/} and print their sizes.
\begin{lstlisting}[style=cmd]
$ find tmp/ -type f -mmin -10 -printf '%p %s\n'
\end{lstlisting}
\nopagebreak[4]
\texttt{-mmin -10} selects recent modifications; \texttt{\%s} prints bytes. Victor's command returned no matches because no regular file under \texttt{tmp/} met that time window.
\end{enumerate}
\subsection{Processes and Job Control}
\begin{enumerate}[label=\arabic*., leftmargin=*, resume]
\item Show your processes in a tree view.
\begin{lstlisting}[style=cmd]
$ pstree -p "$USER"
\end{lstlisting}
\nopagebreak[4]
\texttt{pstree -p} shows process relationships and PIDs for the specified user. Victor instead recorded his own processes with \texttt{ps -u} and \texttt{--forest}, exposing the shell, recording process, and child shell hierarchy.
\item Start \texttt{sleep 120} in the background and show its PID.
\begin{lstlisting}[style=cmd]
$ sleep 120 & echo $!
\end{lstlisting}
\nopagebreak[4]
The ampersand starts a background job; \texttt{\$!} is its PID. Victor captured and printed PID 2476 in his session; PIDs vary between executions.
\item Send a TERM signal to all \texttt{sleep} processes owned by you (don't use \texttt{kill -9}).
\begin{lstlisting}[style=cmd]
$ pkill -TERM -u "$USER" -x sleep
\end{lstlisting}
\nopagebreak[4]
\texttt{-u} restricts matches to the current user's processes and \texttt{-x} requires the exact name. Victor first checked for unrelated sleep processes, then terminated the test process. Waiting for it returned status 143 (128 + SIGTERM 15), and a final process search returned no matches.
\item Show the top 5 processes by memory usage (one-shot, not interactive).
\begin{lstlisting}[style=cmd]
$ ps aux --sort=-%mem | head -n 6
\end{lstlisting}
\nopagebreak[4]
Descending memory percentage puts the largest consumers first; six output lines include one header and five processes. Victor used descending resident-memory size (RSS in KiB) instead; his largest displayed process was \texttt{snapd} at 41,208 KiB.
\end{enumerate}
\subsection{Archiving and Compression}
\begin{enumerate}[label=\arabic*., leftmargin=*, resume]
\item Create a gzipped tar archive \texttt{src.tgz} from \texttt{src/} with relative paths.
\begin{lstlisting}[style=cmd]
$ tar -czf src.tgz src/
\end{lstlisting}
\nopagebreak[4]
\texttt{-c} creates, \texttt{-z} compresses with gzip, and \texttt{-f} names the archive. The relative \texttt{src/} operand stores relative member paths.
\item List the contents of \texttt{src.tgz} without extracting.
\begin{lstlisting}[style=cmd]
$ tar -tzf src.tgz
\end{lstlisting}
\nopagebreak[4]
\texttt{-t} lists members without extraction. Victor's archive contained the two text files, their backups, and the \texttt{src/lib/} directory.
\item Extract only \texttt{file2.txt} from \texttt{src.tgz} into \texttt{tmp/}.
\begin{lstlisting}[style=cmd]
$ tar -xzvf src.tgz -C tmp/ --strip-components=1 src/file2.txt
\end{lstlisting}
\nopagebreak[4]
The named member limits extraction to one file, \texttt{-C} selects the destination, and stripping one path component produces \texttt{tmp/file2.txt}. Victor verified matching content with \texttt{cmp}.
\end{enumerate}
\subsection{Networking and System Information}
\begin{enumerate}[label=\arabic*., leftmargin=*, resume]
\item Show all listening TCP sockets with associated PIDs (no root assumptions).
\begin{lstlisting}[style=cmd]
$ ss -ltnp
\end{lstlisting}
\nopagebreak[4]
The flags select listening TCP sockets, numeric addresses, and process details when permitted. Victor saw two loopback listeners on port 53; the process column was blank in his unprivileged invocation, so no associated PIDs are claimed.
\item Print your default route (gateway) in a concise form.
\begin{lstlisting}[style=cmd]
$ ip route show default
\end{lstlisting}
\nopagebreak[4]
Victor's default route used gateway \texttt{172.23.224.1} through \texttt{eth0}. This is a local environment result, not a fixed gateway for every machine.
\item Display kernel name, release, and machine architecture.
\begin{lstlisting}[style=cmd]
$ uname -srm
\end{lstlisting}
\nopagebreak[4]
The requested fields identify the kernel and architecture. Victor observed Linux, release \texttt{6.18.33.2-microsoft-standard-WSL2}, and \texttt{x86\_64}.
\item Show the last 5 successful logins (or last sessions) on the system.
\begin{lstlisting}[style=cmd]
$ last -n 5
\end{lstlisting}
\nopagebreak[4]
Victor's last five available records were reboot/system-boot sessions rather than individual user logins. The record type is retained as shown; no successful user authentications are inferred.
\end{enumerate}
\subsection{Packages and Services (Debian/Ubuntu)}
\begin{enumerate}[label=\arabic*., leftmargin=*, resume]
\item Show the installed version of package \texttt{coreutils}.
\begin{lstlisting}[style=cmd]
$ dpkg-query -W -f='${Version}\n' coreutils
\end{lstlisting}
\nopagebreak[4]
The query reads installed-package metadata. Victor also printed the package name and observed \texttt{coreutils 9.4-3ubuntu6.3}.
\item Search available packages whose names contain \texttt{ripgrep}.
\begin{lstlisting}[style=cmd]
$ apt-cache search --names-only ripgrep
\end{lstlisting}
\nopagebreak[4]
The search checks package names in the local catalog. It found \texttt{ripgrep}; availability in the catalog does not establish that the package is installed.
\item Check whether service \texttt{cron} is active and print its status line only.
\begin{lstlisting}[style=cmd]
$ systemctl status cron | grep 'Active:'
\end{lstlisting}
\nopagebreak[4]
Filtering the detailed status selects its Active line. Victor used \texttt{systemctl is-active cron}, a concise active-state query, and recorded \texttt{active}.
\end{enumerate}
\subsection{Bash and Scripting}
\begin{enumerate}[label=\arabic*., leftmargin=*, resume]
\item Write a one-liner that loops over \texttt{*.txt} in \texttt{src/} and prints: : .
\textbf{Assumption:} the intended output is each file's name and line count, since
the supplied problem PDF displays an incomplete output format.
\begin{lstlisting}[style=cmd]
$ for f in src/*.txt; do echo "$f: $(wc -l < "$f")"; done
\end{lstlisting}
\nopagebreak[4]
For the stated line-count assumption, \texttt{wc -l} counts each file's lines and the loop prefixes its name. Victor's captured command instead printed filename and contents under a different interpretation of the incomplete prompt; Figure~\ref{Figure:HW2-J} documents that alternative.
\item Write a command that exports CSV rows where \texttt{dept == "CS"} to \texttt{cs.txt} (exclude header).
\begin{lstlisting}[style=cmd]
$ awk -F, 'NR>1 && $3=="CS"' people.csv > cs.txt
\end{lstlisting}
\nopagebreak[4]
The field separator is a comma; \texttt{NR>1} skips the header and field three selects CS. Victor verified that \texttt{cs.txt} contained \texttt{2,Linus,CS} and \texttt{4,Dennis,CS}.
\item Create a variable \texttt{X} with value 42, print it, then remove it from the environment.
\begin{lstlisting}[style=cmd]
$ export X=42; echo "$X"; unset X
\end{lstlisting}
\nopagebreak[4]
\texttt{export} adds the variable to the shell's exported environment; \texttt{unset} removes it. Victor printed \texttt{X=42}, then verified its absence using \texttt{printenv}.
\end{enumerate}
\section{Problem Set Execution Evidence}
\label{Section:LinuxEvidence}
These screenshots were supplied by Victor from his recorded September 27th
session. They show actual commands and outputs, grouped by problem-set section.
They supplement Payton's setup figures. Captions identify the question ranges;
the explanations above distinguish equivalent commands.
Empty output is retained where it is the observed result.
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/03-section-a-q01-q06.png}
\caption{Q1--Q6: navigation and file operations. The moved file retains its original modification time, and the new notes file is empty.}
\label{Figure:HW2-A}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/04-section-b-q07-q12.png}
\caption{Q7--Q12: viewing and searching. The final command remains in follow mode at capture; Victor subsequently stopped it with Ctrl+C.}
\label{Figure:HW2-B}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/05-section-c-q13-q16.png}
\caption{Q13--Q16: text processing. Original text is retained in the backups, while the edited files total 31 bytes.}
\label{Figure:HW2-C}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/06-section-d-q17-q20.png}
\caption{Q17--Q20: permissions and attributes. Group execute was already set; the append-only attribute and owner-only notes-file access are verified.}
\label{Figure:HW2-D}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/07-section-e-q21-q23.png}
\caption{Q21--Q23: links and file searches. The recent-modification search returns no matches.}
\label{Figure:HW2-E}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/08-section-f-q24-precheck.png}
\caption{Q24 and precheck: Victor records his process tree and confirms no existing user-owned sleep processes before the demonstration.}
\label{Figure:HW2-F1}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/09-section-f-q25-q27.png}
\caption{Q25--Q27: background process, SIGTERM, exit status 143, and the five largest processes by resident-memory usage.}
\label{Figure:HW2-F2}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/10-section-g-q28-q30.png}
\caption{Q28--Q30: archive creation, member listing, selected extraction, and successful content comparison.}
\label{Figure:HW2-G}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/11-section-h-q31-q34.png}
\caption{Q31--Q34: sockets, route, kernel, and boot-session history. The unprivileged socket listing contains no process details.}
\label{Figure:HW2-H}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/12-section-i-q35-q37.png}
\caption{Q35--Q37: installed coreutils version, available ripgrep package, and active cron state.}
\label{Figure:HW2-I}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth,height=0.77\textwidth,keepaspectratio]{figures/13-section-j-q38-q40.png}
\caption{Q38--Q40: Victor uses the filename-and-contents interpretation for Q38, then verifies the CS export and removal of X from the environment.}
\label{Figure:HW2-J}
\end{figure}