使用命令行将文本文件拆分为多个较小的文本文件

107

我有多个文本文件,每个文件大约有 100,000 行,我想将它们分割成每个文件 5,000 行的小型文本文件。

我使用了以下代码:

split -l 5000 filename.txt

那会创建文件:

xaa
xab
aac
xad
xbe
aaf

没有扩展名的文件。我只想给它们起一个类似于以下的名称:

file01.txt
file02.txt
file03.txt
file04.txt

如果不可能,我只想让它们有“.txt”扩展名。


2
你在哪个平台上?你谈论的是split(一个Unix / Linux实用程序),但标记为Windows的batch-file - Mark Setchell
1
马克,我在使用Windows系统,但已经安装了Cygwin Bash Shell,因此可以访问split/csplit。 - ashleybee97
@MarkSetchell,是的,我是。 - ashleybee97
Ashleybee97,你找到任何答案了吗? - Deepak Jangir
可能是批处理文件拆分.csv文件的重复问题 - sancho.s ReinstateMonicaCellio
1
PowerShell可以将此答案嵌入到批处理文件中,详情请参考这个链接。在批处理文件中使用PowerShell可以实现文本文件的拆分操作。 - sancho.s ReinstateMonicaCellio
10个回答

159

我知道这个问题很久以前就被问过了,但是我很惊讶没有人给出最直接的Unix答案:

split -l 5000 -d --additional-suffix=.txt $FileName file
  • -l 5000: 将文件分割成每个文件包含 5,000 行的文件。
  • -d: 数字后缀。这将使后缀默认从 00 到 99,而不是从 aa 到 zz。
  • --additional-suffix: 允许您指定后缀,例如扩展名。
  • $FileName: 要拆分的文件的名称。
  • file: 添加到生成文件的前缀。

像往常一样,查看 man split 获取更多详细信息。

