一. 问题描述

「命令行多参数解析时getopt()函数的使用方法小结」一文中介绍了如何用getopt()函数来进行多参数的解析,在该文的「三. 实例」中即用了getopt()函数对38个输入参数进行解析,这些参数用于测试某数值算法,而这些参数中的很大一部分,其参数的取值是有待寻优的,即是说,需要将这些参数在一定的取值范围内对各种取值的情况进行计算,最后分析计算结果找出最佳的参数组合,因而就需要不断的修改参数的取值,然后不断地提交计算。

该数值算法的测试程序为单线程且有若干输入文件及输出结构目录,为了减少测试过程中的手动操作,将采用一台64核Linux系统的服务器来进行测试。整个实现方案虽然很简单,由于平时Linux用得少,许多操作步骤用到一些不熟悉的指令,正所谓好记性不如烂笔头,为了避免日后出现再用到时又忘记而又需要重新花时间去琢磨的情况,在此就花一些时间把整个过程简单的梳理,做个备忘,以供日后参考。

二. 方案概述

  • 测试文件:submit.sh启动程序(参数在此定义)、vos.gcc_v714M.st主程序、mesh.dat输入文件;fluidsolid输出目录,所有这些文件同在一个目录下,如下图所示:

测试文件

  • 测试环境:64核Linux系统

  • 测试方案:每次同时提交进行60份不同的参数进行测试计算

三. 实现过程

1. 为submit.sh启动程序和vos.gcc_v714M.st主程序增加执行权限

1
2
3
4
5
6
7
// 增加执行权限
chmod u+x submit.sh
chmod u+x vos.gcc_v714M.st

// 取消执行权限
// chmod u-x submit.sh
// chmod u-x vos.gcc_v714M.st

增加执行权限后的效果

1
2
-rwxrw-r--. 1 hmliao hmliao      460 Feb  6 02:23 submit.sh*
-rwxrw-r--. 1 hmliao hmliao 460 Feb 6 02:23 vos.gcc_v714M.st*

说明:
第一列共有10个位置,即:-rwxrw-r--
第一个字符指定了文件类型。在通常意义上,一个目录也是一个文件。如果第一个字符是横线,表示是一个非目录的文件。如果是d,表示是一个目录。
从第二个字符开始到第十个共9个字符,3个字符一组,上例中的三组分别是:‘rwx’,‘rw-’‘r--’分别表示了u,g,o,3组用户对文件或者目录的权限。权限字符用横线代表空许可,r代表只读,w代表写,x代表可执行。

u 代表所有者(user)
g 代表所有者所在的组群(group)
o 代表其他人,但不是u和g (other)
a 代表全部的人,也就是包括u,g和o
r 表示文件可以被读(read)
w 表示文件可以被写(write)
x 表示文件可以被执行(如果它是程序的话)

2. 批量创建计算文件

每计算一次都需要submit.shvos.gcc_v714M.stmesh.datfluidsolid这些文件,为了同时计算n次(本例中为60次,下面皆以60次为例进行说明),需要为每次计算准备独立的目录,所以接下来要做的事情是,批量生成60个计算文件夹,再将这5分文件批量复制到各个文件夹中,然后修改每次计算的输入参数submit.sh,输入参数这个得手动修改了,除非测试的参数满足某种规律便于写脚本自动修改,否则就根据测试需求手动慢慢改吧。

