Exploring the /proc filesystem with Python and shell commands
/proc Talk
The Linux /proc virtual filesystem offers a window into a running system – look inside for information on processes and kernel activity.
The proc
filesystem [1] (procfs for short), is a Linux pseudo-filesystem that provides an interface to the operating system's kernel data structures. Procfs leverages the well-known concept of "in Unix, everything is a file" [2] to provide the same uniform interface of Unix file I/O (e.g., open, read, write, close, etc.) for getting kernel- and OS-related information. This uniformity makes it easier for the Linux programmer or system administrator to learn about the kernel, with fewer interfaces to learn.
Procfs is usually mounted at /proc
. Many kinds of information about the operating system (OS) and processes running in it are exposed via pseudo-files in procfs. By reading data from /proc
files, you can learn a lot of useful information about the system.
This article shows some ways of getting information from procfs using custom Python programs and Linux commands.
For comprehensive details about the information each pseudo-file in procfs provides, please refer to the man page for the /proc
filesystem [3]. I will focus on showing practical examples, along with a few fun shell features and tricks.
More on procfs
Procfs is automatically mounted by the kernel at boot time under the /proc
mountpoint.
The fragment (since Linux 3.3)
hidepid=n
specifies a mount option. The values for n
can be
, 1
, or 2
, with
being the most lenient, and 1
and 2
being progressively stricter modes of access with respect to security and privacy of information. Mode
is the default.
Many categories of files and directories reside under the /proc
hierarchy. Each category serves a different purpose.
Table 1 shows the categories of proc
files and what the kind of information each type provides. (In Table 1 and in the examples below, [pid]
is a placeholder for the process ID of the process for which you want information.)
Table 1
Some procfs File Categories
/proc/[pid]/cmdline |
Get the command line of a process |
/proc/[pid]/environ |
Get the environment of a process |
/proc/[pid]/status |
Get the status of a process |
/proc/meminfo |
Get the memory information of a computer |
Procfs provides two kinds of OS information: process-specific and non-process-specific, or general. The first three items in Table 1 are process-specific information, and the last item is an example of general OS information.
In the rest of this article, I reveal the details of the kind of information each of the categories in Table 1 contains, as well as some examples of what you can do with this information.
Get the Number of Numerically Named Directories
Procfs contains a numerically named subdirectory for each running process; the subdirectory is named by the process ID (PID):
/proc/[pid]
The number of numerically named directories (each representing a running process named by the process's PID) is an indication of the number of running processes:
$ ls -ld /proc/[0-9]* | wc -l 84
The command means: list (ls
) the files matching the given file name pattern, and count the number of lines in the output (wc -l
). Only the directory names (the d
in -ld
) are listed in long format (the l
in -ld
). Only files with names made up of just one or more digits ([0-9]*
) are counted.
Get the Command Line of a Process
Files of the form
/proc/[pid]/cmdline
hold the complete command line for the process. The components of the command line appear in this file as a sequence of strings, each terminated by a null byte (ASCII code 0, which is character \0
).
Figure 1 shows how to get the command line of a process with a Python script. The get_proc_cmdline.py
script is shown in Listing 1. The script takes a PID as input and outputs the command line for the process, as referenced in the /proc/[pid]/cmdline
file. As you can see in Listing 1, get_proc_cmdline.py
refers to proc_info.py
(Listing 2) and error_exit.py
(Listing 3).
Listing 1
get_proc_cmdline.py
01 # A program to get the command lines of processes, given their PIDs. 02 03 from __future__ import print_function 04 import sys 05 from proc_info import read_proc_cmdline 06 07 from error_exit import error_exit 08 09 def main(): 10 if len(sys.argv) < 2: 11 error_exit("{}: Error: Need at least one PID to process.\n".format(sys.argv[0])) 12 pids = sys.argv[1:] 13 print("Getting command lines for these PIDs:\n{}".format(' '.join(pids))) 14 for pid in pids: 15 proc_filename = "/proc/{}/cmdline".format(pid) 16 ok, result = read_proc_cmdline(proc_filename) 17 if ok: 18 sys.stdout.write("PID: {} Command line: {}\n".format(pid, result)) 19 else: 20 sys.stderr.write("PID: {} Error: {}\n".format(pid, result)) 21 22 if __name__ == '__main__': 23 main()
Listing 2
proc_info.py
01 # A module with functions to get information from files 02 # in the /proc pseudo file system for various purposes. 03 04 from __future__ import print_function 05 import sys 06 import string 07 import pwd 08 09 from error_exit import error_exit 10 11 def read_proc_cmdline(proc_filename): 12 """ 13 Function to read the command-line of a process, given its proc_filename. 14 Returns a tuple: first item of tuple is a boolean, True if successful, 15 False if not; second item of tuple is the result (cmdline) if first item 16 is True, or an error message if first item is False. 17 """ 18 19 try: 20 with open(proc_filename, 'r') as proc_fil: 21 # Read cmdline value. 22 data = proc_fil.read() 23 # Make it printable. 24 ret_val = (True, data.replace('\0', ' ')) 25 except IOError as ioe: 26 ret_val = (False, "IOError while opening proc file: {} ".format(str(ioe))) 27 except Exception as e: 28 ret_val = (False, "Exception: {}".format(str(xe))) 29 finally: 30 return ret_val 31 32 def read_proc_environ(proc_filename): 33 """ 34 Function to read the environment of a process, given its proc_filename. 35 Returns a tuple: first item of tuple is a boolean, True if successful, 36 False if not; second item of tuple is the result (environ) if first item 37 is True, or an error message if first item is False. 38 """ 39 try: 40 with open(proc_filename, 'r') as proc_fil: 41 # Read environ value. 42 data = proc_fil.read() 43 # Make it printable. 44 ret_val = (True, data.replace('\0', '\n')) 45 return ret_val 46 except IOError as ioe: 47 ret_val = (False, "IOError while opening proc file: {}".format(str(ioe))) 48 except Exception as e: 49 ret_val = (False, "Exception: {}".format(str(ioe))) 50 finally: 51 return ret_val 52 53 def read_proc_status(proc_filename): 54 55 """ 56 Function to read the status of a process, given its proc_filename. 57 Returns a tuple: first item of tuple is a boolean, True if successful, 58 False if not; second item of tuple is the result (status) if first item 59 is True, or an error message if first item is False. 60 """ 61 try: 62 with open(proc_filename, 'r') as proc_fil: 63 # Read desired status fields and values. 64 proc_status = {} 65 for lin in proc_fil: 66 if lin.startswith(('Name:', 'State:', 'Pid:', 'PPid:', 'Uid:', 'Gid:')): 67 parts = lin.split() 68 assert len(parts) > 1 69 proc_status[parts[0]] = parts[1] 70 assert 'Uid:' in proc_status 71 # Get username for uid. 72 uid = proc_status['Uid:'] 73 pwent = pwd.getpwuid(int(uid)) 74 proc_status["Username:"] = pwent.pw_name 75 ret_val = (True, proc_status) 76 except ValueError as ve: 77 ret_val = (False, "ValueError in read_proc_status(): {}".format(str(ve))) 78 except IOError as ioe: 79 ret_val = (False, "IOError in read_proc_status(): {}".format(str(ioe))) 80 except Exception as e: 81 ret_val = (False, "Exception in read_proc_status(): {}".format(str(ioe))) 82 finally: 83 return ret_val
Listing 3
error_exit.py
01 # error_exit.py 02 03 # Purpose: This module, error_exit.py, defines a function with 04 # the same name, error_exit(), which takes a string message 05 # as an argument. It prints the message to sys.stderr, or 06 # to another file object open for writing (if given as the 07 # second argument), and then exits the program. 08 # The function error_exit can be used when a fatal error condition occurs, 09 # and you therefore want to print an error message and exit your program. 10 11 import sys 12 13 def error_exit(message, dest=sys.stderr): 14 dest.write(message) 15 sys.exit(1) 16 17 def main(): 18 error_exit("Testing error_exit with dest sys.stderr (default).\n") 19 error_exit("Testing error_exit with dest sys.stdout.\n", 20 sys.stdout) 21 with open("temp1.txt", "w") as fil: 22 error_exit("Testing error_exit with dest temp1.txt.\n", fil) 23 24 if __name__ == "__main__": 25 main()
The next example uses echo_args.py
, a Python program that echoes its arguments to the standard output
To see get_proc_cmdline.py
at work, consider the example script in Listing 4, echo_args.py
, which is a test script that echoes the command-line arguments used to call the script to standard output, then sleeps for a while, giving the user some time to run another program that reads the command line of the process running echo_args.py
. Run echo_args.py
with arguments, using some combinations of backslashes and quoting to show how they are interpreted by the shell before the Python program receives them. A trailing ampersand &
after the command starts a new background process and outputs the process ID. For example,
$ python echo_args.py arg1 "arg 2" arg\ 3 'arg 4' "arg\ 5" & [1] 2943 $ echo_args.py|arg1|arg 2|arg 3|arg 4|arg\ 5
outputs the PID 2943.
Listing 4
echo_args.py
01 # echo_args.py 02 # Echoes the command-line arguments to the standard output. 03 04 from __future__ import print_function 05 06 import sys 07 import time 08 09 print("Arguments separated by | signs:") 10 print('|'.join(sys.argv)) 11 12 # Sleep for 10 minutes; any reasonable time will do. 13 # It just needs to be enough for us to go to another terminal 14 # and run the command to read this process's arguments via /proc. 15 time.sleep(600)
You can then use get_proc_cmdline.py
to get the command line for the process with PID 2943:
$ python get_proc_cmdline.py 2943 Getting command lines for these PIDs: 2943 PID: 2943 Command line: python echo_args.py arg1 arg 2 arg 3 arg 4 arg\ 5
Tip: The value of the $!
built-in shell variable is the PID of the last background process started, so if you are sure that the command python echo_args.py
is the last background process run on this terminal, you can use $!
instead of the literal value 2943
for the PID.
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Fedora Asahi Remix 41 Available for Apple Silicon
If you have an Apple Silicon Mac and you're hoping to install Fedora, you're in luck because the latest release supports the M1 and M2 chips.
-
Systemd Fixes Bug While Facing New Challenger in GNU Shepherd
The systemd developers have fixed a really nasty bug amid the release of the new GNU Shepherd init system.
-
AlmaLinux 10.0 Beta Released
The AlmaLinux OS Foundation has announced the availability of AlmaLinux 10.0 Beta ("Purple Lion") for all supported devices with significant changes.
-
Gnome 47.2 Now Available
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.