简短的问题:
为什么 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 -A
、less
和其他程序显示为)作为 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
用更改后的版本覆盖原始文件,同时仍保留相同的索引节点。