【问题标题】:combine awk commands output of multiples searches display in one line组合 awk 命令输出的多个搜索显示在一行中
【发布时间】:2017-10-28 01:19:49
【问题描述】:

我的输入如下

<connection name="test1" transport="tcp">
<LPort>host1:11111</hostPort>
<hostPort>host1:11111</hostPort>
<abcd> 1234

<connection name="test2" transport="tcp">
<hostPort>host2:22222</hostPort>
<GPort>host1:12111</hostPort>

<connection name="xyz1" transport="tcp">
<hostPort>host3:33333</hostPort>
<FPort>host1:12113</hostPort>
<efgi> 5678

<connection name="xyz2" transport="tcp">
<LPort>host1:12234</hostPort>
<hostPort>host4:4444</hostPort>

我希望我的输出如下:

test1  host1 1111
test2  host2 2222
xyz1   host3 3333
xyz2   host4 4444

为了解决这个问题,这就是我所做的并且它有效。但在我看来,一定有更好更简单的方法,我没有包括整个逻辑(数组);但是当我有多个时,我一直在使用这种方法 在文件中搜索并且它可以工作。我尝试使用 && 命令组合 awk 命令,但失败了。

以下是我的代码和逻辑的一部分 1)我 cat 文件 2)摆脱多余的字符并用空格替换使用sed 3)我取我想要的值并分配给数组值使用awk 请注意我没有包括其余的逻辑(但它有效) 简而言之,我做了一个 while 循环,然后将值分配给 2 或 3 个数组并在同一行打印它们以获得所需的输出

cat file  | grep -A5 connection  | sed s'/[:="><]/ /g' | awk '/name/ {print $3}'
cat file | grep -A5 connection  | sed s'/[:="><]/ /g' | awk '/hostPort/ {print $2 " " $3}'

如果可能,请提供不涉及使用 sed/awk 或任何其他方式将我的搜索条件存储在数组中的替代解决方案?

如果您能提供解决方案,请提供每个选项的详细信息;如果你可以的话。

谢谢

【问题讨论】:

  • 我建议使用 XML/HTML 解析器 (xmlstarlet, xmllint ...)。
  • @Cyrus,他们在这里无济于事(至少,他们应该将这种结构视为无效)-标签混乱(错位)。特别是:LPort, GPort, FPort, abcd ...
  • 嗨赛勒斯,感谢您的回复。我以前从未使用过解析器,我不确定它们是否适用于此。

标签: arrays linux bash awk sed


【解决方案1】:

强制提醒:假设您的输入是格式良好的 XML,使用 XML 解析器将提供更强大的解决方案(见底部)。

这是一个单一实用程序awk 解决方案:

