解析命令输出

Parse ouput of command

我正在尝试提取命令输出的参数。目前我有这个:

ps -eaf | grep javaagent

输出是这样的:

-Dweblogic.system.BootIdentityFile=/u06/app/oracle/admin/domains/omservices/servers/profiling03/data/nodemanager/boot.properties -Dweblogic.nodemanager.ServiceEnabled=true -Dweblogic.security.SSL.ignoreHostnameVerification=false -Dweblogic.ReverseDNSAllowed=false -javaagent:/u01/home/app/appdyn/AppDynamics/AppServerAgent/ver4.3.1.5/javaagent.jar -Dappdynamics.agent.applicationName=BFL_PE_Omnichannel -Dappdynamics.agent.tierName=CDLV_Profiling -Dappdynamics.agent.nodeName=profiling03 -Xms3g -Xmx3g -XX:MaxPermSize=756m -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:/u06/app/oracle/admin/logs/omservices/profiling03/gc.log -XX:-DisableExplicitGC -Djava.net.preferIPv4Stack=true -Dweblogic.MuxerClass=weblogic.socket.NIOSocketMuxer -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/u06/app/oracle/admin/logs/omservices/profiling03/dump -Dweblogic.Stdout=/u06/app/oracle/admin/logs/omservices/profiling03/profiling03.out -Dprofiling-services.configPath=/cyberbank/profiling/v2/config/ -Dorg.apache.cxf.Logger=org.apache.cxf.common.logging.Log4jLogger -Dprofiling-services.logPath=/u06/app/oracle/admin/logs/omservices/profiling03 -Duser.timezone=GMT-5 -Dweblogic.management.server=t3://ba410018-priv:4065 weblogic.Server

我要提取参数(我对这个路径感兴趣):

-javaagent:/u01/home/app/appdyn/AppDynamics/AppServerAgent/ver4.3.1.5/javaagent.jar

我用下面的命令做了这个:

ps -eaf | grep javaagent | cut -d " " -f XX

其中"XX"是XX所在的列。这个命令的问题是这个过程不会总是一样的。但是javaagent会一直在进程中。

简而言之,一些命令允许我在进程 X 中提取参数 javaagent。

只要您的 javaagent.jar 路径中没有 space,以下内容就应该有效:

ps -eaf | grep -Eo '-javaagent:[^ ]*'

它匹配从 -javaagent 选项开始到下一个 space 被排除的选项。使用 grep-o 标志使其 return 仅匹配部分而不是整行。

通常回答 shell 形式 "How do I parse the output of ...?" 问题的第一步是问替代问题 "How do I get [this information] without having to parse the output of a command?".

当然,一些 类型的解析几乎总是必要的,但是如果您可以将其简化为某些分隔符字符串的可靠拆分,那么您就很好地解决了问题licked,因为有很多方法可以在分隔符处拆分字符串。 (请参阅下面的其中一项。)

注意: 这个答案的其余部分假设一个合理的标准 Linux 安装 bash、Gnu awkps 来自包 procps-ng。它可以很容易地适应其他环境,但细节会使叙述过于复杂。

ps 命令带有一组很好的输出选项,可以很容易地自定义命令的输出。例如,如果你想查看 运行 进程的所有命令行,没有任何其他令人困惑的数据,你可以试试这个:

ps -e -ops=,cmd=

这将打印您系统上所有进程的 pid 和 command-line。 (如果你愿意,你可以使用 -ea,很多人都这样做。对于 ps 的 procps-ng 版本,它没有任何区别。)

输出规范 -o 包含一个 comma-separated 字段列表(其名称记录在标记为 "STANDARD FORMAT SPECIFIERS" 的部分的 man ps 中)。每个字段名后面可以有选择地跟一个 = 和一些文本(不能包含逗号);文本在 header 行中使用,这是第一行输出。仅使用没有文本的 = 表示该列没有 header,如果没有列有 header,则不会打印 header 行。这使 "parsing" 更容易。

可以打印的许多字段 ps 很容易解析,因为它们不能包含白色 space。事实上,联机帮助页列出了几个可以包含 whitespace:

的字段

The following user-defined format specifiers may contain spaces: args, cmd, comm, command, fname, ucmd, ucomm, lstart, bsdstart, start

并且该列表实际上比不同说明符的实际数量大得多,因为 argscommandcmd 的别名——它显示了整个命令行——并且ucmducommcomm 的别名——显示可执行文件的名称。

这些字段可能包含白色space,甚至换行符,这一事实肯定会使解析复杂化。在 command-line 参数的情况下,它实际上使解析变得不可能,因为无法区分包含 space 的 command-line 参数和两个连续的 command-line 参数space 在他们之间。理想情况下,我们需要一种明确的方法来为每个进程提取 command-line 参数。

虽然我们无法从 ps 获得此信息,但我们可以找到信息来源,ps 使用:/proc 文件系统。特别是,对于任何 pid,"file" /proc/PID/cmdline 包含 command-line 组件,每个组件都以 NUL 字节结尾。例如,我的 init 流程如下所示:

$ hd /proc/1/cmdline
00000000  2f 6c 69 62 2f 73 79 73  74 65 6d 64 2f 73 79 73  |/lib/systemd/sys|
00000010  74 65 6d 64 00 2d 2d 73  79 73 74 65 6d 00 2d 2d  |temd.--system.--|
00000020  64 65 73 65 72 69 61 6c  69 7a 65 00 31 38 00     |deserialize.18.|
0000002f

因为 command-line 选项不能包含 NUL 字节,所以这是完全明确的。

我们可以通过简单地遍历 /proc 目录来提取所有 运行 进程的所有命令行:

cat /proc/[1-9]*/cmdline

(我使用 [1-9]* 而不是 * 因为 /proc 文件系统中还有其他子目录。)

但这会产生 newline-separated 行的 NUL-separated 记录,这对于大多数实用程序来说并不是最方便的格式。 (此外,它重新引入了歧义,因为换行符可能是参数的一部分。)幸运的是,Gnu awk 乐于将 NUL 作为记录分隔符处理,并且具有正则表达式匹配功能。因此,要查找以 -javaagent: 开头的所有 运行 个进程的所有参数,我们可以使用以下 one-liner:

awk -v RS='[=13=]' '/^-javaagent:/' /proc/[1-9]*/cmdline

注:

我在上面的描述中跳过了几个细节。其中之一是 /proc/PID/cmdline 只包含 command-line 的前 4k。由于 java 调用通常很长,因此 command-line 的一部分可能会丢失。然而,由于 ps 使用 /proc 文件系统来获取它显示的信息,它会遇到同样的问题。我知道没有简单的解决方法。

另一个问题是 -ocmd= 中显示的数据可以被 运行 进程修改,如果它想以某种方式隐藏或重新处理它自己的 command-line。例如,如果您查看 chrome 进程的条目,您会看到参数是用 space 分隔的,而不是 NUL。