RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1605097
Accepted
Young Sideways
Young Sideways
Asked:2025-01-19 14:47:41 +0000 UTC2025-01-19 14:47:41 +0000 UTC 2025-01-19 14:47:41 +0000 UTC

从 python 脚本执行 ping 命令

  • 772

最后的问题:是否可以用 python 更简洁地编写这个,而不失去容错能力,避免可怕的 bash 正则表达式?
首先,我想说我已经用 Python 编写了大约一周的时间(在此之前,只使用 C/C++)。
我需要ping从我的 python 脚本调用一个命令,不仅有关地址可用性的答案很重要,而且ping.需要注意的是,该脚本将在无法从 pip 传递包的机器上运行。

首先,我以一种相当简单的方式做到了 - 我为输出编写了一个解析器函数subprocess.run,它看起来像这样

SYSTEM_PING_ITERATIONS: int       = 1
SYSTEM_CMD_PING       : str       = "ping -4 -c{0} {1} | tail -n2"

def run_system_cmd(cmd: str) -> Tuple[List[str], int]:
    ret = subprocess.run(cmd,
                         shell=True,
                         executable='/bin/bash',
                         stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT
    )
    output = ret.stdout.decode('utf-8', errors='ignore')
    out_split: List[str] = output.split('\n')
    return (out_split[:-1] if out_split[-1] == '' else out_split, ret.returncode)


def process_ping(interations: int, address: Union[str, List[int]]) -> Dict[str, Union[int, float]]:
    address = address if isinstance(address, str) else '.'.join(map(str, address))

    from_cmd = run_system_cmd(str.format(SYSTEM_CMD_PING, interations, address))
    if from_cmd[1] != 0:
        return {}
    
    output = from_cmd[0]
    if not output or len(output) != 2:
        return {}
    
    suffixes: Dict[str, float] = {
        "ns": 1000000.0,
        "us": 1000.0,
        "ms": 1.0,
        "s" : 0.001
    }
    result: Dict[str, Union[int, float]] = {
        "transmitted": 0  ,
        "received"   : 0  ,
        "loss"       : 0.0,
        "total"      : 0.0,
        "min"        : 0.0,
        "max"        : 0.0,
        "avg"        : 0.0
    }

    data1 = output[0].split(' ')
    if len(data1) < 10:
        return {}
    result['transmitted'] = int(data1[0])
    result['received']    = int(data1[3])
    result['loss']        = float(re.sub('\\D', '', data1[5]))
    result['total']       = float(re.sub('\\D', '', data1[9])) * suffixes[re.sub(r'[^A-Za-z]', '', data1[9])]

    # do not parse further, if destination unreachable
    if result['loss'] == 100.0:
        return result
    
    data2 = output[1].split(' ')[-2:]
    splitted = data2[0].split('/')
    result['min'] = float(splitted[0]) * suffixes[data2[1]]
    result['max'] = float(splitted[2]) * suffixes[data2[1]]
    result['avg'] = float(splitted[1]) * suffixes[data2[1]]

    return result

对于如此简单的任务需要相当多的代码......

在对 python 缺乏足够了解的情况下,我想到的下一件事是更改 bash 命令:

ping -c{0} {1} | tail -n2 | awk -F'[ |,]' '/packets transmitted/ {transmitted=$1; received=$5; loss=$8; time=$13} /rtt min\/avg\/max/ {split($4, rtt, "/"); min=rtt[1]; avg=rtt[2]; max=rtt[3]} END {print transmitted, received, loss, time, min, avg, max, $5}'

对于可访问和不可访问的地址,其输出分别如下:

1 1 0% 0ms 36.477 36.477 36.477 ms

1 0 100% 0ms

这比原始版本更容易解析和检查。

所以现在的问题是:是否可以用 python 更简洁地编写这个,而不失去容错能力,避免可怕的 bash 正则表达式?

python
  • 1 1 个回答
  • 58 Views

