一. 问题描述

「Linux下批量运行测试算例的简单实现」一文中介绍了批量运行测试算例的方案,在其中我提到了修改每次计算的输入参数时,如果参数没有什么规律,只能手动的去修改输入参数文件,然而对于大数量级的测试次数,如果真的要手动去改输入参数,那是非常低效的,在经过两轮的手动改参数后我真受不了了,即便输入参数没有多少规律,也应尽量让程序来代工,手动改就是机械的浪费时间没有任何意义,把手动的过程用代码来实现虽然开始可能会费点时间,但这是个先苦后甜的过程,再者,相比机械的手动改参数,写程序还能多少有点技术性的收获,岂不更好。

这次遇到的问题是写了个代工程序,叫做SubmitUpdater,通过它来更新及生成大批量的Shell脚本文件,它们叫做Submit-1.sh-Submit-N.sh,这N份输入参数文件需要放到Linux服务器上进行计算。在Linux中执行.sh脚本的时候出现异常,具体表现为/bin/bash^M: bad interpreter: No such file or directory。在此对这个问题的解决方案进行简单总结,以做备忘。

二. 具体表现

先看一眼Submit-1.sh里面的内容,很简单的Shell脚本。

1
2
3
4
5
#!/bin/bash
./$1 -2 -L 290 -H 120 -J 0.1 -h 8.0 -D 290 -r 1.5 -c 1.0e-5 -x 3.1 -b 1.6 -q 1 -R 1
-M 0.01 -U -Q -t 0.1 -T 0.05 -d 1000 -v 1513 -f 1 -G 1.0e7 -k 1.42e5 -p 1.18e-6
-g 1.4 -m 0 -s 1.82e-5 -i 0.04 -l 0.01 -I 0.0 -A 0.0 -E 2.5e6 -P 1e-4 -N 0.35 -n 8
-K 0.010000

这是执行Submit-1.sh的情况

1
2
hmliao@songdyn:~> ./Submit-1.sh
-bash: ./Submit1.sh: /bin/bash^M: bad interpreter: No such file or directory

很显然,是/bin/bash^M这里多了个^M导致在Linux下执行的时候无法识别命令,为什么会多出来一个^M呢?这得从Windows系统和Linux系统的换行标识符来说明。

在Windows系统下的换行标识为\r\n,而Linux格式的换行标识为\n。其中\r表示回车符,\n表示新的一行new line。为何Linux下的换行符不包含\r回车符呢?大概是因为在Linux下处理包含回车符的时候会引起一些程序出现问题,因此和Windows的格式不一致。显然Windows和Linux的格式不一致是众所周知的,然而没有在实际中真正遇到的时候,往往我们是不会意识到它们的差异。

回归正题,在Linux下,回车符显示为^M,而新换一行则用$表示,根据上面的分析,我们是在Windows环境下用SubmitUpdater生成Submit-1.sh-Submit-N.shN份Shell脚本,Windows会给它们的行末加上换行标识\r\n,即便你在代码中明明写的是\n。例如:

1
2
3
4
5
6
7
8
#!/bin/bash
./$1 -2 -L 290 -H 120 -J 0.1 -h 8.0 -D 290 -r 1.5 -c 1.0e-5 -x 3.1 -b 1.6 -q 1 -R 1
-M 0.01 -U -Q -t 0.1 -T 0.05 -d 1000 -v 1513 -f 1 -G 1.0e7 -k 1.42e5 -p 1.18e-6
-g 1.4 -m 0 -s 1.82e-5 -i 0.04 -l 0.01 -I 0.0 -A 0.0 -E 2.5e6 -P 1e-4 -N 0.35 -n 8
-K 0.010000

//代码中#!/bin/bash我是如下定义的, 虽然代码里用\n,然而由于是在Windows下生成,会被定义为\r\n
char firstLine[255] = "#!/bin/bash\n";

因此实际上,上面的#!/bin/bash末尾接了一个Windows下的换行标识\r\n,那么在Linux下显示的时候应该就是#!/bin/bash^M$,我们可以在Linux下通过cat -A filename命令来查看一个脚本文件是Windows格式还是Linux格式。

1
2
3
4
5
6
7
hmliao@songdyn:~> cat -A Submit-1.sh
#!/bin/bash^M$
./$1 -2 -L 290 -H 120 -J 0.1 -h 8.0 -D 290 -r 1.5 -c 1.0e-5 -x 3.1 -b 1.6 -q 1 -R 1
-M 0.01 -U -Q -t 0.1 -T 0.05 -d 1000 -v 1513 -f 1 -G 1.0e7 -k 1.42e5 -p 1.18e-6
-g 1.4 -m 0 -s 1.82e-5 -i 0.04 -l 0.01 -I 0.0 -A 0.0 -E 2.5e6 -P 1e-4 -N 0.35 -n 8
-K 0.010000
hmliao@songdyn:~>

从上面可以看到#!/bin/bash^M$这是Windows格式,显然不是Linux能够识别的#!/bin/bash$,既然是格式不对,能想到的解决方法不外乎就是进行格式转换或者直接生成Linux格式的文件

三. 解决方案

1. 格式转换
Google搜了搜格式转换的方法琳琅满目,很多都是这样的情况:用各种文本编辑器打开文件,然后转存为Linux格式。这当然是一种方法,然而对于我的问题并不适用,我有几千份Windows格式的文件,一份份去转存终究是不现实,如果再写个程序来批处理这样的行为显然就没有意义。

