简要介绍一下Dos/Windows格式文件和Unix/Linux格式文件(剪不断理还乱的\r\n和\n)

DOS文件(windows格式文件)中,按一下ENTER就是输入了\r\n,这就是回车换行

MAC文件里,用\r换行

UNIX文件里,用\n换行

自己尝试了一些\r \n的组合,归纳了下规律:

windows下用文本方式文件时

缓冲区里的\n或者\r都会被自动替换为\r\n写入文件,即使是连着的\r\n,也会被替换成\r\n\r\n(但是msdn上面只说\r被替换为\r\n)

windows下用文本方式文件时

文件中的\r\n自动替换为\n存放到缓冲区里(这点和msdn是符合的)

\r不会被替换。

 

假设要读取的文件流长度小于缓冲区长度

如果用fread文本模式读文件,\r\n被替换为\n读进缓存区里

如果是fgets文本模式读文件,碰到第一个\n(这里的\r和\n都是指经过转换后读入缓冲区的,即\r\n被替换为\n)后停止读取,缓存区里存放的是\n之前的字符串(包括这个\n以及之前所有的\r)

 

fread和fwrite的返回值问题

文本模式写文件时,\n或\r自动替换成\r\n,但是fwrite的返回值里还是算写了一个字符(和msdn符合)

文本模式读文件时,\r\n被自动替换为\n,但是也算是读了一个字符(和msdn的不一样,msdn是说算读了2个)

 

如果用UltraEdit打开文本文件有时候会碰到询问你是否要转换为DOS文件,其实就是回车换行符的问题了。做了如下表的尝试,但是规律不太好总结啊。

 

要写入的字符

实际写入的字符(即\n被替换为\r\n后)

Ultraedit转换为DOS格式后的文件内的实际字符

\r

\r

\r\n

\n

\r\n

未提示转换为DOS文件

\r\n

\r\r\n

\r\n

\n\r

\r\n\r

未提示转换为DOS文件

\n\r\n

\r\n\r\r\n

\r\n\r\n

\n\r\n\r

\r\n\r\r\n\r

\r\n\r\n

\n\r\n\n\r

\r\n\r\r\n\r\n\r

\r\n\r\n\r\n\r\n

\r\r\n\n

\r\r\r\n\r\n

\r\n\r\n\r\n

\r\r\r\n\r\n

\r\r\r\r\n\r\r\n

\r\n\r\n\r\n\r\n

\n\r\r

\r\n\r\r

\r\n\r\n\r\n

\n\r\r\r\r

\r\n\r\r\r\r

\r\n\r\n\r\n\r\n\r\n

1.  linux上用vim写的文件test.txt拷贝到windows上, 结果所有的内容都显示在一行中。(当然, 如果你Windows上的编辑显示器够智能, 那就是另外一回事了)

 2. Windows上建立的test.txt拷贝到linux上, 结果linux程序运行异常。(当然, 如果你的linux程序足够健壮, 那就没有问题了)

        ps. 当你在Windows上写好linux shell script, 拷贝到linux下运行, 就会出问题。

        为什么会出现上面的问题呢? 这就需要我们了解\r\n和\n了。

        我们先以Windows为例来说明。 我们来新建一个dos.txt文件, 然后在第一行敲一下enter键, 然后保存文件。 此时, 我们查一下这个文件的大小, 结果发现是2个字节(而不是1个), 用UltraEdit查阅一下, 就知道文件中的两个字节是0x0d和0x0a, 也就是\r和\n.  但是, 如果用C程序来读取dos.txt文件, 发现又根本没有读取到\r, 这是怎么回事呢? 原来: 在Windows上的dos.txt文件中, 按一下enter键, 实际上就相当于C程序写\n字符,而由于历史原因, 写文件的时候, Windows系统会自动在\n前面加上\r, 这样, dos.txt文件中就有了\r\n了, 在读取的时候, Windows系统会自动去掉\r, 所以你的C程序根本读不到\r, 还是\n.

        实话实说, 我发现很多地方讲\r\n和\n, 都说得不太清楚, 下面我来说一下, 希望是更清楚, 而不是更模糊微笑

        Windows系统中有如下等价关系:

        用enter换行 <====> 程序写\n  <====> 真正朝文件中写\r\n(0x0d0x0a) <====>程序真正读取的是\n

        linux系统中的等价关系:

        用enter换行 <====> 程序写\n  <====> 真正朝文件中写\n(0x0a)  <====> 程序真正读取的是\n

        现在, 我们看看本文开头部分的问题。 假设有一个linux下的unix.txt文件,  那么, 它在文件中的换行标志是:\n, 现在把unix.txt拷贝靠Windows上, 那好啊, Windows那双犀利的眼神仿佛是在对unix.txt文件说: 别跟我整什么\n, 我只认识文件中的\r\n, 如果你这个unix.txt文件里面有\r\n, 那我就认为是换行符, 否则, 我不认你。 如此一来, Windows压根就找不到unix.txt中的\r\n, 所以, 对于Windows而言, 压根就没有发现unix.txt有任何换行, 所以, 我们从Windows上看到的unix.txt文件显示在一行里面。

       同理, 假设Windows上有一个dos.txt文件, 那么, 它在文件中的换行标志是\r\n, 现在拷贝到linux下, 那好啊, 正如linus一样, linux这个倔强的家伙貌似在说: 别的我不管, 我遇到文件中的\n, 我就认为是换行, 至于其他的, 我只认为是正常的字符。 如此一来, \r就被当成了文件的正常部分, 也就是说, linux下的C程序不仅仅会读取到\n, 也会读取到它前面的\r.

       实际上Windows上的文件是dos格式的, 而linux上的文件是unix格式的, 我们可以通过linux上的dos2unix和unix2dos来实现转化, 当然, 有的linux环境下, 需要用busybox dos2unix和busybox unix2dos.

       下面, 我们用程序来简要验证一下上面的部分叙述。 现在Windows上创建一个dos.txt文件, 第一行写入12, 然后按enter, 然后在第二行写入3, 不按enter, 然后保存。 我们先来看看Windows上的程序:

