Bash Scripting Series — Level 4

Bash Scripting Series — Level 4: Checking Running Processes and Finding PIDs

Introduction

In Level 3, we created a Bash script that validates a directory and generates a compressed backup. We used conditional statements, loops, variables, and exit-status handling to make the script more reliable.

In Level 4, we move from filesystem operations to process management.

Linux runs many processes simultaneously. These include system services, terminal commands, background jobs, applications, and security tools. Sometimes, you need to determine whether a particular process is running and identify its Process ID (PID).

For example, you might want to check whether a monitoring tool is running, verify that a background task started successfully, or locate a process for troubleshooting.

In this lesson, we will examine a Bash script that searches for a process by name, displays its PID or PIDs, and reports when no matching process is found.

1. The Original Script

Here is the script we will analyze:

#!/bin/bash

# Script to check if a process is running and print its PID(s)

# Ask for a process name
read -p "Enter a process name: " PROCESS_NAME

# Use the first command-line argument if input is empty
if [ -z "$PROCESS_NAME" ]; then
    PROCESS_NAME="$1"
fi

# Continue prompting until a name is supplied
while [ -z "$PROCESS_NAME" ]; do
    echo "Invalid input, try again."
    read -p "Enter a process name: " PROCESS_NAME
done

set -- "$PROCESS_NAME"
PROCESS_NAME="$1"

# Search for the process
PIDS=$(pgrep -x "$PROCESS_NAME")

# Display the results
if [ -n "$PIDS" ]; then
    echo "The process '$PROCESS_NAME' is running successfully."
    echo "The current PIDs are: $PIDS"
else
    echo "Warning! The process '$PROCESS_NAME' is not running."
fi

This version corrects the spacing error in the original conditional statement and fixes the spelling in its messages. We will also examine a few redundant parts of the code.

The intended workflow is:

  1. Obtain a process name.

  2. Reject empty input.

  3. Search for matching processes.

  4. Display their PIDs if any are found.

  5. Print a warning when no match is found.

2. Understanding Processes and PIDs

Before examining the commands, we need to understand two Linux concepts.

Process: A running instance of a program. For example, executing sleep 300 starts a process that sleeps for 300 seconds.

Process ID (PID): A numeric identifier assigned to a process by the operating system.

A computer can run multiple instances of the same program. Consequently, a process name can match more than one PID.

For example, several sleep processes might run simultaneously:

PID      Process
2410     sleep
2485     sleep
2512     sleep

The numbers above are illustrative. Your actual PIDs will differ.

A process is not necessarily the same thing as a service. A service describes a system function or application that may be managed in the background, often through systemd. Its implementation may involve one or more processes.

This lesson focuses on locating processes by their names.

3. Collecting the Process Name

The first important command is:

read -p "Enter a process name: " PROCESS_NAME

The read command accepts input from the user, while -p displays a prompt before reading it.

The variable PROCESS_NAME stores the supplied value.

For example, if you enter:

sshd

The variable contains the string sshd.

The script then checks whether the variable is empty:

if [ -z "$PROCESS_NAME" ]; then
    PROCESS_NAME="$1"
fi

The -z test returns true when the string has zero length. If the user presses Enter without entering a name, the script assigns the first command-line argument, $1, to PROCESS_NAME.

However, there is a design issue here: the script prompts for input before checking the command-line argument. Even when you launch it with an argument, it may still wait for keyboard input.

A cleaner design will check for an argument first and only prompt when no argument is supplied. We will implement this in Section 8.

The Input Loop

Next, the script uses:

while [ -z "$PROCESS_NAME" ]; do
    echo "Invalid input, try again."
    read -p "Enter a process name: " PROCESS_NAME
done

A while loop repeats its commands for as long as its condition is true.

In this case, the loop continues prompting while PROCESS_NAME is empty. When the user enters a non-empty value, the condition becomes false and the loop ends.

This prevents the script from searching for an empty process name.

The improved version will use read -r so that backslashes in input are treated literally.

4. Understanding Positional Parameters and set --

The original script contains these two lines:

set -- "$PROCESS_NAME"
PROCESS_NAME="$1"

Let's examine what they do.

In Bash, $1 refers to the first positional parameter, typically the first argument supplied when running the script. $2 refers to the second, and $# gives the number of positional parameters.

The set -- command replaces the current positional parameters with the arguments that follow --.

For example:

set -- "sshd"

After this command, $1 contains sshd, and $# becomes 1.

The next line assigns $1 back to PROCESS_NAME, which already contains the same value.

