2

我有一个巨大的文件,一个非常大的文件(大约 600+MB 的文本)。事实上他们是jsons。每个json都在一条新线上,只有几种口味。

他们看起来像:

{"text":{"some nested words":"Some more","something else":"Yeah more stuff","some list":["itemA","ItemB","itemEtc"]},"One last object":{"a thing":"and it's value"}}

而我想要的是它通过 sed,吸出文本,并为每一对下一个缩进,所以我们得到:

{
 -{
--[]
 -}
--{}
 -}
}

(我不是 100% 确定我在输出中得到了正确的嵌套,我认为它是正确的)

这可能吗?我看到了this,这是我能想象到的最接近的,但它去掉了括号两个。

我注意到那里的答案使用了分支,所以我认为我需要它,并且我需要执行某种s/pattern/newline+tab/space/g类型命令,但我不知道如何或做什么...

有人可以帮忙吗?它不必是纯粹sed的,但这是首选。

4

4 回答 4

5

这不会很漂亮... =) 这是我作为 sed 脚本的解决方案。请注意,它要求第一行通知 shell 如何调用 sed 来执行我们的脚本。如您所见,使用了“-n”标志,因此我们强制 sed 仅打印我们通过“p”或“P”命令明确命令它的内容。“-f”选项告诉 sed 从文件中读取命令,该文件的名称跟在选项后面。由于脚本的文件名由 shell 连接到最终命令中,它将正确地从脚本中读取命令(即,如果您运行“./myscript.sed”,shell 将执行“/bin/sed -nf myscript .sed”)。

#!/bin/sed -nf

s/[^][{}]//g

t loop
: loop

t dummy
: dummy

s/^\s*[[{]/&/
t open

s/^\s*[]}]/&\
/
t close
d

: open
s/^\(\s*\)[[]\s*[]]/\1[]\
/
s/^\(\s*\)[{]\s*[}]/\1{}\
/

t will_loop
b only_open

: will_loop
P
s/.*\n//
b loop

: only_open

