显示标签为“windows”的博文。显示所有博文
显示标签为“windows”的博文。显示所有博文

2012年5月27日星期日

换行符和BOM引起的麻烦

最近整理电脑里的Python程序,有一个程序运行时出错:
$ ./absent.py
./absent.py: line 1: $'\357\273\277#!': command not found
./absent.py: line 4: $'\r': command not found
然后鼠标箭头变成了一个十字。如果如下:
$ python absent.py
用Python命令来执行是好的。这个文件的前5行如下:
#! /usr/bin/env python
# -*- encoding: utf-8 -*-
# 设置不同的文字屏保,2008年6月5日

import gtk
那个十字以前就碰到过,是用./执行时程序被当成了Shell脚本,所以Python的import gtk语句被当成ImageMagick的import工具来执行了,那个十字形鼠标箭头是import用来选取截图窗口的。

这个文件是我在原单位办公室的Windows电脑下创建的,所以马上意识到是换行符引起的。Linux下的换行符是\n,而Windows下创建的文件换行符是\r\n,所以第一行其实是这样的内容:
#! /usr/bin/env python\r\n
在Linux下,系统只把\n识别为换行符,把python\r连起来识别了。我创建了一个测试程序dos.py
#! /usr/bin/env python
# -*- encoding: utf-8 -*-

import this
然后在Shell下用
sed -i 's/$/^M/g' dos.py
把换行符转成Windows下的\r\n。^M是用Control-V和Control-M打出来的。这个文件执行出错:
$ ./dos.py
: No such file or directory
当然,用Python命令来解释是好的。上面的错误可以在Shell下用等同的命令复现:
$ /usr/bin/env python^M
: No such file or directory
如果真的有python^M这个命令会呢?在~/bin目录里创建到Python解释器的链接:
ln -s /usr/bin/python python^M
~/bin目录在路径的前面,这样python^M命令可以找到了。再执行一次就好了:
 $ ./dos.py
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
......
 其实不要用env,直接在Shebang里面指定执行文件:
#! /home/tux/bin/python
# -*- encoding: utf-8 -*-

import this
出错会更直观:
$ ./dos.py
bash: ./dos.py: /home/tux/bin/python^M: bad interpreter: No such file or directory
~/bin里面创建名为python^M的链接后,就又可以执行了。再回到开始的错误:
$ ./absent.py
./absent.py: line 1: $'\357\273\277#!': command not found
./absent.py: line 4: $'\r': command not found
这个错误和上面dos.py的不同,这'\357\273\277#!'是哪里来的呢?把absent.py文件这样打开:
$ cat -e absent.py
M-oM-;M-?#! /usr/bin/env python^M$
# -*- encoding: utf-8 -*-^M$
# M-hM-.M->M-gM-=M-.M-dM-8M-^MM-eM-^PM-^LM-gM-^ZM-^DM-fM-^VM-^GM-eM--M-^WM-eM-1M-^OM-dM-?M-^]M-oM-<M-^L2008M-eM-9M-46M-fM-^\M-^H5M-fM-^WM-%^M$
^M$
import gtk^M$
原来在#!之前还有几个字符,用bvi打开:
00000000  EF BB BF 23 21 20 2F 75 73 72 2F 62 69 6E 2F 65 ...#! /usr/bin/e
00000010  6E 76 20 70 79 74 68 6F 6E 0D 0A 23 20 2D 2A 2D nv python..# -*-
它们是0xEF, 0xBB0xBF。原来这个文件是用微软的Notepad创建的,在文件头被自动加上了UTF-8的Byte Order Mark。用cat -e absent.py打开时,在#!前面显示的是M-oM-;M-?,这是指EE BB BF是把'o', ';''?'的高位设为1,可以如下验证:
>>> print ' '.join('%X' % (ord(b) + 128) for b in 'o;?')
EF BB BF
要解决问题,必须把前三个字节删除掉,并把换行转换为Unix格式的\n才能用./absent.py正确执行程序。

2012年3月24日星期六

Dropbox的Case Conflict

