【问题标题】:PowerShell: replacing NUL in large file not workingPowerShell:在大文件中替换 NUL 不起作用
【发布时间】:2020-10-10 17:37:49
【问题描述】:

我有一个巨大的 (39 GB) 文本文件,我最终需要将它作为管道分隔文件读入 R。但是,有很多 NUL 字符 \0 无法在 R 中读取。我正在尝试预先在 PowerShell 中替换它们。

PowerShell 代码:

Get-Content file.txt | foreach { $_ -replace '\\0' } | Out-File -Encoding UTF8 file_NEW.txt

我认为这可行,但是当我尝试在 R 中读取新文件时,\0 字符出现在字符串中,我收到此错误:

Error in vroom_(file, delim = delim %||% col_types$delim, col_names = col_names, : 
  embedded nul in string: '||MORALES BELINDA F TRUST||\0||\0|0||PT||||33.824049|-118.192053||3655||N|WESTON|PL|||LONG BEACH|CA|908073855|C033||3655||N|WESTON|PL|||LONG BEACH|CA|908073855|C033|20111117|988||||||||20111027|20111110|TR|2|1527575||KINECTA FCU|KINECTA FCU|||MANHATTAN BEACH|CA|90266|047978|LAWYERS TITLE|03003|232000.00|20111027||CNV|TR|Y|10|20211201|||D|BGJT|V||115|21||0|0||0|||||Y|Y||\r\n06037|5054001029|5054-001-029|1|\0||BONILLA ...

为什么文件中还有 NUL?任何帮助表示赞赏!特别是因为这些功能需要很长时间才能运行。拜托,我只是想阅读这个巨大的文件。

以防万一 R 代码出现错误,请注意它直接取自 this post,使用 vroom 和 arrow 包读取然后创建拼花文件。

【问题讨论】:

  • PoSh 没有将\0 视为$Null。特殊字符是“反引号”和“0”。
  • @Lee_Dailey 是的,但-replace 需要一个正则表达式,并且在那里使用了反斜杠。
  • @vonPryz - 尝试用双引号中的反引号零。 [咧嘴]

标签: r powershell


【解决方案1】:

原因是加倍的反斜杠\\,意味着退格被转义。而不是查看 NUL (0x00),您实际上是在查看 \0 - 两个字符。

正确的语法应该是这样的,

-replace '\0'

话虽如此,处理大文件可以更智能地完成。一种快速的方法是一次处理 10 000 行。请参阅earlier an answer 了解如何分块处理文件。

【讨论】:

    【解决方案2】:
    • vonPryz' helpful answer 显示了您的 -replace 操作的直接问题:在 单个 引用的 PowerShell 字符串 ('...') 中,\ 字符。 不需要转义为\\,以便将逐字传递给-replace operator在幕后使用的.NET正则表达式引擎。

    • r2evans' helpful answer 通过标准 Unix 实用程序的 Windows 端口显示另一种解决方案,该实用程序附带可选的 Rtools 下载,其中将输入文件通过管道传输到
      tr -d '\0' 可能提供最快的解决方案,如果两个输入和输出文件使用相同的字符编码。

    在 PowerShell 领域中,使用 Get-Content 及其默认的逐行处理如此大的输入文件实际上需要很长时间。

    虽然直接使用 .NET API 可能会提供最终最快的解决方案,但使用 Get-Content 的
    -ReadCount 参数提供更简单、更符合 PowerShell 习惯的解决方案:

    Get-Content -ReadCount 1e6 file.txt | foreach { $_ -replace '\0' } | 
      Out-File -Encoding UTF8 file_NEW.txt
    

    -ReadCount 1e6 读取 100 万行(1e6 使用exponential notation,即10 的6 的幂)一次并将它们作为数组 到ForEach-Object cmdlet(其中一个内置别名是foreach);由于-replace 运算符能够将值的数组 操作为其LHS,因此NUL 替换可以一次对数组的所有元素执行。

    根据输入文件中构成平均行的字节数,如果可用内存较多,您可以向上调整该数字,如果可用内存较少,则可以向下调整。您可以使用的数字越高,命令完成的速度就越快。

    【讨论】:

      【解决方案3】:

      我不太了解 powershell 来解决这个问题,但您可以使用 sed 或 tr 替换文件中的 nuls。 tr 和 sed 实用程序在大多数(全部?)类 unix 操作系统(包括 macos)上默认可用。对于 windows,它们包含在 Rtools35 和 Rtools40 中。

      如果您没有通过Sys.which("tr") 找到它,那么您可能需要包含相应实用程序的完整路径。假设 Rtools 安装在根 c:/ 上,那么类似

      • Rtools35:c:/Rtools/bin/tr.exe
      • Rtools40:c:/Rtools40/usr/bin/tr.exe

      它们也包含在 Git-for-Windows 中,作为 git-bash 中的 /usr/bin/tr.exe 和 /usr/bin/sed.exe。 (在文件系统上,它们可能在c:/Program Files/Git/usr/bin/ 下。)

      (sed 的位置相同。)

      我应该注意,我通过 R 的system2 这样做只是为了方便。如果您对 bash 命令行足够熟悉,那么在此处执行此操作同样容易。

      数据生成

      我不知道 nuls 在您的文件中的位置,所以我假设它们是记录(行)终止符。也就是说,在大多数文件中,您会看到每一行都以\n 或\r\n 结尾,但在本例中,我将用\0 (nul) 替换\n。

      charToRaw("a|b\nhello|world")
      #  [1] 61 7c 62 0a 68 65 6c 6c 6f 7c 77 6f 72 6c 64
      ch <- charToRaw("a|b\nhello|world")
      ch[ch == charToRaw("\n")] <- as.raw(0)
      ch
      #  [1] 61 7c 62 00 68 65 6c 6c 6f 7c 77 6f 72 6c 64
      writeBin(ch, "raw.txt")
      readLines("raw.txt")
      # Warning in readLines("raw.txt") :
      #   line 1 appears to contain an embedded nul
      # Warning in readLines("raw.txt") :
      #   incomplete final line found on 'raw.txt'
      # [1] "a|b"
      

      nul 是一个问题(如预期的那样),所以我们在嵌入的 nul 之后看不到任何东西。

      tr

      tr 不喜欢原地做事,所以这会将原始文件作为输入并生成一个新文件。如果文件大小和磁盘空间是一个问题,那么sed 可能是首选。

      system2("tr", c("\\0", "\\n"), stdin = "raw.txt", stdout = "raw2.txt")
      readLines("raw2.txt")
      # Warning in readLines("raw2.txt") :
      #   incomplete final line found on 'raw2.txt'
      # [1] "a|b"         "hello|world"
      

      (这里可以安全地忽略该警告。)

      sed

      sed 可以选择使用-i 参数就地工作。 (没有它,它可以像tr一样操作:在原来的基础上生成一个新文件。)

      system2("sed", c("-i", "s/\\x0/\\n/g", "raw.txt"))
      readLines("raw.txt")
      # Warning in readLines("raw.txt") :
      #   incomplete final line found on 'raw.txt'
      # [1] "a|b"         "hello|world"
      

      (这里可以安全地忽略该警告。)

      记录终止符除外

      如果 nul 不是记录终止符(\n-like)字符,那么您有一些选择:

      1. 用有意义的东西替换\0 字符,例如Z(愚蠢,但你明白了)。这应该按原样使用上述命令,将\\n 替换为您选择的角色。 (tr 需要单个字符,sed 可以根据需要替换为多个字符。)

      2. 完全删除\0,在这种情况下你可以使用tr -d '\0'和sed -i -e 's/\x0//g'(翻译成上面R的system2调用)。

      【讨论】:

        猜你喜欢
        • 2018-07-31
        • 2019-05-23
        • 1970-01-01
        • 2014-07-11
        • 2022-01-25
        • 2022-06-16
        • 2014-04-12
        • 2020-06-17
        • 1970-01-01
        相关资源
        最近更新 更多