awk:隔离一个代码块,然后迭代多个代码块(如果存在)

awk:隔离一个代码块,然后迭代多个代码块(如果存在)

awk † 能否找到“ ”的第 n 次迭代{并返回直到下一个“ }”字符的所有内容?

[编辑:是的......底部埃德·莫顿的解决方案]

† 我一直假设 awk 是完成这项工作的正确工具。欢迎其他想法。

我需要隔离数百个文件中的文本块。有些文件只有一个块,但其他文件则包含数十个。

样本:

$ cat samp2.txt
//////////////////////////////////
// North Carolina office
// satellite branch
//////////////////////////////////
   {
   first   "John"
   last    "Doe"
   address "163 Main Street"
   age     "25"
   gender  "male"
   }

最好将>当前块放入临时文件中,以便脚本可以在处理下一个块之前对其进行操作。无论如何,它们最终都会出现在单独的文件中。

我怀疑可以给 awk 一个索引来查找第 n 个匹配项。 bash 脚本可以管理循环和迭代。

我已经接近了

$ awk '/\{/{flag=1;next}/\}/{flag=0}flag' samp2.txt 
   first   "John"
   last    "Doe"
   address "163 Main Street"
   age     "25"
   gender  "male"

然而,由于上述操作对整个文件进行操作,因此它不适用于包含多个块的文件(例如下面)。无论任何文件中有多少块,我都需要每个块都分开需单独处理。

有些文件包含注释,但许多文件不包含注释——没有标准。我丢弃了它们,但不一致意味着不能依赖评论来跟踪我们的位置。唯一给出的是花括号(和行分隔)。

文本始终以换行符分隔,但块之间并不总是空行。数据对各不相同,因此这不是一个简单的grep 5 lines and proceed解决方案。

$ cat samp3.txt 
//GROUP1
{
first       "John"
address     "124 Main Street"
last    "Jones"
special     "supervisor"
age "35"
gender      "male"
}

//The fourth group
{
first       "John"
address     "125 Main Street"
last    "Jacob"
age "30"
gender      "male"
}
{
first       "John"
address     "523 Main Street"
last    "Jingle"
age "40"
gender      "male"
}

我上面的 awk 语句贯穿所有组,将它们全部合并成一个大段落。

$ awk '/\{/{flag=1;next}/\}/{flag=0}flag' samp3.txt
first       "John"
address     "124 Main Street"
last    "Jones"
special     "supervisor"
age "35"
gender      "male"
first       "John"
address     "125 Main Street"
last    "Jacob"
age "30"
gender      "male"
first       "John"
address     "523 Main Street"
last    "Jingle"
age "40"
gender      "male"

我需要告诉 awk 查找第 n 个“ {”,然后}分别转储到第 n 个“”,如下所示:

first       "John"
address     "124 Main Street"
last    "Jones"
special     "supervisor"
age "35"
gender      "male"
 (awk exits, bash script does its thing)

first       "John"
address     "125 Main Street"
last    "Jacob"
age "30"
gender      "male"
 (awk exits, bash script does its thing)

first       "John"
address     "523 Main Street"
last    "Jingle"
age "40"
gender      "male"
 (awk exits, bash script does its thing)

 [etc]

其意图类似于第 n 个 " { .+ }" 的非贪婪正则表达式匹配。
有了这个,可能有一个更聪明的 perl 解决方案吗?

TIA。

这段代码得到了我所需要的。改编自埃德·莫顿的回答。

awk -v n=$LoopVariable -v RS='}' 'NR==n{gsub(/.*\{\r?\n|\n$/,""); print}' $SourceFile

编辑:输入确实帮助我将问题与我需要的分开。谢谢你。


我发现了 一些 SE问题看起来很相似,但如果这些包含我的解决方案,我对 awk 的了解不够,无法看到其中的联系。

答案1

我没有在你的问题中看到预期的输出,所以我不确定,但你确实说过,Can awk † find the nth iteration of a "{" and return everything up to the next "}" character?这就是你想要做的(使用任何 awk 并假设}{不能出现在你的输入中的其他任何地方):

