文章总结: 本文介绍了一个名为pidinfo的跨平台进程信息获取工具,提供了Shell和Python两个版本。该工具能够获取进程的基本信息(如PID、PPID、用户、CPU/内存使用率、状态、启动时间、命令行等)、工作目录、可执行文件路径以及线程数。Shell版本存在依赖bc命令的兼容性问题,Python版本通过重写解决了该问题,并增加了在Linux下显示进程网络连接信息的功能。 综合评分: 65 文章分类: 安全工具,其他
pidinfo 根据进程 ID 获取进程信息
原创
hyang0 hyang0
生有可恋
2026年5月13日 05:17 湖北
在小说阅读器读本章
去阅读
之前写了一个 Shell 版本的,效果如下:
Shell 版脚本兼容性不好,有些系统中没有 bc 命令,在计算内存用量时会报错。脚本如下:
cat bin/pidinfo.sh#!/bin/bash
# pidinfo - Get process information (works on macOS and Linux)# Usage: pidinfo <pid># - On Linux: uses /proc filesystem# - On macOS: uses ps command
pidinfo() { if [ $# -ne 1 ]; then echo "Usage: pidinfo <pid>" echo "Get detailed process information" return 1 fi
local pid=$1
# Check if process exists if ! ps -p "$pid" > /dev/null 2>&1; then echo "Error: Process $pid does not exist" return 1 fi
echo "========================================" echo "Process Information for PID: $pid" echo "========================================"
# Get basic info with ps local user=$(ps -o user= -p "$pid" | sed 's/^ *//; s/ *$//') local ppid=$(ps -o ppid= -p "$pid" | sed 's/^ *//; s/ *$//') local command=$(ps -o command= -p "$pid" | sed 's/^ *//; s/ *$//') # macOS uses lstart, Linux uses started if ps -o started= -p $$ >/dev/null 2>&1; then local started=$(ps -o started= -p "$pid" | sed 's/^ *//; s/ *$//') else local started=$(ps -o lstart= -p "$pid" | sed 's/^ *//; s/ *$//') fi local mem=$(ps -o rss= -p "$pid" | sed 's/^ *//; s/ *$//') local pcpu=$(ps -o %cpu= -p "$pid" | sed 's/^ *//; s/ *$//') local state=$(ps -o state= -p "$pid" | sed 's/^ *//; s/ *$//')
echo "PID: $pid" echo "Parent PID: $ppid" echo "Executing User: $user" echo "Process State: $state" echo "Started: $started"
# Convert RSS KB to human readable if [ -n "$mem" ] && [ "$mem" -gt 0 ]; then if [ "$mem" -gt 1048576 ]; then local mem_human=$(echo "scale=2; $mem / 1048576" | bc) GB elif [ "$mem" -gt 1024 ]; then local mem_human=$(echo "scale=2; $mem / 1024" | bc) MB else local mem_human="$mem KB" fi echo "RSS Memory: $mem_human" echo "CPU Usage: $pcpu%" fi
# Get working directory (different methods) if [ -d /proc/$pid/cwd ]; then # Linux /proc local cwd=$(readlink /proc/$pid/cwd 2>/dev/null) echo "Working Directory: $cwd" elif command -v lsof >/dev/null 2>&1; then # macOS with lsof local cwd=$(lsof -p "$pid" -a -d cwd 2>/dev/null | grep 'cwd' | awk 'NR==1{print $9}') if [ -n "$cwd" ]; then echo "Working Directory: $cwd" fi fi
# Get executable path if [ -L /proc/$pid/exe ]; then # Linux /proc local exe=$(readlink /proc/$pid/exe 2>/dev/null) echo "Executable Path: $exe" elif command -v lsof >/dev/null 2>&1; then # macOS with lsof local exe=$(lsof -p "$pid" 2>/dev/null | grep 'txt' | awk 'NR==1{print $9}') if [ -n "$exe" ]; then echo "Executable Path: $exe" fi fi
echo "Command Line: $command"
# Get thread count if [ -f /proc/$pid/status ]; then # Linux local threads=$(grep -E "^Threads:" /proc/$pid/status | awk '{print $2}') echo "Thread Count: $threads" elif command -v ps >/dev/null 2>&1; then # macOS: mach factor can get threads via sysctl if command -v sysctl >/dev/null 2>&1; then local threads=$(sysctl -n machdep.task_info.pid "$pid".num_threads 2>/dev/null) if [ -n "$threads" ] && [ "$threads" != "0" ]; then echo "Thread Count: $threads" fi fi fi
echo "========================================"}
# If this script is executed directly instead of sourced, run the functionif [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then pidinfo "$@"fi
将脚本丢给 AI 让它重写一个 Python 版的,并加入进程打开的网络连接信息。
Python 版效果如下:
重写后的 Python 代码为:
#!/usr/bin/env python3"""pidinfo.py - 获取进程详细信息(支持 Linux / macOS)用法: python pidinfo.py <pid>
Linux 下额外显示该进程自身打开的 TCP 连接(通过 /proc 文件系统)macOS 下仅显示基础信息(不显示 TCP 连接,因为缺少 /proc)"""
import osimport sysimport globimport subprocessimport timeimport socketimport structfrom ipaddress import ip_addressfrom datetime import datetime
# ================== 通用工具函数 ==================
def get_process_info(pid): """返回进程基本信息字典,失败返回 None""" try: # 使用 ps 命令获取通用信息(跨平台) # 格式: pid, ppid, user, %cpu, rss, state, lstart, command cmd = ['ps', '-p', str(pid), '-o', 'pid=', '-o', 'ppid=', '-o', 'user=', '-o', '%cpu=', '-o', 'rss=', '-o', 'state=', '-o', 'lstart=', '-o', 'command='] output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True).strip() if not output: return None parts = output.split(None, 8) # 最多9部分,command 可能包含空格 if len(parts) < 9: return None # 解析字段 info = { 'pid': int(parts[0]), 'ppid': int(parts[1]), 'user': parts[2], 'cpu': float(parts[3]), 'rss_kb': int(parts[4]), 'state': parts[5], 'started': ' '.join(parts[6:8]), # lstart 通常是两部分日期时间 'command': parts[8] } return info except Exception as e: return None
def get_thread_count_linux(pid): """Linux 下读取 /proc/pid/status 中的线程数""" try: with open(f'/proc/{pid}/status', 'r') as f: for line in f: if line.startswith('Threads:'): return int(line.split()[1]) except Exception: pass return None
def get_thread_count_macos(pid): """macOS 下通过 sysctl 获取线程数(需要 root 或合适权限)""" try: out = subprocess.check_output( ['sysctl', '-n', f'machdep.task_info.pid.{pid}.num_threads'], stderr=subprocess.DEVNULL, text=True ).strip() return int(out) if out else None except Exception: return None
def get_cwd_linux(pid): """Linux 下读取 /proc/pid/cwd 符号链接""" try: return os.readlink(f'/proc/{pid}/cwd') except Exception: return None
def get_cwd_macos(pid): """macOS 下使用 lsof 获取工作目录(需要安装 lsof)""" try: out = subprocess.check_output(['lsof', '-p', str(pid), '-a', '-d', 'cwd', '-Fn'], stderr=subprocess.DEVNULL, text=True) for line in out.split('\n'): if line.startswith('n/'): return line[1:] # 去掉 'n' 前缀 except Exception: pass return None
def get_exe_linux(pid): """Linux 下读取 /proc/pid/exe 符号链接""" try: return os.readlink(f'/proc/{pid}/exe') except Exception: return None
def get_exe_macos(pid): """macOS 下使用 lsof 获取可执行文件路径""" try: out = subprocess.check_output(['lsof', '-p', str(pid), '-Fn'], stderr=subprocess.DEVNULL, text=True) for line in out.split('\n'): # 可执行文件通常标记为 'n' 且路径不含 'txt' 区分,简单取第一个非 fd 路径 if line.startswith('n/') and ' (deleted)' not in line: return line[1:] except Exception: pass return None
def human_readable_size(kb): """将 KB 转换为人类可读格式""" if kb >= 1024 * 1024: return f"{kb / (1024 * 1024):.2f} GB" elif kb >= 1024: return f"{kb / 1024:.2f} MB" else: return f"{kb} KB"
# ================== TCP 连接相关函数(仅 Linux) ==================
TCP_STATES = { '01': 'ESTABLISHED', '02': 'SYN_SENT', '03': 'SYN_RECV', '04': 'FIN_WAIT1', '05': 'FIN_WAIT2', '06': 'TIME_WAIT', '07': 'CLOSE', '08': 'CLOSE_WAIT', '09': 'LAST_ACK', '0A': 'LISTEN', '0B': 'CLOSING', '0C': 'NEW_SYN_RECV'}
def parse_ipv4_hex(hex_str): """小端序 IPv4 十六进制 -> 点分十进制""" octets = [hex_str[i:i+2] for i in range(0, len(hex_str), 2)] octets.reverse() ip_int = int(''.join(octets), 16) return socket.inet_ntoa(struct.pack('!I', ip_int))
def parse_ipv6_hex(hex_str): """小端序 IPv6 十六进制 -> 标准 IPv6 地址""" # 每4个字符一组(两个字节),反转顺序 groups = [hex_str[i:i+4] for i in range(0, len(hex_str), 4)] groups.reverse() hex_str_nbo = ''.join(groups) addr_bytes = bytes.fromhex(hex_str_nbo) return str(ip_address(addr_bytes))
def parse_address_port(addr_port_str): """解析 'IP:PORT' 十六进制字符串 -> (ip_str, port_int)""" addr_hex, port_hex = addr_port_str.split(':') port = int(port_hex, 16) if len(addr_hex) == 8: # IPv4 ip = parse_ipv4_hex(addr_hex) elif len(addr_hex) == 32: # IPv6 ip = parse_ipv6_hex(addr_hex) else: raise ValueError(f"未知地址格式: {addr_hex}") return ip, port
def get_process_socket_inodes(pid): """返回该进程持有的所有 socket inode 集合""" fd_dir = f'/proc/{pid}/fd' inodes = set() if not os.path.isdir(fd_dir): return inodes try: for fd_name in os.listdir(fd_dir): fd_path = os.path.join(fd_dir, fd_name) try: link = os.readlink(fd_path) except (OSError, PermissionError): continue # 链接格式: socket:[12345678] if link.startswith('socket:[') and link.endswith(']'): inode_str = link[8:-1] if inode_str.isdigit(): inodes.add(int(inode_str)) except PermissionError: print(f"警告: 没有权限读取 {fd_dir},请使用 root 运行以获得精确 TCP 连接信息", file=sys.stderr) return inodes
def get_tcp_connections_linux(pid, valid_inodes): """从 /proc/pid/net/tcp 和 tcp6 中过滤出属于该进程的 TCP 连接""" connections = [] for proto in ['tcp', 'tcp6']: net_path = f'/proc/{pid}/net/{proto}' if not os.path.exists(net_path): continue try: with open(net_path, 'r') as f: lines = f.readlines() except PermissionError: print(f"警告: 无法读取 {net_path},请使用 root 运行", file=sys.stderr) continue except Exception as e: print(f"警告: 读取 {net_path} 时出错: {e}", file=sys.stderr) continue
for line in lines[1:]: # 跳过标题行 parts = line.split() if len(parts) < 10: continue # 字段索引: sl, local_addr, rem_addr, st, tx_queue:rx_queue, tr:tm->when, retrnsmt, uid, timeout, inode, ... local_addr = parts[1] rem_addr = parts[2] st_hex = parts[3] uid = parts[7] inode_str = parts[9] try: inode_int = int(inode_str) except ValueError: continue if inode_int not in valid_inodes: continue
try: local_ip, local_port = parse_address_port(local_addr) remote_ip, remote_port = parse_address_port(rem_addr) except Exception: local_ip, local_port = local_addr, '解析失败' remote_ip, remote_port = rem_addr, '解析失败'
state = TCP_STATES.get(st_hex.upper(), st_hex) connections.append({ 'protocol': proto.upper(), 'local': f"{local_ip}:{local_port}", 'remote': f"{remote_ip}:{remote_port}", 'state': state, 'uid': uid, 'inode': inode_int }) return connections
def print_tcp_connections(pid): """打印进程自身打开的 TCP 连接(仅 Linux)""" if sys.platform != 'linux': return socket_inodes = get_process_socket_inodes(pid) if not socket_inodes: print("\n无法获取该进程的 TCP 连接信息(进程没有 socket 或权限不足)") return conns = get_tcp_connections_linux(pid, socket_inodes) if not conns: print("\n该进程没有打开任何 TCP 连接") return print("\n--- TCP 连接(该进程自身打开) ---") header = f"{'Proto':<6} {'Local Address':<30} {'Remote Address':<30} {'State':<12} {'UID':<6} {'Inode'}" print(header) print("-" * len(header)) for c in conns: print(f"{c['protocol']:<6} {c['local']:<30} {c['remote']:<30} {c['state']:<12} {c['uid']:<6} {c['inode']}")
# ================== 主函数 ==================
def main(): if len(sys.argv) != 2: print(f"用法: {sys.argv[0]} <pid>", file=sys.stderr) sys.exit(1)
try: pid = int(sys.argv[1]) except ValueError: print("错误: PID 必须是整数", file=sys.stderr) sys.exit(1)
# 检查进程是否存在 try: os.kill(pid, 0) except ProcessLookupError: print(f"错误: 进程 {pid} 不存在", file=sys.stderr) sys.exit(1)
info = get_process_info(pid) if not info: print(f"错误: 无法获取进程 {pid} 的信息", file=sys.stderr) sys.exit(1)
# 打印基本信息 print("=" * 40) print(f"进程信息 PID: {pid}") print("=" * 40) print(f"PID: {info['pid']}") print(f"父进程 PID: {info['ppid']}") print(f"执行用户: {info['user']}") print(f"进程状态: {info['state']}") print(f"启动时间: {info['started']}") if info['rss_kb'] > 0: print(f"RSS 内存: {human_readable_size(info['rss_kb'])}") print(f"CPU 使用率: {info['cpu']}%") print(f"命令行: {info['command']}")
# 工作目录(Linux / macOS 不同) if sys.platform == 'linux': cwd = get_cwd_linux(pid) exe = get_exe_linux(pid) threads = get_thread_count_linux(pid) elif sys.platform == 'darwin': cwd = get_cwd_macos(pid) exe = get_exe_macos(pid) threads = get_thread_count_macos(pid) else: cwd = exe = threads = None if cwd: print(f"工作目录: {cwd}") if exe: print(f"可执行文件路径: {exe}") if threads is not None: print(f"线程数量: {threads}")
# 显示 TCP 连接(仅 Linux) if sys.platform == 'linux': print_tcp_connections(pid) else: print("\n注:非 Linux 系统不支持通过 /proc 获取 TCP 连接信息")
print("=" * 40)
if __name__ == '__main__': main()
全文完。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:生有可恋 hyang0 hyang0《pidinfo 根据进程 ID 获取进程信息》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论