修改了主页后,用rsync同步,看到如下的画面:
*deleting   images/rms/17am/7.JPG
*deleting   images/rms/17am/4.jpg
*deleting   images/rms/17am/3.JPG
*deleting   images/rms/17am/2.JPG
*deleting   images/rms/17am/12.JPG
*deleting   images/rms/17am/10.JPG
*deleting   images/rms/17am/1.JPG
<f+++++++++ images/rms/17am/1.jpg (Case Conflict 1)
       66998 100%    3.19MB/s    0:00:00 (xfer#9, to-check=103/1237)
<f+++++++++ images/rms/17am/10.jpg (Case Conflict 1)
       90494 100%    3.92MB/s    0:00:00 (xfer#10, to-check=101/1237)
<f+++++++++ images/rms/17am/12.jpg (Case Conflict 1)
      101524 100%    4.21MB/s    0:00:00 (xfer#11, to-check=99/1237)
<f+++++++++ images/rms/17am/2.jpg (Case Conflict 1)
       76399 100%    2.91MB/s    0:00:00 (xfer#12, to-check=97/1237)
<f+++++++++ images/rms/17am/3.jpg (Case Conflict 1)
       83717 100%    3.07MB/s    0:00:00 (xfer#13, to-check=95/1237)
<f+++++++++ images/rms/17am/4.JPG (Case Conflict 1)
       20923 100%  756.76kB/s    0:00:00 (xfer#14, to-check=93/1237)
<f+++++++++ images/rms/17am/7.jpg (Case Conflict 1)
       77626 100%    2.74MB/s    0:00:00 (xfer#15, to-check=91/1237)
这些图片没有改,怎么要传送,而且出现了Case Conflict 1的字眼。

在rsync的手册里面翻了两下没看出来原因,到这这几个图片所在网页才明白。原来我的缩略图叫1.jpg,而放在同一个目录的大图叫1.JPG,其它几个图片也是如此命名的。最近用了Dropbox,这些文件同步的时候被Dropbox改名了,两个文件扩展名大小写改成一样,其中一个的文件名后加上了(Case Conflict 1)。这应该是Dropbox为了和Windows、OSX等系统下对大小写不敏感的文件系统兼容而为。

那我只好避免这种命名方式了。

2011年11月14日星期一

换行符引起的麻烦

用sed处理一个hosts文件,内容都是类似下面的行:
203.208.46.180 www.google.com
203.208.46.180 music.google.com
203.208.46.180 music.googleusercontent.com
203.208.46.180 music-streaming.l.google.com
203.208.46.180 large-uploads.l.google.com
IP地址和域名之间是用Tab分割的。想用sed把这个文件处理成dnsmasq的DNS记录配置内容,用如下的命令:
sed -e 's|^\(.*\)    \(.*\)$|address=/\2/\1|g'
两个引用的中间是一个TAB。期望的结果如下:
address=/www.google.com/203.208.46.180
address=/music.google.com/203.208.46.180
address=/music.googleusercontent.com/203.208.46.180
address=/music-streaming.l.google.com/203.208.46.180
address=/large-uploads.l.google.com/203.208.46.180
但实际结果出乎意料:
/203.208.46.180ogle.com
/203.208.46.180google.com
/203.208.46.180googleusercontent.com
/203.208.46.180streaming.l.google.com
/203.208.46.180uploads.l.google.com
不但IP和域名的顺序没有调换,域名前面的几个字符还被覆盖了。后来想是不是因为文件换行符的问题。用Vim打开发现果然是DOS格式的换行符。把脚本改一下就可以得到正确结果了:
sed -e 's/^M$//g' -e 's|^\(.*\) \(.*\)$|address=/\2/\1|g'
错误结果也比较好解释了。Windows下的换行符是Carriage Return + Line Feed(\r\n),Linux下的换行符是Line Feed(\n)。Carriage Return就是打字机的打印头回到行首,Line Feed是指纸出来一行,这样打印头就处在下一行的同一个位置了。sed命令中的第二组引用不但包括了域名,还包括了紧随其后、位于 \n前的\r,这样本来应该位于\2后面的/\1,现在要从\2的开头开始打印了,于是就覆盖了域名的前面几个字符。例如对第一行:
address=/www.google.com/
/203.208.46.180
把开头的address=/www.go都覆盖了,所以结果就是:
/203.208.46.180ogle.com
后面的也都要覆盖掉IP长度+1(/)的字符。