Tuesday, October 27, 2026

What is RREF

 

1. What is RREF?

RREF stands for Reduced Row Echelon Form.

A matrix is in RREF when its entries follow four specific rules. These rules make the matrix easier to interpret and help us solve systems of linear equations.

First, remind students that a matrix is a rectangular arrangement of numbers in rows and columns.

For example:


Explain that we must examine the matrix row by row and column by column to decide whether it is in RREF.

2. Why do we need RREF?

Connect this topic to the previous lesson on systems of linear equations.


RREF helps us organize equations systematically, identify leading variables, and determine whether a system has a unique solution, no solution, or infinitely many solutions.

Important teaching point: The matrix above is in row echelon form, but it is not in RREF because the leading 1 in column 3 has nonzero entries above it.

This distinction is central to the slide.

3. How to teach the four conditions

Teach one rule at a time. Ask students to check the rule against the examples at the bottom of the slide.

Rule 1: All zero rows must be at the bottom

A zero row contains only zeros.

Example:


This condition is satisfied because the row of zeros is at the bottom.

But this matrix violates the condition:


The zero row appears before a nonzero row.

Ask students: Can a matrix have no zero rows?

Yes. This rule applies only if zero rows exist.

Rule 2: The first nonzero entry in each nonzero row must be 1

This first nonzero entry is called the leading 1 or pivot 1.

Example:


Each nonzero row begins with a leading 1.

Compare it with:


The first row begins with 2, not 1, so the matrix is not in RREF.

Rule 3: Each leading 1 must be to the right of the leading 1 in the row above

This creates a staircase pattern.


The second row's leading 1 is to the left of the first row's leading 1.

Ask students: Must the leading 1 in the first row be in column 1?

No. It can appear in a later column, provided the staircase pattern is maintained.

Rule 4: Each leading 1 must be the only nonzero entry in its column

This is the rule that distinguishes reduced row echelon form from ordinary row echelon form.


This is not in RREF because the pivot in column 2 has a nonzero entry (2) above it.

Important: Non-pivot columns may contain nonzero entries. Only the columns containing leading 1s must have zeros everywhere else.

4. Explain the three examples on your slide

This is a useful activity because the slide already provides three matrices.

Example 1


RREF
  • Leading 1s occur in columns 1, 2, and 3.

  • Each pivot column contains zeros everywhere else.

  • The pivots move to the right.

  • There are no zero rows, so the zero-row condition is satisfied.

Example 2



RREF
  • Leading 1s occur in columns 1, 2, and 4.

  • The pivot columns have zeros in all other positions.

  • The zero row is at the bottom.

  • Column 3 contains 7 and 4, which is allowed because it is not a pivot column.

Example 3



RREF
  • Leading 1s occur in columns 2, 4, and 6.

  • Their columns contain zeros everywhere else.

  • The pivots form the correct staircase.

  • The zero row is at the bottom.

5. RREF versus row echelon form

Students commonly confuse these two forms. Explain that RREF has all the requirements of row echelon form plus the additional requirement that each pivot column has zeros above and below its leading 1.

Property

Row echelon form (REF)

Reduced row echelon form (RREF)

Zero rows at bottom

Yes

Yes

Leading entries are 1

Not necessarily

Yes

Pivots move right down the rows

Yes

Yes

Zeros below each pivot

Yes

Yes

Zeros above each pivot

Not required

Yes

Pivot is the only nonzero entry in its column

Not required

Yes


MATRIX - BASIC OPERATIONS

 

1: Matrices and Basic Operations

What: A matrix is a rectangular array of numbers with m rows and n columns (an m×n matrix). The entry in row i, column j is written aᵢⱼ.

Why: Matrices organize data and represent linear transformations (rotations, scaling) and systems of equations compactly. Computer graphics, machine learning, engineering, and economics all rely on them.

How:

  • Addition/subtraction: same size only, entry by entry.
  • Scalar multiplication: multiply every entry.
  • Matrix multiplication: (AB)ᵢⱼ = row i of A · column j of B. This needs columns of A = rows of B.
  • Transpose Aᵀ: swap rows and columns.

Worked example:
A = [[1, 2], [3, 4]], B = [[0, 1], [1, 0]]

AB = [[1·0+2·1, 1·1+2·0], [3·0+4·1, 3·1+4·0]] = [[2, 1], [4, 3]]
BA = [[3, 4], [1, 2]]

