如何使用 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。
如果以上方法对您不起作用,请编辑您的问题以提供更具代表性的示例输入和预期输出。
所以我有一个包含块的文件,如下所示:
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。
如果以上方法对您不起作用,请编辑您的问题以提供更具代表性的示例输入和预期输出。