如何将表格导出到控制台?

193

我在显示一个包含n层嵌套表格的表格内容时遇到了问题。我希望能够通过 print 语句或其他快速且简单的方法将其输出到标准输出或控制台,但我无法想出如何实现。我正在寻找类似于使用gdb打印NSDictionary所得到的粗略等效结果。

20个回答

214

如果要求是“快速且简单”,我发现这个函数很有用。由于它可以递归打印嵌套表格,所以非常实用。虽然输出的格式不是最漂亮的,但对于调试来说,这样一个简单的函数很难被超越。

function dump(o)
   if type(o) == 'table' then
      local s = '{ '
      for k,v in pairs(o) do
         if type(k) ~= 'number' then k = '"'..k..'"' end
         s = s .. '['..k..'] = ' .. dump(v) .. ','
      end
      return s .. '} '
   else
      return tostring(o)
   end
end

例如

local people = {
   {
      name = "Fred",
      address = "16 Long Street",
      phone = "123456"
   },

   {
      name = "Wilma",
      address = "16 Long Street",
      phone = "123456"
   },

   {
      name = "Barney",
      address = "17 Long Street",
      phone = "123457"
   }

}

print("People:", dump(people))

生成以下输出:

People: { [1] = { ["address"] = 16 Long Street,["phone"] = 123456,["name"] = Fred,} ,[2] = { ["address"] = 16 Long Street,["phone"] = 123456,["name"] = Wilma,} ,[3] = { ["address"] = 17 Long Street,["phone"] = 123457,["name"] = Barney,} ,}


24
分享不需要外部库的内容,做得好! - Julian Knight
2
我没有写这个。我在某处找到它并修改了它,以产生一个可以打印的字符串。很想给原作者致谢。 - hookenz
@Herrgot - 那将是一个非常庞大的表格和许多嵌套。你无法超越这种简单性。它适用于大多数快速调试用例。 - hookenz
1
可能不是一个非常嵌套的表格,而是一个带有自引用的表格,在某个地方无限循环,这将导致堆栈溢出。 - MoonLite
1
为了方便在我的主代码之外进行调试,我添加了 elseif type(o) == "string" then return tostring("\"" .. o .. "\"")。现在我可以使用输出来测试硬编码表。 - Konrni
显示剩余3条评论

154
我知道这个问题已经被标记为已回答,但是让我在这里推销一下我的库。它叫做inspect.lua,你可以在这里找到它:https://github.com/kikito/inspect.lua
它只是一个单独的文件,你可以从任何其他文件中使用require进行调用。它返回一个函数,将任何Lua值转换为可读的字符串:
local inspect = require('inspect')

