如何使用 sed 查找第 n 个多行文本块

How to find the nth multiline block of text using sed

所以我有一个包含块的文件,如下所示:

menuentry ... {
....
....
}

....

menuentry ... {
....
....
}

我需要查看 bash 脚本中每个菜单项的内容。我对 sed 的经验非常有限,但通过 非常 详尽的搜索,我能够构建以下内容:

cat $file | sed '/^menuentry.*{/!d;x;s/^/x/;/x\{1\}/!{x;d};x;:a;n;/^}/!ba;q'

我可以用我想要的任何数字替换 \{1\} 以获得第 n 个块。这样工作正常,但问题是我需要迭代任意次数:

numEntries=$( egrep -c "^menuentry.*{" $file ) 
for i in $( seq 1 $numEntries); do

    i=$( echo $i | tr -d '\n' ) # A google search indicated sed  encounters problems
                                # when a substituted variable has a trailing return char

    # Get the nth entry block of text with the sed statement from before, 
    # but replace with variable $i
    entry=$( cat $file | sed '/^menuentry.*{/!d;x;s/^/x/;/x\{$i\}/!{x;d};x;:a;n;/^}/!ba;q')

    # Do some stuff with $entry #

done

我尝试了变量周围和 sed 语句周围的 quotes/double 引号和大括号的每种组合,无论采用哪种方式,我都会遇到某种错误。正如我所说,我对 sed 了解不多,而声明中的弗兰肯斯坦正是我从各种 google 搜索中设法拼凑而成的,所以任何帮助或解释将不胜感激!

TIA

我看到了问题。您正在尝试在单引号内使用 $i。 $i 将保留为 $i 正如我在下面发布的那样用 $i 周围的单引号更正它以便 bash 将其视为 3 个字符串

'one string'"$twostrings"'three strings'

#!/bin/bash
#filename:grubtest.sh

numEntries=$( egrep -c "^menuentry.*{" "" )
i=0 
while [ $i -lt $numEntries ]; do
    i=$(($i+1))
    # Get the nth entry block of text with the sed statement from before, 
    entry=$( cat "" | sed '/^menuentry.*{/!d;x;s/^/x/;/x\{'"$i"'\}/!{x;d};x;:a;n;/^}/!ba;q')

    # Do some stuff with $entry #
    echo $entry|cut -d\' -f2

done

当 运行 这样时,这返回了我的 grub 项目 ./grubtest.sh /etc/grub2.cfg

把这项工作分开怎么样?

首先搜索菜单项开始的行(由于个人原因,我这里使用grub,而不是grub2,根据您的需要调整):

entrystarts=($(sed -n '/^menuentry.*/=' /boot/grub/grub.cfg))

然后,在第二步中,从数组中为 n-th 条目选择一个起始值 ${entrystarts[$n]} 并从那里继续? Afaik,入口端很容易通过单个闭合花括号检测到。

for i in ${entrystarts[@]}
do 
    // your code here, proof of concept (note grub/grub2):
    sed -n "$i,/}/p" /boot/grub/grub.cfg
done

sed 用于在单行上进行简单替换,仅此而已,而 shell 循环操作文本非常缓慢且难以稳健地编写。发明 sed 和 shell 的人们还发明了 awk 来完成这样的任务:

awk -v RS= 'NR==3' file

将打印第 3 个 blank-line-separated 文本块,如您的问题所示。这将打印包含字符串 "foobar":

的每个块
awk -v RS= '/foobar/' file

您可能想做的任何其他事情都同样微不足道。

在任何 UNIX 机器上的任何 shell 中使用任何 awk,以上内容将有效、健壮和可移植地工作。例如:

$ cat file
menuentry first {
    Now is the Winter
    of our discontent
}

menuentry second {
    Wee sleekit cowrin
    timrous beastie,
    oh whit a panic's in
    thy breastie.
}

menuentry third {
    Twas the best of times
    Twas the worst of times
    Make up your damn mind
}

.

$ awk -v RS= 'NR==3' file
menuentry third {
    Twas the best of times
    Twas the worst of times
    Make up your damn mind
}

$ awk -v RS= 'NR==2' file
menuentry second {
    Wee sleekit cowrin
    timrous beastie,
    oh whit a panic's in
    thy breastie.
}

$ awk -v RS= '/beastie/' file
menuentry second {
    Wee sleekit cowrin
    timrous beastie,
    oh whit a panic's in
    they breastie.
}

如果您发现自己尝试使用 s、g 和 p(使用 -n)以外的 sed 命令来使用 sed s/old/new 和 sed and/or 之外的任何其他操作,那么您正在使用已过时的结构在 20 世纪 70 年代中期发明了 awk。

如果以上方法对您不起作用,请编辑您的问题以提供更具代表性的示例输入和预期输出。