C# 如何从文件中删除\n个字符?
我有一个问题,应该让大多数人去“WTF”,但我仍然有它 我从一个供应商那里得到了一堆数据文件。它是一种自定义的平面文件格式,声称是CSV,但它不是逗号分隔的,并且值不带引号。所以,根本不是CSVC# 如何从文件中删除\n个字符?,c#,.net,sed,newline,C#,.net,Sed,Newline,我有一个问题,应该让大多数人去“WTF”,但我仍然有它 我从一个供应商那里得到了一堆数据文件。它是一种自定义的平面文件格式,声称是CSV,但它不是逗号分隔的,并且值不带引号。所以,根本不是CSV foo,bar,baz alice,bob,chris 等等,只不过时间长得多,不那么有趣。问题是,有些记录嵌入了换行符(!!!): 这应该是三个字段的两个记录。通常,我只会说“不,这太蠢了”,但我无意中仔细看了看,发现这实际上是一种与实际的行尾序列不同的行尾序列: foo,bar\n rab,baz
foo,bar,baz
alice,bob,chris
等等,只不过时间长得多,不那么有趣。问题是,有些记录嵌入了换行符(!!!):
这应该是三个字段的两个记录。通常,我只会说“不,这太蠢了”,但我无意中仔细看了看,发现这实际上是一种与实际的行尾序列不同的行尾序列:
foo,bar\n
rab,baz\r\n
alice,bob,chris\r\n
请注意第一行上的\n。我已经确定这适用于我发现的所有嵌入换行符的情况。因此,我需要基本上执行s/\n$/
(我尝试了这个特定的命令,但它没有执行任何操作)
注意:我实际上并不关心字段的内容,所以用零替换换行是可以的。我只需要文件中的每一行都有相同数量的记录(理想情况下,在相同的位置)
我为处理文件而编写的工具中有一个现有的解决方案:
Guid g = Guid.NewGuid();
string data = File.ReadAllText(file, Encoding.GetEncoding("Latin1"));
data = data.Replace("\r\n", g.ToString()); //just so I have a unique placeholder
data = data.Replace("\n", "");
data = data.Replace(g.ToString(), "\r\n");
但是,对于大于1GB左右的文件,这将失败。(此外,我还没有对它进行分析,但我怀疑它的速度也很慢)
我可以使用的工具有:
- cygwin工具(sed、grep等)
- .NET
最好的方法是什么?
而不是把整个事情作为一个大的(潜在的巨大的)字符串读入内存中,而是考虑基于流的方法。
打开输入流,一次读取一行,根据需要进行替换。打开一个输出流并将修改后的行写入其中。比如:static void Main( string[] args )
{
using( var inFs = File.OpenRead( @"C:\input.txt" ) )
using( var reader = new StreamReader( inFs ) )
using( var outFs = File.Create( @"C:\output.txt" ) )
using( var writer = new StreamWriter( outFs ) )
{
int cur;
char last = '0';
while( ( cur = reader.Read() ) != -1 )
{
char next = (char)reader.Peek();
char c = (char)cur;
if( c != '\n' || last == '\r' )
writer.Write( c );
last = c;
}
}
}
这里有一个
StreamReader
类,它似乎可以满足我的需要。请注意,这可能是难以置信的特定领域,因此它可能有用,也可能不有用:
class BadEOLStreamReader : StreamReader {
private int pushback = -1;
public BadEOLStreamReader(string file, Encoding encoding) : base(file, encoding) {
}
public override int Peek() {
if (pushback != -1) {
var r = pushback;
pushback = -1;
return r;
}
return base.Peek();
}
public override int Read() {
if (pushback != -1) {
var r = pushback;
pushback = -1;
return r;
}
skip:
var ret = base.Read();
if (ret == 13) {
var ret2 = base.Read();
if (ret2 == 10) {
//it's good, push back the 10
pushback = ret2;
return ret;
}
pushback = ret2;
//skip it
goto skip;
} else if (ret == 10) {
//skip it
goto skip;
} else {
return ret;
}
}
}
编辑:经过一些测试后,awk解决方案在速度方面提供了更好的结果 UNIX/Linux/Cygwin中的标准文件/输入过滤器很难处理二进制文件。要使用过滤器实现这一点,您需要将文件转换为十六进制格式,使用
sed
(或awk
,请参见下面的第二个解决方案),然后将其转换回原始数据。这应该做到:
xxd -c1 -p file.txt |
sed -n -e '1{h}' -e '${x;G;p;d}' \
-e '2,${x;G;/^0d\n0a$/{P;b};/\n0a$/{P;s/.*//;x;b};P}' |
xxd -r -p
好的,这并不容易理解,让我们从简单的部分开始:
将xxd-c1-p file.txt
从二进制转换为十六进制,每行一个字节file.txt
恢复转换xxd-r-p
将前面没有sed
(0d十六进制)的\r
(0a十六进制)替换为零\n
- 在第1行,将行(字节)存储在保持空间中
- 在最后一行,按正确顺序打印两个字节(
)并停止脚本(x;G;p
)d
- 对于中间的行,在保持空间中有当前字节,在模式空间中有2个字节(前一个字节和当前字节)(
)之后,有3种可能的情况:x;G
- 如果是
,则打印\r\n
并将\r
保留在下一个循环的保留空间中,然后停止此循环(\n
命令)b
- 否则,如果它以
结尾(意味着它不是以\n
开头),则在保留空间中存储一个空字符串并停止此循环(\r
命令)b
- 否则打印第一个字符
awk
中可能更容易理解:
xxd -c1 -p file.txt |
awk 'NR > 1 && $0 == "0a" && p != "0d" {$0 = ""}
NR > 1 {print p}
{p = $0}
END{print p}' |
xxd -r -p
它可以通过以下方式进行测试:
printf "foo,bar\nrab,baz\r\nalice,bob,chris\r\n" |
xxd -c1 -p |
sed -n -e '1{h}' -e '${x;G;p;d}' \
-e '2,${x;G;/^0d\n0a$/{P;b};/\n0a$/{P;s/.*//;x;b};P}' |
xxd -r -p
或
做这么简单的事情需要大量的代码 试试这个
tr -d '\n' <dirtyfile >cleanfile
tr-d'\n'cleanfile
这有帮助吗:?您也可以不使用任何内容替换\r
,然后将\n
替换为\r\n
。在我看来,它与普通的CSV类似(嵌入的换行符除外)。没有引号的字段是完全正常的。注意:问题的标题有一个输入错误。我想用零替换\n
,用\r\n
替换\r\n
(例如,别管它)这个!否则,像这样的海量文件会遇到内存/速度问题。他想保留\r\n
,只在\r
单独保存时才删除。@JonB:哦,是的,谢谢,我误读了他的示例。无论如何,方法是一样的。我将快速编写一些示例代码。这行不通<代码>文本阅读器。ReadLine将读取到任何行尾序列,包括\n
或\r
本身。它也不会返回下线序列,所以这只会去掉所有的换行符。另外,问题标题中有一个输入错误。它应该是\n
,而不是\r
一个有趣的(并且完全不可理解的:)解决方案,但它可以扩展吗?例如,如果我需要处理2gb数据文件,我应该去吃午饭吗?不可理解:没错,sed部分不是最容易理解的。您是否了解xxd的用法以及删除孤立对象的想法\n
。awk解决方案可能更容易理解,但效率较低。它是否可扩展:是的,它可以处理任何大小的输入。它是否有效:这取决于你的观点。第一个xxd将2 Gb二进制文件转换为6 Gb文本文件,每行一个字节,这是大量数据。然后,每次读取一个字节,以决定是否需要保留它。sed和awk可能不是速度最好的工具,但是如果你不是程序员或者你没有访问编译器的权限,它们可能是你唯一的解决方案。如果你想要一个快速的解决方案,用C或Java(或Perl或Python,但我不知道最后2个)编写一个优化良好的程序可以快速解决问题,但如果是一次性文件处理,waiti
printf "foo,bar\nrab,baz\r\nalice,bob,chris\r\n" |
xxd -c1 -p |
awk 'NR > 1 && $0 == "0a" && p != "0d" {$0 = ""}
NR > 1 {print p}
{p = $0}
END{print p}' |
xxd -r -p
tr -d '\n' <dirtyfile >cleanfile