某次在查看测试机(Ubuntu)发行版本时,发现得到的结果并不准确;本应得到Ubuntu
,结果显示的却是Debian
,大致代码如下
...
distribution_name = ['centos', 'ubuntu', 'redhat', 'debian', 'fedora']
class_name = {'centos': 'CentOS',
'ubuntu': 'Ubuntu',
'redhat': 'RedHat',
'debian': 'Debian',
'fedora': 'Fedora'}
...
for name in distribution_name:
if name in platform.platform.lower():
_platform = class_name[name]
break
...
项目使用的是可移植版的python
,第一反应是用交互模式验证一下。
>>> import platform
>>> platform.platform()
'Linux-4.4.0-62-generic-x86_64-with-debian-stretch-sid'
得出的结果确实为debian
。既没有报错,也没有异常,那么问题是出在哪里了呢?
遂又使用系统自带的python
验证。
>>> import platform
>>> platform.platform()
'Linux-4.4.0-62-generic-x86_64-with-Ubuntu-16.04-xenial'
然而系统得到的却是正确的结果,难道是移植版本的bug?
在同事的提醒下,意识到应该看一下platform
模块的源码,看看问题是否出在这里。
首先,查看platform
模块中的platform
方法
def platform(aliased=0, terse=0):
result = _platform)_cache.get((aliased, terse), None)
if result is not None:
return resut
system, node, release, version, machine, processor = uname()
...
elif system in ('Linux', ):
disname, distversion, distid = dist('')
...
def uname():
...
try:
system, node, release, version, machine = os.uname()
...
def dist(distname='', version='', id='',
supported_dists=_supported_dists):
return linux_distribution(distname, version, id,
supported_dists=supported_dists,
full_distribution_name=0)
当调用platform
方法时,首先它回去模块缓存信息中查找,若有则直接返回。因为是第一次调用,缓存中肯定不会存有相应信息,这里可以跳过。
接着,通过uname
方法获取system
, node
, release
等信息,而uname
方法主要是调用os.uname()
获得相应信息。
>>> import os
>>> os.uname()
('Linux', 'uyun-VirtualBox', '4.4.0-62-generic', '#83-Ubuntu SMP Wed Jan 18 14:10:15 UTC 2017', 'x86_64')
尝试使用os.uname()
后,除了能得到系统版本外发现并没有期望得到的相应发行版信息,跳过。
然后则是dist()
方法。发现dist()
方法实际上调用的python_implementation()
。最终确定,获取系统版本的关键就在python_implementation()
方法中。
以下为比较可移植版的python
和Ubuntu
自带的python
源码(具体行号可能存在些许偏差)
...
259 _supported_dists = (
260 'SuSE', 'debian', 'fedora', 'redhat', 'centos',
261 'mandrake', 'mandriva', 'rocks', 'slackware', 'yellowdog', 'gentoo',
262 'UnitedLinux', 'turbolinux')
...
...
291 def linux_distribution(distname='', version='', id='',
292 supported_dists=_supported_dists,
293 full_distribution_name=1):
...
315 try:
316 etc = os.listdir('/etc')
317 except os.error:
318 # Probably not a Unix system
319 return distname, version, id
...
...
以上为移植版python
的platform
模块源码。
···
261 _supported_dists = (
262 'SuSE', 'debian', 'fedora', 'redhat', 'centos',
263 'mandrake', 'mandriva', 'rocks', 'slackware', 'yellowdog', 'gentoo',
264 'UnitedLinux', 'turbolinux', 'Ubuntu')
...
293 _distributor_id_file_re = re.compile("(?:DISTRIB_IDs*=)s*(.*)", re.I)
294 _release_file_re = re.compile("(?:DISTRIB_RELEASEs*=)s*(.*)", re.I)
295 _codename_file_re = re.compile("(?:DISTRIB_CODENAMEs*=)s*(.*)", re.I)
296
297 def linux_distribution(distname='', version='', id='',
298 supported_dists=_supported_dists,
299 full_distribution_name=1):
...
321 # check for the LSB /etc/lsb-release file first, needed so
322 # that the distribution doesn't get identified as Debian.
323 try:
324 with open("/etc/lsb-release", "rU") as etclsbrel:
325 for line in etclsbrel:
326 m = _distributor_id_file_re.search(line)
327 if m:
328 _u_distname = m.group(1).strip()
329 m = _release_file_re.search(line)
330 if m:
331 _u_version = m.group(1).strip()
332 m = _codename_file_re.search(line)
333 if m:
334 _u_id = m.group(1).strip()
335 if _u_distname and _u_version:
336 return (_u_distname, _u_version, _u_id)
337 except (EnvironmentError, UnboundLocalError):
338 pass
339
340 try:
341 etc = os.listdir('/etc')
342 except os.error:
343 # Probably not a Unix system
344 return distname,version,id
...
...
以上为Ubuntu
系统自带python
的platform
模块源码。
首先可以看到,Ubuntu
版本中platform
的_supported_dists
元组中多了一个Ubuntu
元素,并且在linux_destribution
方法中,首先会尝试读取/etc/lsb-release
文件;接着通过正则匹配(_distributor_id_file_re
, _release_file_re
, _codename_file_re
),查找相应的值,如果都有结果,则直接返回。
读取/etc/lsb-release
,发现里面存了一些Ubuntu
系统版本信息。
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04 LTS"
那么显然,三个正则都将匹配到对应的值,返回(Ubuntu, 16.04, xenial)
。
最终,正确的获取到Ubuntu
发行版本。