Linux Commands, Part 2: Processes and DevOps
Last week you moved around the Linux file system from the terminal. This week the terminal is used to look at the programs that are running. Every command you type becomes a process: we watch processes with ps and top, end, pause and resume them with kill, and change their priority with nice and renice. The second half answers a question you will hear in every software company, what does DevOps mean?, and then works through the everyday Linux commands a DevOps engineer uses to read logs, compare configuration files and check a machine. The last half hour is yours: questions on paper, then tasks in your own Ubuntu machine.
Every output on this page was produced by really running the command on an Ubuntu 24.04 system as a normal user called student who may use sudo. That system was a small container, so it runs far fewer processes than your VM, and its disk and network names differ. When you repeat the commands, your process IDs, times and memory figures will be different numbers; everything else should match.
Objectives
By the end of the section you should be able to:
- Explain what a process and a PID are, and tell a foreground process from a background one.
- Name the process states (running, sleeping, stopped, zombie) and read their letters
R,S,D,T,Z. - List processes with
ps,ps -uandps -A, and read the live table oftop. - End a process with
killandkill -9, pause and resume it withkill -STOPandkill -CONT, and explain why a zombie cannot be killed. - Start a process with a chosen niceness (
nice) and change it later (renice), and relateNItoPRintop. - Explain what DevOps means, why it replaced two separate teams, and name the phases of its lifecycle with one tool for each.
- Use the DevOps command set:
sudo,id,chown,grep,sort,tail,cut,tr,sed,diff,find,dd,tar,gzip,df,du,free,history,hostname,ifconfig,pingandtelnet.
Before you start: the lab folder
Make a folder for this lab and save these files in it with the Text Editor. The examples below use them.
app.log, a small application log with ten lines:
2026-09-25 09:00:01 INFO web1 service started on port 8080
2026-09-25 09:00:05 INFO db1 connection pool ready
2026-09-25 09:01:12 WARN web1 slow response 1200 ms
2026-09-25 09:02:30 ERROR web2 cannot connect to db1
2026-09-25 09:02:31 INFO web2 retrying connection
2026-09-25 09:02:35 ERROR web2 cannot connect to db1
2026-09-25 09:03:00 info web2 connected to db1
2026-09-25 09:05:44 WARN db1 disk usage 85 percent
2026-09-25 09:06:10 INFO web1 deploy version 2.4 finished
2026-09-25 09:07:02 ERROR web1 out of memoryservers.txt (name, city, port, RAM in GB, separated by commas) and ports.txt (one number per line):
web1,cairo,8080,4
db1,mansoura,5432,16
web2,alexandria,8080,4
cache1,cairo,6379,8
backup1,mansoura,22,328080
22
443
5432
80
3306config.old and config.new, two versions of a configuration file:
port=8080
workers=4
debug=true
log=/var/log/app.logport=9090
workers=4
debug=false
log=/var/log/app.log
timeout=30notes.txt (with blank lines on purpose) and stubborn.sh, a script that refuses the normal kill signal:
deploy steps
build the code
test the code
ship it#!/bin/bash
# ignores the polite kill signal (15, SIGTERM)
trap '' TERM
while true; do
sleep 1
donehosts.txt, five host names in mixed case:
web1
DB2
cache1
Web3
db1Make the script runnable with chmod +x stubborn.sh. The finished folder:
$ ls
app.log config.old db.old hosts.txt ports.txt sizes.txt
config.new db.new deploy.log notes.txt servers.txt stubborn.sh(deploy.log, sizes.txt, db.old and db.new are used in the practice section at the end.)
Part 1: Processes
What is a process?
A process is a program in execution: a running instance of a program. Any command that you execute starts a process, and Linux gives every process a unique number, its process ID (PID).
Processes are of two types:
| Type | Also called | Behaviour |
|---|---|---|
| Foreground | interactive | depends on the user for input; the terminal waits for it |
| Background | non-interactive, automatic | runs independently of the user; the terminal is free |
Ending a command with & starts it in the background. The shell prints a job number in brackets and the PID, and gives you the prompt back at once:
$ sleep 300 &
[1] 953sleep 300 does nothing for 300 seconds, which makes it a convenient, harmless process to experiment on.
Worked example 1: who is running?
Question. A new terminal has just started sleep 300 &. Predict the output of ps.
Solution.
$ ps
PID TTY TIME CMD
942 pts/0 00:00:00 bash
953 pts/0 00:00:00 sleep
954 pts/0 00:00:00 psThree processes: the shell bash that reads your commands, the background sleep (PID 953, the same number the shell printed), and ps itself. Running ps starts a new process every time, so ps always appears in its own list, with a new PID each run.
Process states
A process goes through different states between its creation and its termination:
| State | Letter | Meaning |
|---|---|---|
| Running | R | it is running, or it is ready to run |
| Interruptible sleep | S | waiting for a resource to be available; it wakes up to handle signals |
| Uninterruptible sleep | D | waiting for a resource; it does not wake up for signals |
| Stopped | T | it received a stop signal |
| Zombie | Z | it is dead, but its entry is still present in the process table |
Part 2: Watching processes with ps and top
ps: a snapshot
ps (process status) displays the processes that are running now. Unlike top, its output is not updated in real time: it is a snapshot. Plain ps shows the processes of the current terminal:
| Column | Meaning |
|---|---|
PID | the process ID |
TTY | the terminal the process is attached to |
TIME | total CPU time the process has used |
CMD | the command that launched the process |
ps -u gives more information:
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 942 0.5 0.0 7608 4440 pts/0 Ss 08:03 0:00 bash
student 953 0.0 0.0 5688 2148 pts/0 S 08:03 0:00 sleep 300
student 955 0.0 0.0 10888 4352 pts/0 R+ 08:03 0:00 ps -u%CPU: the amount of computing power the process is taking.%MEM: the amount of memory the process is taking up.STAT: the process state. The first letter is the state from the table above; ignore the extra marks after it for today.
Worked example 2: read the STAT column
Question. In the ps -u output above, which rows are sleeping and which one is running, and why?
Solution. bash (Ss) and sleep 300 (S) are sleeping: the shell waits for you to type, and sleep waits for its timer. ps -u (R+) is running: at the moment the snapshot is taken, ps is the process doing the work of reading the table.
ps -A: every process
Plain ps lists only the processes of this terminal. ps -A lists all processes, including those not attached to any terminal (their TTY is ?):
$ ps -A
PID TTY TIME CMD
1 ? 00:00:00 bash
7 ? 00:00:00 sleep
43 pts/0 00:00:00 bash
53 pts/0 00:00:00 sleep
54 pts/0 00:00:04 yes
56 pts/0 00:00:00 psThis list was captured in a small container, where PID 1 is the shell. On your Ubuntu machine PID 1 is systemd, the first process the system starts, and the list is much longer: the desktop alone runs many processes. To find one program, send the list through a pipe (|) into grep, which keeps only the lines that contain a word:
$ ps -A | grep sleep
7 ? 00:00:00 sleep
53 pts/0 00:00:00 sleeptop: the live view
top displays the running processes in real time with their memory and CPU usage, refreshing every few seconds. Use the up and down arrow keys to move through the list, press k to kill the highlighted process, and q to quit.
Here top was captured once (top -b -n 1 prints one screen and exits) while a sleep 300 and a yes > /dev/null were running in the background. yes prints the letter y forever, and > /dev/null throws that output away, so yes does nothing but keep the CPU busy:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
54 student 20 0 5688 1976 1860 R 90.0 0.0 0:03.76 yes
1 root 20 0 4328 480 240 S 0.0 0.0 0:00.06 bash
7 root 20 0 2700 100 4 S 0.0 0.0 0:00.00 sleep
43 student 20 0 7608 4336 3728 S 0.0 0.0 0:00.07 bash
53 student 20 0 5688 2024 1916 S 0.0 0.0 0:00.01 sleep
55 student 20 0 11848 5420 3340 R 0.0 0.0 0:00.00 top| Column | Meaning |
|---|---|
PID | unique process ID given to each process |
USER | username of the process owner |
PR | priority given to the process while scheduling |
NI | the nice value of the process (the fourth column) |
VIRT | amount of virtual memory used by the process |
RES | amount of physical memory used by the process |
SHR | amount of memory shared with other processes |
S | state: D uninterruptible sleep, R running, S sleeping, T traced or stopped, Z zombie |
%CPU | percentage of CPU used by the process |
%MEM | percentage of RAM used by the process |
TIME+ | total CPU time consumed by the process |
COMMAND | the command used to start the process |
Worked example 3: who is using the CPU?
Question. Before reading the table above, predict the S and %CPU columns for sleep and for yes.
Solution. yes never waits for anything, so it is always running: R, and about 90% of one CPU (the exact figure moves with every refresh). sleep spends all its time waiting for the timer: S and 0.0. The top row itself is R because it was the process drawing the screen.
Part 3: Signals with kill
kill sends a signal
To stop a process, use kill. Despite the name, kill sends a signal to a process, and the signal decides what happens. kill -L lists all the signals:
$ kill -L
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX| Command | Signal sent | Use it |
|---|---|---|
kill PID | 15, SIGTERM (the default) | to ask a process to end |
kill -9 PID | 9, SIGKILL | when the process ignores a normal kill request |
kill -STOP PID | 19, SIGSTOP | to pause (stop) a process |
kill -CONT PID | 18, SIGCONT | to let a stopped process continue |
Worked example 4: the polite kill
Question. Predict the output of ps after these two commands.
$ sleep 300 &
[1] 84
$ kill 84Solution.
$ ps
PID TTY TIME CMD
72 pts/0 00:00:00 bash
91 pts/0 00:00:00 ps
[1]+ Terminated sleep 300With no number, kill sends signal 15, SIGTERM. sleep ends, it is gone from the table, and the shell reports the job as Terminated.
Worked example 5: a process that ignores kill
Question. stubborn.sh (see the lab folder) tells the shell to ignore SIGTERM, then sleeps in a loop. Predict what ps shows after kill 92, and after kill -9 92.
Solution.
$ ./stubborn.sh &
[1] 92
$ kill 92
$ ps
PID TTY TIME CMD
72 pts/0 00:00:00 bash
92 pts/0 00:00:00 stubborn.sh
104 pts/0 00:00:00 sleep
105 pts/0 00:00:00 ps
$ kill -9 92
$ ps
PID TTY TIME CMD
72 pts/0 00:00:00 bash
113 pts/0 00:00:00 ps
[1]+ Killed ./stubborn.shAfter kill 92 the script is still there (the sleep under it is the one second sleep of its loop): SIGTERM was ignored. SIGKILL, signal 9, cannot be ignored, so kill -9 ends it and the shell reports Killed. Use kill -9 only when the normal kill does not work: the process gets no chance to finish its work cleanly.
Stop and continue
A process enters the stopped state when it receives a stop signal:
$ sleep 300 &
[1] 130
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 120 2.7 0.0 7608 4280 pts/0 Ss 08:06 0:00 bash
student 130 0.0 0.0 5688 2024 pts/0 S 08:06 0:00 sleep 300
student 131 0.0 0.0 10888 4344 pts/0 R+ 08:06 0:00 ps -u
$ kill -STOP 130
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 120 1.7 0.0 7608 4340 pts/0 Ss 08:06 0:00 bash
student 130 0.0 0.0 5688 2024 pts/0 T 08:06 0:00 sleep 300
student 138 0.0 0.0 10888 4432 pts/0 R+ 08:06 0:00 ps -u
[1]+ Stopped sleep 300
$ kill -CONT 130
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 120 1.2 0.0 7608 4348 pts/0 Ss 08:06 0:00 bash
student 130 0.0 0.0 5688 2024 pts/0 S 08:06 0:00 sleep 300
student 145 0.0 0.0 10888 4300 pts/0 R+ 08:06 0:00 ps -u-STOP changed the state of sleep from S to T, and the shell reported the job as Stopped. -CONT put it back to S.
Worked example 6: kill a stopped process
Question. sleep 300 is stopped. We send it the default kill. Is it gone? What happens when we then send -CONT?
Solution.
$ sleep 300 &
[1] 366
$ kill -STOP 366
$ kill 366
[1]+ Stopped sleep 300
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 358 0.2 0.0 7612 1968 pts/0 Ss 08:39 0:00 bash
student 366 0.0 0.0 5688 112 pts/0 T 08:39 0:00 sleep 300
student 379 2.2 0.0 10888 4296 pts/0 R+ 08:40 0:00 ps -u
$ kill -CONT 366
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 358 0.2 0.0 7612 2948 pts/0 Ss 08:39 0:00 bash
student 386 50.0 0.0 10888 4396 pts/0 R+ 08:40 0:00 ps -u
[1]+ Terminated sleep 300After kill 366 the process is still in the table in state T: a stopped process does not act on SIGTERM; the signal waits. As soon as -CONT lets it continue, it handles the waiting SIGTERM and is Terminated. kill -9 does not wait: the same experiment with yes shows a stopped process ending at once.
$ yes > /dev/null &
[1] 387
$ kill -STOP 387
[1]+ Stopped yes > /dev/null
$ kill -9 387
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 358 0.1 0.0 8128 2548 pts/0 Ss 08:39 0:00 bash
student 400 0.0 0.0 10888 4420 pts/0 R+ 08:40 0:00 ps -u
[1]+ Killed yes > /dev/nullWorked example 7: the zombie
Question. The command (sleep 1 & exec sleep 60) & makes a parent process (sleep 60) with a child (sleep 1). The child ends after one second, but sleep never collects its children, so the dead child stays in the table. Predict what kill -9 does to the zombie, and what happens when the parent is killed.
Solution.
$ (sleep 1 & exec sleep 60) &
[1] 188
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 178 1.1 0.0 7608 4288 pts/0 Ss 08:07 0:00 bash
student 188 0.0 0.0 5688 2136 pts/0 S 08:07 0:00 sleep 60
student 189 0.0 0.0 0 0 pts/0 Z 08:07 0:00 [sleep] <defunct>
student 190 0.0 0.0 10888 4404 pts/0 R+ 08:07 0:00 ps -u
$ kill -9 189
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 178 0.9 0.0 7608 4288 pts/0 Ss 08:07 0:00 bash
student 188 0.0 0.0 5688 2136 pts/0 S 08:07 0:00 sleep 60
student 189 0.0 0.0 0 0 pts/0 Z 08:07 0:00 [sleep] <defunct>
student 197 0.0 0.0 10888 4388 pts/0 R+ 08:07 0:00 ps -u
$ kill 188
$ ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 178 0.6 0.0 7608 4276 pts/0 Ss 08:07 0:00 bash
student 204 0.0 0.0 10888 4308 pts/0 R+ 08:07 0:00 ps -u
[1]+ Terminated ( sleep 1 & exec sleep 60 )The child 189 is a zombie: state Z, marked defunct, using no memory. kill -9 189 changes nothing, because the process is already dead; only its entry is left in the table, waiting for the parent to collect it. When the parent 188 is ended, the entry disappears with it.
To find the parent of a process, ask ps for the PPID column (the parent process ID); -o chooses the columns. From another run of the same command:
$ (sleep 1 & exec sleep 60) &
[1] 896
$ ps -o pid,ppid,stat,cmd
PID PPID STAT CMD
889 888 Ss bash
896 889 S sleep 60
897 896 Z [sleep] <defunct>
899 889 R+ ps -o pid,ppid,stat,cmdThe zombie 897 has PPID 896: its parent is sleep 60, the process to end. ps -u has no PPID column, so use this form whenever you need the parent.
Interactive: the process table simulator
Open the process table simulator full screen. Press Play for a guided tour of every case above, or start processes yourself, select a row and send it signals. It follows the behaviour of the real runs on this page: kill ends a normal process, stubborn.sh ignores it, a stopped process keeps a SIGTERM until it continues, a zombie ignores everything, and only sudo may lower a niceness.
Part 4: Priorities with nice and renice
Niceness
Linux can prioritize between processes. The priority value of a process is called its niceness. The help text of nice gives the range:
$ nice --help | head -4
Usage: nice [OPTION] [COMMAND [ARG]...]
Run COMMAND with an adjusted niceness, which affects process scheduling.
With no COMMAND, print the current niceness. Niceness values range from
-20 (most favorable to the process) to 19 (least favorable to the process).- Niceness goes from
-20(most favorable) to19(least favorable). The default is0. NI, the fourth column oftop, is the niceness.- Start a process with a niceness other than the default:
nice -n [value] [command]. - Change the niceness of a process that is already running:
renice [value] -p PID.
$ nice -n 10 sleep 300 &
[1] 32
$ top -b -n 1 -p 32
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
32 student 30 10 5688 1932 1824 S 0.0 0.0 0:00.00 sleep
$ renice 15 -p 32
32 (process ID) old priority 10, new priority 15Of the top output only the column header and the process row are shown here (its five summary lines are left out). renice calls the niceness a "priority" in its message: old 10, new 15.
Worked example 8: from NI to PR
Question. Every top row in this lab obeys one rule between NI and PR: 0 and 20, 10 and 30. Predict PR after renice 15, and after sudo renice 5. (The first part of the solution comes from a second run of the same steps, so its PID differs.)
Solution. The rule is
PR = 20 + NIwhere NI is the niceness and PR the scheduling priority top shows. renice 15 gives PR = 20 + 15 = 35, and sudo renice 5 gives PR = 20 + 5 = 25, which is what top printed:
$ renice 15 -p 8751
8751 (process ID) old priority 10, new priority 15
$ top -b -n 1 -p 8751 | tail -2
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
8751 student 35 15 5688 2088 1980 S 0.0 0.0 0:00.00 sleep
$ sudo renice 5 -p 32
[sudo] password for student:
32 (process ID) old priority 15, new priority 5
$ top -b -n 1 -p 32
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
32 student 25 5 5688 344 236 S 0.0 0.0 0:00.00 sleepWorked example 9: lower the niceness
Question. The process has niceness 15. As student we ask for 5. Predict the answer, then run the same request with sudo.
Solution.
$ renice 5 -p 32
renice: failed to set priority for 32 (process ID): Permission denied
$ sudo renice 5 -p 32
[sudo] password for student:
32 (process ID) old priority 15, new priority 5A normal user may raise the niceness of their own process (10 to 15 worked), making it nicer to the others, but may not lower it: Permission denied. sudo runs the command as the superuser root, which may. The same rule stops a normal user from starting a process below 0:
$ nice -n -5 sleep 1
nice: cannot set niceness: Permission deniedPart 5: What DevOps means
Two teams and a wall
From the start, software development had two departments:
| Team | Its job |
|---|---|
| Development | plans, designs and builds the system from scratch |
| Operations | tests and implements whatever is developed, and sends feedback on bugs and rework |
The development team was often idle, waiting for feedback from operations. Sometimes it had already moved to the next project while operations was still reporting on the old code, so it took weeks or even months to close a project. DevOps asks what happens if the two departments come together and work in collaboration: the "wall of confusion" between them is broken. The DevOps symbol is an infinity sign: a continuous process of improving efficiency and constant activity. Companies adapt faster to updates, teams deliver quickly, and deployments are more consistent and smooth.
The DevOps lifecycle
DevOps is implemented in phases, each helped by tools:
| Phase | What happens | Example tools |
|---|---|---|
| Plan | the team plans the objectives to deliver to the customer | |
| Code | everyone works on the same code; versions are stored in a repository and merged when required (version control) | Git |
| Build | the code is made executable | Maven, Gradle |
| Test | the build is tested for bugs, manually and automatically | Selenium |
| Deploy | the tested code goes to the working environment | Ansible, Docker, Kubernetes |
| Monitor | the product is continuously monitored; the feedback goes back to planning | Nagios |
Continuous integration ties the phases together: Jenkins sends the code for building and testing, and if it passes the tests it is sent for deployment.
Companies such as Amazon, Netflix, Walmart, Facebook and Adobe work this way. In 2014 an hour of downtime was estimated to cost Netflix about $200,000. Netflix built a set of tools, the Simian Army, that keeps creating failures in its environment without affecting the users, which pushed its developers to build a system that does not fall apart when something breaks.
Worked example 10: match the tool
Question. Match each tool with its phase: Git, Selenium, Docker, Nagios, Jenkins.
Solution. Git: code (version control). Selenium: test (automation testing). Docker: deploy, with Ansible and Kubernetes. Nagios: monitor. Jenkins: continuous integration, sending code to build and test.
Why Linux commands matter in DevOps
Most organizations run their environments on Linux, and configuration management tools such as Chef, Puppet and Ansible have their main nodes on Linux. A few basic commands let a developer investigate why an application works on one machine but not on another, and describe the problem to the system administrators. The commands below work the same in a development machine, a virtual machine and a container.
Part 6: The DevOps command toolkit
sudo: run one command as root
sudo runs only that command with root (superuser) privileges. It asks for your password the first time.
| Command | Does |
|---|---|
sudo useradd -m NAME | add a new user (-m creates the home folder) |
sudo passwd NAME | create a password for the user |
sudo groupadd NAME | add a new group |
sudo usermod -g GROUP NAME | set the user's primary group |
sudo userdel NAME | delete the user |
sudo groupdel NAME | delete the group |
$ sudo groupadd devops
[sudo] password for student:
$ sudo useradd -m deploy
$ sudo usermod -g devops deploy
$ id deploy
uid=1002(deploy) gid=1002(devops) groups=1002(devops)
$ sudo passwd deploy
New password:
Retype new password:
passwd: password updated successfullyid: user and group IDs
id prints the user name and ID, the primary group and every group of the current user, or of another user when you name one (id deploy above).
$ id
uid=1001(student) gid=1001(student) groups=1001(student),27(sudo)
$ id -u
1001
$ id -g
1001
$ id -G
1001 27
$ id -Gn
student sudo| Option | Prints |
|---|---|
-u | only the effective user ID |
-g | only the effective group ID |
-G | all group IDs |
-n | names instead of numbers (with -u, -g or -G) |
student belongs to the group sudo: that is what allows it to use sudo.
chown: change the owner
Every file has an owner and a group, shown in columns 3 and 4 of ls -l. chown [OWNER][:[GROUP]] FILE changes them. Giving a file to someone else needs sudo:
$ ls -l servers.txt
-rw-r--r-- 1 student student 105 Sep 25 08:03 servers.txt
$ sudo chown deploy servers.txt
$ ls -l servers.txt
-rw-r--r-- 1 deploy student 105 Sep 25 08:03 servers.txt
$ sudo chown root:devops servers.txt
$ ls -l servers.txt
-rw-r--r-- 1 root devops 105 Sep 25 08:03 servers.txt(Afterwards we gave the file back with sudo chown student:student servers.txt, and removed the test user and group with sudo userdel -r deploy and sudo groupdel devops.)
ls, cat and chmod, revisited
You may know these already; the DevOps list adds a few options:
ls -alists hidden entries too;ls -lgives the long format with permissions, owner and time stamp.cat -nnumbers all lines,cat -bonly the non-blank ones,cat -ssqueezes repeated blank lines into one,cat -Eshows a$at the end of each line.chmodsets permissions with numbers:4read,2write,1execute,0none, one digit each for the owner, the group and the others.
$ cat -b notes.txt
1 deploy steps
2 build the code
3 test the code
4 ship it
$ cat -s notes.txt
deploy steps
build the code
test the code
ship it
$ cat -n notes.txt
1 deploy steps
2
3
4
5 build the code
6 test the code
7
8 ship it
$ cat -E notes.txt
deploy steps$
$
$
$
build the code$
test the code$
$
ship it$
$ ls -l stubborn.sh
-rwxr-xr-x 1 student student 102 Sep 25 08:03 stubborn.sh
$ chmod 750 stubborn.sh
$ ls -l stubborn.sh
-rwxr-x--- 1 student student 102 Sep 25 08:03 stubborn.sh750 is 7 = 4 + 2 + 1 (read, write, execute) for the owner, 5 = 4 + 1 (read, execute) for the group and 0 (nothing) for the others: rwxr-x---.
grep: search inside files
grep searches for a word in a file, like Ctrl+F but on the command line, and prints the matching lines.
| Option | Returns |
|---|---|
-n | the matching lines with their line numbers |
-i | matches regardless of upper and lower case |
-c | the number of matching lines |
-v | the lines that do not match |
$ grep ERROR app.log
2026-09-25 09:02:30 ERROR web2 cannot connect to db1
2026-09-25 09:02:35 ERROR web2 cannot connect to db1
2026-09-25 09:07:02 ERROR web1 out of memory
$ grep -n ERROR app.log
4:2026-09-25 09:02:30 ERROR web2 cannot connect to db1
6:2026-09-25 09:02:35 ERROR web2 cannot connect to db1
10:2026-09-25 09:07:02 ERROR web1 out of memory
$ grep -c ERROR app.log
3Worked example 11: grep -i and grep -v
Question. Predict how many lines grep info app.log prints, how many grep -i info app.log prints, and the number grep -c -v INFO app.log prints.
Solution.
$ grep info app.log
2026-09-25 09:03:00 info web2 connected to db1
$ grep -i info app.log
2026-09-25 09:00:01 INFO web1 service started on port 8080
2026-09-25 09:00:05 INFO db1 connection pool ready
2026-09-25 09:02:31 INFO web2 retrying connection
2026-09-25 09:03:00 info web2 connected to db1
2026-09-25 09:06:10 INFO web1 deploy version 2.4 finished
$ grep -v INFO app.log
2026-09-25 09:01:12 WARN web1 slow response 1200 ms
2026-09-25 09:02:30 ERROR web2 cannot connect to db1
2026-09-25 09:02:35 ERROR web2 cannot connect to db1
2026-09-25 09:03:00 info web2 connected to db1
2026-09-25 09:05:44 WARN db1 disk usage 85 percent
2026-09-25 09:07:02 ERROR web1 out of memory
$ grep -c -v INFO app.log
6grep info finds 1 line: case matters and only line 7 has a lowercase info. With -i the four INFO lines match too: 5 lines. -v INFO keeps the lines without an uppercase INFO: two WARN, three ERROR and the lowercase info line, 6.
sort: order the lines
sort sorts lines alphabetically or numerically. -r reverses the order, -n sorts by numerical value and -f ignores case.
Worked example 12: sort and sort -n
Question. Predict sort ports.txt and sort -n ports.txt.
Solution.
$ sort ports.txt
22
3306
443
5432
80
8080
$ sort -n ports.txt
22
80
443
3306
5432
8080Plain sort compares the lines as text, character by character, so 3306 comes before 443 (3 is before 4) and 80 after 5432. -n compares them as numbers. -r reverses either order:
$ sort -r servers.txt
web2,alexandria,8080,4
web1,cairo,8080,4
db1,mansoura,5432,16
cache1,cairo,6379,8
backup1,mansoura,22,32On Ubuntu with the English language setting, plain sort already places a capitalized word next to its lowercase form, so -f changes nothing here:
$ sort hosts.txt
cache1
db1
DB2
web1
Web3
$ sort -f hosts.txt
cache1
db1
DB2
web1
Web3tail: the end of a file
tail prints the last lines of a file, the last 10 by default. Logs grow at the bottom, so the newest events are at the end.
$ tail -n 3 app.log
2026-09-25 09:05:44 WARN db1 disk usage 85 percent
2026-09-25 09:06:10 INFO web1 deploy version 2.4 finished
2026-09-25 09:07:02 ERROR web1 out of memory
$ tail -3 app.log
2026-09-25 09:05:44 WARN db1 disk usage 85 percent
2026-09-25 09:06:10 INFO web1 deploy version 2.4 finished
2026-09-25 09:07:02 ERROR web1 out of memory
$ tail -n +9 app.log
2026-09-25 09:06:10 INFO web1 deploy version 2.4 finished
2026-09-25 09:07:02 ERROR web1 out of memory
$ tail -c 20 app.log
web1 out of memory-n 3 (or the short -3) prints the last 3 lines, -n +9 (or +9) prints from line 9 to the end, and -c 20 prints the last 20 bytes: 19 visible characters plus the newline.
cut: pick columns
cut extracts part of every line: -c selects characters, -d sets the delimiter and -f selects fields.
$ cut -c1-2 servers.txt
we
db
we
ca
ba
$ cut -d , -f 1 servers.txt
web1
db1
web2
cache1
backup1
$ cut -d , -f 1,3 servers.txt
web1,8080
db1,5432
web2,8080
cache1,6379
backup1,22tr: translate characters
tr translates or deletes characters. It reads its input from a pipe (or a redirected file), not from a file name.
| Option | Does |
|---|---|
(none) tr SET1 SET2 | replaces each character of SET1 with the matching one in SET2 |
-d | deletes the characters of the first set |
-s | squeezes repeated characters into one |
-c | works on the characters not in the set |
$ cat servers.txt | tr a-z A-Z
WEB1,CAIRO,8080,4
DB1,MANSOURA,5432,16
WEB2,ALEXANDRIA,8080,4
CACHE1,CAIRO,6379,8
BACKUP1,MANSOURA,22,32
$ echo "web1 db1 cache1" | tr -s " "
web1 db1 cache1
$ echo "web1 web2 db1" | tr -d 0-9
web web dbsed: edit a stream of text
sed edits text without opening an editor. s/old/new/ substitutes the first match on each line; a g at the end substitutes every match.
Worked example 13: sed does not change the file
Question. After sed 's/8080/9090/' servers.txt, predict what cat servers.txt shows.
Solution.
$ sed 's/8080/9090/' servers.txt
web1,cairo,9090,4
db1,mansoura,5432,16
web2,alexandria,9090,4
cache1,cairo,6379,8
backup1,mansoura,22,32
$ cat servers.txt
web1,cairo,8080,4
db1,mansoura,5432,16
web2,alexandria,8080,4
cache1,cairo,6379,8
backup1,mansoura,22,32The file is unchanged: sed prints an edited copy and leaves the file as it is. The g flag matters when a line has more than one match:
$ echo "8080 to 8080" | sed 's/8080/9090/'
9090 to 8080
$ echo "8080 to 8080" | sed 's/8080/9090/g'
9090 to 9090diff: compare two files
diff prints the lines that differ between two files.
Worked example 14: read a diff
Question. Explain every line of diff config.old config.new.
Solution.
$ diff config.old config.new
1c1
< port=8080
---
> port=9090
3c3
< debug=true
---
> debug=false
4a5
> timeout=301c1: line 1 of the first file must be changed into line 1 of the second:port=8080becameport=9090.3c3: line 3 changed:debug=truebecamedebug=false.4a5: after line 4 of the first file, add line 5 of the second:timeout=30.- Lines starting with the less-than sign come from the first file, lines starting with the greater-than sign from the second, and
---separates them. Line 2 (workers=4) and line 4 (log=...) are equal, so they are not printed. Two identical files print nothing:
$ diff config.old config.oldfind: walk the folder tree
find walks through a folder and everything below it, and finds files by name, inode number, owner, dates and permissions. It can also run a command on every file it finds.
| Option | Does |
|---|---|
-name PATTERN | files whose name matches |
-inum N | the file with inode number N (ls -i shows it) |
-links N | files with N links |
-exec CMD | runs CMD on each file found (the file goes where the braces are) |
-ok CMD | like -exec, but asks you first |
For this example two copies of the log were placed in a logs folder:
$ find . -name "*.log"
./app.log
./logs/old/web-0924.log
./logs/web.log
$ find logs -name "*.log" -exec ls -l {} \;
-rw-r--r-- 1 student student 519 Sep 25 08:19 logs/old/web-0924.log
-rw-r--r-- 1 student student 519 Sep 25 08:19 logs/web.log
$ ls -i app.log
4456513 app.log
$ find . -inum 4456513
./app.log
$ find . -name "*.log" -ok rm {} \;
< rm ... ./app.log > ? n
< rm ... ./logs/old/web-0924.log > ? n
< rm ... ./logs/web.log > ? y
$ find . -name "*.log"
./app.log
./logs/old/web-0924.logThe last command asked before each removal, and only logs/web.log (answered y) was deleted.
dd: copy and convert bytes
dd copies data block by block, and can convert it on the way. Because devices appear as files in Linux, dd can also read from or write to them: it is used for tasks such as backing up the boot area of a disk, or getting a fixed amount of random data.
$ dd if=/dev/zero of=disk.img bs=1M count=5
5+0 records in
5+0 records out
5242880 bytes (5.2 MB, 5.0 MiB) copied, 0.013896 s, 377 MB/s
$ ls -l disk.img
-rw-r--r-- 1 student student 5242880 Sep 25 08:28 disk.img
$ dd if=/dev/urandom of=key.bin bs=16 count=1
1+0 records in
1+0 records out
16 bytes copied, 0.000774319 s, 20.7 kB/s
$ ls -l key.bin
-rw-r--r-- 1 student student 16 Sep 25 08:28 key.bin
$ dd if=servers.txt of=upper.txt conv=ucase
0+1 records in
0+1 records out
105 bytes copied, 0.00259888 s, 40.4 kB/s
$ cat upper.txt
WEB1,CAIRO,8080,4
DB1,MANSOURA,5432,16
WEB2,ALEXANDRIA,8080,4
CACHE1,CAIRO,6379,8
BACKUP1,MANSOURA,22,32if is the input file, of the output file, bs the block size and count the number of blocks. The number of bytes copied is
B = n × s = 5 × 1048576 = 5242880where B is the bytes copied, n the count and s the bs in bytes (1M is 1048576). /dev/zero gives endless zero bytes, /dev/urandom endless random bytes, and conv=ucase converts letters to capitals while copying. 0+1 records means zero full blocks and one partial block: the 105-byte file was smaller than one block.
tar and gzip: archive and compress
tar packs many files into one archive file, and with z it also compresses the archive with gzip. The letters after tar are its options: c creates an archive, t lists what is inside, x extracts, z passes the data through gzip and f names the archive file. -C chooses the folder to extract into.
$ tar czf lab10.tar.gz app.log servers.txt notes.txt
$ ls -l app.log servers.txt notes.txt lab10.tar.gz
-rw-r--r-- 1 student student 519 Sep 25 10:31 app.log
-rw-r--r-- 1 student student 499 Sep 25 10:32 lab10.tar.gz
-rw-r--r-- 1 student student 54 Sep 25 10:31 notes.txt
-rw-r--r-- 1 student student 105 Sep 25 10:31 servers.txt
$ tar tzf lab10.tar.gz
app.log
servers.txt
notes.txt
$ mkdir restore
$ tar xzf lab10.tar.gz -C restore
$ ls restore
app.log notes.txt servers.txtThe three files hold 519 + 105 + 54 = 678 bytes, and the compressed archive 499. gzip alone compresses one file and replaces it with a .gz file; gunzip reverses it:
$ gzip app.log
$ ls -l app.log*
-rw-r--r-- 1 student student 264 Sep 25 10:31 app.log.gz
$ gunzip app.log.gz
$ ls -l app.log*
-rw-r--r-- 1 student student 519 Sep 25 10:31 app.logThe log shrank from 519 to 264 bytes: its lines repeat many words, and repeated text compresses well.
df and du: disk space
df (disk free) shows the size, the used and the available space of each file system; du (disk usage) shows how much space a folder takes. -h prints human-readable sizes (K, M, G), and du -s prints one summary line instead of a line for every subfolder.
$ df -h /
Filesystem Size Used Avail Use% Mounted on
overlay 916G 822G 52G 95% /
$ du -h /var/log
40K /var/log/apt
304K /var/log
$ du -sh /var/log
304K /var/logdf -h / asks only about the file system that holds /: it is 95% full, with 52G left. A full disk, like full memory, is a common reason for a failing server. On your VM the Filesystem column names a disk partition under /dev, not overlay, the container's file system. du -h lists /var/log/apt, then /var/log itself, whose 304K includes the 40K of its subfolder.
free: memory
free shows the total, used and free memory and swap, and the buffers used by the kernel. -b, -k, -m and -g choose bytes, kilobytes, megabytes or gigabytes.
$ free -m
total used free shared buff/cache available
Mem: 31866 30491 398 471 2276 1375
Swap: 24575 24575 0
$ free -g
total used free shared buff/cache available
Mem: 31 29 0 0 2 1
Swap: 23 23 0The machine these were run on was short on memory: only 1375 of 31866 available and the swap completely used. That is the first thing to check when an application becomes slow.
history: the commands you ran
history lists the commands you ran, each with an event number; history 5 shows the last five.
$ history 5
177 dd if=servers.txt of=upper.txt conv=ucase
178 cat upper.txt
179 free -m
180 free -g
181 history 5hostname: the machine's name
hostname prints the name the machine is known by on the network.
$ hostname
ubuntu-labifconfig: network interfaces
ifconfig (interface configuration) shows the network interfaces, their addresses and traffic counters; it is also used to assign an address or to enable or disable an interface. -a shows all interfaces, even those that are down, and -s a short list. If your Ubuntu says the command is not found, install it with sudo apt install net-tools.
$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.4 netmask 255.255.0.0 broadcast 172.17.255.255
ether e6:b9:29:25:6c:0c txqueuelen 0 (Ethernet)
RX packets 43 bytes 7442 (7.4 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3 bytes 126 (126.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
$ ifconfig -s
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 1500 43 0 0 0 3 0 0 0 BMRU
lo 65536 0 0 0 0 0 0 0 0 LRUeth0 is the machine's network card with the address 172.17.0.4; lo is the loopback interface, 127.0.0.1, the machine talking to itself (the name localhost).
ping: is the host reachable?
ping sends small test packets to a host and reports whether, and how quickly, each one comes back: it is the first check when a server cannot be reached. -c 4 stops after 4 packets; without it ping runs until you press Ctrl+C.
$ ping -c 4 google.com
PING google.com (142.251.27.138) 56(84) bytes of data.
64 bytes from cv-in-f138.1e100.net (142.251.27.138): icmp_seq=1 ttl=109 time=47.4 ms
64 bytes from cv-in-f138.1e100.net (142.251.27.138): icmp_seq=2 ttl=109 time=49.8 ms
64 bytes from cv-in-f138.1e100.net (142.251.27.138): icmp_seq=3 ttl=109 time=46.8 ms
64 bytes from cv-in-f138.1e100.net (142.251.27.138): icmp_seq=4 ttl=109 time=46.7 ms
--- google.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3003ms
rtt min/avg/max/mdev = 46.666/47.683/49.815/1.263 msEach reply line gives the round-trip time in milliseconds. The summary says all 4 packets came back (0% packet loss), in about 47.7 ms on average. The first line shows the address the name google.com was resolved to.
telnet: connect to a remote machine
telnet connects to another computer, or to a port on it, to run programs and administer it remotely. DevOps uses it to check whether a service is listening.
Worked example 15: is the service up?
Question. A test service was started on port 8080 of this machine; nothing listens on port 9090, and no telnet server is installed. Predict telnet localhost, telnet localhost 8080 and telnet localhost 9090.
Solution.
$ telnet localhost
Trying ::1...
Connection failed: Connection refused
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused
$ telnet localhost 8080
Trying ::1...
Connection failed: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
^]
telnet> quit
Connection closed.
$ telnet localhost 9090
Trying ::1...
Connection failed: Connection refused
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refusedConnected to localhost means something answers on port 8080. Connection refused means nothing listens on that port (telnet with no port tries port 23, the telnet server). To leave a connection, press Ctrl+] and type quit.
Interactive: build a pipeline
Open the pipeline builder full screen. Choose a grep pattern and its options, a cut field, tr, sort and tail: the command line is written for you, and Play walks through the pipe one stage at a time, crossing out the lines each stage removes. Its outputs were checked against the real commands for all 2352 combinations.
Practice
About 30 minutes. Solve Practice 1 to 3 on paper, then check your answers in your own VM. Practice 4 is done in the VM. Answers are at the end.
Practice 1: read the table
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 563 0.7 0.0 7608 4436 pts/0 Ss 09:03 0:00 bash
student 571 0.0 0.0 5688 2088 pts/0 S 09:03 0:00 sleep 500
student 572 0.0 0.0 5688 1984 pts/0 T 09:03 0:00 sleep 700
student 579 98.1 0.0 5688 1920 pts/0 R 09:03 0:03 yes
student 580 0.0 0.0 5688 2036 pts/0 S 09:03 0:00 sleep 90
student 581 0.0 0.0 0 0 pts/0 Z 09:03 0:00 [sleep] <defunct>
student 582 0.0 0.0 10888 4476 pts/0 R+ 09:03 0:00 ps -u- Which process is stopped, and which one is using the CPU?
- Which process is a zombie? How do you remove it from the table?
- What happens after
kill 572? And afterkill -CONT 572that follows it?
Practice 2: priorities
nice -n 19 sleep 100 &starts PID615. What are itsNIandPRintop?- As
student:renice 10 -p 615. What happens? sudo renice -20 -p 615. What areNIandPRnow?- As
student:nice -n -5 sleep 1. What happens?
Practice 3: a deployment log
Save this as deploy.log:
09:10 OK build web1
09:12 FAIL test web1
09:15 OK build web2
09:16 ok test web2
09:20 FAIL deploy web2
09:25 OK deploy web1Write the command, and predict its output, for each task:
- How many lines contain
FAIL? - All lines that contain
okin any case, with their line numbers. - The stage (the third field) of every line.
- The servers (the fourth field) of the
FAILlines only. - The log with
FAILreplaced byRETRY, without changing the file. - The last 2 lines.
sizes.txtholds120,15,7,1024and64, one per line. Predictsort sizes.txtandsort -n -r sizes.txt.db.oldholdsport=5432,user=app,max=100;db.newholdsport=5432,user=admin,max=100,ssl=on. Predictdiff db.old db.new.
Practice 4: in your VM
- Create the lab folder files, and make
stubborn.shrunnable withchmod +x stubborn.sh. - Start
sleep 600 &and./stubborn.sh &, and find both PIDs withps -u. - Stop the
sleepwithkill -STOP, check its state, continue it withkill -CONT, then end it withkill. - Try to end
stubborn.shwithkill. Which signal works? - Start
nice -n 5 sleep 600 &, read itsPRandNIintop, thenrenice 12it and read them again. Predict the values first. - Run
grep ERROR app.log | cut -d " " -f 4 | sortand check the result in the pipeline builder. - Run
id,free -mandhistory 10, and explain each line of the output.
Answers
Practice 1
- Stopped:
572(sleep 700, stateT). Using the CPU:579(yes, stateR,98.1%). 581([sleep], stateZ, marked defunct).killand evenkill -9cannot remove it, because it is already dead. End its parent, whichps -o pid,ppid,stat,cmdshows in the zombie'sPPIDcolumn:580(sleep 90). The zombie's entry disappears with it.kill 572sends SIGTERM to a stopped process: nothing visible happens and it staysT, holding the signal.kill -CONT 572lets it continue; it handles the waiting SIGTERM and the shell reports itTerminated(compare Worked example 6).
Practice 2
$ nice -n 19 sleep 100 &
[1] 615
$ top -b -n 1 -p 615 | tail -2
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
615 student 39 19 5688 2132 2024 S 0.0 0.0 0:00.00 sleep
$ renice 10 -p 615
renice: failed to set priority for 615 (process ID): Permission denied
$ sudo renice -20 -p 615
[sudo] password for student:
615 (process ID) old priority 19, new priority -20
$ top -b -n 1 -p 615 | tail -2
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
615 student 0 -20 5688 2132 2024 S 0.0 0.0 0:00.00 sleep
$ nice -n -5 sleep 1
nice: cannot set niceness: Permission deniedNI = 19andPR = 20 + 19 = 39.Permission denied:10is lower than19, and only root may lower a niceness.NI = -20andPR = 20 - 20 = 0.nice: cannot set niceness: Permission denied; the command still runs, with the normal niceness.
Practice 3
$ grep -c FAIL deploy.log
2
$ grep -n -i ok deploy.log
1:09:10 OK build web1
3:09:15 OK build web2
4:09:16 ok test web2
6:09:25 OK deploy web1
$ grep -v OK deploy.log
09:12 FAIL test web1
09:16 ok test web2
09:20 FAIL deploy web2
$ tail -n 2 deploy.log
09:20 FAIL deploy web2
09:25 OK deploy web1
$ cut -d " " -f 3 deploy.log
build
test
build
test
deploy
deploy
$ sed 's/FAIL/RETRY/' deploy.log
09:10 OK build web1
09:12 RETRY test web1
09:15 OK build web2
09:16 ok test web2
09:20 RETRY deploy web2
09:25 OK deploy web1
$ grep FAIL deploy.log | cut -d " " -f 4
web1
web2
$ sort sizes.txt
1024
120
15
64
7
$ sort -n -r sizes.txt
1024
120
64
15
7
$ diff db.old db.new
2c2
< user=app
---
> user=admin
3a4
> ssl=on
$ cat deploy.log | tr a-z A-Z
09:10 OK BUILD WEB1
09:12 FAIL TEST WEB1
09:15 OK BUILD WEB2
09:16 OK TEST WEB2
09:20 FAIL DEPLOY WEB2
09:25 OK DEPLOY WEB1grep -c FAIL deploy.log:2.grep -n -i ok deploy.log: lines1,3,4and6. Without-i, line4(ok) would be missing.cut -d " " -f 3 deploy.log:build,test,build,test,deploy,deploy.grep FAIL deploy.log | cut -d " " -f 4:web1,web2.sed 's/FAIL/RETRY/' deploy.log;cat deploy.logafterwards still showsFAIL.tail -n 2 deploy.log: the09:20and09:25lines.sortcompares text:1024,120,15,64,7.sort -n -rcompares numbers, largest first:1024,120,64,15,7.2c2(user=appbecameuser=admin) and3a4(after line 3, addssl=on).
The run above also shows two traps: grep -v OK keeps the lowercase ok line, because without -i case matters, and tr a-z A-Z capitalizes the whole log.
Practice 4
ls -lshowsxin the owner's permissions ofstubborn.sh.ps -ulistssleep 600and/bin/bash ./stubborn.sh, each with its PID.- The
STATofsleepbecomesTafter-STOP,Sagain after-CONT, and the shell reportsTerminatedafterkill. killis ignored;kill -9works and the shell reportsKilled.NI = 5,PR = 25; afterrenice 12:NI = 12,PR = 32.web1,web2,web2: the servers of the threeERRORlines, sorted.id: your user ID, primary group and all groups (includingsudoif you may use it).free -m: total, used, free and available memory and swap in megabytes.history 10: your last ten commands with their event numbers.