BASH: 使用grep/awk/sed提取变量数据

5

更新 需要澄清的是,Jon8RFC-LT和DOMAIN也仅是动态内容的通用示例,与IP地址和MAC地址相似;nmblookup根据IP地址检索并显示完全动态的内容。如果使用awk,则需要从nmblookup中提取4个动态值:IP、主机名/资产名称、域名和MAC地址。抱歉造成困扰,我已更新代码以使其更加清晰。

我已经搜索并使用我的Linux书籍几天了,但无法找到我需要的awk/gawk/grep/egrep/sed(我认为我需要其中一个或多个,优雅地)。在bash脚本中,我运行:

su_nmblookup=$(nmblookup -A $ipaddress)

这个函数返回

WARNING: The "idmap backend" option is deprecated
added interface eth0 ip=a07d::a07d:a07d:a07d:a07d%eth0 bcast=b57d::ffff:ffff:ffff:ffff%eth0 netmask=ffff:ffff:ffff:ffff::
added interface eth1 ip=b57d::b57d:b57d:b57d:b57d%eth1 bcast=a07d::ffff:ffff:ffff:ffff%eth1 netmask=ffff:ffff:ffff:ffff::
added interface eth0 ip=234.234.234.234 bcast=12.12.12.12 netmask=255.255.0.0
Socket opened.
 Looking up status of 123.123.123.123
    JON8RFC-LT <00> -         B <ACTIVE> 
    DOMAIN        <00> - <GROUP> B <ACTIVE> 
    JON8RFC-LT <20> -         B <ACTIVE> 
    DOMAIN        <1e> - <GROUP> B <ACTIVE> 

    MAC Address = 4F-A2-4F-A2-4F-A2

我最好的解决办法是使用以下代码进行砍掉:

display=${su_nmblookup/#*Looking/\Looking}
Looking up status of 123.123.123.123
    JON8RFC-LT <00> -         B <ACTIVE> 
    DOMAIN        <00> - <GROUP> B <ACTIVE> 
    JON8RFC-LT <20> -         B <ACTIVE> 
    DOMAIN        <1e> - <GROUP> B <ACTIVE> 

    MAC Address = 4F-A2-4F-A2-4F-A2

然而,我想知道如何返回这些清理过的格式之一。我想学习使用grep / awk / sed从这两个示例中提取数据的工作原理,其中一个保留格式,另一个仅使用换行符。由于格式和gt / lt符号的影响,即使在引用/编码方面,我也花了很多时间才能正常工作!

Looking up status of 123.123.123.123
    JON8RFC-LT
    DOMAIN
    4F-A2-4F-A2-4F-A2
或者,简单地说
JON8RFC-LT
DOMAIN
123.123.123.123
4F-A2-4F-A2-4F-A2
感谢您的帮助!

如果你使用awk,就不需要sed和/或grep。sed仅用于在单行上替换字符串的正则表达式。grep仅用于在文件中查找正则表达式或字符串并打印结果行。awk用于其他所有操作。 - Ed Morton
JON8RFC-LT 在输出中想要的是输入中的第一个 JON8RFC-LT,还是第二个,或者它们总是相同的?对于 DOMAIN 也是同样的问题。 - a5hk
@EdMorton 我不会说 sed 只能用于那个...尽管它远远是最常见的用途,但它实际上比那更有能力(虽然并不总是简单易懂/使用)... - twalberg
@twalberg sed 有时候也可以用来玩 code-golf ;-) - Digital Trauma
@twalberg,对于这个(它确实非常适合),我每天都在使用,sed是最好的选择。但是对于其他任何事情,您应该使用不同的工具,通常是awk。尽管sed能够做更多的事情,但生成的代码非常复杂,难以阅读、难以维护,而且通常难以解释。如果您需要处理跨越多行输入或正在考虑使用超过s、g和p(带有-n)sed构造的内容,则您正在使用错误的工具。 - Ed Morton
3个回答

4
假设将 nmblookup -A 123.123.123.123 的输出重定向到文件 input.txt
awk '/Looking up status of/ {print} /JON8RFC-LT/ {if(a!=1){print "\t"$1;a=1}} /DOMAIN/ {if(b!=1){print "\t"$1;b=1}} /MAC Address/ {print "\t"$4}' input.txt

更新:按照Etan Reisner的建议,内容进行了简化:

awk '/Looking up status of/ {print} /JON8RFC-LT/ && !a {print "\t"$1;a=1} /DOMAIN/ && !b {print "\t"$1;b=1} /MAC Address/ {print "\t"$4}' input.txt

输出:

 Looking up status of 123.123.123.123
    JON8RFC-LT
    DOMAIN
    4F-A2-4F-A2-4F-A2

为动态内容更新

awk '/Looking up status of/,/MAC Address/ {print; getline;print "\t"$1;getline;print "\t"$1;getline;getline;getline;getline;print "\t"$4;exit 0}' input.txt

假设在“查找...”后的两行中您需要第一个单词。然后忽略三行,接着打印MAC地址。

