解析命令输出
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 awk
和 ps
来自包 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
并且该列表实际上比不同说明符的实际数量大得多,因为 args
和 command
是 cmd
的别名——它显示了整个命令行——并且ucmd
和 ucomm
是 comm
的别名——显示可执行文件的名称。
这些字段可能包含白色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。
我正在尝试提取命令输出的参数。目前我有这个:
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 awk
和 ps
来自包 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
并且该列表实际上比不同说明符的实际数量大得多,因为 args
和 command
是 cmd
的别名——它显示了整个命令行——并且ucmd
和 ucomm
是 comm
的别名——显示可执行文件的名称。
这些字段可能包含白色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。