// VC++6.0
 
#include <stdio.h>
#include <string.h>
 
int main()
{
	char szTest[100] = {0};
	int len = 0;
 
	FILE *fp = fopen("dos.txt", "r");
	if(NULL == fp)
	{
		printf("failed to open dos.txt\n");
		return 1;
	}
 
	fgets(szTest, sizeof(szTest) - 1, fp);
	len = strlen(szTest);
	if('\n' == szTest[len - 1])
	{
		printf("yes1\n");
	}
 
	if('\r' == szTest[len - 2])
	{
		printf("yes2\n");
	}
 
 
	memset(szTest, 0, sizeof(szTest));
	fgets(szTest, sizeof(szTest) - 1, fp);
	len = strlen(szTest);
	if('\n' == szTest[len - 1])
	{
		printf("yes3\n");
	}
 
	if('\r' == szTest[len - 2])
	{
		printf("yes4\n");
	}
 
	fclose(fp);
 
	return 0;
}

      程序结果为:

yes1

      我们看到, 读取的时候\r确实被去掉了。

     好, 我们把dos.txt拷贝到linux下, 然后运行下面相同的程序:

// gcc
 
#include <stdio.h>
#include <string.h>
 
int main()
{
	char szTest[100] = {0};
	int len = 0;
 
	FILE *fp = fopen("dos.txt", "r");
	if(NULL == fp)
	{
		printf("failed to open dos.txt\n");
		return 1;
	}
 
	fgets(szTest, sizeof(szTest) - 1, fp);
	len = strlen(szTest);
	if('\n' == szTest[len - 1])
	{
		printf("yes1\n");
	}
 
	if('\r' == szTest[len - 2])
	{
		printf("yes2\n");
	}
 
 
	memset(szTest, 0, sizeof(szTest));
	fgets(szTest, sizeof(szTest) - 1, fp);
	len = strlen(szTest);
	if('\n' == szTest[len - 1])
	{
		printf("yes3\n");
	}
 
	if('\r' == szTest[len - 2])
	{
		printf("yes4\n");
	}
 
	fclose(fp);
 
	return 0;
}
结果:
[taoge@localhost learn_c]$ xxd dos.txt 
0000000: 3132 0d0a 33                             12..3
[taoge@localhost learn_c]$ gcc test.c 
[taoge@localhost learn_c]$ ./a.out 
yes1
yes2
[taoge@localhost learn_c]$ 

    我们看到, 读取的时候, \r还在。    OK, 至此, \r\n和\n的问题基本算是清楚了, 以后再也不用纠结了。

bash: ./a.sh: /bin/bash^M: bad interpreter: No such file or directory的解决方法------dos--->unix

 一些人喜欢用vim来写linux shell script, 但是, 有的人喜欢在Windows下用一些方便的编辑器(比如鼎鼎大名的Notepad++)写好, 然后拷贝文件到linux下, 结果呢, 在执行脚本a.sh的时候, 会出现如下问题:

[taoge@localhost learn_shell]$ ./a.sh 
bash: ./a.sh: /bin/bash^M: bad interpreter: No such file or directory
[taoge@localhost learn_shell]$ 


     什么原因呢, 我们有理由怀疑是文件格式问题? 我们用vim a.sh进入a.sh这个文件, 然后在底部模式下, 执行:set ff查看一下, 结果发现fileformat=dos, 看看, 果然是文件格式问题, 那怎么解决呢?

     方法一:vim a.sh进入a.sh后, 在底部模式下, 执行:set fileformat=unix后执行:x或者:wq保存修改。 然后就可以执行./a.sh运行脚本了。(我亲自试过, 是ok的)

     方法二:直接执行sed -i "s/\r//" a.sh来转化, 然后就可以执行./a.sh运行脚本了。(我亲自试过, 是ok的)

     方法三:直接执行dos2unix a.sh来转化, 然后就可以执行./a.sh运行脚本了。(我的linux上执行dos2unix ./a.sh失败, 但是不要放弃啊, 加个busybox就可以了), 如下:

dos2unix a.sh 
bash: dos2unix: command not found
[taoge@localhost learn_shell]$ busybox dos2unix a.sh 
[taoge@localhost learn_shell]$

    实际上, 经过上述三种方法修改后, 我们都可以再用:set ff再查一下, 发现a.sh的fileformat果然是unix了。   第三种方法最方便, 建议用第三种!

原文链接:

http://blog.sina.com.cn/s/blog_65db99840100kidc.html

https://blog.csdn.net/stpeace/article/details/45767245

https://blog.csdn.net/stpeace/article/details/45604015

猜你喜欢

转载自blog.csdn.net/a3192048/article/details/81129582