76

我正在寻找sscanf()Python 中的等价物。我想解析/proc/net/*文件,在 CI 中可以做这样的事情:

int matches = sscanf(
        buffer,
        "%*d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %*X %*X:%*X %*X:%*X %*X %*d %*d %ld %*512s\n",
        local_addr, &local_port, rem_addr, &rem_port, &inode);

起初我想使用str.split,但是它不会在给定的字符上拆分,而是在sep整个字符串上拆分:

>>> lines = open("/proc/net/dev").readlines()
>>> for l in lines[2:]:
>>>     cols = l.split(string.whitespace + ":")
>>>     print len(cols)
1

如上所述,应该返回 17。

是否有一个 Python 等价于sscanf(不是 RE),或者标准库中有一个字符串分割函数,可以分割我不知道的任何字符范围?

4

13 回答 13

90

还有parse模块。

parse()被设计成与format()(Python 2.6 及更高版本中较新的字符串格式化函数)相反。

>>> from parse import parse
>>> parse('{} fish', '1')
>>> parse('{} fish', '1 fish')
<Result ('1',) {}>
>>> parse('{} fish', '2 fish')
<Result ('2',) {}>
>>> parse('{} fish', 'red fish')
<Result ('red',) {}>
>>> parse('{} fish', 'blue fish')
<Result ('blue',) {}>
于 2012-10-12T04:18:49.763 回答
69

当我处于 C 状态时,我通常使用 zip 和 list 理解来处理类似 scanf 的行为。像这样:

input = '1 3.0 false hello'
(a, b, c, d) = [t(s) for t,s in zip((int,float,strtobool,str),input.split())]
print (a, b, c, d)

请注意,对于更复杂的格式字符串,您确实需要使用正则表达式:

import re
input = '1:3.0 false,hello'
(a, b, c, d) = [t(s) for t,s in zip((int,float,strtobool,str),re.search('^(\d+):([\d.]+) (\w+),(\w+)$',input).groups())]
print (a, b, c, d)

另请注意,您需要所有要转换的类型的转换函数。例如,上面我使用了类似的东西:

strtobool = lambda s: {'true': True, 'false': False}[s]
于 2012-06-18T14:55:37.410 回答
36

Python 没有sscanf等效的内置函数,而且大多数情况下,通过直接处理字符串、使用正则表达式或使用解析工具来解析输入实际上更有意义。

可能对翻译 C 很有用,人们已经实现sscanf了,例如在这个模块中:http: //hkn.eecs.berkeley.edu/~dyoo/python/scanf/

在这种特殊情况下,如果您只想根据多个拆分字符拆分数据,re.split那么它确实是正确的工具。

于 2010-02-01T06:51:50.437 回答
23

re您可以使用该模块拆分一系列字符。

>>> import re
>>> r = re.compile('[ \t\n\r:]+')
>>> r.split("abc:def  ghi")
['abc', 'def', 'ghi']
于 2010-02-01T06:41:49.770 回答
15

re您可以使用命名组解析模块。它不会将子字符串解析为它们的实际数据类型(例如int),但在解析字符串时非常方便。

鉴于此示例行来自/proc/net/tcp

line="   0: 00000000:0203 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 335 1 c1674320 300 0 0 0"

使用变量模仿您的 sscanf 示例的示例可能是:

import re
hex_digit_pattern = r"[\dA-Fa-f]"
pat = r"\d+: " + \
      r"(?P<local_addr>HEX+):(?P<local_port>HEX+) " + \
      r"(?P<rem_addr>HEX+):(?P<rem_port>HEX+) " + \
      r"HEX+ HEX+:HEX+ HEX+:HEX+ HEX+ +\d+ +\d+ " + \
      r"(?P<inode>\d+)"
pat = pat.replace("HEX", hex_digit_pattern)

values = re.search(pat, line).groupdict()

import pprint; pprint values
# prints:
# {'inode': '335',
#  'local_addr': '00000000',
#  'local_port': '0203',
#  'rem_addr': '00000000',
#  'rem_port': '0000'}
于 2010-02-01T23:02:59.580 回答
3

更新:其 regex 模块的 Python 文档re包括一个关于模拟 scanf 的部分,我发现它比上面的任何答案都更有用。

https://docs.python.org/2/library/re.html#simulating-scanf

于 2016-12-19T12:38:41.873 回答
1

您可以将“:”转为空格,然后进行 split.eg

>>> f=open("/proc/net/dev")
>>> for line in f:
...     line=line.replace(":"," ").split()
...     print len(line)

不需要正则表达式(对于这种情况)

于 2010-02-01T06:50:34.053 回答
1

有一个实现基本扫描的 ActiveState 配方 http://code.activestate.com/recipes/502213-simple-scanf-implementation/

于 2010-11-11T17:21:51.983 回答
0

赞成 orip 的答案。我认为使用 re 模块是合理的建议。在使用 Python 处理复杂的正则表达式任务时,Kodos 应用程序很有帮助。

http://kodos.sourceforge.net/home.html

于 2010-08-23T09:46:34.187 回答
0

您可以安装 pandas 并pandas.read_fwf用于固定宽度格式的文件。使用示例/proc/net/arp

In [230]: df = pandas.read_fwf("/proc/net/arp")

In [231]: print(df)
       IP address HW type Flags         HW address Mask Device
0   141.38.28.115     0x1   0x2  84:2b:2b:ad:e1:f4    *   eth0
1   141.38.28.203     0x1   0x2  c4:34:6b:5b:e4:7d    *   eth0
2   141.38.28.140     0x1   0x2  00:19:99:ce:00:19    *   eth0
3   141.38.28.202     0x1   0x2  90:1b:0e:14:a1:e3    *   eth0
4    141.38.28.17     0x1   0x2  90:1b:0e:1a:4b:41    *   eth0
5    141.38.28.60     0x1   0x2  00:19:99:cc:aa:58    *   eth0
6   141.38.28.233     0x1   0x2  90:1b:0e:8d:7a:c9    *   eth0
7    141.38.28.55     0x1   0x2  00:19:99:cc:ab:00    *   eth0
8   141.38.28.224     0x1   0x2  90:1b:0e:8d:7a:e2    *   eth0
9   141.38.28.148     0x1   0x0  4c:52:62:a8:08:2c    *   eth0
10  141.38.28.179     0x1   0x2  90:1b:0e:1a:4b:50    *   eth0

In [232]: df["HW address"]
Out[232]:
0     84:2b:2b:ad:e1:f4
1     c4:34:6b:5b:e4:7d
2     00:19:99:ce:00:19
3     90:1b:0e:14:a1:e3
4     90:1b:0e:1a:4b:41
5     00:19:99:cc:aa:58
6     90:1b:0e:8d:7a:c9
7     00:19:99:cc:ab:00
8     90:1b:0e:8d:7a:e2
9     4c:52:62:a8:08:2c
10    90:1b:0e:1a:4b:50

In [233]: df["HW address"][5]
Out[233]: '00:19:99:cc:aa:58'

默认情况下,它会尝试自动找出格式,但您可以提供一些选项以获得更明确的说明(请参阅文档)。pandas中还有其他 IO 例程,它们对其他文件格式也很强大。

于 2020-01-15T17:53:00.723 回答
0

官方 python 文档中有一个关于如何使用sscanffrom的示例libc

    # import libc
    from ctypes import CDLL
    if(os.name=="nt"):
        libc = cdll.msvcrt 
    else:
        # assuming Unix-like environment
        libc = cdll.LoadLibrary("libc.so.6")
        libc = CDLL("libc.so.6")  # alternative

    # allocate vars
    i = c_int()
    f = c_float()
    s = create_string_buffer(b'\000' * 32)

    # parse with sscanf
    libc.sscanf(b"1 3.14 Hello", "%d %f %s", byref(i), byref(f), s)

    # read the parsed values
    i.value  # 1
    f.value  # 3.14
    s.value # b'Hello'
于 2020-02-25T15:18:45.493 回答
-1

如果分隔符是 ':',您可以在 ':' 上进行拆分,然后在字符串上使用 x.strip() 来删除任何前导或尾随空格。int() 将忽略空格。

于 2010-02-01T11:38:34.423 回答
-1

odiak有一个Python 2 实现。

于 2012-10-11T21:28:21.520 回答