对于我这样的,已经有大批量Windows格式的文件的问题,一种可行的格式转换方法是通过Linux下的dos2unix命令(当然了,同样也有unix2dos),然后在Linux服务器上用dos2unix命令写个循环的脚本文件,就可以轻松的完成格式转换。然而又出现一个问题了,我的Linux服务器上出现-bash: dos2unix: command not found,如下:

1
2
hmliao@songdyn:~> dos2unix Submit-1.sh
-bash: dos2unix: command not found

估计是得安装一下.

update: 2017年12月18日15:33:39

对于 Linux 操作系统,Dos2unix 程序已经被添加到系统的软件源内,可以直接从软件源进行安装。Fedora、CentOS 等 Linux 发行版的安装命令为:

1
sudo yum install dos2unix unix2dos

然而我的账号并没有权限,由于我是通过cygwin/ssh的方式来连接Linux服务器,一个可行的办法是在cygwin模拟的Linux环境下安装dos2unix,然后批量转格式,然后上传到服务器进行计算。

  • 首先下载「apt-cyg」保存的文件名为apt-cyg,没有后缀,然后放到你的cygwin安装目录下的/bin目录里面,然后修改apt-cyg给执行权限,参考以下命令
1
chmod +x /bin/apt-cyg
  • 然后运行cygwin的安装包,在选择包的页面安装wget,如图,接着等待安装完成

安装wget

  • 最后打开cygwin终端,执行apt-cyg install dos2unix进行安装。

此方法经测试有效,如下

1
2
3
hxl650@BoLi-PC /home
$ dos2unix Submit-1.sh
dos2unix: converting file Submit-1.sh to Unix format...

可以看到转换后的文件,没有了^M

1
2
3
4
5
6
7
$ cat -A Submit-1.sh
hxl650@BoLi-PC /home
#!/bin/bash$
./$1 -2 -L 290 -H 120 -J 0.1 -h 8.0 -D 290 -r 1.5 -c 1.0e-5 -x 3.1 -b 1.6 -q 1 -R 1
-M 0.01 -U -Q -t 0.1 -T 0.05 -d 1000 -v 1513 -f 1 -G 1.0e7 -k 1.42e5 -p 1.18e-6
-g 1.4 -m 0 -s 1.82e-5 -i 0.04 -l 0.01 -I 0.0 -A 0.0 -E 2.5e6 -P 1e-4 -N 0.35 -n 8
-K 0.010000

再通过写个循环脚本就可以实现批量转换了,例如

1
2
3
4
5
6
7
8
9
#!/bin/bash
dir_id=1
submitName=Submit-
submitSuffix=.sh
while test $dir_id -le $1
do
dos2unix $submitName$dir_id$submitSuffix
dir_id=`expr $dir_id + 1`
done

批量转换格式后上传到Linux服务器进行计算,则通过格式转换的途径解决了这个问题,但是这他妈的也实在是太繁琐了吧,简直不能忍,也就是一直不甘心一直想把它成功转换,我才坚持把这个方法做通,但是太繁琐了,不推荐。如果有兴趣倒不妨尝试一下。

2. 直接生成Linux格式的文件

前面说过了,不外乎转格式和直接生成正确格式两种方法。现在就简单说说直接生成Linux格式的文件的方法,前面说了半天那么详细的说,为什么到这里就简单说说了呢,那是因为这个方法太简单了。

第一,如果是用文本编辑器手动编辑的脚本文件,那在保存的时候注意一下保存的格式,比如说windows的文本编辑器notepad,在保存的时候,选择为Unix格式的,这样传到Linux服务器的时候格式就没有问题了。当然还有其他很多种文本编辑器了,一样的,就是保存格式设置一下。当然这种情况对我的需求并不适用,我有成百上千份文件,我不可能手动一份创建然后保存。所以需要写程序来做这些工作,也就是前面提到的SubmitUpdater,用SubmitUpdater来生成成百上千份的文件,似乎又回到了需要进行前面格式转换的问题了是不是?其实只要不要在Windows下编译SubmitUpdater即可,把编译工作放到Linux服务器上去做,然后在Linux下执行SubmitUpdater,直接生成Linux格式的脚本文件,这才是正道。不要纠结怎么进行格式转换了,虽然研究起来也蛮有意思。

第二,要把程序放到Linux上去编译,那得自己写makefile了,然而不写不知道,写了才知道makefile有多简单,下面是一个非常好的教程「跟我一起写Makefile:MakeFile介绍」,推荐学习,此处不再赘述了。

update: 2017-6-5 10:36:21
3. 在windows下生成用于linux计算用的文件时保证不输入\r即可
详见:How do I stop fprintf from printing \r’s to file along with \n in Windows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FILE *outputFile = NULL;
outputFile = fopen(outputFileName, "wb");

// Output element sets data
for (int i = 0; i < elementSets.size(); i++)
{
fprintf(outputFile, "8 %s %d\n",
elementSets[i].name,
elementSets[i].element.size());

for (int j = 0; j < elementSets[i].element.size(); j++)
{
fprintf(outputFile, "%d\n", elementSets[i].element[j]);
}
}

四. 参考资料

1. Converting files from Windows format to Unix format with dos2unix
2. cygwin $’\r’: command not found 解决 dos2unix
3. 跟我一起写Makefile:MakeFile介绍