grep 文本文件中的数字范围

grep 文本文件中的数字范围

我在文本文件中有以下文本

$ cat test
20180618:
20180619:
20180620:
20180621:
20180622:
20180623:
20180624:

我尝试使用 grep 查找如下数字范围,

$ grep 201806{19..21} test
grep: 20180619: No such file or directory
grep: 20180620: No such file or directory
grep: 20180621: No such file or directory

我在 ZSH 和 bash 上都遇到上述错误。看起来 grep 将搜索字符串作为文件。

我尝试过另一种方式:

$ grep 201806* test       
zsh: no matches found: 201806*

我只在 ZSH 上收到该错误。在 ZSH 中使用的正确方法是什么*以及如何告诉 grep 到 grep 数字范围?

答案1

是的,grep只对待它的第一的默认情况下,参数作为正则表达式。

这意味着

grep {1..9} file

扩展到

grep 1 2 3 4 5 6 7 8 9 file

将调用grepwith1作为表达式来匹配其他操作数,并且这些其他操作数预计是文件名。

你的另一个命令:

grep 201806* test

这将尝试201806*作为文件名通配模式进行匹配。201806当前目录中没有名称以 开头的文件,因此zshshell 无法扩展该模式并给出错误消息no matches found

在其他类似 Bourne 的 shell 中,如果模式未与任何文件名匹配,它将保持未展开状态并用作grep.当表达式201806*被视为正则表达式时,会匹配20180后跟零个或多个6字符,例如2018066666

相反,您可能想要构造一个正则表达式来匹配您的范围:

grep -E '201806(19|20|21)' test

或者

grep -E '201806(19|2[01])' test

需要-E理解表达式中的(交替)(这种交替使其成为扩展的正则表达式)grep|


您还可以从大括号扩展构造正则表达式:

set -- {19..21}
re=$( IFS='|'; printf '201806(%s)' "$*" )

grep -E "$re" test

这将首先将位置参数 、 和 ,设置$1$2范围$3内所需的数字。然后,该变量re将被设置为将替换为由 分隔的这些数字的201806(%s)位置。printf%s|

grep调用将用作201806(19|20|21)正则表达式。

答案2

grep 201806{19..21} test

由 shell 扩展为:

grep 20180619 20180620 20180621 test

这可以理解为在 3 个文件中grep查找、和。201806192018062020180621test

如果你把它改成:

grep -e201806{19..21} test

然后扩展到:

grep -e20180619 -e20180620 -e20180621 test

其中给出了在 中搜索的3 个e表达式。greptest

或者你可以这样做:

printf '%s\n' 201806{19..21} | grep -f - test

我们将表达式作为多行输入传递给grep(对于某些实现,您可能需要/dev/stdin代替-)。

具体来说zsh,你还可以做到:

numbers=({19..21} 25 31)
grep -E "201801(${(j:|:)numbers})" test

我们使用(j:|:)参数扩展标志将数组元素与|(扩展正则表达式交替运算符)连接起来,以便它可以用作 ERE。

或者您可以使用以下命令将该数组绑定到正则表达式标量:

$ typeset -T re numbers '|'
$ numbers=({19..21} 25 31)
$ echo $re
19|20|21|25|31

虽然正则表达式通常没有数字范围匹配功能,但zsh模式(在extendedglob功能上与正则表达式等效)可以使用<x-y>运算符(仅适用于十进制数字序列):

print -rl -- ${(M)${(f)"$(<test)"}:#*201806<19-21>*}

答案3

不带引号的字符串在执行命令之前由 shell 解释,在您的情况下,您尝试的命令将扩展为grep 20180619 20180620 20180621 test

$ echo grep 201806{19..21} test
grep 20180619 20180620 20180621 test

一种解决方法是指定正则表达式替换:

$ grep -E '201806(19|20|21)' test
20180619:
20180620:
20180621:

您可以使用正则表达式构造数字范围,但这并不容易。看https://www.regular-expressions.info/numericranges.html欲了解详情


另一种选择是使用awk

$ awk -F: '$1>=20180619 && $1<=20180621' ip.txt
20180619:
20180620:
20180621:

在这里,我们分割线:,然后将第一个字段$1与所需的范围进行比较

答案4

  1. POSIXshell (no bash) 与 utils:

    seq 20180618 20180624 | grep -f - test
    
  2. numgrep:

    numgrep '/20180618..20180624/' < test
    

相关内容