$ awk -v n=2 -v RS='}' 'NR==n{gsub(/.*\{\n|\n$/,""); print}' samp3.txt
first       "John"
address     "125 Main Street"
last    "Jacob"
age "30"
gender      "male"

如果你想在 shell 循环中调用它:

$ for i in {1..3}; do
    awk -v n="$i" -v RS='}' 'NR==n{gsub(/.*\{\n|\n$/,""); print}' samp3.txt
    echo "-----"
done
first       "John"
address     "124 Main Street"
last    "Jones"
special     "supervisor"
age "35"
gender      "male"
-----
first       "John"
address     "125 Main Street"
last    "Jacob"
age "30"
gender      "male"
-----
first       "John"
address     "523 Main Street"
last    "Jingle"
age "40"
gender      "male"
-----

但几乎可以肯定,有一种更好的方法可以完成您想做的任何事情,而不是在循环中多次调用 awk,例如,调用 awk 一次以终止符打印每个块},然后将其读入 shell 数组以进行进一步处理:

$ readarray -d '}' -t arr < <(awk 'BEGIN{RS=ORS="}"} {gsub(/.*\{\n|\n$/,"")} $0~/[^[:space:]]/' samp3.txt)
$ for i in "${arr[@]}"; do printf '%s\n' "$i"; echo "-----"; done
first       "John"
address     "124 Main Street"
last    "Jones"
special     "supervisor"
age "35"
gender      "male"
-----
first       "John"
address     "125 Main Street"
last    "Jacob"
age "30"
gender      "male"
-----
first       "John"
address     "523 Main Street"
last    "Jingle"
age "40"
gender      "male"
-----

但实际上,无论您在 shell 循环中执行什么操作,也应该在对 awk 的一次调用中完成。

答案2

我的代码做出的假设可能不正确,这意味着它在许多情况下可能会失败。可能还可以使用更有效的解决方案。

假设1每个GROUP块都由换行符分隔

假设2您希望在每个块执行一个操作

假设3每个GROUP块都会递增(如果不是,您最终可能会得到很多空文件。)

for i in {1..5}; do 
  awk -F"\n" -v RS="" -v inc="GROUP$i" '$0~inc{printf( "%s\n", $0); next}' $inputfile | sed  '/\/\|{\|}/d' > output$i.txt ; 
done

您的示例有GROUP1&4,我添加了一个GROUP5并编写了一个for循环,以从 1-5 的范围递增。该范围将在穿过块时用作关键GROUP。如果组较多,可以相应增加范围。

awk将在循环中使用来提取块。sed将清理(awk可以一次完成所有这些,但我仍在学习),然后将每个块写入其自己的输出文件,与GROUP块的编号相匹配。

输入文件

//GROUP1
{
first       "John"
address     "124 Main Street"
last    "Jones"
special     "supervisor"
age "35"
gender      "male"
}

//GROUP4
{
first       "John"
address     "125 Main Street"
last    "Jacob"
age "30"
gender      "male"
}
{
first       "John"
address     "523 Main Street"
last    "Jingle"
age "40"
gender      "male"
}

//GROUP5
{
first       "Maria"
address     "188 John Street"
last    "Phones"
special     "Supervisors supervisor"
age "35"
gender      "Female"
}

输出

cat output1.txt
first       "John"
address     "124 Main Street"
last    "Jones"
special     "supervisor"
age "35"
gender      "male"

cat output4.txt
first       "John"
address     "125 Main Street"
last    "Jacob"
age "30"
gender      "male"
first       "John"
address     "523 Main Street"
last    "Jingle"
age "40"
gender      "male"

cat output5.txt
first       "Maria"
address     "188 John Street"
last    "Phones"
special     "Supervisors supervisor"
age "35"
gender      "Female"

答案3

你就快到了......稍微调整一下你的代码就会得到单独的块

awk -v n="$loopVar" '/\{/{f=1;++i;next} /\}/{f=0} i==n&&f' file

注意事项:-

  • /\{/将匹配任何地方的左大括号。
  • 稍微好一点的是:NF==1&&$1=="{"
  • 右大括号也一样。
  • 在 awk 之前,通过实用程序运行输入文件dos2unix以清除回车符\r

相关内容