【问题标题】:command egrep to extract one line if find the same word如果找到相同的单词,命令 egrep 提取一行
【发布时间】:2018-11-02 14:59:10
【问题描述】:

我会告诉你这个命令的输出:

[root@test ~]# df -P -k -t xfs | egrep '*/PATH whichI don't have/*'

当我运行我的命令时,我有以下输出:

[root@test ~]# df -P -k -t xfs | egrep '*/PATH whichI don't have/*'
10.20.30.40:/var/contain/name1  3877121024 215982080 3661138944       6% /fofo
10.20.30.50:/var/beta/name2  3877121024 215982080 3661138944       6% /fofo
10.20.30.40:/var/contain/name2  3877121024 215982080 3661138944       6% /fofo
10.20.30.50:/var/beta/toto  3877121024 215982080 3661138944       6% /fofo
10.20.30.60:/var/alpha/name2  3877121024 215982080 3661138944       6% /fofo

如果我只找到一次相同的路径,我只想从不同的其他行中提取一行。所以我想从多行中只提取一行。预期输出:

[root@test ~]# df -P -k -t xfs | egrep '*/PATH whichI don't have/*'  
10.20.30.40:/var/contain/name1  3877121024 215982080 3661138944       6% /fofo
10.20.30.50:/var/beta/name2  3877121024 215982080 3661138944       6% /fofo
10.20.30.60:/var/alpha/name2  3877121024 215982080 3661138944       6% /fofo

如果我找到相同的路径,我只想从多行中提取一行。 IP地址不相同,如果多行包含相同的地址,我想只保留一行。希望对你有帮助

谢谢。

【问题讨论】:

  • 预期输出中的/var/contain/name2/var/beta/toto/var/alpha/name2 行发生了什么变化?它们是如何被省略的?数字是否都与显示的相同——在路径名之前的 IP 地址和名称之后的计数等?它们不能同时安装在/fofo 上吗?您的 MCVE (minimal reproducible example) 确实需要最少(5 行即可),但它也需要足够现实,以便我们知道什么是重要的。
  • 我更改了 IP 地址,对于我来说,当我有相同的 IP 地址时,我只想保留该地址的第一行(根据我的问题)
  • 可以超过5行,行数其变量。

标签: regex linux bash shell


【解决方案1】:

我认为grep 不适合这项工作,而 Awk 是更好的选择(也可以使用 Perl 或 Python,毫无疑问也可以使用其他脚本语言)。

您似乎想要每个 IP 地址的第一条记录,即日志格式中第一个冒号之前的字段。这表明您需要:

awk -F: '!($1 in a) { print; a[$1] = 1 }'

给定问题中的输入显示,输出为:

10.20.30.40:/var/contain/name1  3877121024 215982080 3661138944       6% /fofo
10.20.30.50:/var/beta/name2  3877121024 215982080 3661138944       6% /fofo
10.20.30.60:/var/alpha/name2  3877121024 215982080 3661138944       6% /fofo

你能解释一下这个命令的作用吗?

Awk 使用可以将字符串作为下标的关联数组。 -F: 选项意味着 Awk 将行拆分为冒号处的字段,因此 $1 是第一个冒号之前的文本(IP 地址),并且(在本例中)$2 是第一个冒号之后的所有文本— 而$0 是整个输入行。 awk 程序是一系列“模式-动作”对(或“表达式”或“条件”加上“动作”对)。如果明确指定该操作,则用大括号括起来(如果未指定,则默认为print $0 — 打印输入行)。如果不指定模式,则相当于匹配所有行。

在这个程序中,条件是!($1 in a),它检查$1是否作为下标出现在数组a中;如果没有出现下标,则整个表达式的计算结果为 true。当条件为真时,采取行动。打印(隐式$0),并将a[$1] 设置为1,这样如果再次出现相同的IP 地址,条件将评估为假,从而防止IP 地址重复。

如果您想要最后一个条目而不是第一个条目,您可以使用变体方案,其中每一行都将保存在数组中的正确条目中:{ a[$1] = $0 },然后您将拥有一个 END 模式输入完成后运行:END { for (i in a) print a[i] }。主题变化无穷。


如果我想做同样的事情,但基于 IP 地址后面的单词而不是地址 IP (/word/)。

很大程度上取决于您如何定义“单词”(或 '/word/')。为了让我的生活更轻松,我打算将一个单词视为一组连续的非空白字符。字段分隔符可以是正则表达式,所以我将使用[: ] 来分割冒号或空格; IP 地址后面的单词就是$2。脚本几乎没有变化:

awk -F '[: ]' '!($2 in a) { print; a[$2] = 1 }'

由于冒号后面的名称在样本数据中都不同,所以输出中出现了所有 5 行。但是,如果您多次在同一个文件上运行该脚本(在显示的脚本之后添加data data,其中文件data 包含您的示例输入)并且您在输出中只得到一份行副本,这令人放心.

【讨论】:

  • 它有效,你能解释一下你的命令行吗?如果我想做同样的事情,但如果可能的话,基于 IP 地址后面的单词而不是地址 IP (/word/) 怎么样。我不能使用 Perl 或 Python,因为我有一个很大的 bash 脚本,而且它应该只在 bash 中。谢谢你:)
  • @Hamadagm:我添加了对 Awk 脚本作用的解释。
  • @Jonathan Leffler:感谢您的解决方案和慷慨的解释:)
  • 我不知道为什么从 Bash 脚本运行 Perl 或 Python 与从 Bash 脚本运行 Grep 或 Awk 有很大不同,但就这样吧。我还谈到了你评论的后半部分(问题扩展)。请注意,您尚未准确定义“单词”的含义,并且符号 /word/ 并不能帮助我理解。我对你的意思做了一个合理的猜测——数据中 IP 地址之后出现的整个路径名/var/xyz/pqr——但即使这是错误的,你仍然应该能够接受这些想法并将它们应用于你的真实要求。
  • 是的,我只是在您回答之前尝试了 $2,如果我想在我的 IP 地址之后使用参数,它会起作用。我和你学到了一个新东西,它是“awk 程序”,它解决了我的问题,谢谢 :) @Jonathab Leffler
【解决方案2】:

好吧,假设你只对 /x/y 部分感兴趣,我想你可以这样做:

df -P -k -t xfs > tmpFile
cat tmpFile |cut -d: -f2|cut -d/ -f1-3|sort -u > tmpFile2
while read line; do grep $line tmpFile|head -1; done < tmpFile2

对于您上面提供的数据,输出将是

10.20.30.40:/var/alpha/name2  3877121024 215982080 3661138944       6% /fofo
10.20.30.40:/var/beta/name2  3877121024 215982080 3661138944       6% /fofo
10.20.30.40:/var/contain/name1  3877121024 215982080 3661138944       6% /fofo

希望这会有所帮助。

【讨论】:

  • 不,我不想只提取第一行,我想提取多于一行。
  • 当时我不确定我是否理解您的要求,抱歉。你能指定输入和假想的输出吗?
  • 对不起,我的问题和我的命令不好,我修改了我的问题,希望现在很容易理解。
  • 我也修改了我的答案......希望就是这样,让我知道。如果您需要更详细的内容,可能无法使用简单的命令行来完成。
  • 当我逐行运行你的答案时它不起作用,我将创建一个 bash 脚本。
猜你喜欢
  • 1970-01-01
  • 2012-02-24
  • 1970-01-01
  • 2019-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多