So AB ≠ BA. Matrix multiplication is generally not commutative. Students must understand this early.

Common mistakes: multiplying entry by entry, ignoring size compatibility, assuming AB = BA.

Examples of the Four Basic Operations

1. Addition and Subtraction (same size only)

Add or subtract the entries in the same position.

A = [1  2]     B = [5  6]
    [3  4]         [7  8]

A + B = [1+5  2+6]  =  [ 6   8]
        [3+7  4+8]     [10  12]

A − B = [1−5  2−6]  =  [−4  −4]
        [3−7  4−8]     [−4  −4]

Not allowed: a 2×2 matrix plus a 2×3 matrix. The sizes differ, so some entries have no partner. Show this to your student as a “defined or undefined?” question.


2. Scalar Multiplication

Multiply every entry by the number.

3A = 3 [1  2]  =  [3·1  3·2]  =  [3   6]
       [3  4]     [3·3  3·4]     [9  12]

−2B = −2 [5  6]  =  [−10  −12]
         [7  8]     [−14  −16]

A useful extension: 3A − 2B combines both operations, entry by entry.


3. Matrix Multiplication

Rule: the entry in row i, column j of AB is (row i of A) · (column j of B). Multiply matching entries, then add.

Size check first: (m×n)(n×p) gives an m×p result. The two inner numbers must match.

A = [1  2  3]  (2×3)      B = [ 7   8]  (3×2)
    [4  5  6]                 [ 9  10]
                              [11  12]

Inner numbers are 3 and 3, so AB is defined, and the result is 2×2.

Row 1 · Col 1:  1·7 + 2·9 + 3·11  =  7 + 18 + 33  =  58
Row 1 · Col 2:  1·8 + 2·10 + 3·12 =  8 + 20 + 36  =  64
Row 2 · Col 1:  4·7 + 5·9 + 6·11  = 28 + 45 + 66  = 139
Row 2 · Col 2:  4·8 + 5·10 + 6·12 = 32 + 50 + 72  = 154

AB = [ 58   64]
     [139  154]

Now try BA. This is (3×2)(2×3), which is also defined, but the result is 3×3:

BA = [39  54   69]
     [49  68   87]
     [59  82  105]

This is a powerful lesson: AB and BA can even have different sizes, so they cannot be equal.

Not allowed: (2×3)(2×3). The inner numbers are 3 and 2, which do not match.


4. Transpose

Rows become columns. Row 1 becomes column 1, row 2 becomes column 2, and so on.

C = [1  2  3]   (2×3)
    [4  5  6]

Cᵀ = [1  4]     (3×2)
     [2  5]
     [3  6]

Notice the size flips from 2×3 to 3×2. For a square matrix:

D = [1  2]     Dᵀ = [1  3]
    [3  4]          [2  4]

The diagonal stays fixed, and the other entries swap across it.

Rule to teach next: (AB)ᵀ = BᵀAᵀ. The order reverses, just like with inverses.

Saturday, October 24, 2026

JWT WITH SIMPLEJWT- djangorestframework-simplejwt

 

JWT with djangorestframework-simplejwt

What changes compared with the PyJWT version

Before (PyJWT)Now (simplejwt)
jwt_utils.py: you wrote create/decode by handDeleted. simplejwt builds and verifies tokens
authentication.py: you parsed the header, handled expiry and errorsA short subclass that only says how to find your User
One access tokenAccess token and refresh token, with a ready-made expiry system
Manual expiry settingSIMPLE_JWT settings dict

Roles, permissions, serializers and ViewSets stay almost the same.

Why a subclass is needed: simplejwt assumes Django’s user model (AUTH_USER_MODEL). Your User is a separate table (UserId, Username, Password, RoleId), and your project already has migrations and an admin superuser, so switching AUTH_USER_MODEL now would break migrations. The subclass keeps your model and still uses simplejwt for everything else.

Step 0: Install

bash
pip install djangorestframework-simplejwt

You can remove PyJWT usage. simplejwt installs its own dependency.

Step 1: Models (models.py, unchanged)

What: Role and User. Why: each user has one role, and passwords are stored only as hashes.

python
from django.contrib.auth.hashers import make_password, check_password

class Role(models.Model):
    RoleId = models.AutoField(primary_key=True)
    RoleName = models.CharField(max_length=30, unique=True)   # Admin, Manager, Employee

    def __str__(self):
        return self.RoleName


