旧答案仍然包含(IMO)有用的信息:

旧答案仍然包含(IMO)有用的信息:

简短的问题:

为什么 sed 不会对文件进行任何更改,有什么方法可以检查吗?

长问题:

我尝试过运行一个以前一直可以处理我的文件的 sed 命令。我学到了这个这里九月回来。每个季度我都会收到 4 个巨大的文件,其中有一堆空白和一个应该是一个的列,但被分成了两个。我运行以下命令来浏览空白区域并合并第 41 列和第 42 列:

sudo sed -i -e 's/ \{1,\}"/"/g' -e 's/" \{1,\}/"/g' -e 's/","//41' original_file.txt

昨天第一次,什么事都没有发生。它会等待大约 3 秒,然后什么也不会发生,而通常需要 20-30 分钟。我检查了文件,空格仍然存在。我的系统上仍然有 3 倍的可用文件大小,RAM 中的可用文件大小(512GB ram)也有两倍,这并不是说 ram 很重要,只是想把它扔进去。

我尝试使用将其写入另一个文件

sudo sed -e 's/ \{1,\}"/"/g' -e 's/" \{1,\}/"/g' -e 's/","//41' original_file.txt > formatted_file.txt

这会创建formatted_file.txt但它是完全空白的。

谁能告诉我我做错了什么或如何检查问题?

编辑:

示例输入可以在堆栈溢出除了有 300 多个列之外。

答案1

在评论中发现输入文件位于大尾数法 UTF-16格式而不是普通的老式 7 位 ASCII 或 8 位扩展 ascii。 UTF-16 是一种每个字符 2 个字节的格式,如果用于对纯 ASCII 进行编码,则“ASCII”字符0x00(NUL 字节,^@cat -Aless和其他程序显示为)作为 2-字节的第一个字节字节对(大端。小端相反)。

修复方法是将文件转换为纯 ASCII。例如,您不需要使用标准fromdos或类似的实用程序将 CR-LF(dos/windows 行结尾)转换为 LF(unix 行结尾),而是需要执行如下操作将文本转换为其余部分可用的格式脚本的sed

sed -e '1 s/^\xff\xfe|^\xfe\xff//; s/\x00//g; s/\x0d$//'

这个sed脚本:

  • 从第一行的开头删除0xfffe或字节顺序标记。0xfeff
  • 从所有输入行中删除所有 NUL 字符,无论它们出现在何处。
  • 0x0d删除任意行末尾的回车符 ( )

注意:这仅适用于仅包含 ASCII 字符的 UTF-16 编码文本。它将完全破坏任何包含其他类型字符(例如非英语文本)的 UTF-16 文本文件。

最后,perl对各种常见格式的文本具有出色的支持,包括纯 ascii、UTF-8、UTF-16 等。它具有用于处理所有格式并在所有格式之间进行转换的库模块。将简单sed脚本转换为 是相当容易的perl,因此脚本的 Perl 版本可能很简单(未经测试,但它甚至可能有效):

#!/usr/bin/perl

use strict;
use feature 'unicode_strings';

while(<>) {

  s/^\xff\xfe|^\xfe\xff// if ($. == 1);  # strip Byte Order marker from 1st line

  s/\x0d$//;  # strip CR from each end-of-line
  s/ *"/"/g;  # get rid of all spaces immediately before " characters
  s/" */"/g;  # get rid of all spaces immediately after " characters

  # A very primitive split(). Should use a real CSV parser here, like the
  # Text::CSV module which properly copes with embedded quotes and commas etc
  # in string fields.   This would also allow proper processing of each field to
  # remove any extra whitespace characters rather than the quick-and-dirty hack of
  # global regexp substitutions above.
  my @fields = split /,/;

  # perl arrays start from zero.  This appends the "fake" field 42 onto field 41,
  # and then deletes field 42.
  $fields[40] .= $fields[41];
  delete $fields[41];

  print join(',',@fields), "\n";
}

旧答案仍然包含(IMO)有用的信息:

awk是比 更好的工具来完成这项工作sed

例如,使用 GNU awk(或任何其他awk理解 PCRE 的工具,如\s\S):

awk '{$0=gensub(/\s*(\S+)/,"\\1",42)}1' original > fixed

通过删除紧邻第 42 列之前的所有空格来合并第 41 列和第 42 列。

对于非 PCRE awk,请使用[[:space:]]代替\s[^[:space:]]代替\S

awk '{$0=gensub(/[[:space:]]*(\[^[:space:]]+)/,"\\1",42)}1' original > fixed

此外,根据输入文件的确切性质,perl对于这项工作来说,可能是比awk.例如,它具有用于解析 CSV 文件和处理 CSV 记录中的各个字段的模块。


顺便说一句,我认为该sed脚本很糟糕,尤其是因为您使用的是多个参数而不是带有as 命令分隔符的-e单个 sed 脚本。;如果你想使用,sed那么至少要有效且高效地使用它。你的sed脚本最好写成:

sed -e 's/ \{1,\}"/"/g; s/" \{1,\}/"/g; s/","//41' original > fixed

甚至:

sed -e 's/ \{1,\}"/"/g
        s/" \{1,\}/"/g
        s/","//41' original > fixed

您仍然需要修复错误,但至少您将有一些更可读的内容可供调试 - 这使得更容易发现问题所在。

另外顺便说一句,-i或者--in-place不像您想象的那样“到位”编辑。它的工作原理是创建一个临时文件,然后将其移动到位。这会破坏任何需要 inode 保持不变的内容,包括硬链接。

最好将更改后的输出写入临时文件(例如 temp.txt),然后cat temp.txt > original.txt; rm temp.txt用更改后的版本覆盖原始文件,同时仍保留相同的索引节点。

相关内容