Therefore, these two lines are unnecessary for the script's intended purpose. They also discard the script's existing positional parameters, which can be problematic if the script needs to process additional arguments later.

We will remove them from the improved version.

5. Searching for Processes with pgrep

The central command is:

PIDS=$(pgrep -x "$PROCESS_NAME")

pgrep searches for processes matching a supplied pattern and prints the PIDs of matching processes.

The options and syntax are important:

  • pgrep searches for matching processes.

  • -x requires an exact match to the process name.

  • "$PROCESS_NAME" supplies the name to search for.

  • $(...) captures the command's standard output.

  • PIDS= stores that output in a variable.

Suppose you enter sleep and two matching processes are running. The command might produce:

2410
2485

The variable PIDS will contain both numbers, separated by a newline.

If no matching process is found, pgrep produces no PID output, so PIDS will normally be empty.

Why Use -x?

Consider the following processes:

sleep
sleep-helper

A search without exact matching may match names according to the supplied pattern. Using pgrep -x sleep asks for an exact process-name match instead of a partial name match.

One limitation is that pgrep -x matches the process name exposed by the operating system, not necessarily the full command or the name of a service. On Linux, the process name used for this matching is also subject to length limitations.

If you need to inspect full command lines, options such as pgrep -af can provide more context, but they use different matching behavior and should not automatically replace an exact-name search.

6. Checking Whether a Match Was Found

After searching, the script evaluates:

if [ -n "$PIDS" ]; then
    echo "The process '$PROCESS_NAME' is running successfully."
    echo "The current PIDs are: $PIDS"
else
    echo "Warning! The process '$PROCESS_NAME' is not running."
fi

The -n test checks whether a string is non-empty.

The condition evaluates as follows:

  • If PIDS contains one or more PID values, the script prints a confirmation and the results.

  • If PIDS is empty, the script prints a warning.

The space before the closing ] is required. The original line:

if [ -n "$PIDS"]; then