1 个回答

  • Voted
  1. Best Answer
    Pak Uula
    2025-01-19T17:14:16Z2025-01-19T17:14:16Z

    恕我直言,您的一行解决方案看起来非常可读

    真正的 Python 风格是冗长、冗长、绝不单行的。因此,是否值得将文本页面围起来以替换一行脚本?

    #!/usr/bin/env python3
    "ping"
    import argparse
    from subprocess import CompletedProcess, run, PIPE
    from itertools import dropwhile
    from typing import Iterable, Tuple
    
    
    def run_ping(address: str, count: int = 4, interval: float = 0.1, args:Iterable = []) -> CompletedProcess:
        '''Запускает ping и ждёт завершения процесса'''
        return run(['ping', '-i', str(interval), '-c', str(count), '-q', address, *args],
                   stdout=PIPE,
                   stderr=PIPE,
                   check=False,
                   text=True,
                   encoding='ascii',
                   env={'LANG': 'C'},
                   )
    
    
    def get_ping_stats_text(ping_proc: CompletedProcess) -> Tuple[str | None, str | None]:
        '''Возвращает две последние строки из вывода ping.
    
        Если строк меньше двух, возвращает (None, None).'''
        lines = dropwhile(
            lambda line: not line.startswith('---'),
            ping_proc.stdout.splitlines())
        # lines состоит из строки '--- ping statistics ---' и двух строк с данными
        lines = list(lines)
        if len(lines) < 3:
            return None, None
        return lines[1:]
    
    
    TRANSMITTED = 'packets transmitted'
    RECEIVED = 'received'
    LOST = 'packet loss'
    TIME = 'time'
    ERRORS = 'errors'
    
    def parse_packet_data(line: str) -> dict:
        '''Возвращает набор пар с ключами 'packets transmitted', 'received', 'packet loss', 'time'.'''
        return dict(
            map(
                # в выводе ping сначала идёт значение, затем ключ
                # кроме случая с 'time', там наоборот
                lambda p: (p[1], p[0]) if p[0] != 'time' else (p[0], p[1]),
                map(
                    # разделить пару на первое слово и остальной текст
                    lambda _str: _str.strip().split(' ', 1),
                    # Разбить строку на пары, разделённые запятыми
                    line.split(',')
                    )
                )
            )
    
    def parse_ping_rtt(line: str) -> Tuple[list, str]:
        '''Возвращает список времён RTT в мс.'''
        if ' = ' not in line:
            return None, None
        times_str, unit = line.split(' = ')[1].split()
        times = times_str.split('/')
        return times, unit
    
    def get_ping_stats(ping_proc: CompletedProcess) -> dict|None:
        '''Возвращает словарь с данными о пинге.'''
        first, second = get_ping_stats_text(ping_proc)
        if first is None:
            return None
        stats = parse_packet_data(first)
        times, unit = parse_ping_rtt(second)
        return {
            'packets': stats,
            'times': times ,
            'unit': unit,
        }
    
    def flatten_ping_stats(stats: dict) -> list:
        '''Возвращает список значений из словаря stats.'''
        result = [
            stats['packets'][TRANSMITTED],
            stats['packets'][RECEIVED],
            stats['packets'][LOST],
            stats['packets'][TIME],
        ]
        if stats['times']:
            result.extend(stats['times'])
            result.append(stats['unit'])
        return result
    
    def main():
        '''Вызывает ping и выдаёт сокращённые результаты.'''
        arg_parser = argparse.ArgumentParser(
            description='Ping a host and print the results')
        arg_parser.add_argument(
            'address',
            help='The address to ping')
        arg_parser.add_argument(
            '-c', '--count',
            type=int,
            default=10,
            help='The number of packets to send')
        arg_parser.add_argument(
            '-i', '--interval',
            type=float,
            default=0.1,
            help='The interval between packets')
        arg_parser.add_argument(
            'extra_args',
            nargs=argparse.REMAINDER,
            help='Extra arguments to pass to ping')
        
        args = arg_parser.parse_args()
        address = args.address
        
        ping_proc = run_ping(address, args.count, args.interval, args.extra_args)
        stats = get_ping_stats(ping_proc)
        if stats is None:
            print('No data')
            return
        print(' '.join(flatten_ping_stats(stats)))
        
    if __name__ == '__main__':
        main()
    
    • 2

相关问题

  • 是否可以以某种方式自定义 QTabWidget?

  • telebot.anihelper.ApiException 错误

  • Python。检查一个数字是否是 3 的幂。输出 无

  • 解析多个响应

  • 交换两个数组的元素,以便它们的新内容也反转

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5