【问题标题】:How can I split a large CSV file into multiple files of rougly equal size using bash tools alone?如何仅使用 bash 工具将大型 CSV 文件拆分为大小大致相等的多个文件?
【发布时间】:2017-11-02 05:23:57
【问题描述】:

请注意,CSV 文件的每个单元格中可能有也可能没有多个换行符,并且每个拆分文件也必须是有效的 CSV 文件。

我尝试过使用 split,但是,如果我按行数拆分,它没有考虑到 CSV 可以在字段内有换行符,如果我按文件大小拆分,它有时会剪切最后一行文件分成两半,这意味着它不再是有效的 CSV 文件。

你可以在这里找到一个测试文件: https://pastebin.com/raw/pw9PF9U1

看起来像这样:

post_title,tax:wcpv_product_vendors,post_content
Product title 1,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"
Product title 2,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"
Product title 3,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"
Product title 4,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"
Product title 5,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"
Product title 6,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"
Product title 7,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"
Product title 8,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"
Product title 9,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"

另请注意,当我在 vim 中打开 csv 时,它的每一行末尾都有一个 ^M 符号。这可能有助于正确拆分。

【问题讨论】:

  • 请发布带有“字段内换行符”的数据样本,并提供预期的测试输出。少于 10 条记录应该没问题。
  • 您好!我在原始问题中添加了一个示例文件。 You can view it here.
  • ^M 只是 vim 显示的 CRLF (DOS) 行结束。知道你应该通过dos2unix 运行它是很有用的,而不是用于拆分文件。

标签: bash csv split


【解决方案1】:

这是 awk 中的一个。您在一个文件中提供文件名和最大“行数”(例如-v m=3),它会在不以&lt; 开头的行上拆分文件(基于您的数据)所以基本上是标题和产品标题行:

$ awk -v m=3 'NR==1{j=0}{if($0!~/^</){i++;if(i>m){i=1;j++}};print > "split-" j}' file
$ ls -1rt
split-3
split-2
split-1
split-0
$ cat split-3
Product title 9,Sample,"<div class=""productdetails"">
<h2 style=""margin: 0px 0px 15px; line-height: 1.2; text-align: center;"">Title</h2>
<p style=""color: #333333; margin: 0px; font-size: 13px; line-height: 23.1111px; padding: 0px; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS';""><strong>Features:</strong></p>
<ul style=""padding: 0px 40px; margin: 0px; color: #333333; font-family: sans-serif, Arial, Verdana, 'Trebuchet MS'; font-size: 13px; line-height: 20.8px;"">
<li style=""list-style: none;"">Testing testing</li>
<li style=""list-style: none;"">One two three</li>
</ul>
</div>"

解释:

awk -v m=3 '           # provide m
NR==1 {                # on the first record
    j=0                # set j to 0
}
{
    if($0!~/^</) {     # when a line not starting with a < is met
        i++            # increase line counter
        if(i>m) {      # if line counter exceeds max
            i=1        # reset it back to 1
            j++        # split file name index
        }
    }
    print > "split-" j # output
}' file

【讨论】:

  • 我接受这个作为答案,因为它非常适合我提供的示例,但是,在我要拆分的实际文件中,新行不一定以“
【解决方案2】:

将 file.csv 拆分为 5 个文件

split -n 5 file.csv

【讨论】:

  • 感谢您的回复,很遗憾这不起作用,因为它没有考虑到行需要在 CSV 文件中完成。
  • @Luke split -n l/5 file.csv 怎么样?额外的 l 表达式可防止在行内拆分
  • 几乎@etopylight - 我只是试了一下。不幸的是,它并没有很好地工作,因为它没有考虑到 CSV 的每个字段中可以有多行。运行 split -n l/5 file.csv 会拆分文件,但是拆分后的 CSV 文件的第一行和最后一行位于不正确的列中。
  • @Luke 感谢您的反馈。是否可以获得“每个字段内的多行”csv的样本?做一些测试对我们很有帮助
  • 您好!我在原始问题中添加了一个示例文件。 You can view it here.
【解决方案3】:

如果您需要支持嵌入的换行符, 那么单独使用 Bash 就没有简单的方法来做到这一点。 否则split 可能是一个不错的选择。

可以在 Bash 中实现 CSV 解析器(您想要的方言), 但是对于一个脆弱的解决方案来说,这将是很多工作。

最好不要为此使用 Bash, 但是其他一些具有良好库支持的语言可以正确解析 CSV。 比如 Python, 其中包含一个csv 包。

【讨论】:

  • 好吧 - Python 可能是要走的路。
【解决方案4】:

这是一种仍然使split 可用的方法。

这里的动机是使用空字节字符\0而不是换行符\n作为分割的记录分隔符。

首先,我们可以使用sed在每行不以&lt;开头的开头添加\0

sed 's/^[^<]/\x0&/' file.csv > file_tmp.csv

接下来,我们可以照常使用split

split -n l/5 -t '\0' --filter='sed 's/\x0//g' > $FILE.csv' file_tmp.csv split_
  • -n l/5 将文件拆分为大约 5 等份而不拆分记录
  • -t '\0' 使用空字节字符作为记录分隔符
  • --filter='sed 's/\x0//g' &gt; $FILE.csv' 删除拆分文件中的所有空字节字符

【讨论】:

  • 这是一个很好的答案 - 它非常适合我提供的测试文件。不幸的是,在我要拆分的真实文件中,产品描述中的新行不一定以“
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-06
  • 1970-01-01
  • 2017-01-27
  • 2022-01-16
  • 2013-04-05
  • 2011-01-02
  • 1970-01-01
相关资源
最近更新 更多