机器学习项目服务器上线部署全流程记录
Overview1.Java端1.1 安装JDK1.81.2 安装Tomcat71.2.1 配置Tomcat7启动内存1.3 检查安装php和Apache21.4 设置Apache2反向代理1.5 安装MySQL5.71.6 安装配置phpmyadmin2.Perl后台2.1 设置Apache2 CGI2.2 Perl相关模块安装2.3 gearman消息队列安装3. Python相关安装4. R语言及相关包安装 Overview 我们团队完整的机器学习项目已经做了两个了，分别是Bastion4和Bastion6。之前两个项目上线部署的记录过于片段化，针对现在马上要做完的Bastion3项目，我们在此完整记录搭建服务器各种环境的全部流程，以备不时之需。 完整的项目基本分为三大部分：Java处理业务逻辑；Perl后台消息队列；R机器学习模型预测。每个大部分还有很多细节，我们会在每部分都详细记录。 新申请的云服务器预装的仍然是Ubuntu16.04LTS发行版的Linux系统。 1.Java端 Java端负责将用户提交的任务信息提交至后台Perl服务器并记录在MySQL数据库中。因此这部分会安装JDK1.8，Tomcat7和MySQL5.7。 1.1 安装JDK1.8 安装JDK1.8，用apt命令很方便，不用单独下载解压。命令如下： sudo apt-get install software-properties-common -y sudo add-apt-repository ppa:webupd8team/java -y sudo apt-get update sudo apt-get install oracle-java8-installer oracle-java8-set-default -y 安装完成之后，JDK1.8会被安装在/usr/lib/jvm/java-8-oracle中，在/etc/profile或者~/.bashrc中配置JAVA_HOME变量 (我用的是~/.bashrc文件)： export JAVA_HOME=/usr/lib/jvm/java-8-oracle 安装好之后，需要以下命令使配置生效： source ~/.bashrc 1.2 安装Tomcat7 使用以下的命令安装Tomcat7: sudo apt-get install tomcat7 安装过程中，可能会报如下的错误(即使你已经配置好了JAVA_HOME): Jul 09 16:52:16 bastiondb systemd[1]: Starting LSB: Start Tomcat.... Jul 09 16:52:16 bastiondb tomcat7[6413]: * no JDK or JRE found - please...E Jul 09 16:52:16 bastiondb systemd[1]: tomcat7.service: Control process e...1 Jul 09 16:52:16 bastiondb systemd[1]: Failed to start LSB: Start Tomcat.. Jul 09 16:52:16 bastiondb systemd[1]: tomcat7.service: Unit entered fail.... Jul 09 16:52:16 bastiondb systemd[1]: tomcat7.service: Failed with resul.... Hint: Some lines were ellipsized, use -l to show in full. dpkg: error processing package tomcat7 (--configure): subprocess installed post-installation script returned error exit status 1 Errors were encountered while processing: tomcat7 E: Sub-process /usr/bin/dpkg returned an error code (1) 出现这个错误，说明tomcat7已经装好了，但是由于tomcat在启动时找不到JAVA_HOME的路径，所以报错。查看/etc/default/tomcat7，会发现下面这行： #JAVA_HOME=/usr/lib/jvm/openjdk-6-jdk 去掉注释，并改为我们自己的JAVA_HOME路径： JAVA_HOME=/usr/lib/jvm/java-8-oracle 重新运行安装命令： sudo apt-get install tomcat7 安装成功... 安装好之后，tomcat7已经在运行了，使用下面命令查看： ps aux | grep tomcat 用下面的命令启动/停止/重启Tomcat7了: sudo service tomcat7 start/stop/restart 但是现在还有一个问题，Tomcat7的端口是8080，访问的时候必须在地址后面加上:8080，这样看起来不太和谐。由于Apache已经占用了80端口，所以我们采用Apache反向代理转发Tomcat请求的方式来解决这个问题。 1.2.1 配置Tomcat7启动内存 Tomcat启动内存不大，在网站数据量增多的情况下，就会在网页访问时报错，很难根据错误信息推测到这里。我们提前增大Tomcat的启动内存。 修改/usr/share/tomcat7/bin/catalina.sh文件 sudo vim /usr/share/tomcat7/bin/catalina.sh 可以看到： # OS specific support. $var _must_ be set to either true or false. cygwin=false darwin=false os400=false case "`uname`" in CYGWIN*) cygwin=true;; Darwin*) darwin=true;; OS400*) os400=true;; 在这段前面添加： JAVA_OPTS="-Xmx4096m" 就可以将tomcat启动内存增加为4G，保存并重启tomcat，就可以了。 下面的内容选择性安装，如果没有特殊需求，最好不要修改 默认的tomcat工作主目录为/var/lib/tomcat6/，网站目录为/var/lib/tomcat6/webapps，如果你的文章会产生大量文件，并且需要存放在网站的目录下，那这个目录就不太合适，因为这个目录通常占用的是系统盘空间。 假设你有一个新的扩展盘，目录为/my_extended_disk，在上面创建一个目录为tomcatWebapps，并想把这个目录，当做你的tomcat网站目录，修改/etc/tomcat7/server.xml sudo vim /etc/tomcat7/server.xml 把 <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> 修改为 <Host name="localhost" appBase="/my_extended_disk/tomcatWebapps" unpackWARs="true" autoDeploy="true"> 然后重启服务器，就可以了。 1.3 检查安装php和Apache2 一般来说，装好的Ubuntu 16.04系统都会默认装好php和Apache2，但配置使用之前最好还是检查一下。 使用下面命令检查php： php -v 如果已经安装，大概会显示下面的内容： PHP 7.0.32-0ubuntu0.16.04.1 (cli) ( NTS ) Copyright (c) 1997-2017 The PHP Group Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies with Zend OPcache v7.0.32-0ubuntu0.16.04.1, Copyright (c) 1999-2017, by Zend Technologies 说明安装了php 7.0。如果没装的话，用下面的命令安装： sudo apt-get install php7.0 在Ubuntu 16.04中，直接用 sudo apt-get install php 默认安装的也是php7.0版本。 使用下面命令检查Apache2： apache2 -v 如果已经安装，大概会显示下面的内容： Server version: Apache/2.4.18 (Ubuntu) Server built: 2018-04-18T14:53:04 说明没装的话，用下面的命令安装： sudo apt-get install apache2 注意：如果php和Apache2都没有安装的情况下，安装顺序一定要先装php，在装Apache2，如果顺序反了，可能会导致Apache2服务器不能识别php页面，在你访问这些页面的时候，直接显示页面的代码，而不是页面本身。 参见 php页面不能被解析成网页，而是直接显示了页面代码。 1.4 设置Apache2反向代理 Apache2默认是随系统安装的，但有时候为了快速安装，去掉了也有可能。由于Apache2是随系统自动启动的，如果Apache2已经安装了并且没有人主动关闭的话，使用： ps aux | grep apache2 就应该看到进程信息，如果没看到，用下面的命令安装： sudo apt-get install apache2 安装成功之后，使用服务器的地址(比如bastion3.erc.monash.edu)访问，就应该看到Apache2的欢迎界面。 如果安装成功，但是网址却无法访问，说明80端口没开，联系管理员，打开这个端口。 修改Apache2配置文件： sudo vim /etc/apache2/apache2.conf 在文件末尾添加以下内容： ProxyPass / http://bastion3.erc.monash.edu:8080/ ProxyPassReverse / http://bastion3.erc.monash.edu:8080/ 可以让Apache2把http://bastion3.erc.monash.edu请求转发到http://bastion3.erc.monash.edu:8080/由tomcat7处理。如果你在Apache2中有一个网站cgi-bin，你访问http://bastion3.erc.monash.edu/cgi-bin时，就想访问Apache2下的cgi-bin，而不是转成http://bastion3.erc.monash.edu:8080/cgi-bin由tomcat7处理，那么改成这样就可以了： ProxyPass /phpmyadmin ! ProxyPass /cgi-bin ! ProxyPass / http://bastion3.erc.monash.edu:8080/ ProxyPassReverse / http://bastion3.erc.monash.edu:8080/ 注意：这里同时写了一个phpmyadmin的配置，是因为我们会使用phpmyadmin管理和访问数据库。参考1.6小节的内容。 重启Apache2服务： sudo service apache2 restart 如果报错： Job for apache2.service failed because the control process exited with error code. See "systemctl status apache2.service" and "journalctl -xe" for details. 根据提示，使用 systemctl status apache2.service 查看错误信息： Jul 09 17:15:56 bastiondb apache2[7914]: * The apache2 configtest failed. Jul 09 17:15:56 bastiondb apache2[7914]: Output of config test was: Jul 09 17:15:56 bastiondb apache2[7914]: AH00526: Syntax error on line 223 o Jul 09 17:15:56 bastiondb apache2[7914]: Invalid command 'ProxyPass', perhap Jul 09 17:15:56 bastiondb apache2[7914]: Action 'configtest' failed. Jul 09 17:15:56 bastiondb apache2[7914]: The Apache error log may have more Jul 09 17:15:56 bastiondb systemd[1]: apache2.service: Control process exite Jul 09 17:15:56 bastiondb systemd[1]: Failed to start LSB: Apache2 web serve Jul 09 17:15:56 bastiondb systemd[1]: apache2.service: Unit entered failed s Jul 09 17:15:56 bastiondb systemd[1]: apache2.service: Failed with result 'e 发现Apache2不能识别新添加的ProxyPass，导致重启失败。这是因为Apache2的代理模块proxy_http未启用，使用下面命令： sudo a2enmod proxy_http 重新启动Apache2： sudo service apache2 restart 成功...这时候再输入服务器的地址，即可访问到tomcat的默认项目了，后面不用加:8080。 1.5 安装MySQL5.7 先更新一下系统，再用apt-get命令直接安装： sudo apt-get update sudo apt-get install mysql-server 安装过程会让选择yes/no,选yes；还有就是设置root用户密码。 安装完成可以查看是不是安装好了,即查看MySQL的状态： sudo service mysql status 显示如下即可： ?.mysql.service - MySQL Community Server Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2018-03-19 14:35:03 UTC; 27s ago Main PID: 17432 (mysqld) CGroup: /system.slice/mysql.service ?..17432 /usr/sbin/mysqld Mar 19 14:35:02 bastion3 systemd[1]: Starting MySQL Community Server... Mar 19 14:35:03 bastion3 systemd[1]: Started MySQL Community Server. 安装好之后，可以用以下命令启动/停止/重启： sudo service mysql stop/stop/restart 本项目的数据的形式和之前项目的很相似，所以可以将之前的Bastion6项目数据库文件导出，然后再导入本项目。 导出命令： mysqldump -uroot -p bastion6 > bastion6.sql 回车之后输入数据库密码即可。 将bastion6.sql内容修改为自己需要的： CREATE DATABASE IF NOT EXISTS bastion3; USE bastion3; DROP TABLE IF EXISTS `job`; CREATE TABLE `job` ( `jobID` int(100) NOT NULL AUTO_INCREMENT, `jobName` varchar(256) NOT NULL, `orgnization` varchar(1000) , `email` varchar(100) NOT NULL, `status`int(1) NOT NULL, `createTime` datetime NOT NULL, `startTime` datetime , `endTime` datetime , `sequence` longtext NOT NULL, `sequenceNumber` int(10) NOT NULL, PRIMARY KEY (`jobID`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `predictresult`; CREATE TABLE `predictresult` ( `jobID` int(100) NOT NULL AUTO_INCREMENT, `jobName` varchar(256) NOT NULL, `predictionResult` longtext, PRIMARY KEY (`jobID`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `pssm`; CREATE TABLE `pssm` ( `id` int(100) NOT NULL AUTO_INCREMENT, `seq` varchar(10000), `output` varchar(500) , PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `downloadInfo`; CREATE TABLE `downloadInfo` ( `id` int(100) NOT NULL AUTO_INCREMENT, `type` varchar(10000), `count` varchar(500) , PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8; INSERT INTO `downloadInfo` VALUES (1,'originalData','0'),(2,'independentData','0'),(3,'predictData','0'); 然后登录数据库: mysql -u root -p 回车之后输入密码。 将上面的SQL命令输入，回车即可创建完整的数据库表。 至此，Java部分安装配置就算完成了。 1.6 安装配置phpmyadmin 命令行查看和管理MySQL还是有点不方便，所以我们这里安装一个phpmyadmin，就可以使用web界面查看和操作数据库。 这时候直接使用网址+phpmyadmin，浏览器会显示找不到phpmyadmin。那是因为apache2默认的网站路径是/var/www/html，而不是/var/www，编辑/etc/apache2/sites-enabled/000-default.conf： sudo vim /etc/apache2/sites-enabled/000-default.conf 把DocumentRoot /var/www/html修改为DocumentRoot /var/www 重启apache2，重新访问，就可以了。 安装phpmyadmin sudo apt-get install phpmyadmin 安装过程中，会弹出一个Configuring phpmyadmin框，让你选择要运行phpmyadmin的服务器，默认是apache2，还可以选择lighttpd，因为我们之前安装的web服务器是apache2，所以直接敲回车就行了。 然后安装一阵子继续弹出一个Configuring phpmyadmin框，提示你是否需要配置数据库，毕竟phpmyadmin本身就是管理数据库的，不配置好数据库，也就没有安装的意义了，默认选择的是Yes，回车，会转到新的界面让你输入密码，也就是刚才安装MySQL时设置的密码，输入之后敲回车确认，再重复确认密码，完成安装。 安装完成之后，发现在apache2的网站目录/var/www下，并没有多出来一个phpmyadmin，这是因为Ubuntu系统在安装phpmyadmin时，默认安装在了/usr/share/下: ls /usr/share/ 就可以看到里面有phpmyadmin。 我们直接创建一个软连接： sudo ln -s /usr/share/phpmyadmin /var/www/phpmyadmin 这样就可以在/var/www下访问到phpmyadmin。 如果发现虽然能访问phpmyadmin了，但是浏览器直接显示了代码，而不是网页，说明apache2没有自动安装解析php的包。 先用下面的命令看看你装的php版本： php --version 如果是php7.0，那么安装下面的包： sudo apt-get install libapache2-mod-php7.0 然后重启apache2，现在应该可以访问到正常的网页了。具体原因，参见php页面不能被解析成网页，而是直接显示了页面代码。 用户名是root，密码是MySQL设置的密码。 注意： 由于phpmyadmin网站暴露在公网之后，可能会招到攻击，比如使用脚本破解密码等，因此我们需要配置一下白名单，中只允许指定IP访问。修改/etc/apache2/apache2.conf，在里面添加: <Directory /var/www/phpmyadmin> Options Indexes FollowSymLinks Includes ExecCGI AllowOverride All # Key configuration for white list Order Deny,Allow Allow from xx.xx.xx.xx Allow from xx.xx.xx.xy Deny from All </Directory> 其中，/var/www/phpmyadmin为phpmyadmin网站路径，xx.xx.xx.xx和xx.xx.xx.xy为允许访问的IP地址。 2.Perl后台 Perl的主要作用是它的消息队列以及和Java通讯的CGI模块。因此需要配置Apache2 CGI以及安装相应的Perl模块。 2.1 设置Apache2 CGI 在安装CGI模块之前，最好先将make安装好： sudo apt-get install make 然后安装Perl的CGI模块，推荐使用cpan命令安装 (一定要用sudo权限打开)： 先打开cpan： sudo cpan 然后输入下面的命令: install CGI 下面修改CGI程序的根目录： sudo vim /etc/apache2/apache2.conf 将配置文件中对应的地方改为如下： <Directory /var/www/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> 在/var/www/cgi-bin目录中新建一个cgi_test.pl文件，以创建一个Perl CGI网站，文件内容为： #!/usr/bin/perl -w use warnings; use CGI qw(:standard); #! must use 'my' to define a variable print header; my $now_string = localtime(); print "Hello, CGI using Perl! It's $now_string NOW! "; 为此文件添加执行权限： sudo chmod +x /var/www/cgi-bin/cgi_test.pl 在命令行中执行此文件： sudo perl /var/www/cgi-bin/cgi_test.pl 如果之前的设置正确，则会显示以下内容： Content-Type: text/html; charset=ISO-8859-1 Hello, CGI using Perl! It's Tue Mar 20 14:17:15 2018 NOW! 至此，虽然可以在命令行中运行此程序，但是还不能作为服务器远程访问。我们需要做以下配置，让Apache2对Perl支持： sudo vim /etc/apache2/sites-enabled/000-default.conf 将下面的内容： <VirtualHost *:80> ... ServerAdmin webmaster@localhost DocumentRoot /var/www ... ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> 修改为： <VirtualHost *:80> ... ServerAdmin webmaster@localhost DocumentRoot /var/www ScriptAlias /cgi-bin/ /var/www/cgi-bin/ <Directory "/var/www/cgi-bin"> AllowOverride all Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all AddHandler cgi-script .cgi .pl </Directory> ... ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> 在Apache中启用CGI模块。注意 VirtualHost *:80这里的端口号尽量不要改动，因为使用网址或者ip地址访问，不加端口号的话，默认就是访问80端口号，如果改了端口号，就得显示加上。 默认情况下，这个模块是没有开启的，这点可以用以下命令确认： ls -l /etc/apache2/mods-enabled/ | grep cgi 显示为空表示当前启用的与CGI相关的模块为空。用下面的命令查看有哪些模块可以被启用： ls -l /etc/apache2/mods-available/ | grep cgi 显示如下： -rw-r--r-- 1 root root 74 Mar 19 2016 authnz_fcgi.load -rw-r--r-- 1 root root 115 Mar 19 2016 cgid.conf -rw-r--r-- 1 root root 60 Mar 19 2016 cgid.load -rw-r--r-- 1 root root 58 Mar 19 2016 cgi.load -rw-r--r-- 1 root root 89 Mar 19 2016 proxy_fcgi.load -rw-r--r-- 1 root root 89 Mar 19 2016 proxy_scgi.load 这样，我们在mods-enabled中创建软连接，指向mods-available中的cgid.*文件： sudo ln -s /etc/apache2/mods-available/cgid.load /etc/apache2/mods-enabled/ sudo ln -s /etc/apache2/mods-available/cgid.conf /etc/apache2/mods-enabled/ 重启Apache服务器： sudo service apache2 restart 此时，如果在浏览器中访问：http://bastion3.erc.monash.edu/cgi-bin/cgi_test.pl应该会显示： Hello, CGI using Perl! It's Tue Mar 20 14:17:15 2018 NOW! 这样就算成功了。 2.2 Perl相关模块安装 在安装这部分模块之前，最好保证gcc和g++两个程序已经安装好了，如果没有安装好，那么按照以下命令安装： sudo apt-get install gcc sudo apt-get install g++ Perl模块仍然要使用sudo cpan命令安装，然后依次输入以下命令： install Storable install DBI install IO::All install Bio::SeqIO install Bio::Seq install Capture::Tiny install SOAP::Transport::HTTP install MIME::Lite install Mail::Sendmail 2.3 gearman消息队列安装 当前最新版的gearman是gearmand-1.1.12。我们执行下面几步，先将其下载到本地主文件夹，并解压缩。 cd ~ sudo apt-get update wget https://launchpad.net/gearmand/1.2/1.1.12/+download/gearmand-1.1.12.tar.gz tar zxvf gearmand-1.1.12.tar.gz cd gearmand-1.1.12/ 进入gearmand-1.1.12文件夹后，如果直接运行 ./configure 就会报缺少如下几个依赖包错误： configure: error: could not find boost configure: error: Could not find a version of the library configure: error: could not find gperf configure: error: Unable to find libevent configure: error: Unable to find libuuid 所以，我们先将这些依赖都安装好： sudo apt-get install libboost-dev sudo apt-get install libboost-all-dev sudo apt-get install gperf sudo apt-get install libevent-dev sudo apt-get install uuid-dev 安装好之后，如果没有错误，则运行： ./configure 然后仍在gearmand-1.1.12文件夹下运行下面两条命令，编译时间比较长： sudo make sudo make install 这个过程中如果出现了错误，就运行下面的命令清除一下之前编译产生的可执行文件以及object文件(即扩展名为o的文件)： sudo make clean 继续重新安装编译： ./configure sudo make sudo make install 没有错误的话，就安装gearman的job server： sudo apt-get install gearman-job-server 安装好以后，运行一下gearman: gearman -d 会提示错误： gearman: error while loading shared libraries: libgearman.so.8: cannot open shared object file: No such file or directory 这表示找不到libgearman.so.8所在的目录。这时我们打开/etc/ld.so.conf文件： sudo vim /etc/ld.so.conf 添加一句话: include /usr/local/lib 保存退出，并执行下面这句： sudo /sbin/ldconfig 这样就不会出错了。 启动一下job server： gearmand -d 报错如下： gearmand: Could not open log file "/usr/local/var/log/gearmand.log", from "/home/ubuntu/gearmand-1.1.12", switching to stderr. (No such file or directory) 我们这样解决：在/usr/local/下面新建var子目录，进去，新建log子目录，再进去，新建文件gearmand.log。这样就没有问题了。 用sudo权限运行下面的命令： sudo gearmand -d -L 127.0.0.1 -p 4730 -d表示daemon，在后台运行； -L表示监听的ip，默认是localhost； -p表示监听的端口号port，默认是4730。 此时，使用下面的命令，即可查看gearman当前状态： sudo lsof -i:4730 如果显示如下，就表示成功安装： COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 27831 gearman 10u IPv4 320614 0t0 TCP localhost:4730 (LISTEN) 这样gearman在ubuntu16.04上面就安装成功了。 至此，我们还需要安装三个Perl模块： Gearman::Server Gearman::Client Gearman::Worker 在安装Gearman::Server的时候，需要依赖Net::SSLeay,IO::Socket::SSL和Gearman::Util,而安装Net::SSLeay的时候，如果没有libssl-dev这个系统包，是安装不上Net::SSLeay的。所以我们必须要先安装好libssl-dev。 sudo apt-get install libssl-dev 然后必须按顺序依次安装Net::SSLeay，顺序不能乱: 继续打开cpan： sudo cpan 然后 install Net::SSLeay install IO::Socket::SSL install Gearman::Util 依次安装： install Gearman::Server install Gearman::Client install Gearman::Worker 都安装后之后，可以重启一下机器。 启动之后，用如下命令观察4730端口，查看gearman的job server是否已经启动(gearmand -d这条命令是否生效)： sudo lsof -i:4730 显示如下： COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 907 gearman 10u IPv4 21414 0t0 TCP localhost:4730 (LISTEN) 可以知道，这个server是开机自启动的。 至此，gearman消息队列的安装就算完成了。 每次重启服务器，都需要重启worker脚本。worker和client脚本都会放在/var/www/cgi-bin/目录下面，因此启动worker和命令如下(比如我们要开16个worker)： sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_1.txt & sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_2.txt & sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_3.txt & sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_4.txt & ... sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_16.txt & 而client会在提交任务的时候，由Java调用，因此不需要我们手动启动。 现在不再需要手动重启了。 运行sudo crontab -e（e代表edit），可以编辑创建一个cron任务，可能需要先选择编辑器，熟悉哪个选哪个就行了，我选的3. vim： @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_1.txt 2>&1 @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_2.txt 2>&1 @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_3.txt 2>&1 @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_4.txt 2>&1 ... @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_16.txt 2>&1 这样之后服务器重启，就不必再手动开这些进程了。 使用sudo crontab -l(l代表list)可以查看所有你创建的cron任务。注意：用sudo创建的任务用sudo查看，才能看得到，同样sudo crontab -l查看不到crontab -e 创建的任务。 相对于手动执行脚本，这里的配置有三个变化： 把log日志重定向到了/var/log/目录下，可以直接在这里查看错误信息。 不再需要用nohup命令了，nohup是因为我们用ssh登录，需要在ssh退出后脚本依然运行才这么做的，现在重启时系统系统这个脚本，这个脚本会一直留在系统中，不必再用nohup。 由cron启动脚本时，当前工作目录可能跟想象中不一样，我们执行sudo perl /var/www/cgi-bin/worker_bastion3.pl（不管用不用nohup）时，脚本的工作目录在/var/www/cgi-bin，而由cron执行时，当前工作目录却是sudo perl /var/www/cgi-bin/worker_bastion3.pl，导致脚本中使用相对目录的地方会报错，比如这里： $seqs > io("sequence/$jobName.fasta"); … 当然，好的习惯是尽量不要用相对路径，因为你不知道这个脚本会在哪里或者哪种环境下执行，执行时的当前工作目录也可能无法预测。 3. Python相关安装 Python在此系列项目当中，主要是用来提取特征，因此需要依次安装以下几个工具包： sudo apt-get install python-scipy sudo apt-get install python-dev sudo apt-get install python-numpy sudo apt-get install python-matplotlib sudo apt-get install python-pandas sudo apt-get install python-sklearn 4. R语言及相关包安装 R语言在此系列项目当中，既要提取部分特征，又要负责训练模型，还要使用ggplot2画图，所以有很多相关的包是要安装的。 首先安装R语言： sudo apt-get install r-base 然后安装以下几个包 (其实这个服务器在机器学习部分主要作用就是预测，因为训练部分的工作都会在线下进行，不过仍然将所有机器学习部分必须的包都记录在此，以备查阅)。 sudo权限启动R语言： sudo R 这里必须用sudo启动R安装，这样R包一般会安装在/usr/local/lib/R/site-library中，不用sudo权限，会把R包装在当前用户的个人空间，比如~/R/x86_64-pc-linux-gnu-library/3.2。在普通状态下这两个位置并无不同，但是如果当前用户进入root模式(比如用sudo -i命令)，再使用R就不能正确找到R包了。很不幸，因为我们用了crontab设置了worker开机自启动，导致在sudo模式调用了worker的程序，并由worker进一步调用了R程序，就没能找到普通权限下安装的R包，导致报错了。 然后在启动的R语言对话框中输入以下命令： install.packages("protr") install.packages("caret") install.packages("e1071") install.packages("plyr") install.packages("RSNNS") install.packages("ROCR") install.packages("ggplot2") install.packages("randomForest") 至此一个完整的机器学习项目的服务器就已经算是搭建好了。 需要格外注意的是，因为我们的缓存文件都在外接硬盘上，所以每次都重启服务器之后都需要手动加载此硬盘： sudo mount /dev/vdb /bastion3_cache/ 现在不用这么做了，通过配置/etc/fstab可以直接再重启服务器时自动挂载，在/etc/fstab文件中添加下面的行（需要用sudo编辑）： /dev/vdb /bastion3_cache auto defaults,nofail,x-systemd.requires=cloud-init.service,comment=cloudconfig 0 2 其中，/dev/vdb是要挂在的物理硬盘盘符，/bastion3_cache是挂载盘目录。 如果你不清楚哪个盘需要挂载，用df -h查看： ubuntu@bastion3:~$ df -h Filesystem Size Used Avail Use% Mounted on udev 32G 0 32G 0% /dev tmpfs 6.3G 180M 6.2G 3% /run /dev/vda1 9.9G 4.2G 5.2G 45% / tmpfs 32G 0 32G 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock tmpfs 32G 0 32G 0% /sys/fs/cgroup /dev/vdb 473G 9.0G 440G 3% /bastion3_cache tmpfs 6.3G 0 6.3G 0% /run/user/1000 如果有硬盘为未挂载，那Mounted on这列应该是空的，或者是/mnt（默认挂载在/mnt目录）。挂载前需要自己创建一个目录，比如sudo mkdir /bastion3_cache。
Overview1.下载安装gearman2.安装perl扩展包3.运行测试perl脚本 Overview Bastion4这个项目经过我们实验验证和安全考虑，决定舍弃kafka而转用gearman这个消息队列框架，具体分析将在后续文章中给出，这里只记录gearman相关的安装。 1.下载安装gearman 最新版的gearman是gearmand-1.1.12。我们执行下面几步，先将其下载到本地主文件夹，并解压缩。 sudo apt-get update wget https://launchpad.net/gearmand/1.2/1.1.12/+download/gearmand-1.1.12.tar.gz tar zxvf gearmand-1.1.12.tar.gz cd gearmand-1.1.12/ 进入gearmand-1.1.12文件夹后，如果直接运行 ./configure 就会报缺少如下几个依赖包错误： configure: error: could not find boost configure: error: Could not find a version of the library configure: error: could not find gperf configure: error: Unable to find libevent configure: error: Unable to find libuuid 所以，我们先将这些依赖都安装好： sudo apt-get install libboost-dev sudo apt-get install libboost-all-dev sudo apt-get install gperf sudo apt-get install libevent-dev sudo apt-get install uuid-dev 安装好之后，如果没有错误，仍在gearmand-1.1.12文件夹下运行下面两条命令，编译时间比较长： sudo make sudo make install 这个过程中如果出现了错误，就运行下面的命令清除一下之前编译产生的可执行文件以及object文件(即扩展名为o的文件)： sudo make clean 继续重新安装编译： ./configure sudo make sudo make install 没有错误的话，就安装gearman的job server： sudo apt-get install gearman-job-server 安装好以后，运行一下gearman: gearman 会提示错误： gearman: error while loading shared libraries: libgearman.so.8: cannot open shared object file: No such file or directory 这表示找不到libgearman.so.8所在的目录。这时我们打开/etc/ld.so.conf文件： sudo vim /etc/ld.so.conf 添加一句话: include /usr/local/lib 保存退出，并执行下面这句： sudo /sbin/ldconfig 这样就不会出错了。 启动一下job server： gearmand -d 报错如下： gearmand: Could not open log file "/usr/local/var/log/gearmand.log", from "/home/young/gearmand-1.1.12", switching to stderr. (No such file or directory) 我们这样解决：在/usr/local/下面新建var子目录，进去，新建log子目录，再进去，新建文件gearmand.log。这样就没有问题了。 用sudo权限运行下面的命令： sudo gearmand -d -L 127.0.0.1 -p 4730 -d表示daemon，在后台运行； -L表示监听的ip，默认是localhost； -p表示监听的端口号port，默认是4730。 这样gearman在ubuntu14.04上面就安装成功了。 2.安装perl扩展包 perl端需要3个扩展包： Gearman::Server Gearman::Client Gearman::Worker 可以用之前Chris的博客BioPerl（一）：安装BioPerl中介绍的方法，先安装好CPAN。 之后，用sudo权限打开CPAN： sudo cpan 然后依次安装3个扩展包： install Gearman::Server install Gearman::Client install Gearman::Worker 都安装后之后，可以重启一下机器。 启动之后，用如下命令观察4730端口，查看gearman的job server是否已经启动(gearmand -d这条命令是否生效)： sudo lsof -i:4730 如果不加sudo是看不到的，所以linux命令建议都要加上sudo。 可以看到： COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 576 gearman 9u IPv4 12861 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 10u IPv6 12862 0t0 TCP *:4730 (LISTEN) 服务器端可能会只显示 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 576 gearman 9u IPv4 12861 0t0 TCP *:4730 (LISTEN) 可见这个server是开机启动的。 3.运行测试perl脚本 client.pl将消息发送到server，相当于生产者； worker.pl来处理这些消息，并把结果返回给client.pl， 相当于消费者。我们观察输出即可。 client.pl代码如下： #!/usr/bin/perl use strict; use warnings; use Gearman::Client; use Storable; use Storable qw(freeze); use Storable qw(thaw); use IO::All; # fork this process my $pid = fork(); if ($pid == 0) { # do this in the child print "start new client \n"; my $client = Gearman::Client->new; print "finish new client \n"; print "start job_servers \n"; $client->job_servers('127.0.0.1',4730); print "finish job_servers \n"; # 设置异步任务 print "start new_task_set \n"; my $tasks = $client->new_task_set; print "finish new_task_set \n"; print "start add_task \n"; #handle database my @rows=('hello','byebye'); $tasks->add_task( # 开始任务,多个参数 showMessage => freeze(\@rows), # 注册回调函数 { on_complete => \&complete }, ); print "finish add_task \n"; print "start wait \n"; # 等待任务结束 $tasks->wait; print "finish wait \n"; exit; } print "The background task will be finished shortly.\n"; sub complete{ my $ret = ${ $_[0] }; #io("complete.txt")->print($ret); print $ret, "\n"; } worker.pl代码如下： #!/usr/bin/perl use strict; use warnings; use Gearman::Worker; use Storable qw(thaw); use Storable qw(freeze); print "start new worker \n"; my $worker = Gearman::Worker->new; print "finish new worker \n"; print "start job_servers \n"; $worker->job_servers('127.0.0.1',4730); print "finish job_servers \n"; # Worker 注册可以使用的功能 print "start register_function \n"; $worker->register_function( showMessage => \&showMessage ); print "finish register_function \n"; # 等待连接的任务 print "start work \n"; $worker->work while 1; print "finish work \n"; sub showMessage{ my @row=@{ thaw($_[0]->arg) }; my $job = \@row; print "\n"; print "$row[0] \n"; print "$row[1] \n"; print "start sleep \n"; my $date = &getTime(); print $date->{date}," ",$date->{hour},":",$date->{minute},":",$date->{second}; print "\n"; sleep(10); print "finish sleep \n"; $date = &getTime(); print $date->{date}," ",$date->{hour},":",$date->{minute},":",$date->{second}; print "\n"; my $ret = "hello world"; return $ret; } sub getTime { my $time = shift || time(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); $year += 1900; $mon ++; $min = '0'.$min if length($min) < 2; $sec = '0'.$sec if length($sec) < 2; $mon = '0'.$mon if length($mon) < 2; $mday = '0'.$mday if length($mday) < 2; $hour = '0'.$hour if length($hour) < 2; my $weekday = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat')[$wday]; return { 'second' => $sec, 'minute' => $min, 'hour' => $hour, 'day' => $mday, 'month' => $mon, 'year' => $year, 'weekNo' => $wday, 'wday' => $weekday, 'yday' => $yday, 'date' => "$year-$mon-$mday" }; } 我们先运行worker.pl sudo perl worker.pl 再查看4730端口： sudo lsof -i:4730 发现多了两个占用： COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 576 gearman 9u IPv4 12861 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 10u IPv6 12862 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 33u IPv4 191760 0t0 TCP localhost:4730->localhost:37151 (ESTABLISHED) perl 4508 root 3u IPv4 192605 0t0 TCP localhost:37151->localhost:4730 (ESTABLISHED) 再运行多个client.pl sudo perl client.pl 每启动一个client.pl就会多两个端口占用。 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 576 gearman 9u IPv4 12861 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 10u IPv6 12862 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 33u IPv4 191760 0t0 TCP localhost:4730->localhost:37151 (ESTABLISHED) gearmand 576 gearman 34u IPv4 215493 0t0 TCP localhost:4730->localhost:37189 (ESTABLISHED) perl 4508 root 3u IPv4 192605 0t0 TCP localhost:37151->localhost:4730 (ESTABLISHED) perl 4709 root 3u IPv4 214688 0t0 TCP localhost:37189->localhost:4730 (ESTABLISHED) 每运行一次worker.pl表示启动一个新的消费者。如果只启动一个消费者，而启动多个生产者，就可以很好地观察到“排队”效果了：一定是一个任务执行完成之后才会开始处理另一个任务。 这样gearman和perl扩展包的安装使用就结束了。 这篇文章主要参考了如下几篇文章： Gearman Job Server Gearman 使用 Gearman 实现分布式处理 Gearman 安装使用 以及 问题处理 Ubuntu下Gearman安装搭建
Overview1. Apache2基本配置2. 配置Apache2支持Perl CGI程序2.1 创建一个Perl网站2.2 配置Apache服务器3. 可能遇到的一些错误3.1 500 Internal Server Error3.2 503 Service Unavailable3.3 404 Not Found3.4 403 Forbidden4. 小结参考文章 Overview 这次我们开发Bastion4服务器使用了JAVA+Perl的架构，后端用Perl做服务器提供Webservice，用JAVA框架Struts接收处理用户请求，再跟Perl服务器交互。 我们使用Apache2作为Perl服务器，由于Apache2默认并不支持Perl，因此需要简单配置一下，使得Apache2以CGI的方式支持Perl运行。在配置的过程中，参考了一些网页，但由于各个网页都没有完整地描述配置过程（至少在我们的服务器上是这样），所以这里记录一下我们配置的全过程。 1. Apache2基本配置 我们为Bastion4申请了一个新的云服务器，版本为NeCTAR Ubuntu 16.04 LTS x86_64，自带了Apache2服务器，下面简单列出了Apache2的一些基本信息： /etc/apache2/ Apache2的配置文件目录 /var/www/ Apache2的网站存放目录 /var/log/apache2/ Apache2的日志存放目录 /etc/apache2/apache2.conf Apache2的配置主文件，以前的Apache版本中，主配置文件名字叫httpd.conf，所以很多网页中还用到这个名字，不知道的话会造成很多混淆。尽管apache2.conf是主配置文件，但是多数具体的配置信息并不是写在这个文件中的，/etc/apache2目录中有还有几个文件夹分别记录了几个模块的配置信息，apache2.conf负责把他们导入整合在一起。 /etc/apache2/sites-enabled/ 看名字就可以明白，Apache2的网站配置文件一般都是放在这个目录中的，这个目录下的000-default.conf是我们经常要修改的配置文件。 我们假定Apache存放网站的根目录是/var/www/，如果不确定(因为默认好像是/var/www/html这个目录)，打开/etc/apache2/apache2.conf目录，查看下面的配置： <Directory /var/www/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> 如果在你的配置里是 <Directory /var/www/html/> 修改成 <Directory /var/www/> 这样，当你把一个名叫Mywebsite的网站(通常是一个文件夹)，扔到/var/www中时，你就可以以http://localhost/Mywebsite/访问到这个网站了。 如果你的配置是<Directory /var/www/html/>，那你需要把你的网站扔到/var/www/html中，才能使用http://localhost/Mywebsite/访问，这是因为<Directory>设置了Apache的根目录，当你使用http://****/Mywebsite访问时，Apache会去当前的根目录/Mywebsite找这个网站。 由于Apache的端口号默认是80，而不加端口号的url访问都会被服务器默认定向到80端口，因此这里不需要添加端口号，如果你手动将Apache端口号修改成了别的端口号（比如8888），那你需要使用http://localhost:8888/Mywebsite/访问这个网站。 2. 配置Apache2支持Perl CGI程序 2.1 创建一个Perl网站 在/var/www下创建网站的目录，我们创建一个名字叫cgi-bin的网站作为例子： mkdir /var/www/cgi-bin 需要使用管理员权限的命令，请自己加上sudo。 在/var/www/cgi-bin中创建一个cgi_test.pl脚本，输入下面的内容： #!/usr/bin/perl -w use warnings; use CGI qw(:standard); #! must use 'my' to define a variable print header; my $now_string = localtime(); print "Hello, CGI using Perl! It's $now_string NOW! "; 为cgi_test.pl添加可执行权限： chmod +x /var/www/cgi-bin/cgi_test.pl 在命令行运行cgi_test.pl： /var/cgi-bin/www/cgi_test.pl 会得到下面的结果： Content-Type: text/html; charset=ISO-8859-1 Hello, CGI using Perl! It's Mon Aug 1 03:35:42 2016 NOW! 成功运行这个perl脚本需要先安装perl的CGI库 尽管现在我们可以在本地以命令行的形式运行这个perl脚本，但还是不能让这个脚本在服务器中运行，访问http://localhost:8888/cgi-bin/cgi_test.pl，浏览器会直接显示整个脚本的内容： #!/usr/bin/perl -w use warnings; use CGI qw(:standard); #! must use 'my' to define a variable print header; my $now_string = localtime(); print "Hello, CGI using Perl! It's $now_string NOW! "; 而不是只把脚本打印的内容显示出来，所以，接下来我们继续配置Apache服务器。 2.2 配置Apache服务器 打开/etc/apache2/sites-enabled/000-default.conf配置文件，找到下面的配置信息： <VirtualHost *:8080> ... ServerAdmin webmaster@localhost DocumentRoot /var/www ... ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> 我们需要添加一些配置信息，添加之后，000-default.conf的内容如下： <VirtualHost *:8080> ... ServerAdmin webmaster@localhost DocumentRoot /var/www ScriptAlias /cgi-bin/ /var/www/cgi-bin/ <Directory "/var/www/cgi-bin"> AllowOverride all Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all AddHandler cgi-script .cgi .pl </Directory> ... ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> 很容易看得出来，这部分配置主要是用来添加Apache对perl的支持。 默认情况下，Apache并没有启用CGI模块，我们可以从/etc/apache2/mods-enabled/中确认这点。 ls -l /etc/apache2/mods-enabled/ | grep cgi 显示结果为空。 ls -l /etc/apache2/mods-available/ | grep cgi 显示下面的结果： -rw-r--r-- 1 root root 74 Mar 19 09:48 authnz_fcgi.load -rw-r--r-- 1 root root 58 Mar 19 09:48 cgi.load -rw-r--r-- 1 root root 115 Mar 19 09:48 cgid.conf -rw-r--r-- 1 root root 60 Mar 19 09:48 cgid.load -rw-r--r-- 1 root root 89 Mar 19 09:48 proxy_fcgi.load -rw-r--r-- 1 root root 89 Mar 19 09:48 proxy_scgi.load 从名字上就可以看出来，mods-enabled中存放的是已经启用的模块，mods-available中存放的是所有可用的模块，所以启用CGI模块变得很简单，把mods-available中的相关文件在mods-enabled中创建软连接就可以了。 在mods-enabled中创建软连接，指向mods-available中的cgid.*文件： ln -s /etc/apache2/mods-available/cgid.load /etc/apache2/mods-enabled/ ln -s /etc/apache2/mods-available/cgid.conf /etc/apache2/mods-enabled/ 再次查看mods-enabled： ls -l /etc/apache2/mods-enabled/ | grep cgi 显示结果为： lrwxrwxrwx 1 root root 37 Jul 24 11:55 cgid.conf -> /etc/apache2/mods-available/cgid.conf lrwxrwxrwx 1 root root 37 Jul 24 11:55 cgid.load -> /etc/apache2/mods-available/cgid.load 重启Apache服务器： service apache2 restart 或者重新载入配置文件: service apache2 reload 稳妥的方式是restart Apache服务器，而不只是reload服务器，参见3.2。 在浏览器中访问：http://localhost:8888/cgi-bin/cgi_test.pl就会显示： Hello, CGI using Perl! It's Mon Aug 1 03:48:37 2016 NOW! 可以看到perl脚本在Apache服务器中以CGI脚本的形式运行了。 3. 可能遇到的一些错误 实际上，如果你严格按照上面的步骤，基本上不会出现错误，但是在这里，还是列出一些常见的错误，以便真的遇到这些问题时可以快速查看。 本节主要取自Perl/CGI script with Apache2，所以里面的脚本名字（这里是echo.pl，脚本的路径（这里是/var/cgi-bin而不是/var/www/cgi-bin）和错误信息都沿用了这篇文章的内容，与本文上面使用的脚本名字，脚本路径和脚本内容内容稍有不同，但也都大同小异。 3.1 500 Internal Server Error 如果你使用浏览器访问perl脚本时，看到500 Internal Server Error错误，打开Apache的错误日志/var/log/apache2/error.log。 如果显示下面的错误信息： [Wed Mar 19 15:19:15.740781 2014] [cgid:error] [pid 3493:tid 139896478103424] (8)Exec format error: AH01241: exec of '/var/cgi-bin/echo.pl' failed [Wed Mar 19 15:19:15.741057 2014] [cgid:error] [pid 3413:tid 139896186423040] [client 192.120.120.120:62309] End of script output before headers: echo.pl 说明脚本的第一行sh-bang line没有正确指向'perl'的安装路径，检查你的perl脚本，确认第一行是下面的样子： #!/usr/bin/perl 其实这样的错误非常罕见，除非你是新手，而且又完全自己手写了一段perl脚本。 如果显示下面的错误信息： No such file or directory: AH01241: exec of '/var/cgi-bin/echo.pl' failed [Wed Mar 19 15:24:33.505429 2014] [cgid:error] [pid 3412:tid 139896261957376] [client 192.120.120.120:58087] End of script output before headers: echo.pl 说明echo.pl是DOS格式，而非Unix格式，如果你经常的是Windows，或者喜欢在Windows下写好脚本再上传到Unix/Linux服务器中，那么这会是一个常见而且通用的错误。使用dos2unix命令转换脚本的格式： dos2unix /var/cgi-bin/echo.pl 系统默认可能并没有装dos2unix命令，需要自己安装一下。如果你用的是Ubuntu，用sudo apt install dos2unix安装。 如果显示下面的错误信息： [Wed Mar 19 15:40:31.179155 2014] [cgid:error] [pid 4796:tid 140208841959296] (13)Permission denied: AH01241: exec of '/var/cgi-bin/echo.pl' failed [Wed Mar 19 15:40:31.179515 2014] [cgid:error] [pid 4702:tid 140208670504704] [client 192.120.120.120:60337] End of script output before headers: echo.pl 说明你没有为perl脚本添加可执行权限，使用chmod命令很容易更正这个错误： chmod +x /var/cgi-bin/echo.pl 如果显示下面的错误信息： Wed Mar 19 16:02:20.239624 2014] [cgid:error] [pid 4703:tid 140208594970368] [client 192.120.120.120:62841] malformed header from script 'echo.pl': Bad header: hi 上面错误对应的脚本： #!/usr/bin/perl use strict; use warnings; print "hi\n"; print qq(Content-type: text/plain\n\n); 在perl脚本输出Content-type之前输出了别的字符，而浏览器解析的时候把那些字符当做了Content-type，所以报了Bad header错误。所以，不要在print qq(Content-type: text/plain\n\n);之前，输出别的字符。 如果显示下面的错误信息： [Wed Mar 19 16:08:00.342999 2014] [cgid:error] [pid 4703:tid 140208536221440] [client 192.120.120.120:59319] End of script output before headers: echo.pl 还是一个跟header相关的问题，说明你的脚本在打印Content-type之前就出了错误或者异常，error.log中也可能在上面的错误信息之前提供其他更具体的相关信息。所以，请仔细检查打印Content-type之前的perl代码。 关于End of script output before headers错误，原文作者认为可能与Premature end of script headers是同样的原因。 3.2 503 Service Unavailable 如果在本文2.2节为mods-enabled创建了软连接之后，并及时重载了apache，会报下面的错误： [Wed Mar 19 15:30:22.515457 2014] [cgid:error] [pid 3927:tid 140206699169536] (22)Invalid argument: [client 192.120.120.120:58349] AH01257: unable to connect to cgi daemon after multiple tries: /var/cgi-bin/echo.pl 我猜reload只是重新载入了服务器配置信息，并未将CGI进程启动，所以尽管已经配置了，但是由于CGI模块并未启动，所以自然也就没法连接CGI的守护进程。 所以，如果你只是修改了配置文件，reload就可以了，如果你启用或禁用了某个模块，需要restart，因为所有启用的模块，是在服务器启动过程中启动的。 稳妥的方式是restart Apache服务器，而不只是reload服务器。 3.3 404 Not Found 如果显示下面的错误信息： [Wed Mar 19 15:35:13.487333 2014] [cgid:error] [pid 4194:tid 139911599433472] [client 192.120.120.120:58339] AH01264: script not found or unable to stat: /usr/lib/cgi-bin/echo.pl 而echo.pl确实已经存在了，那么检查/etc/apache2/sites-enabled/000-default.conf中的DocumentRoot和ScriptAlias是否配置正确。 3.4 403 Forbidden 如果你遇到403 Forbidden错误，问题一般也出现在/etc/apache2/sites-enabled/000-default.conf这里。检查<Directory>，确保里面涉及到权限的语句正确配置了。 4. 小结 以CGI的方式运行perl是最直接最原始的方式，如果你已经成功地以CGI形式运行了perl脚本，建议你尝试下面的方式： PSGI： 更灵活也更高效的方式。 Perl Dancer或者Mojolicious：成熟的perl服务器框架，非常易用。 参考文章 Background start Linux, install Apache2 在apache下以cgi方式运行perl
Overview1. 被调用的perl脚本2. 调用脚本testSystemCall.pl2.1 system("command") （推荐）2.2 exec("command");3. `command`3. 参考资料 Overview 有很多时候，我们需要在perl脚本中调用系统命令，比如调用系统的某个软件做一件事。也可以说是在perl脚本中调用外部命令，比如在一个perl脚本中调用另一个perl脚本。 有很多种方式可以实现这个目的，这里我列出来3中常见的方式，并以一个例子说明这三种方式的不同之处。 1. 被调用的perl脚本 我们在一个perl脚本(取名为testSystemCall.pl)中调用另一个脚本（取名为printCallInfo.pl）。printCallInfo.pl中内容如下： #!/usr/bin/perl -w # usage: perl printCallInfo.pl number # number: an Integer use strict; use warnings; my $countNumber = $ARGV[0] or die "Need to input an integer on the command line\n"; print "This perl script is called $countNumber times\n"; 使用方法已经在脚本中说明，比如使用： perl printCallInfo.pl 5 屏幕上就会显示： This perl script is called 5 times 2. 调用脚本testSystemCall.pl 我们在脚本中分别查看使用以下三种方式调用printCallInfo.pl： system("command"); exec("command"); `command`; testSystemCall.pl脚本内容如下： #!/usr/bin/perl -w # usage: perl testSystemcall.pl number # number : an enum number # 1: use system("command") # 2: use exec("command") # 3: use `command`; use strict; use warnings; my $exceType = $ARGV[0] or die "Need to input an integer(1,2,3) on the command line\n"; print "Begin loop to invoke system call: \n"; for(my $i=1; $i<=5;$i++) { ($exceType == 1) and system("perl printCallInfo.pl $i"); ($exceType == 2) and exec("perl printCallInfo.pl $i"); ($exceType == 3) and `perl printCallInfo.pl $i`; } print "End loop.\n"; 这个脚本需要指定一个1或者2或者3的整数，指定1代表执行system("perl printCallInfo.pl $i")，指定2或者3以此类推。 这里注意or和and的使用，也可以分别使用||和&&代替。巧妙使用这种方式可以节省很多if else语句。 2.1 system("command") （推荐） 使用该命令将开启一个子进程执行引号中的命令，父进程将等待子进程结束并继续执行下面的代码。 使用命令： perl testSystemCall.pl 1 执行了system("perl printCallInfo.pl $i");，得到下面的结果： Begin loop to invoke system call: This perl script is called 1 times This perl script is called 2 times This perl script is called 3 times This perl script is called 4 times This perl script is called 5 times End loop. 结果在意料之中。 2.2 exec("command"); 效果同system命令类似，区别是不会开启子进程，而是取代父进程，因此执行完引号中的命令后进程即结束。一般和fork配合使用。 使用命令： perl testSystemCall.pl 2 执行了exec("perl printCallInfo.pl $i");，得到下面的结果： Begin loop to invoke system call: This perl script is called 1 times 结果出乎意料，如果你刚才还不太理解这一小节引用的那句话，现在应该明白了。exec命令取代了父进程，执行一次结束之后进程结束，因为这个进程就是父进程本身，因此父进程也跟着结束了。testSystemCall.pl脚本中的后续内容也就不会执行了。 3. `command` 使用反引号调用外部命令能够捕获其标准输出，并按行返回且每行结束处附带一个回车。反引号中的变量在编译时会被内插为其值。 好像更不好理解了。先执行一下： perl testSystemCall.pl 3 执行了`perl printCallInfo.pl $i`;这句代码，显示如下： Begin loop to invoke system call: End loop. 什么都没打印，好像没执行一样。其实已经执行了，只是`perl printCallInfo.pl $i`没有把printCallInfo.pl中的输出直接输出到屏幕上，而是返回给了testSystemCall.pl，只是我们在testSystemCall.pl中并没有接收这个返回值。 我们修改一下testSystemCall.pl，将 ($exceType == 3) and `perl printCallInfo.pl $i`; 改成： ($exceType == 3) and print `perl printCallInfo.pl $i`; 我们将`perl printCallInfo.pl $i`返回的内容再重新打印出来，重新执行： perl testSystemCall.pl 3 显示如下： Begin loop to invoke system call: This perl script is called 1 times This perl script is called 2 times This perl script is called 3 times This perl script is called 4 times This perl script is called 5 times End loop. 显示内容是不是更符合我们的期望了~ 3. 参考资料 Perl调用外部命令的方式和区别
Overview1. BioPerl格式化fasta序列2. 使用BioPerl去除fasta文件中长度过短的序列 Overview 在处理fasta格式序列的过程中，我们经常会发现得到的fasta格式并不是很标准，比如有一个fasta文件中有多条这样形式的序列： >gi|28898692|ref|NP_798297.1| hypothetical protein VP1918 [Vibrio parahaemolyticus RIMD 2210633]|1 MKKTTLMSAVVATLSLVGCQSTTGSSDAQPEQTSHISQAVYEVEFHAAQSFLSQASQLEQSFADFCLAPK NDVEPVQQQWHSTMLAWMALQGQERGPATALEQSWNVQFWPDKKNTTGRKMSALTKADKVWTVEEISTQS VTVQGLGALEWLLYDDASTLNTNSNVCASGVAIAENLHDKAQIIANSWAENPWKSLQKTEWESEYISLLS NQLEYSMKKLSRPLAKIGHPRPYFSESWRSETSLSNLKANLESLHQLYFANGKGLDALLRAQGKTQLADR 它的序列内容并不是一行，而是四行，尽管这是被允许的，但是有的时候出于一些目的，我们想要序列的内容是一行，而不是多行。 我自己写了一个脚本用来将每一行的换行符去掉，并保留最后一行的换行符，代码看起来很丑陋。既然BioPerl可以帮我们自动处理序列的很多信息，那是否也能帮我们格式化序列的内容，我抱着试试看的态度试了一下，真的可以... 1. BioPerl格式化fasta序列 其实复用了 BioPerl（二）：使用BioPerl读取fasta文件 中的脚本： #!/usr/bin/perl -w use strict; use warnings; use Bio::SeqIO; use Bio::Seq; my $catchseq_seqio_obj = Bio::SeqIO->new(-file=>"unFormatedFastaFile.fasta", -format=>'fasta'); # 在这儿处理每个序列的信息 while(my $seq_obj = $catchseq_seqio_obj->next_seq) { # ... my $seq = $seq_obj->seq; # ... # 重新拼成fasta格式 # 注意，$display_name中是不包含fasta的标识符>的，所以拼接时要手动加上> my $seqContent = ">".$display_name.$desc."\n".$seq."\n"; } # 省略了将 $seqContent重新写到文件.. 只需要使用BioPerl把不标准的Fasta格式文件读取到，再写到新的文件中就可以了，重写之后，格式为： >gi|28898692|ref|NP_798297.1| hypothetical protein VP1918 [Vibrio parahaemolyticus RIMD 2210633]|1 MKKTTLMSAVVATLSLVGCQSTTGSSDAQPEQTSHISQAVYEVEFHAAQSFLSQASQLEQSFADFCLAPKNDVEPVQQQWHSTMLAWMALQGQERGPATALEQSWNVQFWPDKKNTTGRKMSALTKADKVWTVEEISTQSVTVQGLGALEWLLYDDASTLNTNSNVCASGVAIAENLHDKAQIIANSWAENPWKSLQKTEWESEYISLLSNQLEYSMKKLSRPLAKIGHPRPYFSESWRSETSLSNLKANLESLHQLYFANGKGLDALLRAQGKTQLADR 可以看到序列中的换行符已经去除了。既然可以格式化序列内容，也可以顺便做点别的，比如读取一个fasta序列时，去除那些序列长度过小的序列，不然使用有些特征提取算法时会出现异常。 2. 使用BioPerl去除fasta文件中长度过短的序列 这里给出一个完整点的例子，写一个叫removeShortFastaEntries.pl，内容如下： #!/usr/bin/perl -w # usage: perl removeShortFastaEntries.pl original.fasta formatedEntries.fasta [50] use strict; use warnings; use Bio::SeqIO; use Bio::Seq; # 在命令行设置要处理的fasta文件名字 my $input_file = $ARGV[0] or die "Need to input fasta file on the command line\n"; # 在命令行设置处理后的fasta文件名字 my $output_file = $ARGV[1] or die "Need to input output file name on the command line\n"; # 在命令行设置序列长度阈值，只有大于这个长度的序列才保留,默认是50 my $length_threadhold = ($ARGV[2] or 50); print "The sequence whose length is no more than $length_threadhold will be removed!\n"; my $catchseq_seqio_obj = Bio::SeqIO->new(-file=>"$input_file", -format=>'fasta'); # 创建一个文件句柄，准备写入处理后的序列 open (O,">$output_file"); while(my $seq_obj = $catchseq_seqio_obj->next_seq) { # 处理每个序列的信息 my $display_name = $seq_obj->display_name; my $desc = $seq_obj->desc; my $seq = $seq_obj->seq; my $seq_type = $seq_obj->alphabet; my $seq_length = $seq_obj->length; # 长度大于$length_threadhold，就写出新的文件。 if($seq_length > $length_threadhold) { my $seqContent = ">".$display_name." ".$desc."\n".$seq."\n"; # 打印到屏幕的提示信息 print "Writing formated data to $output_file\n"; print $seqContent; # 写入文件 print O $seqContent; print "success!\n"; } } #关闭文件 close O; 用法如下： perl removeShortFastaEntries.pl original.fasta formatedEntries.fasta 60 这里的original.fasta，不是一定要后缀名是.fasta，只要文件里面的内容是fasta格式的序列就可以了，名字可以是original.out，original.txt等。最后的数字是可选参数，如果不指定，默认是50。
Overview1. 使用BioPerl读取单条fasta序列2. 使用BioPerl读取多条fasta序列3.参考资料 Overview 在 BioPerl（一）：安装BioPerl 中我们不只安装了BioPerl，还给出了一个使用BioPerl的手动构造了一个Bio::Seq对象的例子，这个对象中包含了我们手动填入的fasta格式的信息。既然可以手动构造，那么也就可以从fasta文件中读取序列信息，由BioPerl自动填充成Bio::Seq对象。 1. 使用BioPerl读取单条fasta序列 我们有一个名字叫做singleEntry.fasta的文件，里面有一条序列内容： >gi|28898513|ref|NP_798118.1| hypothetical protein VP1739 [Vibrio parahaemolyticus RIMD 2210633]|1 MTSQKNIEEQDEVVVIEERDKRTHIYIAIAAVLGLAFGGLAGSVLTANKWESTYQVLEDKYQALAQDKTALVSQVKTREESLDKEIQEKVDALLAEKDAAHQKALKKLQTQLTEVEKVNLSLESQVKQQNDKLSSSKTENEKLTRQADMQATMFERSREVFQKELKISQDLEALEKEREKLLPKIDKLKKECDVFLEGKSWDVKSDACDKHDEANSRLSQIDQLIEVYKMDLKQIKEITSDMGL 这是一个标准的fasta格式的序列，一共有两行，第一行是序列的各种标识，第二行是序列内容，注意，整个序列是一行，只是因为序列太长显示不下，才会自动换行。 我们写了一个名叫readSingleFastaByBioPerl.pl的脚本，内容如下： #!/usr/bin/perl -w use strict; use warnings; # Bio::SeqIO类用来读取文件 use Bio::SeqIO; # Bio::Seq类用来存储fasta序列，一条fasta序列对应一个Bio::Seq对象 use Bio::Seq; # 读取fasta文件，文件名可以用单引号，也可以用双引号， # -format指定了文件的内容是fasta格式，注意这里的文件不一定是.fasta后缀名，只要内部序列是fasta格式就可以了，文件名可以是.txt或者.out等后缀。 my $catchseq_seqio_obj = Bio::SeqIO->new(-file=>"singleEntry.fasta", -format=>'fasta'); # Bio::SeqIO读取一个文件，会创建一个Bio::SeqIO对象，这里名字叫$catchseq_seqio_obj，有多少条序列，就会有多少个Bio::Seq对象 #下面这句取Bio::Seq对象的方法看起来像是一个典型的迭代器的遍历方法，只是我们这里只有一条序列，所以取一次就可以了。 my $seq_obj = $catchseq_seqio_obj->next_seq; # Bio::Seq对象($seq_obj)只能改包含了很多序列相关的属性，使用下面的方式取出来。 # 序列名字 my $display_name = $seq_obj->display_name; # 序列的描述 my $desc = $seq_obj->desc; # 序列字符串 my $seq = $seq_obj->seq; # 序列的类型（dna还是蛋白质？） my $seq_type = $seq_obj->alphabet; # 序列的长度 my $seq_length = $seq_obj->length; #使用双引号时，$display_name会显示为$display_name存储的内容，如果想显示$display_name这几个字符，需要使用\$display_name，去掉$的引用含义。 #使用单引号时，$display_name会显示为这几个字符本身 print "\$display_name:\n$display_name\n"; print "\$desc:\n$desc\n"; print "\$seq:\n$seq\n"; print "\$seq_type:\n$seq_type\n"; print "\$seq_length:\n$seq_length\n"; 运行脚本readSingleFastaByBioPerl.pl perl readSingleFastaByBioPerl.pl 显示如下： $display_name: gi|28898513|ref|NP_798118.1| $desc: hypothetical protein VP1739 [Vibrio parahaemolyticus RIMD 2210633]|1 $seq: MTSQKNIEEQDEVVVIEERDKRTHIYIAIAAVLGLAFGGLAGSVLTANKWESTYQVLEDKYQALAQDKTALVSQVKTREESLDKEIQEKVDALLAEKDAAHQKALKKLQTQLTEVEKVNLSLESQVKQQNDKLSSSKTENEKLTRQADMQATMFERSREVFQKELKISQDLEALEKEREKLLPKIDKLKKECDVFLEGKSWDVKSDACDKHDEANSRLSQIDQLIEVYKMDLKQIKEITSDMGL $seq_type: protein $seq_length: 244 可以看出，Bio::Seq对象帮我们解析了序列信息并按照指定的格式存储起来。第一行的序列标志被分成了两部分，以第一个空格为分割，前面是序列名称，存在属性display_name中，后面是属性描述，存在属性desc中。序列被完整地存在了seq属性中，除了这些序列中包含的信息之外，Bio::Seq对象还分析了序列的其他属性，如序列类型，序列长度等。 其实Bio::Seq对象不只有这些属性，参考HOWTO:Beginners，可以找到这个对象所有的属性。 与 BioPerl（一）：安装BioPerl 中构造Bio::Seq对象的例子比较不难发现，每个属性都有一个get和set方法。以display_name属性为例，当我们构造Bio::Seq对象时，我们使用display_name属性的set方法$seq_obj->display_name("gi|147605|gb|J01673.1|ECORHO");为该属性赋值，当我们读取Bio::Seq对象的display_name时，我们使用该属性的get方法$seq_obj->display_name，这是一个通用的方式，可以以JAVA的get/set方式类比这种存取变量的方式。 2. 使用BioPerl读取多条fasta序列 在实际过程中，我们读取的文件中通常包含了多条fasta序列。其实前面已经提到了，一个Bio::SeqIO对象对应一个读取的文件，里面有多少fasta序列，Bio::SeqIO对象中就有多少个Bio::Seq对象，因此只要循环取一下就可以了： #!/usr/bin/perl -w use strict; use warnings; use Bio::SeqIO; use Bio::Seq; my $catchseq_seqio_obj = Bio::SeqIO->new(-file=>"multipleEntry.fasta", -format=>'fasta'); # 在这儿处理每个序列的信息 #更像Iterator了，$catchseq_seqio_obj->next_seq返回一个Bio::Seq对象，当读取完所有的Bio::Seq对象之后，$catchseq_seqio_obj->next_seq就会返回undef，循环结束。 while(my $seq_obj = $catchseq_seqio_obj->next_seq) { # 序列名字 my $display_name = $seq_obj->display_name; # 省略下面的... print "\$display_name:\n$display_name\n"; # 省略下面的... } 3.参考资料 初品BioPerl（第三篇：从本地文件中获取fasta序列) HOWTO:Beginners
Overview1. BioPerl的安装1.1 perl的版本1.2 CPAN是什么？1.3 升级CPAN1.4 安装/升级Module::Build，并设置为默认安装器1.5 安装expat库 （Optional）1.6 使用CPAN安装BioPerl1.7 运行一个例子 Overview 今天闲暇之余，看到了一篇关于BioPerl的博客 初品BioPerl（第三篇：从本地文件中获取fasta序列），发现BioPerl可以让很多事变得简单，fasta格式处理起来也事半功倍。 其实宋老师之前已经跟我提过BioPerl了，只不过我一直以任务为导向，觉得perl脚本就够处理，所以一直也没去了解。好东西自然马上就要用起来，我也马上装上了BioPerl，这里记录下来BioPerl的安装，以后有需要，就把BioPerl相关的东西持续写下去。 1. BioPerl的安装 直接查看BioPerl的文档 BioPerl Main Page，提供了从安装到快速入门等各种文档，这里我参考的是Installing BioPerl on Unix，实际上，无论你用的是Mac还是各种版本的Linux，都可以参考这个页面，因为这里提供了一种通用形式的安装方式：Installing using CPAN。 1.1 perl的版本 使用 perl -version 查看你系统中的perl版本号，我的系统中perl版本信息为： This is perl 5, version 16, subversion 3 (v5.16.3) built for darwin-thread-multi-2level Young说使用perl 6没办法安装BioPerl，可能是因为perl 6版本太新了，还没有对应的BioPerl模块，所以这里我们安装前要确保使用的perl版本号是perl 5。 1.2 CPAN是什么？ 百度百科关于CPAN的解释清晰明了： CPAN（Comprehensive Perl Archive Network）中译为“Perl综合典藏网”，“Perl综合档案网”或者“Perl程序库”。它包含了极多用Perl写成的软件和其文件。 CPAN亦是一支Perl程序的名字，其作用是让使用者容易从CPAN下载、安装、更新及管理其他在CPAN上的Perl程式。 CPAN的成功引来很多其他编程语言社群的模仿。CTAN和CRAN都参考了CPAN的命名方式。另外PEAR（PHP Extension and Application Repository）也是类似CPAN的PHP程式网站。 你也可以通过 CPAN官网 查看CPAN相关的各种信息，比如CPAN中的模块，CPAN的镜像等。 其实不止PHP，如果有使用R语言的经验，就会马上想起到R语言的程序库CRAN，当你使用R语言的install.packages函数去安装包时，实际上R语言就是去CRAN中找到这个包，然后下载下来并安装到你的本地环境中。 从CPAN安装BioPerl只需要几句命令就可以了，perl会自动把BioPerl放在指定的位置并配置到路径，完全不需要手动参与。 1.3 升级CPAN 既然CPAN是perl的在线程序库，那么CPAN中的内容就是一直不停更新的，所以安装一个perl库之前，先升级一下CPAN总是好的。 假设你的电脑上已经安装配置好了perl，使用下面的命令更新CPAN： perl -MCPAN -e shell 执行了这条命令，命令行就会进入CPAN的执行环境，等待你输入新的指令，输入 install Bundle::CPAN 执行CPAN升级，注意这句命令的名字是install，如果你本地还没有CPAN，那么就会下载配置最新的CPAN到本地，如果已经有了CPAN,那么更新到最新。 安装或者更新完CPAN之后，使用 q 退出CPAN的执行环境。 1.4 安装/升级Module::Build，并设置为默认安装器 使用 cpan 进入CPAN的执行环境，并依次输入下面几条指令： install Module::Build o conf prefer_installer MB o conf commit q 上面几条执行依次完成了安装，配置，提交配置，退出CPAN的执行环境。 1.5 安装expat库 （Optional） 其实我装BioPerl的时候并没有装这个库，而是在写这篇blog的时候才装，如果你需要处理xml文件，可能你需要安装expat库， 如果使用的是Ubuntu，可以直接使用下面的命令安装： sudo aptitude install libexpat-dev 如果使用的是别的linux系统或者mac，使用下面的方式： 使用wget下载expat库 wget http://sourceforge.net/projects/expat/files/expat/2.0.1/expat-2.0.1.tar.gz 或者直接在浏览器中输入 http://sourceforge.net/projects/expat/files/expat/2.0.1/expat-2.0.1.tar.gz/download 下载。 解压expat-2.0.1.tar.gz并进入 tar -zxvf expat-2.0.1.tar.gz cd expat-2.0.1 配置expat库 ./configure --prefix=/usr/bin 如果你没有将expat库安装在/usr/bin这样的系统目录，而是安装在了自己的自定义目录，比如/non-standard这个目录，你需要告诉CPAN，进入CPAN的执行环境： cpan 输入下面的指令： o conf makepl_arg "EXPATLIBPATH=/non-standard/lib EXPATINCPATH=/non-standard/include" o conf commit q 不过不推荐这种方式。 1.6 使用CPAN安装BioPerl 使用 perl -MCPAN -e shell 或者直接使用 cpan 进入CPAN的执行环境，输入： d /bioperl/ 会显示出来下面的信息： cpan[1]> d /bioperl/ Reading '/Users/wangjiawei/.cpan/Metadata' Database was generated on Fri, 25 Dec 2015 03:17:02 GMT Distribution BOZO/Fry-Lib-BioPerl-0.15.tar.gz Distribution CDRAUG/Dist-Zilla-PluginBundle-BioPerl-0.20.tar.gz Distribution CJFIELDS/BioPerl-1.6.901.tar.gz Distribution CJFIELDS/BioPerl-1.6.923.tar.gz Distribution CJFIELDS/BioPerl-1.6.924.tar.gz Distribution CJFIELDS/BioPerl-DB-1.006900.tar.gz Distribution CJFIELDS/BioPerl-Network-1.006902.tar.gz Distribution CJFIELDS/BioPerl-Run-1.006900.tar.gz Distribution CJFIELDS/Bundle-BioPerl-2.1.9.tar.gz Distribution CJFIELDS/Dist-Zilla-PluginBundle-BioPerl-0.23.tar.gz Distribution RBUELS/Dist-Zilla-PluginBundle-Bioperl-0.01.tar.gz 11 items found 每个人的电脑显示的信息可能稍有不同，依然是在CPAN的执行环境中，安装CJFIELDS/BioPerl-1.6.924.tar.gz force install CJFIELDS/BioPerl-1.6.924.tar.gz 注意：这里必须添加force，因为在安装了BioPerl之后，会默认执行大量的test，测试BioPerl的每一个函数，而这些函数依赖很多其他库，如果这些依赖库没有安装在你的电脑里，那么这个测试就会被skip，最后的测试报告就是有大量测试不通过，默认就不会安装BioPerl。但实际上，我们不可能把所有的依赖库都安装上，不安装这些库，依然不影响我们使用BioPerl的常用功能。并且，如果我们用到了一个BioPerl函数依赖某个没安装的库，我们再安装就可以了。使用force就可以忽视这些不通过的测试，依然将BioPerl安装到我们的电脑上。 1.7 运行一个例子 我们采用一个例子（来自 初品BioPerl（第二篇：构造一条fasta序列 ）来检查下BioPerl是否已安装： #!/usr/bin/perl -w #加载Bio::Seq模块。 use Bio::Seq; #调用Bio::Seq模块的new方法，可以建立一个序列对象，命名为$seq_obj。 $seq_obj = Bio::Seq->new; #给这个新建的$seq_obj对象赋予三个属性的值：dispaly_name（序列的名称），desc（序列的描述）以及seq（序列的内容） $seq_obj->display_name("gi|147605|gb|J01673.1|ECORHO"); $seq_obj->desc("E.coli rho gene coding for transcription termination factor"); $seq_obj->seq("AACCCTAGCACTGCGCCGAAATATGGCATCCGTGGTATCCCGACTCTGCTGCTGTTCAAAAACGGTGAAG"); #创建了三个普通的标量变量来存放三个属性值 my ($fasta_name,$fasta_desc,$fasta_seq); #调用对象的display_name方法来得到名称 $fasta_name = $seq_obj->display_name; #调用对象的desc方法来得到描述 $fasta_desc = $seq_obj->desc; #调用对象的seq方法来得到序列 $fasta_seq = $seq_obj->seq; #现在可以打印三个属性值了 print "NAME:\t$fasta_name\nDESCRIBE:\t$fasta_desc\nSEQUENCE:\t$fasta_seq\n"; 执行这个perl脚本，打印出来下面的结果： NAME: gi|147605|gb|J01673.1|ECORHO DESCRIBE: E.coli rho gene coding for transcription termination factor SEQUENCE: AACCCTAGCACTGCGCCGAAATATGGCATCCGTGGTATCCCGACTCTGCTGCTGTTCAAAAACGGTGAAG 可以看到，已经可以正常使用BioPerl了。
Overview 最近经常需要使用到Perl处理文件，因此在此记录一下经常使用到的Perl知识。 1. Perl脚本中接受命令行参数 很多时候我们编写一个Perl脚本，都是用来处理一个文件，输出为另一个文件，例如，脚本file_converter.pl将input.txt中的格式处理之后转化为另一种格式存储在output.txt中，则通常我们的使用习惯是 ./file_converter.pl input.txt output.txt 在Perl脚本中可以使用@ARGV获取命令行的参数，上面的例子中，@ARGV的值为2，使用$ARGV[0]和$ARGV[1]几个获取到这两个文件名字，示例如下： if (@ARGV != 2) { print "==Error: Please input the file you need to convert and the file name of the result\n"; } my $original_file_name = $ARGV[0]; my $output_file = $ARGV[1]; 2. 判断要使用的变量是否已定义or已赋值 在使用一个变量时，有时需要先判断这个变量是不是第一次被使用，这时我们通常可以选择定义此变量时只声明，不赋值。而在使用时，使用两种方式判断该变量是不是已经被赋值了。代码如下： my $name_not_init; my $name="Gly"; if(!$name_not_init) { print "null\n" } else { print "not null\n"; } if(defined($name_not_init)) { print "defined\n"; } else { print "not defined\n"; } if(!$name) { print "null\n" } else { print "not null\n"; } if(defined($name)) { print "defined\n"; } else { print "not defined\n"; } 运行结果为： null not defined not null defined 即当一个变量只是被声明，未被赋值之前，都是未定义的，$name_not_init和defined($name_not_init)都为假。 但是，当对数组变量使用defined时，会报一下诊断信息： defined(@array) is deprecated （D-废弃-反对-可选）通常defined对数组并没有作用，因为它只是检查一个未定义的标量值。可以使用刚才说的另一种方式： if (!@array) { #数组为空 } 3. 获取数组长度 非常简单，直接将一个数组变量赋给一个普通变量或者使用scalar，示例如下： my $count_array = @line_texts; #method 1 my $count_array = scalar @line_texts; #method 2 4. 从网页获取数据 直接使用一段代码展示，该代码从http://www.uniprot.org/uniprot/P01282.fasta处请求内容，这个网站是一个蛋白质序列信息查询网站，如果查询的ID信息存在，则返回该序列的信息，格式如下： #>sp|P01282|VIP_HUMAN VIP peptides OS=Homo sapiens GN=VIP PE=1 SV=1 MDTRNKAQLLVLLTLLSVLFSQTSAWPLYRAPSALRLGDRIPFEGANEPDQVSLKEDIDM LQNALAENDTPYYDVSRNARHADGVFTSDFSKLLGQLSAKKYLESLMGKRVSSNISEDPV PVKRHSDAVFTDNYTRLRKQMAVKKYLNSILNGKRSSEGESPDFPEELEK 以上数据以每一行之后以空格结尾，第一行为序列描述信息，我们需要的是提取第一行数据之外的序列详细信息，并去掉每一行之后的空格，我们需要的是以下结果： MDTRNKAQLLVLLTLLSVLFSQTSAWPLYRAPSALRLGDRIPFEGANEPDQVSLKEDIDMLQNALAENDTPYYDVSRNARHADGVFTSDFSKLLGQLSAKKYLESLMGKRVSSNISEDPVPVKRHSDAVFTDNYTRLRKQMAVKKYLNSILNGKRSSEGESPDFPEELEK 如果查询的ID信息不存在，则会返回一个包含Not found的网页，此时我们使用-代替序列内容。 完整代码如下: #!/usr/bin/perl -w #载入Web请求和解析所需要的模块 use LWP::UserAgent; use HTML::Element; use HTML::TreeBuilder; #需要请求内容的网页 my $url = "http://www.uniprot.org/uniprot/P01282.fasta"; my $root = new HTML::TreeBuilder; my $ua = LWP::UserAgent->new; #设置请求，这里使用的是GET请求，如果是POST参数，需要在$ua->request($req)之前设置一些参数，这里不展开 my $req = HTTP::Request->new('GET' => $url); #发送请求 my $res = $ua->request($req); #获得请求的网页内容，这里的网页内容是包含HTML代码的原生内容，需要自己抽取信息（不过这个网址恰好比较干净，比较方便获取） my $res_content= $res->content(); #使用$solved_web_result存储最终需要得到的内容 my $solved_web_result=""; #以换行符为分割，截断获得的网页内容，存在数组中 my @web_results=split(/\n/,$res_content); #去掉第一行的内容，并将剩下的内容合并起来 my $first_loop_count=0; foreach $web_line (@web_results) { if($first_loop_count!=0) { $solved_web_result=$solved_web_result.$web_line; } $first_loop_count++; } #如果查询的ID信息不存在，则使用-代替。 if ($solved_web_result=~m/not found/) { $solved_web_result="-"; } #最后加上换行符 $solved_web_result=$solved_web_result."\n"; print $solved_web_result;