class User(models.Model):
    UserId = models.AutoField(primary_key=True)
    Username = models.CharField(max_length=50, unique=True)
    Password = models.CharField(max_length=255)               # stores the HASH
    RoleId = models.ForeignKey(Role, on_delete=models.PROTECT)

    def set_password(self, raw_password):
        self.Password = make_password(raw_password)

    def check_password(self, raw_password):
        return check_password(raw_password, self.Password)

    # DRF checks request.user.is_authenticated, so our custom model must provide it
    @property
    def is_authenticated(self):
        return True

    def __str__(self):
        return self.Username
bash
python manage.py makemigrations
python manage.py migrate

If you already did this earlier, skip it.

Step 2: Create roles and the first admin (shell, unchanged)

python
from employees.models import Role, User

admin_role = Role.objects.create(RoleName='Admin')
Role.objects.create(RoleName='Manager')
Role.objects.create(RoleName='Employee')

u = User(Username='admin', RoleId=admin_role)
u.set_password('Admin@123')      # hashes the password
u.save()

Step 3: Settings (settings.py)

What: Tell simplejwt how long tokens live and which field identifies your user.
Why: USER_ID_FIELD defaults to id, but your primary key is UserId. Without this, token creation fails with an AttributeError.

python
from datetime import timedelta

REST_FRAMEWORK = {
    # Every request is checked by our subclass (Step 4)
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'employees.authentication.CustomJWTAuthentication',
    ],
    # Secure by default: login is required unless an endpoint opts out
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),   # short-lived: used on every request
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),      # long-lived: only used to get a new access token
    'USER_ID_FIELD': 'UserId',                        # your primary key column name
    'USER_ID_CLAIM': 'user_id',                       # name stored inside the token
    'AUTH_HEADER_TYPES': ('Bearer',),                 # Authorization: Bearer <token>
}

simplejwt signs tokens with your SECRET_KEY by default. You don’t need to add it to INSTALLED_APPS.

Step 4: Authentication class (employees/authentication.py)

What: Override one method, get_user.
Why: simplejwt already reads the header, checks the signature, and checks expiry. The only part it can’t know is where your users live.

python
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import InvalidToken, AuthenticationFailed
from rest_framework_simplejwt.settings import api_settings

from .models import User


class CustomJWTAuthentication(JWTAuthentication):

    # Called after the token is verified. Must return the user object,
    # which DRF puts in request.user.
    def get_user(self, validated_token):
        try:
            user_id = validated_token[api_settings.USER_ID_CLAIM]
        except KeyError:
            raise InvalidToken('Token has no user identification.')

        try:
            # select_related loads the role in the same query
            # (a deleted user or changed role takes effect immediately)
            return User.objects.select_related('RoleId').get(pk=user_id)
        except User.DoesNotExist:
            raise AuthenticationFailed('User not found.', code='user_not_found')

Compare with the 40-line PyJWT version. Header parsing, 401 responses, and expired/invalid token messages are all inherited.

Step 5: Role-based permissions (employees/permissions.py, unchanged)

What: Permission classes that check the user’s role name.
Why: simplejwt handles authentication (who are you). Roles are your own rule, so authorization stays custom.

python
from rest_framework.permissions import BasePermission, IsAuthenticated


def role_required(*allowed_roles):
    """Builds a permission class that allows only the given role names."""

    class RolePermission(BasePermission):
        message = f'Access denied. Required role: {", ".join(allowed_roles)}.'

        def has_permission(self, request, view):
            user = request.user
            return bool(
                user
                and user.is_authenticated
                and user.RoleId.RoleName in allowed_roles
            )

    return RolePermission


# Any logged-in user can READ; only listed roles can WRITE
class RoleBasedWriteMixin:
    write_roles = ('Admin',)             # override in each ViewSet

    def get_permissions(self):
        if self.action in ('list', 'retrieve'):
            return [IsAuthenticated()]
        return [role_required(*self.write_roles)()]

Step 6: Serializers (serializers.py, unchanged)

python
from .models import User

class LoginSerializer(serializers.Serializer):
    Username = serializers.CharField()
    Password = serializers.CharField(write_only=True)    # accepted, never returned


