In this article, we will explore how to use the Python language as a tool to detect various operational information for Linux systems. Let's study together.
What kind of python?
When I mention Python, I usually refer to CPython 2 (2.7, to be exact). When the same code does not run in CPython3 (3.3), we explicitly point it out and give the alternative code, explaining the differences between them. Make sure you have installed CPython, enter Python or Python3 in the terminal and you will see a python prompt appear in your terminal.
Note that all scripts will have #!/usr/bin/env Python as the first line, meaning we want the Python parser to run the scripts. Therefore, if you use the chmod +x your-script.py command to add executable permissions to your script, you can use the./your-script.py command to run your script directly (you will see this in this article)
Explore the Platform Module
The Platform module in the standard library has a number of functions that allow us to examine various system information. Let's go. Open the Python Interpreter (translator: Enter Python directly on the command line) and explore some of these functions. Let's start with the Platform.uname () function:
>>> Import platform>>> platform.uname (' Linux ', ' fedora.echorand ', ' 3.7.4-204.fc18.x86_64 ', ' #1 SMP Wed Jan 16:44:29 UTC ', ' x86_64 ')
If you know the uname command on Linux, you will realize that this function is an interface to the uname command. In Python 2, this function returns a tuple consisting of the system type (or kernel type), hostname, version number, release number, host hardware architecture, and processor type. You can use an index to get a single property, like this:
>>> platform.uname () [0] ' Linux '
In Python 3, this function returns a default named tuple:
>>> platform.uname () uname_result (system= ' Linux ', node= ' Fedora.echorand ', release= ' 3.7.4-204.fc18.x86_64 ' , version= ' #1 SMP Wed Jan 16:44:29utc ', machine= ' x86_64 ', processor= ' x86_64 ')
Because the return value is a default-named tuple, we can easily get a single property from the variable name instead of remembering the subscript of each property, like this:
>>> platform.uname (). System ' Linux '
The Platfrom module also provides some direct interfaces to get the above attribute values, like these:
>>> Platform.system () ' Linux ' >>> platform.release () ' 3.7.4-204.fc18.x86_64 '
The function linx_distribution () returns the details of the Linux distribution you are using. For example, in a Fedora 18 system, this command returns the following information:
>>> platform.linux_distribution (' Fedora ', ' + ', ' spherical Cow ')
The return value is a tuple consisting of the release version name, version number, and codename. You can use the _supported_dists property to print which release versions are supported by the Python version you are using:
>>> platform._supported_dists (' SuSE ', ' Debian ', ' fedora ', ' redhat ', ' CentOS ', ' Mandrake ', ' Mandriva ', ' Rocks ' , ' Slackware ', ' yellowdog ', ' Gentoo ', ' unitedlinux ', ' turbolinux ')
If your Linux distribution is not one of those (or one of the derivative versions), then you will not see any useful information when you call the above function.
The last Platfrom function we want to explore is the architecture () function. When you call this function without adding any arguments, the function returns a tuple that consists of a bit schema and a python executable file format. Like what:
>>> platform.architecture (' 64bit ', ' ELF ')
In a 32-bit Linux system, you will see:
>>> platform.architecture (' 32bit ', ' ELF ')
If you specify any other system executable as a parameter, you will get a similar result:
>>> platform.architecture (executable= '/usr/bin/ls ') (' 64bit ', ' ELF ')
We encourage you to explore other functions in the Platfrom module that let you find the version of Python you are currently using. If you really want to know how this module gets this information, you can look at the lib/platfrom.py file in the Python source code directory.
The OS and SYS modules are also a useful module for acquiring the system properties, just like local byteorder. Next, we will not use Python, your standard library module, to explore some common ways to get information about Linux systems, this time through the proc and SYS file system. It is important to note that the information obtained through these file systems differs in different hardware architectures. Therefore, it should be remembered when reading this article and writing scripts to get system information from these files.
CPU Information
/proc/cpuinfo This file contains the processing unit information for your system. For example, here's a Python script that has the same functionality as entering Cat/proc/cpuinfo on the command line
#! /usr/bin/env python "" "Print out The/proc/cpuinfo file" "" from __future__ import print_function with open ('/proc/cpu Info ') as F: For line in F: print (Line.rstrip (' n '))
When you run the script in Python 2 or Python 3, you will see that all the contents of the/proc/cpuinfo file are displayed on your screen. (In the above script, the Rstrip () method removes the line breaks for each line)
The next code listing uses the Startwith () string method to display your computer's processing unit model
#! /usr/bin/env python "" "Print the model of your processing units" "" from __future__ import print_function with open (' /proc/cpuinfo ') as F: For line on F: # Ignore The blank line separating the information between # details Abou T-processing Units if Line.strip (): if Line.rstrip (' n '). StartsWith (' model name '): model_name = Line.rstrip (' n '). Split (': ') [1] print (model_name)
When you run this script, you will see the model of all the processing units of your machine. For example, here's what I saw on my computer:
Intel (R) Core (tm) i7-3520m CPU @ 2.90GHzIntel (r) Core (tm) i7-3520m CPU @ 2.90GHzIntel (r) Core (tm) i7-3520m CPU @ 2.90GHzIn Tel (R) Core (TM) i7-3520m CPU @ 2.90GHz
So far we've had several ways to get our computer system architecture. Technically, all of these methods actually present the architecture of the system kernel that you are running. So, if your computer is actually a 64-bit machine, but running a 32-bit kernel, the above method will show that your computer is a 32-bit architecture. To find out the correct schema for your computer, you can view the LM property of the list of properties in/proc/cpuinfo. The 1m attribute represents the long mode and will only appear on a computer with a 64-bit architecture. The following script shows you how to do it:
#! /usr/bin/env python "" "Find the Real Bit Architecture" "" from __future__ import print_function with open ('/proc/cpuinfo ') As F: For line on F: # Ignore The blank line separating the information between # details about the Processin G Units if Line.strip (): if Line.rstrip (' n '). StartsWith (' flags ') or Line.rstrip (' n '). StartsWith (' Features '): if ' lm ' in Line.rstrip (' n '). Split (): print (' 64-bit ') else: print (' 32-bit ')
As we have seen so far, we are able to access the/proc/cpuinfo file and use simple text processing techniques to read the information we are looking for. In order to provide data to other programs in a friendly way, the best approach might be to convert the content obtained from/proc/cpuinfo to a standard data organization, such as a dictionary type. The method is simple: If you look at this file, you will find that for each processing unit it is a key-value pair (in the previous example, the model name here is a key when we print the processor model name.) The information for each of the different processor units is separated by a blank line. This allows us to easily build a dictionary data structure with each processing unit's key. These keys (key) have a value, and each value corresponds to all the information in the/proc/cupinfo file for each processing unit. The next code listing shows you how to do it:
#!/usr/bin/env/python "" "/proc/cpuinfo as a python dict" "from __future__ Import print_functionfrom collections Import Or Dereddictimport pprint def cpuinfo (): "Return the information in/proc/cpuinfo as a dictionary in the following Format: cpu_info[' proc0 ']={...} cpu_info[' Proc1 ']={...} ' Cpuinfo=ordereddict () procinfo=ordereddict () nprocs = 0 with open ('/proc/cpuinfo ') as F: For line in F : if not Line.strip (): # End of one processor cpuinfo[' proc%s '% nprocs] = ProcInfo nprocs=nprocs+1< c14/># Reset procinfo=ordereddict () else: If Len (Line.split (': ')) = = 2: procinfo[line.split (': ') [0].strip ()] = Line.split (': ') [1].strip () Else: procinfo[line.split (': ') [0].strip ()] = ' return ' Cpuinfo if __name__== ' __main__ ': cpuinfo = Cpuinfo () for processor in Cpuinfo.keys (): print (cpuinfo[ processor][' model name '])
This code uses a ordereddict (ordered dictionary) instead of the usual dictionary type, in order to first sort the key value pairs found in the file before saving. Therefore, the first processing unit data is shown first, followed by the second one, and so on. If you call this function, it will return a dictionary type to you. Every key in a dictionary is a processing unit. You can then use the key to filter the information you are looking for (as shown in the If __name= ' __main__ ' statement block). When the above script runs, it prints out the model name of each processing unit (shown by the print (cpuinfo[processor][' model name ') statement)
Intel (R) Core (tm) i7-3520m CPU @ 2.90GHzIntel (r) Core (tm) i7-3520m CPU @ 2.90GHzIntel (r) Core (tm) i7-3520m CPU @ 2.90GHzIn Tel (R) Core (TM) i7-3520m CPU @ 2.90GHz
Memory information
Similar to/proc/cpuinfo, the/proc/meminfo file contains the main memory information for your computer. The next script produces a dictionary containing the contents of this file and outputs it.
#!/usr/bin/env python from __future__ import print_functionfrom collections import Ordereddict def meminfo (): "' Ret Urn the information in/proc/meminfo as a dictionary "' meminfo=ordereddict () with open ('/proc/meminfo ') As F: For line in F: meminfo[line.split (': ') [0]] = line.split (': ') [1].strip () return meminfo if __name__== ' __main__ ': #print (Meminfo ()) meminfo = Meminfo () print (' Total memory: {0} '. Format (meminfo[' Memtotal ']) print (' free memory: {0} '. Format (meminfo[' Memfree '))
As seen earlier, you can also use specific keys to get any information you want (__name__== ' __main__ ' statements are shown quickly). When you run this script, you can see output similar to the following:
Total memory:7897012 kbfree memory:249508 KB
Network statistics
Below, we will explore the network equipment of our computer system. We will retrieve the network interface of the system and the byte data sent and received when the system is turned on. This information can be obtained in the/proc/net/dev file. If you have reviewed the contents of this file, you will find that the first two lines contain the header information-i.e. In the file is the network interface name, the second and third columns show the bytes received and transmitted (such as the total bytes sent, the number of packets, error statistics, and so on). We are interested in how to get the total data sent and received by different network devices. The next code listing shows how we can present this information from/proc/net/dev:
#!/usr/bin/env pythonfrom __future__ Import print_functionfrom Collections import Namedtuple def netdevs (): ' ' RX an D TX Bytes For each of the network devices "with open ('/proc/net/dev ') as f: net_dump = F.readlines () Devic e_data={} data = namedtuple (' Data ', [' Rx ', ' TX ']) for line in net_dump[2:]: Line = Line.split (': ') if Line[0].strip ()! = ' lo ': device_data[line[0].strip ()] = data (float (line[1].split () [0])/(1024.0*1024.0), Float (Line[1].split () [8])/(1024.0*1024.0)) return device_data if __name__== ' __main__ ': Netdevs = Netdevs ( ) for Dev in Netdevs.keys (): print (' {0}: {1} MIB {2} MIB '. Format (Dev, Netdevs[dev].rx, netdevs[dev].tx))
When you run the above script, the data received and sent from your network device after your most recent reboot is output in MIB. As shown below:
em1:0.0 MIB 0.0 mibwlan0:2651.40951061 MIB 183.173976898 MIB
You may use a persistent storage mechanism and this script to write your own data using a monitoring program.
Process
The/proc directory also contains the directories for each running process. The names of these directories are named with the corresponding process ID. So, if you traverse all the numbered directories in the/proc directory, you will get a list of all the currently running processes. The Process_list () function in the following code listing returns a list containing all the currently running process IDs. The length of this list is equal to the total number of system running processes, as you can see from running this script:
#!/usr/bin/env python "" "List of all process IDs currently active" "" from __future__ import Print_functionimport osdef Pro Cess_list (): PIDs = [] for subdir in Os.listdir ('/proc '): if Subdir.isdigit (): pids.append (SubDir) return PIDs if __name__== ' __main__ ': PIDs = process_list () print (' Total number of running processes:: {0} '. Format (len (PIDs)))
When you run the above script, the output is similar to the following output:
Each process directory contains a large number of other files and directories that contain a variety of shared libraries and other information about the process invocation commands that are used.
Block devices
The next script lists all the block device information by accessing the SYSFS virtual file system. You can find all the block devices on the system in the/sys/block directory. Therefore, there will be/sys/block/sda,/sys/block/sdb and other similar directories on your system. To find these devices, we can traverse the/sys/block directory and match what we're looking for with a simple regular expression.
#!/usr/bin/env python "" "Read block device data from Sysfs" "" from __future__ import print_functionimport globimport reimp ORT OS # ADD any other device pattern to read Fromdev_pattern = [' sd.* ', ' mmcblk* '] def size (device): nr_sectors = Open (device+ '/size '). Read (). Rstrip (' n ') sect_size = open (device+ '/queue/hw_sector_size '). Read (). Rstrip (' n ') # The Sect_size is in bytes, so we convert it to GiB and then send it back return (float (nr_sectors) *float (sect_size)) /(1024.0*1024.0*1024.0) def Detect_devs (): For device in Glob.glob ('/sys/block/* '): for pattern in Dev_pattern : if Re.compile (pattern). Match (Os.path.basename): print (' device:: {0}, Size:: {1} GiB '. Format ( device, size (device)) if __name__== ' __main__ ': Detect_devs ()
If you run this script, you will see output similar to the following:
Device::/SYS/BLOCK/SDA, Size:: 465.761741638 gibdevice::/sys/block/mmcblk0, Size:: 3.70703125 GiB
When I ran this script, I inserted an extra SD card. So you'll see that this script detects it (the second line of the output above, the translator notes). You can also extend this script to identify other block devices (such as virtual hard disks).
Building command-line tools
The default behavior of allowing users to specify command-line parameters to customize a program is a common feature of all Linux command-line tools. The Argparse module allows you to have an interface similar to the built-in tool interface. The next code listing shows a program that gets all the users on your system and prints out the corresponding landing shell.
#!/usr/bin/env python "" "Print all the users and their login shells" "" from __future__ import print_functionimport pwd # G ET the Users from/etc/passwddef getusers (): users = Pwd.getpwall () for user in users: print (' {0}:{1} '. Format (User.pw_name, User.pw_shell)) if __name__== ' __main__ ': getusers ()
When running the above script, it will print out all the users on your system and their landing shell
Now, let's say you want the script user to be able to choose whether they want to see other users of the system (such as Daemon,apache). We implement this functionality by extending the previous code using the Argparse module, just like the following code.
#!/usr/bin/env python "" "Utility to play around with users and passwords on a Linux system" "from __future__ import print _functionimport pwdimport argparseimport os def read_login_defs (): Uid_min = None Uid_max = None if Os.path.exists ('/ Etc/login.defs '): With open ('/etc/login.defs ') as F:login_data = F.readlines () for line in LOGIN_DATA:I F line.startswith (' Uid_min '): uid_min = Int (Line.split () [1].strip ()) If Line.startswith (' Uid_max '): UI D_max = Int (Line.split () [1].strip ()) return uid_min, Uid_max # Get the Users from/etc/passwddef getusers (no_system=fals E): uid_min, Uid_max = Read_login_defs () if uid_min is None:uid_min = $ if Uid_max is None:uid_max = 60000 Users = Pwd.getpwall () for user in users:if no_system:if user.pw_uid >= uid_min and User.pw_uid <= UID _max:print (' {0}:{1} '. Format (User.pw_name, User.pw_shell)) Else:print (' {0}:{1} '. Format (user.pw_name, user. Pw_shell)) If __name__==' __main__ ': parser = argparse. Argumentparser (description= ' User/password Utility ') parser.add_argument ('--no-system ', action= ' store_true ', dest= ' No_system ', default = False, help= ' Specify to omit system users ') args = Parser.parse_args () getusers (ARGS.N O_system)
Using the Help option to run the above script, you will see a friendly help message with optional (and useful) options
$./getusers.py--helpusage:getusers.py [-h] [--no-system] user/password Utility Optional arguments:-H,--help Show This help message and exit--no-system specify to omit system users
An example of the above script is called as follows:
$./getusers.py--no-systemgene:/bin/bash
When you pass an invalid argument, the script will error:
$./getusers.py--paramusage:getusers.py [-h] [--no-system]getusers.py:error:unrecognized arguments:--param
Let's take a quick look at how we use the Parser=argparse of the Argparse module in the script above. Argumentparser (description= ' User/password Utility ') This line of code creates a new Argumentparser object using an optional parameter that describes the role of the script.
Then we are in the next line of code: parser.add_argument (' –no-system ', action= ' store_true ', dest= ' no_system ', default = False, help= ' Specify To omit system users ') uses the Add_argument () method to add parameters that allow the script to recognize command-line options. The first parameter of this method is the option name provided by the script consumer as a parameter when invoking the script. The next parameter, Action=store_true, indicates that this is a Boolean option. In other words, this parameter will have an effect on the program to some extent. The dest parameter specifies a variable to hold the option value and provide it for use by the script. If the user does not provide an option, the default value can be set to false by using the parameter default=false. The final argument is that the script shows Help about this option. Finally, use the Parse_args () method to process the parameter: Args=parser.parse_args (). Once processed, use Args.option_dest to get the user-supplied option value, where option_dest is the dest variable you specified when you set the parameter. This line of code getusers (Args.no_system) calls Getusers () as a parameter with the user-supplied No_system option value.
The next script shows you how to provide the user with a non-boolean option in your script. This script rewrites the code Listing 6 and adds additional options to allow you to specify which network devices you are interested in detecting.
#!/usr/bin/env pythonfrom __future__ Import print_functionfrom Collections import Namedtupleimport argparse def netdevs ( Iface=none): "RX and TX bytes for each of the network devices" with open ('/proc/net/dev ') as F:net_dump = F.R Eadlines () device_data={} data = namedtuple (' Data ', [' Rx ', ' TX ']) for line in Net_dump[2:]: line = Line.split (': ') If not iface:if line[0].strip ()! = ' lo ': Device_data[line[0].strip ()] = data (float (line[1].split () [0])/(1024 .0*1024.0), Float (Line[1].split () [8])/(1024.0*1024.0)) else:if line[0].strip () = = Iface: Device_data[line[0].strip ()] = data (float (line[1].split () [0])/(1024.0*1024.0), Float (line [1].split () [8])/(1024.0*1024.0)) return device_data if __name__== ' __main__ ': parser = argparse. Argumentparser (description= ' Network Interface Usage Monitor ') parser.add_argument ('-I ', '--interface ', dest= ' iface ', help= ' Network interface ') args = parser.Parse_args () Netdevs = Netdevs (iface = args.iface) for dev in Netdevs.keys (): print (' {0}: {1} MIB {2} MIB '. Format (DE V, Netdevs[dev].rx, Netdevs[dev].tx))
When you run this script without providing any parameters, it actually runs the same result as the previous version. However, you can also specify which network devices you might be interested in. As an example:
$./net_devs_2.py em1:0.0 MIB 0.0 mibwlan0:146.099492073 MIB 12.9737148285 mibvirbr1:0.0 MIB 0.0 mibvirbr1-nic:0.0 MIB 0.0 MiB $./net_devs_2.py--helpusage:net_devs_2.py [-h] [-I IFACE] Network Interface Usage Monitor Optional arguments: -H,--help show this help message and Exit-i IFACE,--interface IFACE Network interface $./net_devs_2.py-i WLAN 0wlan0:146.100307465 MIB 12.9777050018 MIB
Enable your scripts to run anywhere
With the help of this article, you may already be able to write one or more useful scripts that you want to use every day, just like other Linux commands. The simplest way to achieve this is to make the scripts run (add the Run permission to the script, the translator notes) and set the bash alias (bash aliases) for the commands. You can also delete the. py suffix and put the file in a standard location such as/usr/local/sbin.
Other useful standard library modules
In addition to the standard libraries we have seen so far in this article, there are many other standard libraries that may be useful: Subprocess,configparser, readline and curses.
Next?
At this stage, based on your own Python experience and in-depth exploration of Linux, you choose one of the following ways. If you've already written a lot of shell scripts or command pipelines to explore Linux in depth, try Python. If you want to write your own scripting tools in a simpler way to perform a variety of tasks, try Python. Finally, if you've already programmed on Linux with other kinds of Python, I wish you the pleasure of using Python to explore Linux in depth.