如何搜索该行上的匹配项,然后搜索不匹配项,但包含匹配行的上下文行?

如何搜索该行上的匹配项,然后搜索不匹配项,但包含匹配行的上下文行?

假设我有一个文件包含:

⟫ cat schema.rb 
  create_table "things", id: :serial, force: :cascade do |t|
    t.string "other_column"
    # ...
    t.datetime "created_at"
  end

  create_table "users", id: :serial, force: :cascade do |t|
    t.citext "email"
    # ...
    t.datetime "created_at", precision: 0
  end

我想找到所有匹配的行created_at但是不是匹配precision:。这很容易:

⟫ grep created_at schema.rb 
    t.datetime "created_at"
    t.datetime "created_at", precision: 0

⟫ grep created_at schema.rb | grep -v precision:
    t.datetime "created_at"

但是如果我想获取匹配行的一些上下文行,以便我可以看到create_table它们出现在哪个块中,该怎么办?在最后添加-C/标志为时已晚,因为第一个 grep 已经删除了所有上下文行。-Bgrep -v

⟫ grep created_at schema.rb | grep -v precision: -B3
    t.datetime "created_at"

但将其添加到第一个grep还为时过早,因为grep -v仅删除其匹配行,而不删除其匹配行周围的上下文行:

⟫ grep created_at -B3 schema.rb | grep -v precision: -B3
  create_table "things", id: :serial, force: :cascade do |t|
    t.string "other_column"
    # ...
    t.datetime "created_at"
--
  create_table "users", id: :serial, force: :cascade do |t|
    t.citext "email"
    # ...

有什么方法可以让它只包含第一行中匹配行的上下文行grep(或者等效地,grep -v删除其匹配行周围的上下文行)?

  create_table "things", id: :serial, force: :cascade do |t|
    t.string "other_column"
    # ...
    t.datetime "created_at"

或者是否有另一个命令可以为我执行此操作?

sed也许是一个简单的脚本——如果它需要的不仅仅是一个简单的 sed脚本,我也可以将其写入,ruby以便更容易阅读和维护)。

答案1

如果我将 2 个命令链接在一起,我认为我尝试做的事情是不可能的grep(因为上下文行与每个单独的grep命令相关)。

我突然想到,消极的前瞻可能正是我想要的。然后这一切都可以通过一个grep命令来完成。

令我惊讶的是,看起来 GNUgrep实际上确实支持正则表达式后向/前向 — 但前提是您使用该--perl-regex选项。

这是一个grep命令,它给了我我正在寻找的东西:

⟫ grep --perl-regexp 'created_at(?!(.*precision:))' schema.rb -B3
  create_table "things", id: :serial, force: :cascade do |t|
    t.string "other_column"
    # ...
    t.datetime "created_at"

答案2

awk解决方案

$ awk '/create_table/,/created_at/&&!/precision:/' schema.rb
  create_table "things", id: :serial, force: :cascade do |t|
    t.string "other_column"
    # ...
    t.datetime "created_at"
  create_table "users", id: :serial, force: :cascade do |t|
    t.citext "email"
    # ...
    t.datetime "created_at", precision: 0
  end
$

以下这些都没有实现确切地你想要什么,但我会把它们扔进去,以防万一在某种程度上有用。

打印 create_table 行和匹配的 sed 解决方案

查找 create_table 行,如果找到,则存储在保存缓冲区“h”中。查找created_at,后跟精度,如果找到,则忽略它。单独查找created_at,如果找到,则打印它。

$ sed -n '/create_table/h;/created_at.*precision:/d;/created_at/{H;g;p}' schema.rb
  create_table "things", id: :serial, force: :cascade do |t|
    t.datetime "created_at"
$

打印 create_table 行和匹配的 awk 解决方案

如果您特别想要针对匹配的 create_table 行,您可以使用

$ awk '/create_table/{t=$0}/created_at/&&!/precision:/{print t"\n"$0}' schema.rb
  create_table "things", id: :serial, force: :cascade do |t|
    t.datetime "created_at"
$

打印表格的 awk 解决方案

更进一步,如果您只想要其中包含匹配行的表名称,您可以使用

$ awk '/create_table/{t=$2}/created_at/&&!/precision:/{print substr(t,2,length(t)-3)}' schema.rb
things
$

答案3

难道简单地反转两个greps 就可以做你想要的事情,消除precision前面的行,这样它们就不会与created_at第二个中的行匹配吗grep

grep -v precision schema.rb | grep -A1 -B3 created_at 
create_table "things", id: :serial, force: :cascade do |t|
   t.string "other_column"
   # ...
   t.datetime "created_at"
 end

相关内容