class RegisterSerializer(serializers.ModelSerializer):
    Password = serializers.CharField(write_only=True, min_length=6)

    class Meta:
        model = User
        fields = ['UserId', 'Username', 'Password', 'RoleId']

    def create(self, validated_data):
        raw_password = validated_data.pop('Password')
        user = User(**validated_data)
        user.set_password(raw_password)      # hash, never save the raw value
        user.save()
        return user

Step 7: Views (views.py)

Imports:

python
from rest_framework import viewsets, status, filters
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.exceptions import TokenError, InvalidToken
from rest_framework_simplejwt.settings import api_settings

from .models import Department, Employee, Project, User
from .serializers import (
    DepartmentSerializer, EmployeeSerializer, ProjectSerializer,
    LoginSerializer, RegisterSerializer,
)
from .permissions import role_required, RoleBasedWriteMixin

Login (public)

What: Check the password, then let simplejwt create the tokens.
Why: RefreshToken.for_user(user) reads user.UserId (from USER_ID_FIELD) and builds a signed token. Claims you add to the refresh token are copied into the access token, so the client can read the role without an extra call.

python
class LoginView(APIView):
    authentication_classes = []          # nobody has a token yet
    permission_classes = [AllowAny]      # public endpoint

    def post(self, request):
        serializer = LoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        username = serializer.validated_data['Username']
        password = serializer.validated_data['Password']

        try:
            user = User.objects.select_related('RoleId').get(Username=username)
        except User.DoesNotExist:
            user = None

        # Same message for "no such user" and "wrong password",
        # so attackers can't discover which usernames exist.
        if user is None or not user.check_password(password):
            return Response({'detail': 'Invalid username or password.'},
                            status=status.HTTP_401_UNAUTHORIZED)

        refresh = RefreshToken.for_user(user)       # creates the token pair
        # Custom claims (readable by anyone, so never put secrets here)
        refresh['username'] = user.Username
        refresh['role_id'] = user.RoleId.RoleId
        refresh['role_name'] = user.RoleId.RoleName

        return Response({
            'roleid': user.RoleId.RoleId,
            'username': user.Username,
            'token': str(refresh.access_token),     # send this with every request
            'refresh': str(refresh),                # keep this to get a new access token
        })

Refresh (public)

What: Exchange a refresh token for a new access token.
Why: The access token expires quickly (60 minutes). This lets the user stay logged in without typing the password again.

simplejwt’s built-in TokenRefreshView looks the user up in Django’s user table, which doesn’t contain your users. So this small view replaces it.

python
class RefreshView(APIView):
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        try:
            refresh = RefreshToken(request.data.get('refresh'))   # verifies signature and expiry
        except TokenError as e:
            raise InvalidToken(e.args[0])

        # Make sure the user still exists
        if not User.objects.filter(pk=refresh[api_settings.USER_ID_CLAIM]).exists():
            return Response({'detail': 'User not found.'},
                            status=status.HTTP_401_UNAUTHORIZED)

        return Response({'token': str(refresh.access_token)})

Register (Admin only)

python
class RegisterView(APIView):
    # Without this, anyone could create an Admin account
    permission_classes = [role_required('Admin')]

    def post(self, request):
        serializer = RegisterSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)

Protect your ViewSets

python
class DepartmentViewSet(RoleBasedWriteMixin, viewsets.ModelViewSet):
    write_roles = ('Admin',)
    queryset = Department.objects.all()
    serializer_class = DepartmentSerializer


class EmployeeViewSet(RoleBasedWriteMixin, viewsets.ModelViewSet):
    write_roles = ('Admin', 'Manager')
    queryset = Employee.objects.select_related('DepartmentId').prefetch_related('projects')
    serializer_class = EmployeeSerializer
    filter_backends = [filters.SearchFilter]
    search_fields = ['Contact', 'Designation']


class ProjectViewSet(RoleBasedWriteMixin, viewsets.ModelViewSet):
    write_roles = ('Admin', 'Manager')
    queryset = Project.objects.prefetch_related('Employees')
    serializer_class = ProjectSerializer

Step 8: URLs (employees/urls.py)

python
from .views import (DepartmentViewSet, EmployeeViewSet, ProjectViewSet,
                    LoginView, RefreshView, RegisterView)

urlpatterns = [
    path('login/', LoginView.as_view(), name='login'),
    path('token/refresh/', RefreshView.as_view(), name='token_refresh'),
    path('register/', RegisterView.as_view(), name='register'),
    path('', include(router.urls)),     # keep the router include last
]

