查找并返回包含字符串的行块

Find and return blocks of lines containing a string

提问人:Magi 提问时间:11/14/2023 最后编辑:John KugelmanMagi 更新时间:11/16/2023 访问量:104

问:

我有一个以下类型的大文件:

key = asbh
some
lines
of
**text**

key = kafeia
some
more
**text**
and
additionally
more
**text**
and
more

key = lklfh
this
is
another
block

注意(如果重要):“key”行从不包含感兴趣的字符串(“text”)。

我将一个块称为一个以“key”开头的一行和下一行之间的所有行(所以在这个例子中,3个块)。我想返回所有包含字符串“text”的块。 即期望的输出:

key = asbh
some
lines
of
**text**

key = kafeia
some
more
**text**
and
additionally
more
**text**
and
more

我尝试了多种方法,我希望我的方向是正确的,但似乎无法让它工作。 这些是我的尝试:

  1. less myfile.txt | sed -n '/key/,/text/p' | less

    我相信这可能从它第一次看到“key”开始并继续前进(所以返回很多不相关的块),直到它看到“text”某处并停止。 这是受到这里类似问题的启发,但它没有拉动多个块的条件,也没有在块内匹配模式的条件。

  2. less myfile.txt | grep -Pzl '(?s)^key([^key]|\n)*text' | less

    我认为这可能会更好,如果我能让它工作,我可能会扩展它,因为它目前只尝试获取键和文本之间的文本(而不是直到下一个键)。

  3. 我试图理解 if 语句是如何工作的,特别是考虑到这个线程,但我是 unix 的新手,所以如果有人可以解释,我将不胜感激。

bash shell sed grep

评论

0赞 Hamid Molareza 11/14/2023
最好使用像 Python 这样的语言或至少一个 bash 脚本文件(允许编写更多命令)。您想建议一个 python/bash-script 解决方案吗?
0赞 Mark Setchell 11/14/2023
是否可以接受从下一个空行开始并停止所有行,而不是停在下一个键处?key
0赞 tshiono 11/14/2023
如果是您的选择,请尝试。awkawk -v RS= -v ORS="\n\n" '/^key/ && /text/' myfile.txt
3赞 tripleee 11/14/2023
顺便说一句,这是一个伟大的无用用用的更少
1赞 tripleee 11/14/2023
通常的反模式是当你可以说 or 并且 没有做任何有用的事情时。使用 instead 是错误的,因为它专门用于交互式检查文件内容的工具。cat foo | grep bargrep bar foogrep bar <foocatlesscatless

答:

0赞 Hamid Molareza 11/14/2023 #1

为了代码的整洁度和可读性,我建议使用 Python 或 bash 脚本文件。

with open('file.txt', 'r') as file:
    lines = file.readlines()

blocks = []
current_block = []

for line in lines:
    if line.startswith('key'):
        # Check if the current block contains 'text'
        if any('text' in block_line for block_line in current_block):
            blocks.extend(current_block)
        current_block = [line]
    else:
        current_block.append(line)

# Check the last block in case it ends with 'text'
if any('text' in block_line for block_line in current_block):
    blocks.extend(current_block)

# Print or use the blocks as needed
for block in blocks:
    print(block.strip())

如果坚持在一行中执行此操作,也可以使用以下代码:

awk '/^key/ { if (block ~ /text/) print block; block=""; } { block = block $0 RS } END { if (block ~ /text/) print block }' file.txt

我在几个文件上检查了这种方法,它是正确的。我希望它对你有用。

评论

0赞 Magi 11/14/2023
单行代码对我不起作用,我无法理解它的结构是如何工作的(比如 {},$0, RS 的全球组织),所以我无法重新工作。Python 我不想用于大文件(就像我的一样),最好直接工作。
0赞 Andrej Podzimek 11/14/2023 #2

Bash 可能不是最好的工具,但它肯定可以完成这项工作。以下代码段没有任何错误处理;这只是一个恰好适用于问题输入的示例。

read_blocks() {
  local -r needle="$1"
  local -n _blocks="$2"  # declare -A
  local -n _keys="$3"    # declare -a
  local -i needle_seen=0
  local line key='UNSET'
  _blocks=()
  _keys=()
  while IFS= read -r line; do
    if [[ "$line" = 'key = '* ]]; then
      ((needle_seen)) && _keys+=("$key") || unset '_blocks["$key"]'
      ((needle_seen = 0)) || :
      key="${line#key = }"
    else
      [[ "$line" = *"$needle"* ]] && ((++needle_seen)) || :
      _blocks["$key"]+="$line"$'\n'
    fi
  done
  ((needle_seen)) && _keys+=("$key") || unset '_blocks["$key"]'
}

declare -A blocks
declare -a keys
read_blocks 'text' 'blocks' 'keys' < /path/to/input

for key in "${keys[@]}"; do
  printf 'key = %s\n' "$key"
  printf '%s' "${blocks["$key"]}"
done

如果不需要保留块的顺序,那么你可以完全放弃数组,在写入输出时简单地迭代。keys${!blocks[@]}

2赞 potong 11/14/2023 #3

这可能对你有用 (GNU sed):

sed -n '/^key/!{H;$!d};x;/text/p' file

关闭隐式打印。-n

如果一行没有开始,请将其附加到保留空间并删除,除非它是最后一行。key

否则,交换到保留空间,如果集合匹配,则打印它。text

注意文件结束条件自然会下降到匹配条件。保持/模式空间在匹配时触发。key

评论

0赞 Magi 11/14/2023
谢谢!工作。最初,我尝试使用这个非常有用的 sed 教程(对于任何感兴趣的人)重新工作: grymoire.com/Unix/Sed.html 有一件事我不明白(尽管它有效!)是声明是如何组织的。这是我的理解范围:/matching string/ 如果不匹配,即 !,做 {append to hold, delete something?};如果匹配交换保留空间(稍后打印)/条件在这里,但不确定它是如何工作的/如果满足条件,则打印 特别是,如果我有条件,这是一些通用语法还是只是为了匹配特定字符串?
0赞 potong 11/14/2023
要查看程序的流程,请应用该选项并将 patten/hold 空格与输入行匹配。--debug
1赞 Paul Hodges 11/14/2023 #4

使用“段落”模式微不足道,因为您有一个空行分隔块。awk

$: awk -v RS= -v ORS='\n\n' /text/ file
key = asbh
some
lines
of
**text**

key = kafeia
some
more
**text**
and
additionally
more
**text**
and
more

我重新添加了显式换行符;请注意,这将在文件末尾附加一个,即使它不存在。

解释:

-v在运行中设置 ariables。
将 ecord 分隔符设置为空,启用“段落模式”,直到看到它看到一个空行,并将它读取的所有内容(是的,整个块)称为一个“记录”。
将 Utput ecord 分隔符设置为两个换行符,在段落模式读取删除空行后将其添加回去
扫描触发器字符串的几行的“记录”。如果它返回 true,由于我没有提供任何其他要执行的代码,它会打印该块。 (请注意,由于这是一个如此简单的检查,我不需要在它周围加上引号;我选择将它们关闭以减少噪音。YMMV.)
vRS=RSORS=$'\n\n'ORS/text/

补遗

刚刚注意到 tshiono 的评论比我早几个小时。
相应地修改了我自己的 - 到期的信用。

评论

1赞 Paul Hodges 11/16/2023
谢谢 Ed. 会更新。我只是有点喜欢 $'\n\n'。没有真正的原因。
1赞 Paul Hodges 11/16/2023
如果没有必要,不这样做的正当理由。另外 - 少输入一个字符,哈哈