批量生成文件夹及批量复制的脚本,命名为CreateFiles.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
dir_id=1
while test $dir_id -le $2
do
mkdir $1$dir_id
cp -r copyFiles/*.* $1$dir_id
cp -a fluid $1$dir_id
cp -a solid $1$dir_id
dir_id='expr $dir_id + 1'
done

说明:

test是Linux里的一个命令,在本例中test的作用是评估一个表达式$dir_id -le $2,如果条件为真,则返回一个 0 值,同时执行do下面的命令。如果表达式不为真,则返回一个大于 0 的值,也可以将其称为假值,结束执行,即done。

$dir_id -le $2这个表达式中dir_id=1,表示从1开始计数,$2表示用户通过命令行输入的第二个参数,这个参数在本例中就是要计算的次数n=60次。那么就简单了,$dir_id -le $2的含义:如果 $dir_id 小于或等于 $2,则为真,执行do后面的命令,否则结束循环。

mkdir $1$dir_id中的$1表示用户通过命令行输入的第一个参数,在本例中的含义是测试文件夹的名称,例如test,$1$dir_id就表示test1、test2、……、test60,mkdir通过while循环来创建这60个文件夹。

cp -r copyFiles/*.* $1$dir_idcp -a fluid $1$dir_idcp -a solid $1$dir_id这三条命令是拷贝文件,由于目录下还有其他一些文件,为了方便,本例将submit.shvos.gcc_v714M.stmesh.dat统一放在copyFiles这个文件夹下,这里面只有这3个文件,没有其他干扰文件,复制起来简单方便,将这个文件下的所有文件通过while循环分布拷贝至60个文件夹中,同时再将输出结果的两个文件夹,也通过while循环拷贝至60个文件夹中。

dir_id='expr $dir_id + 1'这个是更新表达式,不用多说了。

执行

1
./CreateFiles.sh test 60	// test为文件夹名称,自行定义,60为数量,自行定义

效果
生成的60个文件夹,每个文件夹中含有上述的5份文件

3. 修改每次计算的输入参数

如前所述,输入参数这个得手动修改了,除非测试的参数满足某种规律便于写脚本自动修改,否则就根据测试需求手动慢慢改吧。

4. 批量启动测试程序,并且这个测试需要后台运行

本例是用 Cgywin64 Terminal/ssh 登录了远程的 Linux 服务器,每一次的计算任务要花至少1天的时间,因此需要让命令提交后不受本地关闭终端窗口/网络断开连接的干扰。当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)信号从而关闭其所有子进程。因此,一种简单有效的解决方法是让进程忽略 HUP 信号,60个文件夹下的计算程序都通过nohup的方式启动执行,把这个启动执行的过程同样也写成一个脚本,一键提交,这个脚本命名为StartAll.sh

StartAll.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
dir_name=test
dir_id=1
while test $dir_id -le 60
do
cd $dir_name$dir_id
nohup ./submit.sh vos.gcc_v714M.st &
cd ..
dir_id=`expr $dir_id + 1`
done

说明:
nohup ./submit.sh vos.gcc_v714M.st &nohup表示忽略HUP信号,标准输出和标准错误缺省会被重定向到 nohup.out 文件中;./submit.sh是每次计算的输入参数文件,60次各不相同的,vos.gcc_v714M.st是计算主程序,都是一样的;在结尾加上&来将命令放入后台运行。

整个思路即是:首先进入test1文件夹,用后台运行的方式启动程序,然后退出至主目录,通过循环进入test2,又启动程序,由此循环实现n=60次的启动。在主目录执行./StartAll.sh后可以看到所有60个程序都已经启动且是后台运行,如下图所示:

启动60个计算程序,后台运行

5. 计算完成后,必要的删除操作

计算完成后,可能需要批量删除文件及文件夹,下面的命令可能会用到。

1
2
3
4
5
// 删除目录,不管目录中有没有文件,会将一些隐藏的配置文件也删除,小心使用
hmliao@songdyn:~> rm -r /home/hmliao/*

// 强制删除,没有提示,会将一些隐藏的配置文件也删除,小心使用
hmliao@songdyn:~> rm -rf /home/hmliao/*

6. Linux跨服务器复制文件

1
scp file1.txt hmliao@songdyn.case.edu:/home/hmliao

上述为实现过程的备忘,若有更多操作,后续再进行更新。

2016年2月9日17:27:10
于克利夫兰

更新

前面顺利提交了测试算例,接下来是漫长繁琐的数据分析,然而数据分析并不一定要等到算例结束再开始,在算例计算的过程中,根据输出的数据,很容易就可以判别出哪些算例的结果是无效的,对于这些算例就没有必要再接着算下去,这个时候就需要把服务器上对应的进程给结束。那么现在问题来了,60个程序的名称都是一样的即vos.gcc_v714M.st,又是通过./的方式而不是绝对路径的形式提交,所以这个时候查看进程,无法分辨出60个vos.gcc_v714M.st分别属于哪个test,也即是无法轻易的找到对应关系,比如这个vos.gcc_v714M.st属于test1,而那个vos.gcc_v714M.st属于test2,类似这样。

不能轻易的分辨出对应关系,那么想要结束某个进程就不知道该如何下手了。

在Linux里查看进程,一般可用top和ps命令。top的效果如下图:

Top查看进行信息

PID是每个进程的ID,所有进行都有自己的ID且唯一。在COMMAND那一列,可以看到程序的名称,这个时候我们发现前面提交的60个任务所有都叫同一个名字vos.gcc_v714M.st,这样如果想结束某个进行显然不知道对应关系,没法下手。

于是想通过ps命令来查看每个进程所运行的主程序的路径,也就是说它们各自属于哪个文件夹,我们前面定义了60个文件,如果现在能看到各个进程运行的主程序来自于哪个文件夹,这样对应关系就清楚了。于是尝试用下面的方式来查看详细的路径信息:

1
ps aux|grep vos.gcc_v714M.st

然而由于是通过./的方式提交的,所以依然看不到程序的路径信息。

当然,一个蠢方法是可以看到路径信息的,那就是通过已知的PID,然后挨个去看,但这没有任何意义,这没有办法一次把所有的信息列出来,60个要去看60次,也不是想看哪个就能找到哪个,没有人会愿意这么做的。

1
ls -l /proc/PID/exe

所以,这个问题没有得到解决。从计算的结果来看,目前测试的60组数据都不行,那么就把所有的进程一起结束了吧,多简单,有两种方法

1
killall vos.gcc_v714M.st // 这个指的是结束所有指定名称的进程,测试没成功
1
pkill -uhmliao // -u指的是用户名,hmliao是对应的用户,代表把该用户的所有进程结束,测试成功

接下来要做的是,在下次提交的任务的时候,使得进行自带分辨信息,能想到的办法有两种,第一,提交的时候用绝对路径,那样的话用ps aux|grep vos.gcc_v714M.st这样的方式就可以看到每一个进程程序的路径,这样的方法能想像出有多麻烦,首先,提交的指令加上了绝对路径之后肯定很不清晰了,一大串一大串的,其次,查看进行的时候也是有一大串的路径信息,看花眼哟,所以这是一种不可取的选择,也就没有去尝试了。

最简单的莫过于把60个程序改下名字,加上编号或者其他任意需要的信息,然后通过top一看便知,这样就轻易的能找到某个我们想要的进程的PID,再想对它做点什么,还不是分分钟的事。

这样,就在原来CreateFiles.sh的基础上,把原来的单纯拷贝的操作,改为拷贝并重命名即可实现。

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
dir_id=1
while test $dir_id -le $2
do
mkdir $1$dir_id
cp -r copyFiles/*.* $1$dir_id
cp -f vos.gcc_v714M.st $1$dir_id/$1$dir_id.st //把原来在copyFiles里的vos.gcc_v714M.st复制并重命名
cp -a fluid $1$dir_id
cp -a solid $1$dir_id
dir_id=`expr $dir_id + 1`
done

StartALL.sh也做相应的调整

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
dir_name=test
dir_id=1
while test $dir_id -le 61
do
cd $dir_name$dir_id
nohup ./submit.sh $dir_name$dir_id.st & // 启动的是对应的修改后的名称
cd ..
dir_id=`expr $dir_id + 1`
done

最后通过top来查看,效果如下,对应信息一目了然,想结束哪个直接可以看到其对应的PID。

Top查看进行信息

若干参考资料:
1.ps 进程查看器
2.Linux的pkill和pgrep命令详解
3.linux对文件进行复制、移动(重命名)、删除的命令详解
4.一个很好的命令(pkill),一次性杀死某用户所有进程。PS:其他杀进程命令

若有更多操作,后续再进行更新。
2016年2月11日13:46:13