is incorrectly formatted because it is missing that space. Bash treats [ as a command, so its arguments must be separated correctly.

An Important Limitation

Checking whether PIDS is non-empty works for the original script's basic purpose, but it does not distinguish between every possible pgrep outcome.

For example, pgrep can return an error rather than a normal no-match result. In a more robust script, we should inspect the command's exit status so that a genuine search error is not reported as though the process simply were not running.

We will address this in the improved version.

7. How to Run and Test the Script

These steps work on typical Linux distributions, including Ubuntu and Kali Linux.

Step 1: Create the File

Open the terminal and run:

nano process_check.sh

Paste the original script into Nano, then save with Ctrl + O, press Enter, and exit with Ctrl + X.

Step 2: Make It Executable

chmod +x process_check.sh

Step 3: Run the Script

./process_check.sh

Enter a process name when prompted.

You can start with a process that is likely to be running on your system, such as systemd on a typical systemd-based Linux installation. Availability varies by environment, so the following controlled test is more reliable.

Step 4: Create a Test Process

Open a second terminal and run:

sleep 300

This starts a process that remains active for up to 300 seconds.

While it is running, open another terminal and execute:

./process_check.sh

Enter:

sleep

You should see output similar to:

The process 'sleep' is running successfully.
The current PIDs are: 2410

The PID is only an example; your system will display its own value.

To test the other outcome, enter a process name that does not match any running process, such as:

process_that_does_not_exist

The script should display its warning.

When you finish testing, return to the terminal running sleep 300 and press Ctrl + C to stop that test process.

Testing with pgrep Directly

You can also test the underlying command without running your script:

pgrep -x sleep

This prints the PIDs of matching processes.

To inspect the process names and PIDs in a more readable format, use:

ps -eo pid,comm

This displays a process listing containing each process's PID and command name.

8. An Improved Version of the Script

The following version addresses the main problems in the original:

  • It accepts a process name as a command-line argument.

  • It prompts interactively only when no argument is supplied.

  • It rejects empty input.

  • It avoids the unnecessary set -- operation.

  • It correctly handles multiple matching PIDs.

  • It distinguishes between no matches and an error reported by pgrep.

  • It returns meaningful exit statuses.

#!/bin/bash

# Check whether a process name was supplied as an argument
if [ "$#" -gt 1 ]; then
    echo "Usage: $0 [process_name]"
    exit 2
fi

# Use the argument or prompt the user
if [ "$#" -eq 1 ]; then
    PROCESS_NAME="$1"
else
    read -r -p "Enter a process name: " PROCESS_NAME
fi

# Keep asking while the input is empty
while [ -z "$PROCESS_NAME" ]; do
    echo "Error: Process name cannot be empty."
    read -r -p "Enter a process name: " PROCESS_NAME
done

# Search for exact process-name matches
if PIDS=$(pgrep -x -- "$PROCESS_NAME"); then
    echo "Process '$PROCESS_NAME' is running."
    echo "PID(s):"
    printf '%s\n' "$PIDS"
    exit 0
else
    STATUS=$?

    if [ "$STATUS" -eq 1 ]; then
        echo "Warning: Process '$PROCESS_NAME' is not running."
        exit 1
    else
        echo "Error: Unable to search for process '$PROCESS_NAME'."
        exit "$STATUS"
    fi
fi

Understanding the Improvements

Command-line argument handling

if [ "$#" -gt 1 ]; then

The script rejects extra arguments. It then checks whether exactly one was supplied. If so, it uses $1; otherwise, it asks the user for input.

Examples:

./process_check.sh sleep
./process_check.sh

The first command searches immediately, while the second displays a prompt.

Capturing the command's result

if PIDS=$(pgrep -x -- "$PROCESS_NAME"); then

Bash checks the exit status of pgrep directly. The -- marks the end of command options, helping ensure that input beginning with a hyphen is not interpreted as an option.

Distinguishing failure from no matches

STATUS=$?

This captures the exit status from the failed conditional command. For pgrep, status 1 means that no processes matched, while other non-zero statuses can indicate an error.

The script therefore displays an appropriate message rather than treating every unsuccessful search as an ordinary absence.

Displaying multiple PIDs

printf '%s\n' "$PIDS"

This prints the captured PID list with each value on its own line. Quoting the variable preserves the embedded newlines.

9. When and Where Is Process Checking Useful?

Process validation appears frequently in system administration, troubleshooting, and automation.

Monitoring scripts: A script can check whether a particular monitoring agent is running before performing additional checks.

Background tasks: An administrator can verify whether a long-running job remains active.

Troubleshooting: A process lookup can help identify the PID of an application that appears to be running incorrectly.

Security operations: During authorized testing or defensive investigations, process information can help analysts understand which tools or applications are active on a system.

Automation: A script can check for a process before launching another task, although a simple check alone cannot guarantee that the process will remain running afterward.

For service management, tools such as systemctl are usually more suitable when the question is whether a named systemd service is active. pgrep is useful when you specifically need to search for processes by name.

10. Common Mistakes

Forgetting spaces inside test brackets

Incorrect:

if [ -n "$PIDS"]; then

Correct:

if [ -n "$PIDS" ]; then

Assuming every process name uniquely identifies one process

Several processes can have the same name, so pgrep may return multiple PIDs. Your script should be able to display all the results.

Confusing process names with full commands

The name reported by pgrep -x may not include command-line arguments or the full executable path. Use process-listing tools when more detail is needed.

Ignoring command errors

An empty result is not the same as every possible search failure. Checking pgrep's exit status makes the script more reliable.

Assuming a process will remain active

A process can terminate immediately after the search. A PID lookup provides a snapshot of matching processes, not a guarantee of continued execution.

11. Practical Exercises

Complete these exercises in your Linux virtual machine.

Exercise 1 — Basic process lookup: Run the script and search for sleep, using the test process from Section 7.

Exercise 2 — Multiple PIDs: Open two terminals and start sleep 300 in each. Run the script and verify that it displays both PIDs.

Exercise 3 — Command-line arguments: Modify the script so that it accepts a process name from $1 and prints a usage message when too many arguments are provided.

Exercise 4 — Process details: Extend the script to show the PID and process name together using ps.

Exercise 5 — Exit codes: Make the script return 0 when a matching process exists, 1 when no match is found, and a different status for invalid usage or a search error.

Exercise 6 — Service versus process: Choose a service-managed application in your Linux environment. Compare the information provided by pgrep with the status shown by systemctl, where applicable.

Conclusion

In Level 4, we learned how to search for running processes using pgrep, capture process IDs in a variable, validate user input, and display results with conditional statements.

We also explored positional parameters, loops, exit statuses, and the difference between a process lookup and a service-status check.

These techniques provide a foundation for scripts that monitor applications, troubleshoot Linux systems, and support authorized security operations.

Next step: Expand this script to monitor a process repeatedly at a chosen interval, record its status in a log file, and report when the process disappears. That will introduce additional concepts, including loops, delays, logging, and continuous monitoring.

Comments

Popular posts from this blog

Information disclosure in version control history

Linux AAA

Source code disclosure via backup files