Step 9: Test in Postman

1. Login: POST http://127.0.0.1:8000/api/login/

json
{"Username": "admin", "Password": "Admin@123"}

Response:

json
{
  "roleid": 1,
  "username": "admin",
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "refresh": "eyJhbGciOiJIUzI1NiIs..."
}

2. Call a protected API: Authorization tab, type Bearer Token, paste token.

3. Refresh: POST /api/token/refresh/ with {"refresh": "<refresh value>"} returns a new token.

RequestResult
GET /api/employees/ without a token401
GET /api/employees/ with an expired or bad token401 with a message
GET /api/employees/?search=dev as Employee role200
POST /api/employees/ as Employee role403
POST /api/employees/ as Manager or Admin201
DELETE /api/departments/1/ as Manager403
POST /api/register/ as non-Admin403
POST /api/login/ with a wrong password401

Tip: paste the access token at jwt.io to see user_id, username, role_id, role_name, exp. It also proves the payload is readable, so never put passwords in it.

Creating users directly in MySQL FOR JWT

 

Creating users directly in MySQL

Yes, it works, with one catch: the Password column must contain a Django password hash, not the plain password. Your check_password() compares the typed password against a hash. If you insert Admin@123 as plain text, login always fails with “Invalid username or password.”

Step 1: Generate a hash

Run this in the Django shell, which only creates the hash and saves nothing:

bash
python manage.py shell
python
from django.contrib.auth.hashers import make_password
print(make_password('Admin@123'))

The output looks like this (yours will differ, because each hash has a random salt):

pbkdf2_sha256$870000$k3J9xQ2mP1aB$Zr8f0...long-string...=

Copy the whole line.

Step 2: Check the table and column names

sql
SHOW TABLES;
DESCRIBE employees_user;

Django names tables <app>_<model>, so yours should be employees_role and employees_user. The foreign key column is RoleId_id, because your field is called RoleId and Django adds _id.

Step 3: Insert the data

Roles first, because users reference them:

sql
INSERT INTO employees_role (RoleName) VALUES ('Admin'), ('Manager'), ('Employee');
SELECT * FROM employees_role;     -- note the RoleId values (usually 1, 2, 3)

Then the user, pasting your own hash:

sql
INSERT INTO employees_user (Username, Password, RoleId_id)
VALUES ('admin',
        'pbkdf2_sha256$870000$k3J9xQ2mP1aB$Zr8f0...paste-your-full-hash...=',
        1);                       -- 1 = the RoleId of Admin

Keep the hash inside single quotes. It contains $, which is fine in MySQL but can be mangled if you paste it into a bash command line.

Step 4: Test login

POST http://127.0.0.1:8000/api/login/
{"Username": "admin", "Password": "Admin@123"}

You should get roleid, username, token and refresh.

Common problems

SymptomCause
Always “Invalid username or password”Plain text stored instead of a hash, or the hash was cut off when pasting
Cannot add or update a child row: a foreign key constraint failsThe RoleId_id value doesn’t exist in employees_role
Unknown column 'RoleId'Use RoleId_id in the users table
Table doesn't existRun SHOW TABLES; and use the real names
Duplicate entryThe username is already taken (Username is unique)

Notes

  1. One hash, many users is fine. If several test users should share a password, reuse the same hash. Each real user should have their own.
  2. Don’t store plain text and “fix it later.” Passwords stored as plain text are a real security risk, and your login will never accept them anyway.
  3. Use SQL only for seeding test data. For normal user creation, use the /api/register/ endpoint (Admin only), which hashes the password for you.
  4. Don’t create users in the Django admin unless you register a custom form. The default admin form would save the typed password as plain text, which fails login for the same reason.

Wednesday, October 21, 2026

JAVA INSTALLED OR NOT

 We can do this step by step on your Windows computer:

  1. Check whether Java is installed.

  2. If Java is missing or the version is not Java 21, install JDK 21 (LTS).

  3. Configure JAVA_HOME and the Windows PATH.

  4. Install Spring Tools (STS), the Eclipse-based IDE for Spring Boot development.

  5. Verify Java and STS with a simple Spring Boot project.

Since your earlier PowerShell output showed that java was not recognized, Java might be missing or its PATH might not be configured correctly. Let's verify before installing anything.

Step 1 — Check Java in Windows PowerShell

Open PowerShell and execute these commands one by one.