print(inspect({1,2,3})) -- {1, 2, 3}
print(inspect({a=1,b=2})
-- {
--   a = 1
--   b = 2
-- }

它可以正确缩进子表格,并正确处理“递归表”(包含对自身的引用)的情况,因此不会陷入无限循环。它以合理的方式对值进行排序。它还打印出metatable信息。

祝好!


1
也许你应该将你的库添加到Lua Wiki中(http://lua-users.org/wiki/TableSerialization)。我发现你的库还打印元表,而其他库则没有。 - Michal Kottman
2
已将 inspect.lua 添加到维基。 - kikito
8
@Hack-R,这个可以在luarocks上找到:luarocks install inspect - kikito
2
@JohnLee 注意,最近版本的库会尽力避免重复使用相同的表格。如果它们出现了多次,它会将它们压缩成<table n>。请参考https://github.com/kikito/inspect.lua/blob/master/spec/inspect_spec.lua#L171。 - kikito
1
做得很好,非常有用。 - N Altun
显示剩余4条评论

74

请随意浏览Lua关于表序列化的维基百科,其中列出了几种将表格转储到控制台的方式。

你只需要选择最适合你的方法。有很多种方法可以做到这一点,但我通常会使用Penlight中的一个方法:

> t = { a = { b = { c = "Hello world!", 1 }, 2, d = { 3 } } }
> require 'pl.pretty'.dump(t)
{
  a = {
    d = {
      3
    },
    b = {
      c = "Hello world!",
      1
    },
    2
  }
}

9
笨拙的新手问题:我如何安装像pl.pretty这样的扩展?如果我可以像gem install一样做一些事情而不用搞清楚tar球并找到在我的硬盘上放置东西的理想位置,那将是很好的。 有没有快速/无痛的“这样做”的方法? - Cliff
1
哎呀,我在发表上一条评论之前应该看看主页!安装并不像我希望的那样快速/无痛,但也不算太糟糕。 - Cliff
7
使用Cliff luarocks安装Penlight。 - zztczcx
penlight明确表示这不是一个序列化函数。 - jaiks
@jaiks 这是真的,也应该注意到。话虽如此,原问题要求“显示内容”,“将其转储到标准输出”和“快速而肮脏”,而Penlight(以及本问题中的其他答案)都能够满足这些要求。 - Michal Kottman
显示剩余2条评论

32

发现了这个:

-- Print contents of `tbl`, with indentation.
-- `indent` sets the initial level of indentation.
function tprint (tbl, indent)
  if not indent then indent = 0 end
  for k, v in pairs(tbl) do
    formatting = string.rep("  ", indent) .. k .. ": "
    if type(v) == "table" then
      print(formatting)
      tprint(v, indent+1)
    elseif type(v) == 'boolean' then
      print(formatting .. tostring(v))      
    else
      print(formatting .. v)
    end
  end
end

从这里开始 https://gist.github.com/ripter/4270799

对我来说效果很好...


25
我看过的大多数Lua打印表格函数在处理深度递归时会出现问题,并且往往会在递归太深时导致堆栈溢出。我编写的这个打印表格函数没有这个问题。由于它处理字符串拼接的方式,它还能处理非常大的表格。在我个人使用这个函数时,它能在约一秒钟内将63000行输出到文件中。
输出也保留了Lua语法,可以轻松修改脚本,通过将输出写入文件可实现简单的持久存储,但需要修改以允许格式化数字、布尔值、字符串和表格数据类型。
function print_table(node)
    local cache, stack, output = {},{},{}
    local depth = 1
    local output_str = "{\n"

    while true do
        local size = 0
        for k,v in pairs(node) do
            size = size + 1
        end

        local cur_index = 1
        for k,v in pairs(node) do
            if (cache[node] == nil) or (cur_index >= cache[node]) then

                if (string.find(output_str,"}",output_str:len())) then
                    output_str = output_str .. ",\n"
                elseif not (string.find(output_str,"\n",output_str:len())) then
                    output_str = output_str .. "\n"
                end

                -- This is necessary for working with HUGE tables otherwise we run out of memory using concat on huge strings
                table.insert(output,output_str)
                output_str = ""

                local key
                if (type(k) == "number" or type(k) == "boolean") then
                    key = "["..tostring(k).."]"
                else
                    key = "['"..tostring(k).."']"
                end

                if (type(v) == "number" or type(v) == "boolean") then
                    output_str = output_str .. string.rep('\t',depth) .. key .. " = "..tostring(v)
                elseif (type(v) == "table") then
                    output_str = output_str .. string.rep('\t',depth) .. key .. " = {\n"
                    table.insert(stack,node)
                    table.insert(stack,v)
                    cache[node] = cur_index+1
                    break
                else
                    output_str = output_str .. string.rep('\t',depth) .. key .. " = '"..tostring(v).."'"
                end

                if (cur_index == size) then
                    output_str = output_str .. "\n" .. string.rep('\t',depth-1) .. "}"
                else
                    output_str = output_str .. ","
                end
            else
                -- close the table
                if (cur_index == size) then
                    output_str = output_str .. "\n" .. string.rep('\t',depth-1) .. "}"
                end
            end

            cur_index = cur_index + 1
        end

        if (size == 0) then
            output_str = output_str .. "\n" .. string.rep('\t',depth-1) .. "}"
        end

        if (#stack > 0) then
            node = stack[#stack]
            stack[#stack] = nil
            depth = cache[node] == nil and depth + 1 or depth - 1
        else
            break
        end
    end

    -- This is necessary for working with HUGE tables otherwise we run out of memory using concat on huge strings
    table.insert(output,output_str)
    output_str = table.concat(output)

    print(output_str)
end

这里有一个例子:

local t = {
    ["abe"] = {1,2,3,4,5},
    "string1",
    50,
    ["depth1"] = { ["depth2"] = { ["depth3"] = { ["depth4"] = { ["depth5"] = { ["depth6"] = { ["depth7"]= { ["depth8"] = { ["depth9"] = { ["depth10"] = {1000}, 900}, 800},700},600},500}, 400 }, 300}, 200}, 100},
    ["ted"] = {true,false,"some text"},
    "string2",
    [function() return end] = function() return end,
    75
}

print_table(t)

输出:

{
    [1] = 'string1',
    [2] = 50,
    [3] = 'string2',
    [4] = 75,
    ['abe'] = {
        [1] = 1,
        [2] = 2,
        [3] = 3,
        [4] = 4,
        [5] = 5
    },
    ['function: 06472B70'] = 'function: 06472A98',
    ['depth1'] = {
        [1] = 100,
        ['depth2'] = {
            [1] = 200,
            ['depth3'] = {
                [1] = 300,
                ['depth4'] = {
                    [1] = 400,
                    ['depth5'] = {
                        [1] = 500,
                        ['depth6'] = {
                            [1] = 600,
                            ['depth7'] = {
                                [1] = 700,
                                ['depth8'] = {
                                    [1] = 800,
                                    ['depth9'] = {
                                        [1] = 900,
                                        ['depth10'] = {
                                            [1] = 1000
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    ['ted'] = {
        [1] = true,
        [2] = false,
        [3] = 'some text'
    }
}

9

如前所述,您需要自己编写代码。以下是我简单的版本(非常基础):

function tprint (t, s)
    for k, v in pairs(t) do
        local kfmt = '["' .. tostring(k) ..'"]'
        if type(k) ~= 'string' then
            kfmt = '[' .. k .. ']'
        end
        local vfmt = '"'.. tostring(v) ..'"'
        if type(v) == 'table' then
            tprint(v, (s or '')..kfmt)
        else
            if type(v) ~= 'string' then
                vfmt = tostring(v)
            end
            print(type(t)..(s or '')..kfmt..' = '..vfmt)
        end
    end
end

示例:

local mytbl = { ['1']="a", 2, 3, b="c", t={d=1} }
tprint(mytbl)

输出结果(Lua 5.0):

table[1] = 2
table[2] = 3
table["1"] = "a"
table["t"]["d"] = 1
table["b"] = "c"

1
非常原创!我喜欢它。 - Jack G

4
我制作了这个版本以打印带缩进的表格。可能可以扩展为递归工作。
function printtable(table, indent)
  print(tostring(table) .. '\n')
  for index, value in pairs(table) do 
    print('    ' .. tostring(index) .. ' : ' .. tostring(value) .. '\n')
  end
end

4
我使用自己的函数打印表格内容,但不确定它在你的环境中的表现如何:
---A helper function to print a table's contents.
---@param tbl table @The table to print.
---@param depth number @The depth of sub-tables to traverse through and print.
---@param n number @Do NOT manually set this. This controls formatting through recursion.
function PrintTable(tbl, depth, n)
  n = n or 0;
  depth = depth or 5;

  if (depth == 0) then
      print(string.rep(' ', n).."...");
      return;
  end

  if (n == 0) then
      print(" ");
  end

  for key, value in pairs(tbl) do
      if (key and type(key) == "number" or type(key) == "string") then
          key = string.format("[\"%s\"]", key);

          if (type(value) == "table") then
              if (next(value)) then
                  print(string.rep(' ', n)..key.." = {");
                  PrintTable(value, depth - 1, n + 4);
                  print(string.rep(' ', n).."},");
              else
                  print(string.rep(' ', n)..key.." = {},");
              end
          else
              if (type(value) == "string") then
                  value = string.format("\"%s\"", value);
              else
                  value = tostring(value);
              end

              print(string.rep(' ', n)..key.." = "..value..",");
          end
      end
  end

  if (n == 0) then
      print(" ");
  end
end

4

最简单的方法,具有循环引用处理功能:

function dump(t, indent, done)
    done = done or {}
    indent = indent or 0

    done[t] = true

    for key, value in pairs(t) do
        print(string.rep("\t", indent))

        if type(value) == "table" and not done[value] then
            done[value] = true
            print(key, ":\n")

            dump(value, indent + 2, done)
            done[value] = nil
        else
            print(key, "\t=\t", value, "\n")
        end
    end
end

1
PrintTable() 定义在哪里? - Mike Lyons
1
@MikeLyons 这是一个递归函数,我把PrintTable错放到了dump的位置 >.< - Francisco

3

我想提出2种解决方案:一种是快而简单的方法,另一种则能正确转义所有的键和值,但规模较大。

简单且快速的解决方案(仅适用于“安全”输入):

local function format_any_value(obj, buffer)
    local _type = type(obj)
    if _type == "table" then
        buffer[#buffer + 1] = '{"'
        for key, value in next, obj, nil do
            buffer[#buffer + 1] = tostring(key) .. '":'
            format_any_value(value, buffer)
            buffer[#buffer + 1] = ',"'
        end
        buffer[#buffer] = '}' -- note the overwrite
    elseif _type == "string" then
        buffer[#buffer + 1] = '"' .. obj .. '"'
    elseif _type == "boolean" or _type == "number" then
        buffer[#buffer + 1] = tostring(obj)
    else
        buffer[#buffer + 1] = '"???' .. _type .. '???"'
    end
end

使用方法:

local function format_as_json(obj)
    if obj == nil then return "null" else
        local buffer = {}
        format_any_value(obj, buffer)
        return table.concat(buffer)
    end
end

local function print_as_json(obj)
    print(_format_as_json(obj))
end

print_as_json {1, 2, 3}
print_as_json(nil)
print_as_json("string")
print_as_json {[1] = 1, [2] = 2, three = { { true } }, four = "four"}

带有键/值转义的正确解决方案

我用纯Lua编写了一个小型库,专门用于这个特定的用例:https://github.com/vn971/fast_json_encode

或者特别是包括格式化和打印机的这个文件:https://github.com/vn971/fast_json_encode/blob/master/json_format.lua


这实际上就是我在寻找的,尽管它并不是原帖所问的特定问题。感谢提供如此简单的解决方案。这使得在像NodeMCU这样空间受限的Lua环境中使用变得更加容易。 - Kevin Ghadyani
如果键或值包含引号、换行符或控制字符,则会生成无效的JSON。 - CherryDT
@CherryDT,好的评论,谢谢。我应该明确指出它根本不会转义任何内容。如果您需要正确但速度较慢的解决方案,请使用以下方式:https://github.com/vn971/fast_json_encode/blob/master/json_format.lua 如果它对您有用或无法正常工作,我很乐意听取您的反馈。 - VasiliNovikov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接