Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/257.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何从文件中删除\n个字符?_C#_.net_Sed_Newline - Fatal编程技术网

C# 如何从文件中删除\n个字符?

C# 如何从文件中删除\n个字符?,c#,.net,sed,newline,C#,.net,Sed,Newline,我有一个问题,应该让大多数人去“WTF”,但我仍然有它 我从一个供应商那里得到了一堆数据文件。它是一种自定义的平面文件格式,声称是CSV,但它不是逗号分隔的,并且值不带引号。所以,根本不是CSV foo,bar,baz alice,bob,chris 等等,只不过时间长得多,不那么有趣。问题是,有些记录嵌入了换行符(!!!): 这应该是三个字段的两个记录。通常,我只会说“不,这太蠢了”,但我无意中仔细看了看,发现这实际上是一种与实际的行尾序列不同的行尾序列: foo,bar\n rab,baz

我有一个问题,应该让大多数人去“WTF”,但我仍然有它

我从一个供应商那里得到了一堆数据文件。它是一种自定义的平面文件格式,声称是CSV,但它不是逗号分隔的,并且值不带引号。所以,根本不是CSV

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
    将前面没有
    \r
    (0d十六进制)的
    \n
    (0a十六进制)替换为零
sed部分的思想是将前一个字节存储在保持空间中,并处理前一个字节和当前字节:

  • 在第1行,将行(字节)存储在保持空间中
  • 在最后一行,按正确顺序打印两个字节(
    x;G;p
    )并停止脚本(
    d
  • 对于中间的行,在保持空间中有当前字节,在模式空间中有2个字节(前一个字节和当前字节)(
    x;G
    )之后,有3种可能的情况:
  • 如果是
    \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