awk -v RS= -F '<connection name="|<hostPort>' '
  {
    sub(/".*/, "", $2)
    split($3, tokens, /[:<]/)
    printf "%-6s %s %s\n", $2, tokens[1], tokens[2]
  }
' file
  • -v RS= 告诉awk段落将输入拆分为记录,其中段落是一系列非空行。

  • -F '&lt;connection name="|&lt;hostPort&gt;' 通过出现&lt;connection name=" 或(|) &lt;hostPort&gt; 将每个段落拆分为字段,因此感兴趣的数据将位于第二个的开始和第三个字段($2$3)。

  • sub(/".*/, "", $2) 从字段 2 中删除第一个 " 之后的所有内容,实际上只留下连接名称。

  • split($3, tokens, /[:&lt;]/) 通过:&lt; 的出现将第三个字段拆分为一个标记数组,在第一个数组元素中生成主机名,在第二个数组元素中生成端口。

  • printf "%-6s %s %s\n", $2, tokens[1], tokens[2] 打印输出行,将连接名称右填充至至少 6 个带空格的字符,如示例输出中所示;如果您只想用一个空格分隔输出字段,只需省略 -6


可选阅读:可在 shell 脚本中使用的 XML 解析实用程序 (CLI)

  • xmllint一些平台上预装

    • ma​​cOS/FreeBSD/PC-BSD(可能是其他 BSD 变体)
    • 一些 Linux 发行版:FedoraCentOS
    • 在其他情况下,可能会提供软件包;例如,在 Ubuntu 上:
      sudo apt-get install libxml2-utils
    • 警告:虽然xmllint 支持XPath 1.0 查询,但它几乎无法控制输出格式
  • 按需安装替代方案 - 优于 xmllint

    • xmlstarlet

      • xmlstarlet 功能强大且灵活,支持广泛的操作。

      • ma​​cOS:通过Homebrewbrew install xmlstarlet 安装

      • Linux:很可能它可以通过您平台的包管理器安装;例如,在基于 Debian 的发行版上,例如 Ubuntu
        sudo apt-get install xmlstarlet
      • Windows:从sourceforge手动下载并安装。
    • xidel

      • xidel 需要手动download and installation,但它的强大和灵活性弥补了这种不便。

      • 支持 Linuxma​​cOSWindows


以下是对比上面列出的 3 个实用程序的解决方案

假设file 中包含以下格式正确的 XML 文档 - 请注意 &lt;connection&gt; 元素现在如何包含在单个顶级 &lt;doc&gt; 元素中:

<doc>
  <connection name="test1" transport="tcp">
    <LPort>host1:11111</LPort>
    <hostPort>host1:11111</hostPort>
    <abcd>1234</abcd>
  </connection>

  <connection name="test2" transport="tcp">
    <hostPort>host2:22222</hostPort>
    <GPort>host1:12111</GPort>
  </connection>

  <connection name="xyz1" transport="tcp">
    <hostPort>host3:33333</hostPort>
    <FPort>host1:12113</FPort>
    <efgi>5678</efgi>
  </connection>

  <connection name="xyz2" transport="tcp">
    <LPort>host1:12234</LPort>
    <hostPort>host4:4444</hostPort>
  </connection>
</doc>

xmllint解决方案:

xmllint 缺乏对查询结果格式的控制需要一个重要的awk 辅助命令:

echo 'cat //connection/@name | //hostPort/text()' | xmllint --shell file | awk -F\" '
  NR % 2 { next }                  # skip separator lines
  NR % 4 == 2 { conn = $2; next }  # save connnection name
  { 
    split($0, tokens, ":")
    printf "%-6s %s %s\n", conn, tokens[1], tokens[2] 
  }
'

xmlstarlet解决方案:

xmlstarletsel 子命令通过在后台将选项转换为 XLST 模板来支持非常灵活的提取:

xmlstarlet sel -t  -m '//connection' -v 'str:align(@name, "      ")' \
           -o ' ' \
           -c 'str:replace(hostPort, ":", " ")' -n file

xidel解决方案:

xidel非常灵活,不仅支持XML,还支持HTML和JSON。

虽然它不支持 XLST,但它支持 XQuery,这是 XPath 的超集,具有类似 XSLT 的功能,可实现强大的转换。Reino 致敬。
然而,据我所知,没有填充功能,因此使用了 - 直接 - 辅助 awk 命令:

xidel file -q --xquery \
  'for $c in //connection return concat($c/@name, " ", replace($c/hostPort, ":", " "))' |
    awk '{ printf "%-6s %s %s\n", $1, $2, $3 }'

也就是说,XQuery 甚至支持用户定义的函数,所以你可以编写自己的填充函数:

xidel -q file --xquery '
  declare function pad($s as xs:string?) as xs:string 
  {
    substring(concat($s, "      "), 1, 6)
  }
  for $c in //connection return concat(pad($c/@name), " ", replace($c/hostPort, ":", " "))
'

【讨论】:

  • @Reino:我已将其更改为您现在看到的内容,因为它 (a) 适用于我发布的示例 XML 文档(请注意封闭的 &lt;doc&gt; 元素,它在您在链接页面中使用的文档)和(b)它适用于 Linux,这就是这个问题的意义所在(您的命令假定为 Windows)。至于为什么您的 cmets 消失了:我已将它们标记为 过时,因为其中提出的观点已纳入答案。虽然我无法亲自让 cmets 消失,但 版主 同意我的评估,这才是真正让 cmets 消失的原因。
  • @Reino:我已经将&lt;connection&gt; 元素包含在一个单一的顶级元素中更清楚了。您对等价的看法是正确的,但您需要注意问题的上下文。起初,在我研究 XQuery 之前,我盲目地按原样尝试了你的命令,没有意识到引用在 Unix 平台上不起作用。
【解决方案2】:

使用单一 sed 方法:

sed -n '/<connection/{N;N; s/<connection name="\([^"]*\)".*<hostPort>\([^:]*\):\([^<]*\).*/\1 \2 \3/p}' file

输出:

test1 host1 11111
test2 host2 22222
xyz1 host3 33333
xyz2 host4 4444

  • N;N; - 将接下来的 2 行附加到模式空间(包括换行符)

  • connection name="\([^"]*\) - 捕获连接名称

  • &lt;hostPort&gt;\([^:]*\):\([^&lt;]*\) - 捕获主机名端口号

【讨论】:

  • 嗨 Roman - 我确实尝试过使用它但是得到了以下错误 >>> 替换命令中的错误标志:'}'
  • @theuniverseisflat,检查你的命令是否有错别字。它工作正常
  • 我复制并粘贴了你的命令;但是我在我的mac电脑上没有工作,也许使用的shell有什么问题。但是我在工作中尝试它。再次感谢
  • @theuniverseisflat,正如您添加的 linux 标签 - 它可以在 linux 上运行。你没说什么关于 macOS 的事
  • 询问是否有超过 2 行引用你的命令 N;N;让我们说 3 或 5 ;你会如何改变这个?我以前没用过这个?你在什么外壳里?
【解决方案3】:

合并空行分隔的块并使用反向引用从每个块中提取所需的值:

sed '${/^$/!{H;s/.*//;};};/^$/!{H;d;};/^$/{x;s/^\n<connection name="\([^"]*\)".*<hostPort>\([^:]*\):\([^<]*\).*/\1 \2 \3/;};' file

【讨论】:

  • 嗨 SlePort 我尝试使用 ur 命令,但出现以下错误,您能否提供更多详细信息?猫文件 | sed '${/^$/!{H;s/.*//;}};/^$/!{H;d;};/^$/{x;s/^\n([^:]*):([^
  • 您必须在 BRE 模式下转义括号:\(\) 或使用 -E 标志:sed -E。而且你不需要将你的 sed 传送到cat。只需复制并粘贴我的 sed 命令即可进行测试。
猜你喜欢
  • 2019-05-12
  • 1970-01-01
  • 2019-04-20
  • 1970-01-01
  • 2020-01-07
  • 1970-01-01
  • 2014-08-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多