s/^\s*[[{]/&\
/
P
s/.*\n//
s/[][{}]/ &/g
b loop

: close
s/ \([][{}]\)/\1/g
P
s/.*\n//
b loop

在开始之前,我们必须首先将所有内容都剥离成方括号和方括号。这是第一个“s”命令的职责。它告诉 sed 用任何内容替换不是括号或方括号的每个字符,即。去掉它。请注意,匹配中的方括号表示要匹配的一组字符,但是当其中的第一个字符是“^”时,它实际上将匹配除“^”之后指定的字符之外的任何字符。因为我们想要匹配右方括号,并且我们需要用方括号关闭要忽略的字符组,所以我们通过将右方括号设置为“^”之后的第一个字符来告诉组中应该包含右方括号。然后我们可以指定其余的字符:左方括号,打开方括号和右方括号(忽略字符组:“][{}”),然后用右方括号关闭组。我试图在这里详细说明,因为这可能会造成混淆。

现在来看看实际的逻辑。该算法非常简单:

while line isn't empty
    if line starts with optional spaces followed by [ or {
        if after the [ or { there are optional spaces followed by a respective ] or }
            print the respective pair, with only the indentation spaces, followed by a newline
        else
            print the opening square or normal bracket, followed by a newline
            remove what was printed from the pattern space (a.k.a. the buffer)
            add a space before every open or close bracket (normal or square)
        end-if
    else
        remove a space before every open or close bracket (normal or square)
        print the closing square or normal bracket, followed by a newline
        remove what was printed from the pattern space
    end-if
end-while

但是有几个怪癖。首先,sed 不直接支持“while”循环或“if”语句。我们能得到的最接近的是“b”和“t”命令。“b”命令分支(跳转)到一个预定义的标签,类似于 C 的 goto 语句。“t”也分支到预定义的标签,但前提是自当前行上运行的脚本开始或自上一个“t”命令以来发生了替换。标签是用“:”命令编写的。

因为很可能第一个命令实际上至少执行了一次替换,所以它后面的第一个“t”命令将导致分支。因为我们需要测试一些其他替换,我们需要确保下一个“t”命令不会因为第一个命令而自动成功。这就是为什么我们从它上面一行的“t”命令开始(即,无论它是否分支,它仍然会在同一点继续),所以我们可以“重置”“t”使用的内部标志命令。

因为“循环”标签会从至少一个“b”命令分支到,所以在执行“b”时可能会设置相同的标志,因为只有“t”命令可以清除它。因此,我们需要执行相同的解决方法来重置标志,这次使用“虚拟”标签。

我们现在通过检查是否存在开方括号或开闭括号来开始算法。因为我们只想测试它们是否存在,所以我们必须将匹配替换为它自己,也就是“&”代表的意思,如果匹配成功,sed 会自动为“t”命令设置内部标志。如果匹配成功,我们使用“t”命令分支到“open”标签。

如果不成功,我们需要看看我们是否匹配一个封闭的正方形或正常的括号。该命令几乎相同,但现在我们在右括号后添加一个换行符。我们通过在放置匹配项的位置(即“&”之后)添加一个转义的换行符(即反斜杠后跟一个实际的换行符)来做到这一点。与上面类似,如果匹配成功,我们使用“t”命令跳转到“close”标签。如果不成功,我们将认为该行无效,并立即清空模式空间(缓冲区)并在下一行重新启动脚本,所有这些都使用单个“d”命令。

输入“open”标签,我们将首先处理一对匹配的开闭括号的情况。如果我们匹配它们,我们将在它们之前打印缩进空格,它们之间没有任何空格,并以换行符结尾。每种类型的括号对(方形或普通)都有一个特定的命令,但它们是类似的。因为我们必须跟踪有多少缩进空间,所以我们必须将它们存储在一个特殊的“变量”中。我们通过使用组捕获来做到这一点,它将存储从“(”之后开始并在“)”之前结束的匹配部分。因此,我们使用它来捕获行首之后和左括号之前的空格。然后我们继续匹配左括号,后跟空格和相应的右括号。当我们编写替换时,我们确保使用特殊变量“\1”重新插入空格,该变量包含匹配中第一个组捕获存储的数据。然后我们编写相应的一对开括号和右括号以及转义的换行符。

如果我们设法进行了任何替换,我们必须打印我们刚刚编写的内容,将其从模式空间中删除并使用该行的剩余字符重新开始循环。因此,我们首先使用“t”命令分支到“will_loop”标签。否则,我们分支到“only_open”标签,它将处理只有一个开括号的情况,没有连续的相应闭括号。

在“will_loop”标签内,我们只需使用“P”命令打印模式空间中的所有内容,直到第一个换行符(我们手动添加)。然后,我们手动删除第一个换行符之前的所有内容,以便我们继续处理该行的其余部分。这类似于“D”命令的作用,但不需要重新启动脚本的执行。最后,我们再次分支到循环的开头。

在“only_open”标签内,我们以与之前类似的方式匹配一个左括号,但现在我们重写它并附加一个换行符。然后我们打印该行并将其从模式空间中删除。现在我们将所有括号(打开或关闭,方括号或普通括号)替换为前面的单个空格字符。这样我们就可以增加缩进。最后,我们再次分支到循环的开头。

最后的标签“close”将处理一个右括号。我们首先删除括号前的每个空格,有效地减少缩进。为此,我们需要使用捕获,因为虽然我们想匹配空格和后面的括号,但我们只想写回括号。最后,将所有内容打印到我们在输入“关闭”标签之前手动添加的换行符,从模式空间中删除我们打印的内容并重新启动循环。

一些观察:

  1. 这不会检查代码的语法正确性(即 {{[}] 将被接受)
  2. 它会在遇到括号时添加和删除缩进,无论它们的类型如何。这意味着当它添加缩进时,即使遇到的右括号不是同一类型,它也会删除它。

希望这会有所帮助,很抱歉这篇长文=)

于 2012-09-26T03:21:55.707 回答
3

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

sed 's/[^][{}]//g;s/./&\n/g;s/.$//' file |
sed -r '/[[{]/{G;s/(.)\n(.*)/\2\1/;x;s/^/\t/;x;b};x;s/.//;x;G;s/(.)\n(.*)/\2\1/' |
sed -r '$!N;s/((\{).*(\}))|((\[).*(\]))/\2\5\3\6/;P;D'

解释:

  1. 第一个 sed 命令在自己的行上生成一个大括号/方括号流
  2. 第二个 sed 命令缩进每个括号
  3. 第三个 sed 命令将这些成对的括号减少为一行

如果您对正确缩进的括号感到满意,则可以省略第三个命令。

于 2012-09-26T09:18:45.617 回答
2

我认为您的预期输出应如下所示:

{
-{
--[]
-}
-{
-}
}

这是一种使用方法GNU awk

awk -f script.awk file.txt

内容script.awk

BEGIN {
    FS=""
    flag = 0
}

{
    for (i=1; i<=NF; i++) {
        if ($i == "{" || $i == "[") {
            flag = flag + 1
            build_tree(flag, $i)
            printf (flag <=2) ? "\n" : ""
        }
        if ($i == "}" || $i == "]") {
            flag = flag - 1
            printf (flag >= 2) ? $i : \
                build_tree(flag + 1, $i); \
                printf "\n"
        }
    }
}

function build_tree (num, brace) {
    for (j=1; j<=num - 1; j++) {
        printf "-"
    }
    printf brace
}
于 2012-09-26T03:46:51.487 回答
2

我知道这是一个古老的线程,反正没人在看,但现在有一个更简单的方法。

猫文件.txt | jq'。| sed 's/ //-/g' | tr -dc '[[]{}()]\n-' | sed '/^-*$/d'

第一个 sed 中有 2 个空格。

于 2015-05-18T22:55:20.257 回答