1
你不需要将这个放在文件中才能运行。awk 可以很好地处理管道输入。此外,在第一个片段的 /Looking .../ 情况下,{print} 动作是默认的,因此您不需要显式列出它。 - Etan Reisner
2
你也可以通过让 awk 在模式中执行 a/b 匹配来缩短代码(例如 /DOMAIN/ && !b {print "\t"$1;b=1})。 - Etan Reisner
@Etan Reisner,谢谢,我已经更新了答案。我通常使用print,因为它更易读。 - a5hk
谢谢!我稍微尝试了一下,但是无法弄清如何使用第三个版本提取主机名(JON8RFC-LT)和域名(DOMAIN)作为动态内容。 - Jon8RFC

3

我看到你想要一个 grep/awk/sed 的答案,但你可能会有兴趣知道,你所需要的可以完全通过 内置命令实现:

unset results
declare -A results
while read; do
    case $REPLY in
        *'Looking up status of '*) ip="${REPLY##* }";;
        *'MAC Address = '*) mac="${REPLY##* }";;
        *'    '*) tmp="${REPLY#    }"; results[${tmp%% *}]=1 ;;
    esac
done < <(nmblookup -A $ipaddress)
printf "%s\n" ${!results[@]}
echo $ip
echo $mac

这段代码片段可以放置在您现有的脚本中。
该脚本读取每行输入,并应用一个 case 开关以匹配您感兴趣的模式。每个模式都有自己的一组命令来格式化您需要的数据。对于以4个空格开头的行,我们使用bash关联数组来确保我们只得到每个 JON8RFC-LT 和 DOMAIN 行的一个。
请注意,关联数组需要 4.0或更高版本。

2
仅限于Bash 4.0及更高版本,因为需要使用declare -A来记录。 - Etan Reisner
谢谢!这看起来很不错,但不幸的是我希望将代码保留在一个文件中,以便部署到多台机器。 - Jon8RFC
@Jon8RFC 不需要多个脚本文件。我稍微编辑了答案,以展示您如何将其添加到现有的脚本文件中,假设该现有脚本以 #!/bin/bash 开头,并且您正在使用 bash 4.0 或更高版本。 - Digital Trauma
1
你可以使用选择性引用来使case分支更易读,例如: *'Looking up status of '*) - mklement0
@DigitalTrauma 感谢您提供这个学习机会,我从中受益匪浅。现在我明白了这些工具的强大之处,以后在导出和记录方面也会参考这个。 - Jon8RFC

2

这是一个带有注释的awk解决方案,它提供了两种输出格式 - 美化和原始格式 - 可以通过变量进行选择:

# Set this to:
#  * 1 for a "pretty" display with header line and indentation
#  * 0 for printing the raw data items only.
pretty=1

awk -v pretty=$pretty '
    # Skip lines before "Looking up ..."
  !startRow && /Looking up status of / { startRow=NR; }
  !startRow { next }
    # Parse the lines of interest relative to the "Looking up ..." row.
  NR==startRow { ip=$5; header=$0; next } # IP address
  NR==startRow+1 { nm=$1; next }          # name, e.g.: "JON8RFC-LT"
  NR==startRow+2 { dm=$1; next }          # domain, e.g.: "DOMAIN"
  /MAC Address =/ { ma=$4; exit }         # MAC address, e.g.: "4F-A2-4F-A2-4F-A2"
  END {         # all relevant lines processed; output result
    if (pretty) # print with header and indentation
      { print header; print "\t" nm; print "\t" dm; print "\t" ma }
    else        # print raw data items only
      { print nm; print dm; print ip; print ma }
  }' <(nmblookup -A $ipaddress)

一些快速指针:

  • -v pretty=$pretty 根据一个 shell 变量定义了一个 awk 变量;请注意,整个 awk 程序被用 单引号 括起来,以防止意外的 shell 扩展在 awk 程序内部发生,这应该被视为独立于 shell 的世界。
  • !startRow:如果尚未定义,则 awk 变量在数字/布尔上下文中默认为 0/false,因此此表达式在 startRow 设置为非零值之前计算为 false
  • /Looking up status of/ 是一个正则表达式,用于匹配当前输入行;NR 包含当前的基于 1 的行(行)编号。
  • next 跳过当前行的其余模式/操作,并继续到下一行。
  • NR==startRow 是一个模式,如果当前行的索引与存储在 startRow 中的值匹配,则评估为 true。
  • $1,例如,表示当前行的 第一个字段 - 默认情况下,awk 根据空格将行分成字段 - 从索引 1 开始,结束索引存储在变量 NF 中。
  • END 是一个特殊的模式,其关联的块在所有输入行都被处理后执行;请注意,前一个操作中的 exit 命令仍然导致处理 END 操作。
  • <(...)进程替换 的一个实例,它将任何命令的输出作为伪文件提供。

谢谢您加入这个。我对所有这些都非常新,我将探索您的代码并从中学习。 - Jon8RFC
@Jon8RFC 不客气;学习 awk 是非常值得的。我已经修改了我的答案,提供了一个仅使用 awk 的解决方案,并添加了更多的注释。 - mklement0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接