# 1. Check Java version
java -version

# 2. Check Java compiler
javac -version

# 3. Check JAVA_HOME
$env:JAVA_HOME

# 4. Check where Java is located
where.exe java

If java -version returns 21.x.x, Java 21 is installed and accessible. If it says not recognized, Java may be missing or not configured in PATH.

Step 2 — Install Java 21 if required

Microsoft Build of OpenJDK

JDK 21 — Windows x64

Download from the official Microsoft OpenJDK page.

Choose the Windows x64 .msi installer for a typical Intel/AMD Windows PC.

Download JDK 21

Microsoft Learn

  1. Open the download page and locate OpenJDK 21.

  2. Download the Windows x64 .msi installer.

  3. Run the installer and follow the setup wizard.

  4. Enable the option to configure Java environment variables if the installer offers it.

  5. Close PowerShell and open a new PowerShell window.

  6. Verify:

java -version
javac -version

Both commands should report version 21.

Configure JAVA_HOME if necessary

If Java is installed but JAVA_HOME is empty, first locate the actual JDK installation folder. A common path is:

C:\Program Files\Microsoft\jdk-21...

Set JAVA_HOME to your actual JDK folder using Windows Search → Edit the system environment variables → Environment Variables. Add %JAVA_HOME%\bin to Path if it is missing. Reopen PowerShell and test again.

Step 3 — Install Spring Tools (STS)

How to use Spring Tool Suite?. Download the Spring Tool Suite from… | by Ekta Dhobley | Medium

Spring Tools for Eclipse

The IDE for creating Java and Spring Boot applications.

Open the official Spring Tools download page

Tools

  1. Open the official download page.

  2. Select Spring Tools for Eclipse — Windows x86_64.

  3. Download the latest stable release.

  4. Follow the installation or extraction instructions provided for that release.

  5. Launch Spring Tools and select a workspace folder, for example:

D:\SpringBootWorkspace

6. If STS asks for a Java installation, select your JDK 21 folder.

Step 4 — Verify STS

Inside STS, open Window → Preferences → Java → Installed JREs (the menu may vary slightly by release).

Confirm that JDK 21 is selected. Then create a test project using File → New → Spring Starter Project.

For a beginner Spring Boot application, you can select:

  • Project: Maven

  • Language: Java

  • Java version: 21

  • Dependencies: Spring Web

Saturday, October 17, 2026

Unix Pipes and grep

 

Unix  Pipes and grep

The objective is to use pipes (|), grep, find, wc, uniq, and output redirection to solve the exercises and save both the commands and their outputs in lab5_q2.txt.

(a) What does who | grep A20\* do?

who | grep A20\*
  • who displays users currently logged in.

  • | sends the output of who to grep.

  • grep A20\* searches for lines matching the pattern A20*.

Important: In basic grep regular expressions, \* means zero or more occurrences of the preceding character, 0. Therefore, this pattern matches A2 followed by zero or more zeros, such as A2, A20, or A200. It does not mean “any text starting with A20.”

If the intention is to find lines containing text beginning with A20, use:

who | grep 'A20'

If the intention is to match a username beginning with A20, use:

who | grep '^A20'

(b) Count files under /usr/include whose names start with l, contain a number, and end with .h

find /usr/include -type f -name 'l*[0-9]*.h' | wc -l

Explanation:

  • find /usr/include searches recursively.

  • -type f selects regular files.

  • -name 'l*[0-9]*.h' matches filenames beginning with l, containing at least one digit, and ending with .h.

  • wc -l counts the matching file paths.

(c) Find French F1 drivers from 2017 and 2018, using uniq

Assuming each line contains the driver's nationality and the driver name appears first, try:

grep -i 'French' /home/lab_files/F1_drivers_2017 /home/lab_files/F1_drivers_2018 | sort | uniq

This searches both files, selects lines containing French, sorts the results, and removes identical duplicate lines.

Note: uniq removes duplicate lines, not duplicate driver names appearing in different team/year records. If the same driver has different records in 2017 and 2018, the file format determines how to extract the driver name alone. You can inspect the data using:

head /home/lab_files/F1_drivers_2017

Save the commands and their outputs in lab5_q2.txt

Run this block in your terminal. It records each command and its output in the requested file.