对于Mac来说,默认版本的 split 比较简化。您可以使用以下命令安装 GNU 版本。(请参阅此问题以获取更多 GNU 工具

brew install coreutils

然后,您可以通过将 split 替换为 gsplit 来执行上述命令。有关详细信息,请查看 man gsplit


3
如果我可以加100分的话,我一定会这么做!有了您发布的语法,我能够在大约0.3秒的时间内将一个大于380M的文件拆分成10M个小文件。 - bakoyaro
2
似乎“-d”和“--additional-suffix”选项不再受支持(OSX 10.12.6)。 - Stefano Munarini
4
对于Mac电脑,您可以使用brew install coreutils安装GNU版本的split,并将上面的命令中的split替换为gsplit - ursan
你如何使用分隔符代替行数? - AGrush
@AGrush,我不确定你的使用情况是什么,但我认为你可以使用“-t”标志,它会在用户指定的分隔符上进行拆分,而不是在换行符上进行拆分。然后,您可以使用“-l”标志来指定要将多少个拆分组合在一起放入输出文件中。 - ursan
在 Windows 10 上,如果您已安装了 WSL,则可以挂载 Windows 目录并使用此 split 命令。使用以下命令进入 Windows 目录:cd /mnt/c/ - Michał Stochmal

22

这里有一个C#的例子(因为这就是我搜索的内容)。我需要将一个大约有1.75亿行的23GB csv文件进行分割以便于查看。我将其分成了每个文件一百万行。这段代码在我的电脑上用了约5分钟来完成:

var list = new List<string>();
var fileSuffix = 0;

using (var file = File.OpenRead(@"D:\Temp\file.csv"))
using (var reader = new StreamReader(file))
{
    while (!reader.EndOfStream)
    {
        list.Add(reader.ReadLine());

        if (list.Count >= 1000000)
        {
            File.WriteAllLines(@"D:\Temp\split" + (++fileSuffix) + ".csv", list);
            list = new List<string>();
        }
    }
}

File.WriteAllLines(@"D:\Temp\split" + (++fileSuffix) + ".csv", list);

2
你基本上可以把它扔进LINQPad中,然后随心所欲地调整。无需编译任何东西。好的解决方案。 - Zachary Dow

15
@ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET /a fcount=100
SET /a llimit=5000
SET /a lcount=%llimit%
FOR /f "usebackqdelims=" %%a IN ("%sourcedir%\q25249516.txt") DO (
 CALL :select
 FOR /f "tokens=1*delims==" %%b IN ('set dfile') DO IF /i "%%b"=="dfile" >>"%%c" ECHO(%%a
)
GOTO :EOF
:select
SET /a lcount+=1
IF %lcount% lss %llimit% GOTO :EOF
SET /a lcount=0
SET /a fcount+=1
SET "dfile=%sourcedir%\file%fcount:~-2%.txt"
GOTO :EOF

这里是一个本地的Windows批处理程序,应该可以完成任务。

我不会说它会很快(每个5K行输出文件少于2分钟),也不会说它能免疫批处理的字符敏感性。实际上,它取决于您的目标数据的特点。

我在测试中使用了一个名为q25249516.txt的文件,其中包含100K行数据。


修订后的更快版本

REM

@ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET /a fcount=199
SET /a llimit=5000
SET /a lcount=%llimit%
FOR /f "usebackqdelims=" %%a IN ("%sourcedir%\q25249516.txt") DO (
 CALL :select
 >>"%sourcedir%\file$$.txt" ECHO(%%a
)
SET /a lcount=%llimit%
:select
SET /a lcount+=1
IF %lcount% lss %llimit% GOTO :EOF
SET /a lcount=0
SET /a fcount+=1
MOVE /y "%sourcedir%\file$$.txt" "%sourcedir%\file%fcount:~-2%.txt" >NUL 2>nul
GOTO :EOF

请注意,我在测试中使用了llimit为50000。如果llimit*100大于文件中的行数,则会覆盖早期的文件编号(通过将fcount设置为1999并在文件重命名行中使用~3代替~2来修复)。


1 MB 花费了 5 分钟太长时间。 - shareef
@shareef:所需时间应该取决于文件中的行数,而不是文件大小。我不确定你指的是1Mb还是1M行。我在最新版本上进行的测试是1M行和11Mb长。 - Magoo
这很好,但它在每行末尾留下了一个空白行。有什么方法可以防止这种情况发生吗? - Arya
@arya:我不理解“每行末尾有一个空白行”的意思。行尾是Windows标准的CRLF,输出中没有空行。也许你正在使用一个将CR和LF都计算为新行的实用程序? - Magoo

8
这款“文件分割器”Windows命令行程序很好用:https://github.com/dubasdey/File-Splitter 它是开源的,简单易用,有文档支持,经过验证,对我很有帮助。
示例:
fsplit -split 50 mb mylargefile.txt

这个工具很理想,但它会用“灌木”替换非ASCII字符 : )。只是提醒大家注意这个问题。 - Michał Stochmal
1
@Michał Stochmal:在这个工具的文档中提到:...按大小拆分会产生二进制文件...因此您必须按行数拆分。 - user2590805

8

你也许可以使用 awk 实现以下操作

awk '{outfile=sprintf("file%02d.txt",NR/5000+1);print > outfile}' yourfile

基本上,它通过将记录号(NR)除以5000,加1,取整数并在2个位置处填充零来计算输出文件的名称。
默认情况下,当您没有指定其他任何内容时,awk会打印整个输入记录。因此,print > outfile将整个输入记录写入输出文件。
由于您正在Windows上运行,您不能使用单引号,因为它不喜欢那样。我认为您必须将脚本放在一个文件中,然后告诉awk使用该文件,类似于这样:
awk -f script.awk yourfile

script.awk 将包含以下脚本:

{outfile=sprintf("file%02d.txt",NR/5000+1);print > outfile}

或者,您可以这样做:
awk "{outfile=sprintf(\"file%02d.txt\",NR/5000+1);print > outfile}" yourfile

2
这将使第一个文件比其他文件少一行。正确的公式是(NR-1)/5000+1 - David Balažic

7

语法看起来像:

$ split [OPTION] [INPUT [PREFIX]] 

其中prefix是PREFIXaa、PREFIXab等,只需使用正确的前缀即可完成操作,或者使用mv进行重命名。我认为$ mv * *.txt可以实现此功能,但请先在小范围内进行测试。

:)


我喜欢这个,但是后缀丢失了。你有什么想法吗?如何保留后缀。 - cV2

5
我的需求有些不同。我经常使用逗号分隔和制表符分隔的ASCII文件,其中单个行是单个数据记录。而且它们非常大,所以我需要将它们分成可管理的部分(同时保留标题行)。
因此,我回到了我的经典VBScript方法,并拼凑了一个小的.vbs脚本,可以在任何Windows计算机上运行(它会自动由Window的WScript.exe脚本主机引擎执行)。
这种方法的好处是它使用文本流,因此底层数据并没有加载到内存中(或者至少不是一次性加载)。结果是它非常快速,而且不需要太多内存来运行。我刚刚使用这个脚本在我的i7上拆分的测试文件大小约为1 GB,有大约1200万行测试数据,生成了25个部分文件(每个文件大小约为500k行)-处理时间约为2分钟,任何时候都没有超过3 MB的内存使用量。
这里的警告是它依赖于文本文件具有“行”(意味着每个记录都用CRLF分隔),因为文本流对象使用“ReadLine”函数逐行处理。但是,如果你正在使用TSV或CSV文件,它就非常完美。
Option Explicit

Private Const INPUT_TEXT_FILE = "c:\bigtextfile.txt"  'The full path to the big file
Private Const REPEAT_HEADER_ROW = True                'Set to True to duplicate the header row in each part file
Private Const LINES_PER_PART = 500000                 'The number of lines per part file

Dim oFileSystem, oInputFile, oOutputFile, iOutputFile, iLineCounter, sHeaderLine, sLine, sFileExt, sStart

sStart = Now()

sFileExt = Right(INPUT_TEXT_FILE,Len(INPUT_TEXT_FILE)-InstrRev(INPUT_TEXT_FILE,".")+1)
iLineCounter = 0
iOutputFile = 1

Set oFileSystem = CreateObject("Scripting.FileSystemObject")
Set oInputFile = oFileSystem.OpenTextFile(INPUT_TEXT_FILE, 1, False)
Set oOutputFile = oFileSystem.OpenTextFile(Replace(INPUT_TEXT_FILE, sFileExt, "_" & iOutputFile & sFileExt), 2, True)

If REPEAT_HEADER_ROW Then
    iLineCounter = 1
    sHeaderLine = oInputFile.ReadLine()
    Call oOutputFile.WriteLine(sHeaderLine)
End If

Do While Not oInputFile.AtEndOfStream
    sLine = oInputFile.ReadLine()
    Call oOutputFile.WriteLine(sLine)
    iLineCounter = iLineCounter + 1
    If iLineCounter Mod LINES_PER_PART = 0 Then
        iOutputFile = iOutputFile + 1
        Call oOutputFile.Close()
        Set oOutputFile = oFileSystem.OpenTextFile(Replace(INPUT_TEXT_FILE, sFileExt, "_" & iOutputFile & sFileExt), 2, True)
        If REPEAT_HEADER_ROW Then
            Call oOutputFile.WriteLine(sHeaderLine)
        End If
    End If
Loop

Call oInputFile.Close()
Call oOutputFile.Close()
Set oFileSystem = Nothing

Call MsgBox("Done" & vbCrLf & "Lines Processed:" & iLineCounter & vbCrLf & "Part Files: " & iOutputFile & vbCrLf & "Start Time: " & sStart & vbCrLf & "Finish Time: " & Now())

3

以下是一个使用C#编写的算法,可以将大文件分割成多个大小为10M的小文件,而且不会出现内存不足的情况。

var fileSuffix = 0;
int lines = 0;
Stream fstream = File.OpenWrite($"{filename}.{(++fileSuffix)}");
StreamWriter sw = new StreamWriter(fstream);

using (var file = File.OpenRead(filename))
using (var reader = new StreamReader(file))
{
    while (!reader.EndOfStream)
    {
        sw.WriteLine(reader.ReadLine());
        lines++;

        if (lines >= 10000000)
        {
              sw.Close();
              fstream.Close();
              lines = 0;
              fstream = File.OpenWrite($"{filename}.{(++fileSuffix)}");
              sw = new StreamWriter(fstream);
        }
    }
}

sw.Close();
fstream.Close();

0

我为此创建了一个简单的程序,你的问题帮助我完成了解决方案... 我添加了一个特性和几个配置项。 如果您想在每几行后添加特定字符/字符串(可配置),请查看备注。 我已经添加了代码文件: https://github.com/mohitsharma779/FileSplit


0
这段Python代码将把任何txt文件分割成1MB大小的文件。
import os
from tqdm import tqdm

# Directorul în care se află fișierele txt
directory = r"d:\2022_12_02"

# Funcție pentru împărțirea fișierelor în părți mai mici
def split_txt_files(file_path, max_size):
    with open(file_path, 'rb') as file:
        data = file.read()

    # Verifică dacă fișierul este deja mai mic sau egal cu dimensiunea maximă dorită
    if len(data) <= max_size:
        return [data]

    parts = []
    current_part = b''

    for byte in data:
        if len(current_part) + 1 > max_size:
            parts.append(current_part)
            current_part = b''
        current_part += bytes([byte])

    if current_part:
        parts.append(current_part)

    return parts

# Ittrează prin fișierele txt din director
for filename in os.listdir(directory):
    if filename.endswith(".txt"):
        file_path = os.path.join(directory, filename)
        parts = split_txt_files(file_path, 1024 * 1024)  # 1 MB în bytes

        # Configurăm tqdm pentru afișarea progresului
        progress_bar = tqdm(total=len(parts), desc=f"Splitting {filename}", unit="part")

        for i, part in enumerate(parts):
            part_filename = f"{os.path.splitext(filename)[0]}_part{i+1}.txt"
            part_path = os.path.join(directory, part_filename)

            with open(part_path, 'wb') as part_file:
                part_file.write(part)

            # Actualizăm progresul
            progress_bar.update(1)
            progress_bar.set_postfix({"Current Part": i+1})

            print(f"Fișierul {part_filename} a fost creat.")

        # Terminăm bara de progres
        progress_bar.close()

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