{
    echo "Part (a): who | grep A20\*"
    who | grep A20\*

    echo
    echo "Part (b): find /usr/include -type f -name 'l*[0-9]*.h' | wc -l"
    find /usr/include -type f -name 'l*[0-9]*.h' | wc -l

    echo
    echo "Part (c): French F1 drivers"
    grep -i 'French' /home/lab_files/F1_drivers_2017 /home/lab_files/F1_drivers_2018 | sort | uniq
} > lab5_q2.txt 2>&1

Verify the saved file:

cat lab5_q2.txt

The output will depend on the files and logged-in users on your lab machine. The block above records the commands' results, including error messages, but does not print the command text automatically beyond the headings. If your instructor requires the literal commands as well as their outputs, use this version instead:

{
    echo 'Command: who | grep A20\*'
    who | grep A20\*

    echo 'Command: find /usr/include -type f -name "l*[0-9]*.h" | wc -l'
    find /usr/include -type f -name 'l*[0-9]*.h' | wc -l

    echo "Command: grep -i 'French' /home/lab_files/F1_drivers_2017 /home/lab_files/F1_drivers_2018 | sort | uniq"
    grep -i 'French' /home/lab_files/F1_drivers_2017 /home/lab_files/F1_drivers_2018 | sort | uniq
} > lab5_q2.txt 2>&1

For the shortest command in part (c): the pipeline above is concise, but to ensure each driver appears only once even when their team details differ, I would need to see the first few lines of the two F1 files to identify the correct driver-name column.

is it 1 or l? find /usr/include -type f -name 'l*[0-9]*.h' | wc -l

It is a lowercase letter l, not the number 1.

find /usr/include -type f -name 'l*[0-9]*.h' | wc -l

In the pattern 'l*[0-9]*.h':

  • l — lowercase letter l, as in lion.

  • * — any sequence of characters (including none).

  • [0-9] — any single digit from 0 to 9.

  • *.h — any sequence of characters followed by .h.

So the filename must start with lowercase l, contain at least one digit, and end with .h.

1. Processes, Pipes, Grep and Email

(a) List all running processes in full format

ps -ef

Explanation:

  • ps — displays process information.

  • -e — selects all processes.

  • -f — displays the full-format listing, including user, PID, parent PID, start time, and command.

(b) Display only the SSH processes using a pipe and text filter

ps -ef | grep 'ssh'

Explanation:

  • ps -ef — lists all processes in full format.

  • | (pipe) — passes the output of ps -ef to the next command.

  • grep 'ssh' — displays only lines containing the text ssh.

Note: This command may also display the grep 'ssh' command itself. To exclude that line, you can use:

ps -ef | grep '[s]sh'

(c) Redirect the SSH process listing to a file

Replace studentID with your actual student ID.

ps -ef | grep '[s]sh' > ssh_list_studentID.txt

For example, if your student ID is student123:

ps -ef | grep '[s]sh' > ssh_list_student123.txt

Explanation:

  • > — redirects the command output to a file.

  • If the file does not exist, it is created.

  • If the file already exists, its previous contents are overwritten.

Summary

Question

Command

(a) All processes in full format

ps -ef

(b) Only SSH-related processes

ps -ef \| grep '[s]sh'

(c) Save results to a file

ps -ef \| grep '[s]sh' > ssh_list_studentID.txt

Important: The filter matches any command line containing ssh, including related commands such as sshd.

(a) Copy mid_semester_revision.txt to your home folder

Run this command in Terminal 1:

cp /home/lab_files/mid_semester_revision.txt ~/

Explanation:

  • cp — copies a file.

  • /home/lab_files/mid_semester_revision.txt — source file path.

  • ~/ — your home directory.

Verify that the file was copied:

ls -l ~/mid_semester_revision.txt

(b) Open a second terminal and answer the questions using VI

Terminal 1

  1. Open the copied file in the VI editor:

    vi ~/mid_semester_revision.txt
  2. Read the questions in the file.

  3. Keep this terminal open while you work.

Terminal 2

  1. Open a second terminal window or tab.

  2. Log in with your student account if required by your lab environment.

  3. Test Linux commands here before using them in Terminal 1.

For example, test:

ps -ef
who
grep --help

Use the appropriate commands for the questions in the revision file.

Useful VI commands

Key/command

Purpose

i

Enter insert mode to type answers

Esc

Return to command mode

:w

Save changes

:wq

Save and exit

:q!

Exit without saving

Important: You should edit the copied file in your home folder, not the original file in /home/lab_files.