魑魅魍魉 发布的文章

在 Unix Shell 中, 命令替换(Command Substitution) 是一个非常强大的概念,它被用来将某一个命令的输出作为参数直接插入到另一个命令中,如下例中,我将 date 命令的值赋于 *$today$ 变量,然后再将其输出:

cox@Cox:~$ today=$(date)
cox@Cox:~$ echo "$today"
2012年 11月 21日 星期三 01:39:31 CST

在上面的示例中,我们可以把两人行命令简化为一行:

cox@Cox:~$ echo $(date)
2012年 11月 21日 星期三 01:57:20 CST

或者像下面这样更复杂的使用:

cox@Cox:~$ echo "今天是 $(date +%A),现在时间是 $(date +%H:%M)"
今天是 星期三,现在时间是 01:59

这个示例中, echo 命令调用了 date 命令两个,第一次打印出星期几,第二次打印出当前时间,上面出现的三个命令的顺序是:

第一个 date ⇒ 第二个 date ⇒ echo

当然了,上面只是示例,其实我们有更简单的办法来打印出同样的信息:

cox@Cox:~$ date "+今天是 %A,现在时间是 %H:%M"
今天是 星期三,现在时间是 02:02

但是有一个问题需要注意,嵌套命令的结果会被进行 单词分割,除非所有的内容都被包裹在一个 双引号 中,否则这会导致有些时候你得到的并不是你自己想的,比如下面这行命令:

cox@Cox:~$ ls | grep Ubuntu
Ubuntu One
cox@Cox:~$ for i in $(ls | grep Ubuntu); do echo $i; done
Ubuntu
One

看到他们的不同了没有?我的当前目录下面本身是有一个名为 Ubuntu One 的目录,但是第二个命令本身的意途是想遍历当前目录中所有名称包含 Ubuntu 的文件,但是 Ubuntu One 却被当作两个来处理了,这是因为这个目录中有一个空格的存在,如前面所说的,结果会被进行单词分割,空格使得这本身是一个目录的名称被分割为两个了,对于这个问题,应该使用下面这样的命令去实现:

cox@Cox:~$ for i in Ubuntu*; do echo $i; done
Ubuntu One

命令是可以无限制嵌套的,比如下面这个示例:

cox@Cox:~$ echo $(echo (这是第一层嵌套($(echo 这是第二层嵌套))))
(这是第一层嵌套(这是第二层嵌套))

替换命令可以创建 Subshell ,这很有用,因为 subshell 所作的任何修改都只对它本身有效,不会污染程序的全局环境,比如下面这个示例:

cox@Cox:~$ var=$(cd ../../usr/bin/; pwd)
cox@Cox:~$ echo "$var"
/usr/bin
cox@Cox:~$ pwd
/home/cox

在命令替换中,父命令会去除子命令的输出结果中所有的换行:

cox@Cox:~$ ls -lh | grep Ubuntu
drwxrwxr-x 7 cox  cox      4.0K 11月 20 19:16 Ubuntu One
drwxrwxr-x 2 cox  cox      4.0K 11月 21 02:20 Ubuntu Two
cox@Cox:~$ echo $(ls -lh | grep Ubuntu)
drwxrwxr-x 7 cox cox 4.0K 11月 20 19:16 Ubuntu One drwxrwxr-x 2 cox cox 4.0K 11月 21 02:20 Ubuntu Two

通用性

$(command) 命令可以在 KornShellBASH以及PosixShell 中使用,老一些的 shell (比如BourneShell)则需要使用返引号"",形式是 *command*。

就像我以前说过的,我所想要的一个博客系统应该是最简单的,我只需要发布一些文章和几个静态页面而已,WordPress或者Drupal这种东西实在是太大了,所以我一直都是使用TextPattern作为我的博客发布系统的,从今天开始,我开始转到一个基本Python的纯静态发布系统 - Felix Felicis

静态发布是我最喜欢的方式(没有之一),我喜欢的网站发布系统应该是这样的,内容可以以最简单的方式管理,根据模板发布成为纯HTML页面,之后需要动态调用的(比如说评论需要服务器端处理)使用一个统一的接口(这似乎Movable Type是最合适的,但是它实在是太太大了),而现在这个就完全满足了我的要求。

今天一个下午的时间写了一个同步工具,我只需要在本地发布,然后该同步工具就会自动的将发布得到的HTML页面、CSS/JS文件、图片资源等等同步更新到我的一个S3桶里面去,而我的域名因为绑定到这个桶,所以就有了你现在看到的这个博客了,关于如何绑定域名到S3的桶,请看我前面专门介绍该问题的文章《Amazon S3的域名绑定》

我的同步工具现在还只是实现了将更新上至S3桶中,但是还没有任何其它优化,比如如果文件没有更新就不上传,如果本地已经不存在的,在S3服务器也同步删除掉那个对象等等功能,不过都还在开发中,应该今天晚上就会搞定的吧,刚刚开始使用S3没多久,还有很多事情需要做啊……评论我已经使用了DISQUS的服务,这样一来就省得我再在自己的服务器上写评论系统了,而且这个东西用起来并不比我写的差(应该说是好得多吧……)。

现在更新文章的方式再简单不过了:

  1. 在本地相应的目录中(比如: */home/cox/cox.im/content*)创建一个新文件(我取名为:*My Blog Is Using Static Publish System.md*)。
  2. 打开该文件,以下面这样的格式输入内容:
    # 我的博客现在已经开始使用静态发布系统进行更新了
    
    - url_title: my-blog-is-using-static-publishing-system
    - date: 2012-11-19 17:36:36
    - public: true
    - tags: Blog, Python, Amazon, S3, CMS, Boto
    - category: tech
    
    --------------------------------------------------------
    
    就像我以前说过的,我所想要的一个博客系统应该是最简单的,我只需要发布一些文章和几个
    静态页面而已,WordPress或者Drupal这种东西实在是太大了,所以我一直都是使用TextPattern
    作为我的博客发布系统的,从今天开始,我开始转到一个基本Python的纯静态发布系统。
  3. 保存该文件,在当前目录(*settings.py*)文件所在目录,执行下面命令:
    $ liquidluck build -v
  4. 重新发布完成之后,就可以使用Tornado服务器进行预览:
    $ liquidluck server
  5. 打开 http://127.0.0.1:8000 即可预览刚才的更新了,如果都没有问题,则执行我的同步工具:
    $ python sync.py
  6. 同步完成之后(这需要一些时间,但是如果是在服务器上面更新的话,那应该会快很多),即可打开自己的博客查看更新了http://cox.im/.

上面这是一种特定的格式,在第一个—————-*之前的所有内容都属于文章的Meta信息,之后的就是文章正文,Meta信息的格式就是上面这种的,我给本文定义了 *url_title*、*date*等属性,这些并不全都是必要的,但是 *date 如果有的话就称之为文章(博客文章),如果没有这个属性的话就是页面(静态页面是不需要跟踪时间的)。所有的这些属性都是可以在模板中使用的,模板引擎使用的是 *jinja2*。

我准备把现在使用的这个系统进行一些修改,增加一些功能,比如发布的时候可以选择发布在本地服务器、FTP发布、SHELL登陆远程服务器发布、S3发布等等,同时还需要增加一些其它更通用的细节功能吧,根本自己的需求来修改……

我一直使用的都是 MarkDown,使用起来很方便,当然 Felix Felicis 系统本身还支持reStructuredText,你也可以自己扩展适合你自己使用习惯的MarkUp Language。

这么算一下,已经有一个星期没有离开店子和仓库了,现在的生活就是这么简单,早上八九点醒来开始一天的工作,中午吃饭,再吃晚饭,再到晚上八九点,开始学习/写代码,晚上两人三点煮个饭拌点饭扫光,啊,不错,一天就这么过去了。

感觉得到自己在进步,虽然不知道这进步能给我带来什么,但是总不停在原地要好,虽然不在IT这个行业,但是一直还是放不下这个东西,或许这样更好,兴趣永远都还是我的兴趣,我可以学习一切我以前没有时间和精力去学习的东西,当然,不是为了工作,而仅仅只是兴趣。

只是感觉自己落后这个行业太远太远,所以,只能更加努力啊……

  • Python还是我使用的最主要的语言
  • 开始使用Bash脚本并开发了一些实用的服务器工作
  • 开始使用Golang
  • 开始使用Amazon AWS服务
  • 还是在使用Linode 的VPS服务
  • 还是回到Textpattern,最简单的才是我感觉最好的
  • 接了两人个小项目,不过最后的这一个钱还没收到
  • ……

一不小心又把博客域名给换了,以前的*note.costony.com*和*cox.antusoft.com*都会自动的转身到现在的这个新地址,对于使用 *cox.antusoft.com*转向过来的都可以对应到与cox.antusoft.com上面相同的文章上面,但是从note.costony.com上面转向过来的就不一定了,因为数据库使用的不是一样的。

其实我并不是一个米农,我只是想找一个最短的域名,自己好好地写写博客,但是在网上混了这么多年,也一直没有找到,或者说找到了要么别人注册着在用了,要么是我等草根买不起的,比如那个chenxi.com别人一出价就是四十万,按我现在的这点收入要不吃不喝100个月,可能买得起吗?

另外,虽然我一直最喜欢的网站内容发布方式是 Movable Type的那种纯静态的发布,但是因为它实在是太大了,我只是想写个博客,在Movable Type里面还得先建一个网站,然后在网站上面再建一个博客,太麻烦了,我还是一个ID控,这个让我对WordPress是直接Pass掉了的(虽然我也用过,但那都是以前的版本),现在的WordPress让我完全无法控制ID。

现在正在研究Golang以及Amazon AWS相关的产品,发现自己又落后了不少,得努力补习上来啊,博客的服务器转移到了 Amazon EC2上面,应该是可以免费使用一年吧,不过不知道像我这种资源乱用的人是不是每个月都会超出使用限制,甚至现在那个限制是多少我都没有认真的去研究过,反正应该不多吧。

另外,我现在正在准备把博客的前端界面换一下下,感觉还是有点儿太复杂了,换个更简单的。

Amazon EC2 的默认用户名为 *ec2-user*,在默认情况下,我们是没有办法修改这个用户的名称的,原因是这样的:1) 我们只能使用该用户登陆服务器;2) 登陆之后就无法修改它的用户名,但是我们还是有别的办法来修改它的。

直接不管它,创建一个新用户

这个方法其实足够了,创建了一个新的 EC2 Instance之后,使用下面的命令修改 root 帐户的密码:

[ec2-user@coxantu ~]$ sudo passwd root

这会提示你输入新密码以及重复输入一次,之后使用下面命令创建一个新用户:

[ec2-user@coxantu ~]$ su
密码:
[root@coxantu ec2-user]# useradd antu -p password

之后如下命令,修改 */etc/ssh/sshd_config* 文件

[root@coxantu ec2-user]# vi /etc/ssh/sshd_config

找到下面这部分并作相应的修改:

# To disable tunneled clear text passwords, change to no here!
#PasswordAuthentication yes

修改为

# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication yes

之后重启 *sshd* 服务:

[root@coxantu ec2-user]# /etc/init.d/sshd restart
停止 sshd:                                                [确定]
正在启动 sshd:                                            [确定]

之后我们再退出,重新使用密码认证的方式就可以登陆服务器了,比如:

cox@CoxStation:~$ ssh cox@ec2-23-22-97-113.compute-1.amazonaws.com
cox@ec2-23-22-97-113.compute-1.amazonaws.com's password:

   __|  __|_  )
   _|  (     /   Amazon Linux AMI
  ___|___|___|

https://aws.amazon.com/amazon-linux-ami/2012.09-release-notes/
There are 3 security update(s) out of 29 total update(s) available
Run "sudo yum update" to apply all updates.
[cox@coxantu ~]$

这个时候我们也没有必要再去删除前面那一个默认用户了,当然,如果你想你的系统“干干净净”的,那你直接删除它即可。

rdesktop 可以在Linux中连接Windows远程桌面,不过像我这种天天挂Q的人还是很不方便,毕竟要么在远程桌面和Linux桌面中切换,要么在那个远程桌面的小窗口里面上Q,而QQ的Linux版本用起来还是很不爽,所以,在前面的文章里面带过 Seamless 这个事情之后,我还是在今天好好的研究了一下下这个东西。

如果你也和我一样想使用这个东西,可以从「Cendio 网站」下载,不过我在网上找了很多资料都直接提供了 Seamless的下载地址,但是这个地址却总是下载不了,会转向到http://www.cendio.com/seamlessrdp/,在这里面只能注册自己的邮箱地址然后下载 ThinLinc 软件的完整版,总共有90Mb,对于只想使用 seamlessrdp 的我来说太大了,那个小东西只有几百Kb,为了方便大家,我把这个提取出来再发布了,可以从「http://dl.antusoft.com/tools/thinlinc/tl-wts-tools.exe」这个地址下载,它是一个安装文件,在Windows桌面安装之后,会一同安装 “*seamlessrdpshell.exe*”工具。

如果你使用的是默认的安装,那么该文件的路径为:“*C:Program FilesThinLincWTSToolsseamlessrdpshell.exe*”,这个地址需要记住,因为我们在后面会使用到它。

完成Windows端的软件安装之后,我们现在只需要在Linux端执行 rdesktop 时加入一个 -s 参数即可,当然,还需要指定要打开的Windows软件的路径,比如我现在想打开我仓库的二号机的远程桌面,并且运行“*D:Program FilesBAISON_PGBS3000+CLIENT_CSCKBSBusiness.exe*”程序,那么可以使用下面这行命令:

rdesktop -s "C:Program FilesThinLincWTSToolsseamlessrdpshell.exe D:Program FilesBAISON_PGBS3000+CLIENT_CSCKBSBusiness.exe" 192.168.1.5 -u <Windows Username> -p <Windows Password>

这会在桌面的右下角打开一个默认大小的窗口,并且只运行 BSBusiness.exe 程序(它会自动打开)。(将 修改为你的Windows登陆用户名, 修改为你的帐户密码即可)。

但是这还不是很好,我需要它的窗口能大一些,而且不想放在右下角,一出来就能在我想要的位置,比如正中间(或者左上角),我们可以将上面的命令做下面这样的修改:

rdesktop -g 1400x900 -s "C:Program FilesThinLincWTSToolsseamlessrdpshell.exe D:Program FilesBAISON_PGBS3000+CLIENT_CSCKBSBusiness.exe" 192.168.1.5 -u <Windows Username> -p <Windows Password>

这会打开一个尺寸为 1400px * 900px 大小的窗口,其窗口的右下角还是对在桌面的右下角,这你应该看到,参数 -g 1400x900 指定了打开了桌面的大小,或许你还可以再修改成为下面这个样子:

rdesktop -g 1400x900+100 -s "C:Program FilesThinLincWTSToolsseamlessrdpshell.exe D:Program FilesBAISON_PGBS3000+CLIENT_CSCKBSBusiness.exe" 192.168.1.5 -u <Windows Username> -p <Windows Password>

这个时候你看到窗口,它已经不在右下角了,而是以它的左上角为定位点,距桌面上边距离为0,而左边距离为100px进行定位,我们还可以再加一个参数,用来修改窗口的上边与桌面上边的距离:

rdesktop -g 1400x900+100+100 -s "C:Program FilesThinLincWTSToolsseamlessrdpshell.exe D:Program FilesBAISON_PGBS3000+CLIENT_CSCKBSBusiness.exe" 192.168.1.5 -u <Windows Username> -p <Windows Password>

这还不是我想要的结果,我想要的是没有Windows桌面的出现,我想让程序就像是安装在Linux系统中一样,这个时候我们可以使用下面这样的命令:

rdesktop -A -s "C:Program FilesThinLincWTSToolsseamlessrdpshell.exe D:Program FilesBAISON_PGBS3000+CLIENT_CSCKBSBusiness.exe" 192.168.1.5 -u <Windows Username> -p <Windows Password>

这只是把 -g 这个参数去掉,再加上了一个 -A 这个参数,结果应该看到只有程序窗口,不再有桌面背景之类的了。

该版本增加了开源的PHP内容管理系统的安装,系统默认提供了由安图软件提供的开源软件源中精选的一些开源PHP内容管理系统,用户还可以自己为该脚本增加支持的CMS系统,只需要增加remote_apps 变量的项目即可,具体方法请见脚本说明。

您可以直接使用下列命令获得该脚本:

wget http://dl.antusoft.com/tools/server-setup-tool/sst-v0.3.sh

或者直接点击下载链接下载到本地

#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

# Check if the user if root
if [ $(id -u) != "0" ]; then
    echo "ERROR: You must login as root ro run this script!"
    exit 1
fi

#clear

# default global variables
declare -A vars
declare -A apps
# do you want to upgrade the server first before set it up?
vars=(
[upgrade_server]="n"
# the default website meta information
[default_website_owner]="cox"
[default_website_domain]="_"
[default_website_error_log]="n"
[default_website_access_log]="n"
[default_website_applications]=""
[default_website_error_log]="error.log"
[default_website_access_log]="access.log"
[default_website_log]="y"
# mysql database's root password
[mysql_root_password]="root"
# install nginx extras or not
[install_nginx_extras]="y"
[install_common_cgi_support]="y"
[install_perl_support]="y"
# install phpmyadmin to the document root of default website or not?
[install_phpmyadmin]="y"
[install_movabletype]="n"
)
export vars

apps=(
[apps_server_root]="http://dl.antusoft.com/"
[apps_server_root2]="http://dl.coin.so/"
[phpmyadmin]="tools/phpmyadmin/phpMyAdmin-3.5.3-all-languages.tar.gz"
[wordpress]="cms/wordpress/wordpress-3.4.2-zh_CN.tar.gz"
[wordpress_en]="cms/wordpress/wordpress-3.4.2.tar.gz"
[drupal]="cms/drupal/drupal-7.17.tar.gz"
[openatrium]="cms/drupal/dist/openatrium-6.x-1.5-core.tar.gz"
[dokuwiki]="cms/dokuwiki/dokuwiki-2012-10-13.tgz"
[textpattern]="cms/textpattern/textpattern-4.5.2.tar.gz"
[movabletype]="cms/movabletype/MT-5.2.tar.gz"
[mediawiki]=""
)

remote_apps=(
# you can add you own cms list here
# one record per line
# format:
# [CMS_NAME_KEY]="CMS_REMOTE_DOWNLOAD_URL"
# e.g.
[mediawiki]="http://download.wikimedia.org/mediawiki/1.20/mediawiki-1.20.0.tar.gz"
)

export remote_apps
export apps

# hightlight echo
function hightlight() {
    echo -e "33[40;31;5m $1 33[0m "
}

# separate line
function separate_line() {
if [ "$1" = "b" ]; then
echo "================================================================="
else
echo "-----------------------------------------------------------------"
fi
}

# function set variable to set vars
function set_variable() {
    separate_line
    # if not variable name
    if [ "$1" = "" ]; then
        hightlight "ERROR: no variable name!"
        return
    fi
    # if got a value
    if [ "$2" != "" ]; then
        vars[$1]=$2
    fi
    echo "Set the value of variable: $1"
    # echo the description of this value
    echo $3
    # get new value from user input
    read -p "current value is $(hightlight ${vars[$1]}) ): " v
    if [ "$v" != "" ]; then
        vars[$1]=$v
        echo "$1 => $v"
    else
        echo "no changes, default value will be used."
        echo "$1 => ${vars[$1]}"
    fi
}

function declare_variable() {
if [ "$2" != "" ]; then
    echo $2
else
    echo $1
fi
}

function check_user() {
echo $(cat /etc/passwd|grep $1|wc -l)
}

function create_user() {
if [ "$(check_user $1)" = "0" ]; then
useradd $1
fi
}

function copyright() {
separate_line b
echo "Server Setup Tool for Ubuntu - by Cox"
separate_line
echo "Version: 0.3"
echo "Modified: 2012/11/16"
echo "Url: http://cox.antusoft.com/project/ubuntu-server-setup-tool/"
separate_line
echo "Author: Cox Antu"
echo "Email: cox # antusoft.com"
echo "Url: http://cox.antusoft.com/"
echo "Mobile: 183-7488-8188"
echo "QQ: 54778899"
separate_line b
echo ""
}

function help() {
echo "-h --help for help"
echo "-s --setup for setup"
separate_line
}

function update_server() {
apt-get update
if [ "${vars[upgrade_server]}" = "y" ]; then
    echo "upgrading your server..."
    stty -echo
    echo y | apt-get upgrade
    stty echo
fi
separate_line
}

function install_mysql() {
echo "Installing mysql for you..."
echo y | apt-get install mysql-client mysql-server
separate_line
}

function install_nginx() {
echo "Installing nginx for you..."
if [ "${vars[install_nginx_extras]}" = "y" ]; then
    echo "You are going to install nginx-extras"
    echo y | apt-get install nginx-extras
else
    echo y | apt-get install nginx
fi
separate_line
}

function install_php() {
echo "Installing php support for nginx..."
echo y | apt-get install 
  php5 php5-fpm php5-mysql php5-curl php5-gd php5-idn 
  php-pear php5-imagick php5-imap php5-mcrypt php5-memcache 
  php5-mhash php5-ming php5-ps php5-pspell php5-recode 
  php5-snmp php5-sqlite php5-tidy php5-xmlrpc php5-xsl 
  php5-json

/etc/init.d/php5-fpm restart
separate_line
}

function php_socket() {
if [ -S /var/run/php5-fpm.sock ]; then
echo "unix:/var/run/php5-fpm.sock"
else
echo "127.0.0.1:9000"
fi
}

function install_common_cgi_support() {
echo "Installing common cgi support for nginx..."
echo y | apt-get install fcgiwrap
separate_line
}

function install_perl_support() {
echo "Installing perl support for nginx"
echo y | apt-get install 
  dbconfig-common libarchive-zip-perl libcache-perl 
  libclass-data-inheritable-perl libclass-errorhandler-perl 
  libclass-inspector-perl libclass-load-perl libclass-singleton-perl 
  libclass-trigger-perl libcommon-sense-perl libconvert-binhex-perl 
  libcrypt-dh-gmp-perl libdata-objectdriver-perl libdatetime-perl 
  libdatetime-timezone-perl libdbd-pg-perl libdbd-sqlite3-perl 
  libfile-nfslock-perl libgd-gd2-perl libheap-perl libimage-size-perl 
  libio-stringy-perl libipc-run-perl libjcode-pm-perl libjson-perl 
  liblist-moreutils-perl liblucene-queryparser-perl libmath-round-perl 
  liblwp-authen-wsse-perl libmime-charset-perl libmime-encwords-perl 
  libmodule-runtime-perl libnet-openid-common-perl libio-pty-perl 
  libossp-uuid-perl libossp-uuid16 libpackage-deprecationmanager-perl 
  libpackage-stash-perl libpackage-stash-xs-perl libparams-classify-perl 
  libparams-util-perl libparams-validate-perl libpq5 libsoap-lite-perl 
  libsub-install-perl libtask-weaken-perl libtext-simpletable-perl 
  libtry-tiny-perl liburi-fetch-perl libxml-atom-perl 
  libxml-xpath-perl libjson-xs-perl libxml-libxslt-perl 
  libdata-optlist-perl libdatetime-locale-perl libfcgi-perl 
  libmime-tools-perl libtheschwartz-perl libnet-openid-consumer-perl
separate_line
}

function setup_website_directly() {
# $1 => website domain name
# $2 => website owner
# $3 => applications list for install
# $4 => log or not to log
# $5 => error log
# $6 => access log
domain=$(declare_variable ${vars[default_website_domain]} $1)
owner=$(declare_variable ${vars[default_website_owner]} $2)
applications=$(declare_variable ${vars[default_website_applications]} $3)
log=$(declare_variable ${vars[default_website_log]} $4)
error=$(declare_variable ${vars[default_website_error_log]} $5)
access=$(declare_variable ${vars[default_website_access_log]} $6)

conf_file="/etc/nginx/sites-available/$domain"
listen="80"
server_name="$domain"
domain_root="/home/$owner/websites/$domain"
root="$domain_root/public"
logs_root="$domain_root/logs"

error_log="$logs_root/$error"
access_log="$logs_root/$access"
touch error_log
touch access_log
error_log="error_log $logs_root/$error"
access_log="access_log $logs_root/$access"
# if not to log the website
if [ "$log" = "n" ]; then
    error_log="#$error_log"
    access_log="#access_log"
fi
php_fastcgi_pass="$(php_socket)"

create_user $owner

mkdir -p $root
mkdir -p $logs_root

chown $owner:www-data -R $domain_root
chmod g+w -R $domain_root

cat >>$root/info.php<<EOF
<?php phpinfo(); ?>
EOF

# create configuration file
if [ -f $conf_file ]; then
    cp $conf_file $conf_file.bak
    cat /dev/null > $conf_file
fi

cat >>$conf_file<<EOF
server {
  listen 80;
  server_name $server_name;
  root $root;
  $error_log;
  $access_log;
  index index.php index.html index.htm;

  # php
  location ~ .php$ {
      try_files $uri = 404;
      fastcgi_pass $php_fastcgi_pass;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      include fastcgi_params;
  }

  # perl cgi movable type
  location ~ ^/mt.*.cgi {
    # the exact pattern is required fo MT.
    fastcgi_split_path_info ^(/mt.*.cgi)(.*)$;

    fastcgi_pass unix:/var/run/fcgiwrap.socket;
    include fastcgi_params;

    # MT related
    fastcgi_param PERL5LIB $document_root/lib;
    fastcgi_param MT_HOME $document_root;
    fastcgi_param MT_CONFIG $document_root/mt-config.cgi;

    # fcgiwrap related
    fastcgi_param SCRIPT_NAME $document_root$fastcgi_script_name";
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name";
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
  }

}
EOF

cur_dir="$(pwd)"
cd /etc/nginx/sites-enabled/
if [ ! -L $domain ]; then
ln -s $conf_file
fi
/etc/init.d/nginx restart

echo "Setup website for $domain succeed!"
separate_line
}

# applications installer
function application_installer() {
# $1 => application name
# $2 => application domain name
# $3 => application owner
# $4 => application subfolder

app=$(declare_variable "textpattern" $1)
domain=$(declare_variable ${vars[default_website_domain]} $2)
owner=$(declare_variable ${vars[default_website_owner]} $3)
subfolder=$(declare_variable "" $4)

if [ "$1" == "" ]; then
  read -p "Do you want to select an application? (default to $app): " na
  if [ "$na" != "" ]; then
    app=$na
  fi
  read -p "Do you want to select an domain of this app? (default to $domain): " nd
  if [ "$nd" != "" ]; then
    domain=$nd
  fi
fi

read -p "do you want to install the application in a sub folder of your website
root? (default to n ):(y/N) " sf
if [ "$sf" != "" ]; then
subfolder=$app
fi

uri="${apps[$app]}"
url="${apps[apps_server_root]}$uri"
if [ "$uri" = "" ]; then
url="${remote_apps[$app]}"
fi
conf="/etc/nginx/sites-available/$domain"
root="/home/$owner/websites/$domain/public"

echo "URI: $uri"
echo "URL: $url"
echo "CONF: $conf"
echo "ROOT: $root"

# if the domain has been used
if [ -f "$conf" ]; then
hightlight "the domain has been used, do you want to choose another one?"
echo "If you want to select a new domain, just type it here,"
echo "or you can just pass this note and use this domain, if so, "
echo "i will make a backup of your exist website"
read -p "(default to $domain)" new_domain
  if [ "$new_domain" != "" ]; then
  # copy exist website
  # this will be separated to a standalone function
  # mv "$conf" "$root/$domain.conf"
  # cd "/home/$owner"
  # echo "backup exist website"
  # tar zcvf "$domain.backup.tar.gz" "/home/$owner/websites/$domain"
  # mv "$root" "$root.old"
  # setup_website_directly $domain $owner ""
    domain=$new_domain
    setup_website_directly $domain $owner ""
  fi
else
  setup_website_directly $domain $owner
fi

cd $root
wget -c $url

to="/tmp/application_installer/$app"
if [ ! -d $to ]; then
mkdir -p $to
fi

tar zxvf "$root/$(basename $url)" -C $to

sub=""
# how many files or folders?
dirs=($(ls $to))
if [ "${#dirs[@]}" = "1" ]; then
from="$to/${dirs[0]}/*"
else
from="$to/*"
fi
if [ "$subfolder" = "" ]; then
to="$root/"
else
to="$root/$subfolder/"
fi
if [ ! -d $to ]; then
mkdir -p $to
fi

echo "mv $from $to"
mv $from $to
echo "Install $app succeed"

echo "you can open your browser and visit : http://$domain/ to run
the application..."

}

function setup_website() {

read -p "(domain name): " domain
read -p "(owner): " owner
read -p "(applications): " applications
read -p "(log Y/n): " log
read -p "(error log): " error_log
read -p "(access log): " access_log

setup_website_directly $domain $owner $applications $log $error_log $access_log

}

function setup() {
copyright

# upgrade the server or not
set_variable upgrade_server "" "If you want to upgrade the server
 before set it up, please choose y"

# install common nginx or with extras
set_variable install_nginx_extras "" "Do you want to install the extra
 functions of nginx?"

# set the server's default domain name
set_variable default_website_domain "" "This should not be a domain you
 want to use in your pub site."

# set the owner of the default website
set_variable default_website_owner "" "The website's content will be
 stored in his home folder"

# set website logs
set_variable default_website_log "" "If you do not want to log your
  website, pleast choose n."
set_variable default_website_error_log
set_variable default_website_access_log

# install perl support or not?
set_variable install_perl_support "" "If you want to run perl
 applications in your server, you should choose y to install perl
 support for nginx server."

separate_line b

# update or upgrade the server
update_server

# install mysql server the client
install_mysql

# install nginx or nginx-extras
install_nginx

# install php and some extra libs
install_php
# install perl support or not
if [ "${vars[install_perl_support]}" = "y" ]; then
    install_perl_support
    install_common_cgi_support
fi

setup_website_directly
application_installer
# $1 => website domain name
# $2 => website owner
# $3 => applications list for install
# $4 => log or not to log
# $5 => error log
# $6 => access log
}

# ==================================================================== #
# Start the program

case "$1" in
--help|-h)
help
;;
--setup-website|-sw)
setup_website
;;
--setup|-s|--install|-i)
setup
;;
--install-app|-ia)
application_installer
;;
*)
setup
esac

一直都是使用的Linode的VPS,不过慢慢的,发现很不划算,我的网站访问量不是很大,但是图片量很大,比如我女儿的相册,上传的基本上都是原图或者大图,现在光图片就已经达到一个多G了,而总是出现的问题是除了VPS的空间容量不够用了之外,其它的资源都是有余很多的,所以我就想直接把静态文件都保存到Amazon S3服务上去,而我的服务器仅仅只做为程序的运行平台,而它的价格则低得多。

Amazon提供的费用计算器中粗略计算一了一下,差不多也就$12左右,我预定存储数据量为20Gb,每一个月的出流量为100Gb(这需要11.88美金,而其实我一个月也就几个G的出流量,按现在的实际情况来算的话,也就0.5美金不到,其它的都没什么钱了),从这个可以看出来,如果我的出流量不多的话,其实只需要5美金不到就足够了。

但是有一个问题是,Amazon并没有提供一种方法让你可以直接绑定自己的域名,所以,还是需要我们自己通过别的办法来实现这个,其实也是十分简单的事情,我们将使用到域名的CNAME记录,具体操作是这样的:

  1. 确定要绑定的域名,比如我这里是安图软件的文件下载站,域名使用 *download.antusoft.com*。
  2. 登陆 Amazon S3 控制台,创建一个新的*Buckets*,这里需要注册,名称选择你上面确定的域名,所以,我需要创建的Buckets的名称应该为:*download.antusoft.com*。
  3. 在本地创建两个文件 index.html 与 error.html ,并上传至刚才创建的 Bucket 的根目录中,并且设置这两个文件可以给公众访问(点文件上点击鼠标右键-> Make Public 或者选中文件,点击上方工具条中的 Actions,在下拉菜单中选择 Make Public)
  4. 打开改才创建的 Buckets 的属性设置面板,找到 Website 标签,将 Enable 复选框钩上(即开启网站服务),之后你还需要指定该网站的主索引文件和错误文件,我使用的分别是:
+ Index Document : *index.html*
+ Error Document : *error.html*
  1. 设置 download.antusoft.com 这个域名的CNAME记录至Amazon S3为你提供的那一个 URL 地址上,之后等待域名生效即可。

生效之后我们就可以使用 http://download.antusoft.com 这个地址来访问你刚才的S3 Bucket,整个过程我们其实可以把 Bucket 当作是一台静态文件存储服务器,我们只需要能通过我们的域名访问到这台“服务器”即可,而 Amazon S3 会将你域名的 CNAME 记录直接绑定到名为这个域名的Bucket上面。

今天在写我的 Ubuntu Server Setup Tool 时,因为在本地的环境下面 php5-fpm 默认监听 *127.0.0.1*,但是在我的 Linode VPS 上面,安装之后却默认监听 *unix:/var/run/php5-fpm.sock*,应该是版本不同的原因吧,所以我需要有一种办法,让程序知道是应该使用哪种监听方式(我们需要把这个写进 Nginx 的配置文件的),这个时候就用到我现在要说的这些东西了。

先来解决我的问题:我应该使用哪种监听方式?

因为,如果是以 Unix Socket 的方式监听的话,那肯定是需要有一个 Socket 文件的,而以IP+端口的方式却没有,所以,我现在可以把问题简单的转化为,是否存在一个相关的 Socket 文件,对于我现在要解决的这个问题,其实就是:是否存在 /var/run/php5-fpm.sock 文件,这样一来问题自然而然的就已经解决了:

#!/bin/bash
LISTEN_METHOD="127.0.0.1:9000"
if [ -S /var/run/php5-fpm.sock ]; then
  LISTEN_METHOD="unix:/var/run/php5-fpm.sock"
fi
echo $LISTEN_METHOD

执行包含上面这段代码的可执行文件,就可以知道是哪一直方式了(这个只是 apt-get 安装之后默认配置的位置,如果你修改了php5-fpm 的 Socket 文件的路径,那么这个需要修改一下下(不过如果你都知道修改了,那么我想你也应该知道使用哪种方式了吧,只是对于我的自动安装脚本来说,还是要自动选择比较好,就像上面这样的。

判断文件是否存在

要判断一个文件是否存在其实是很简单的一件事情,只需要像下面这样:

if [ ! -f /path/to/file ]; then
    touch /path/to/file
fi

在上面的示例中,会先判断 /path/to/file* 这个文件是否存在,如果不存在,则创建它,这里我们使用了“非”,即“!*”符号,如果去掉这个符号,那么就是“如果文件存在就怎么样”。

在上面的判断语句中 *-f* 是一个参数,它表示“文件存在,并且是一个普通文件”,与该参数具有同样功能的还有很多其它参数,但是不管参数蛤才能,后面部分都是你想判断的文件的路径。

与文件判断相关的参数

下面这些参数都是我们可能在 Shell 脚本中使用得到的:

参数|意义 -|- -a|文件存在 -b|文件存在并且是一个特殊的块文件 -c|文件存在并且是一个特殊字符文件 -d|文件存在并且是一个目录 -e|文件存在(与 -a 是一样的) -f|文件存在并且是一个普通文件 -g|文件存在,并且有setgid(2)设置 -G|文件存在,并且属于当前进程相同的组 -k|文件存在并且设置了sticky 标记 -L|文件存在,并且是一个动态链接 -O|文件存在,并且与当前进程属于相同的用户ID -p|文件存在,并且是先入先出(FIFO)的特殊文件或者命名管道 -r|文件存在,并且是当前进程可读取的 -s|文件存在,并且文件大小大于0 -S|文件存在,并且是一个Socket -t|文件描述符是对外开放的,并且关联一个终端设备 -u|文件存在,并且有 setuid(2) -w|文件存在,并且是当前进程可写的 -x|文件存在,并且是当前进程可执行的 -n|字符串长度不为0 -o|指定的选项已经设置 -z|字符串长度为0

上表中,-n*,-o,-z*是用来判断字符串或者选项的。

如果我们想直接在终端中判断而不使用 if 判断,那么我们还可以使用*test*命令,比如:

test -S /var/run/php5-fpm.sock

上面这样并不会显示任何内容,但是我们可以这样:

root@li485-100:~# test -S /var/run/php5-fpm.sock && echo "exist" || echo "Not exist"
exist
root@li485-100:~# test -S /var/run/php5-fpm.socket && echo "exist" || echo "Not exist"
Not exist

由于Nginx没有对CGI的原生支持,但是他同样还是可以运行类似Movable Type这样的程序(包括其它的一些基于CGI的程序),我们要使用到的中具就是 *fcgiwrap*,它就像是一个代理一样,把FCGI请求转发为CGI请求,所以,使用它,我们不需要修改一行程序代码。

首先需要安装 fcgiwrap:

root@CoxStation:~# aptitude install fcgiwrap

fcgiwrap* 可以以一个系统守护进程运行,并以一个Unix Socket文件访问(/var/run/fcgiwrap.socket)。现在我想让Movable Type的管理端通过 http://mt.csmumu.net访问到,下面这个就是完整的 server 配置代码。

server {
    server_name mt.csmumu.net;
    access_log /home/cox/logs/mt.csmumu.net.access.log;
    error_log /home/cox/logs/mt.csmumu.net.error.log;
    root   /home/cox/websites/mt.csmumu.net/public;

    location ~ ^/mt.*.cgi {
        ## The exact pattern is required for MT.
        fastcgi_split_path_info ^(/mt.*.cgi)(.*)$;

        include fastcgi_params;
        fastcgi_pass unix:/var/run/fcgiwrap.socket;

        ## MT-related
        fastcgi_param PERL5LIB $document_root/lib;
        fastcgi_param MT_HOME $document_root/;
        fastcgi_param MT_CONFIG $document_root/mt-config.cgi;

        ## fcgiwrap-related
        fastcgi_param SCRIPT_NAME     $document_root$fastcgi_script_name";
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name";
        fastcgi_param PATH_INFO       $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
    }
}

感谢 CSS3 给我们带来了这么多的惊喜,这才让我可以完成今天的这个小作品,不使用任何 JavaScript 代码,实现可循环的幻灯片,它还带渐变等动画。不过这似乎是我对 CSS3 的一种乱用吧,因为比起使用 JavaScript 的各种库来说,使用 CSS3 来实现麻烦得多,而且它还有严重的浏览器兼容性问题,尤其是在神一样的国度,闲话少扯,下面入正题。

HTML 结构

整个 Slider 的HTML结构十分简单,就和我们平常写的 HTML 没有什么两样,下面是整个 Slider 的 HTML 代码:

<div>
  <div>
    <ul>
      <li>
        <a href="#"><img src="image-1.jpg" alt="First Image" /></a>
        <div>
          <h3>第一个幻灯片</h3>
          <p>这里一个幻灯片的介绍文字,您以前应该看到过很多这样的东西了吧?</p>
        </div>
      </li>
      <li>
        <a href="#"><img src="image-2.jpg" alt="Second Image" /></a>
        <div>
          <h3>第二个幻灯片</h3>
          <p>这里一个幻灯片的介绍文字,您以前应该看到过很多这样的东西了吧?</p>
        </div>
      </li>
      <li>
        <a href="#"><img src="image-3.jpg" alt="Third Image" /></a>
        <div>
          <h3>第三个幻灯片</h3>
          <p>这里一个幻灯片的介绍文字,您以前应该看到过很多这样的东西了吧?</p>
        </div>
      </li>
      <li>
        <a href="#"><img src="image-4.jpg" alt="Fourth Image" /></a>
        <div>
          <h3>第四个幻灯片</h3>
          <p>这里一个幻灯片的介绍文字,您以前应该看到过很多这样的东西了吧?</p>
        </div>
      </li>
      <li>
        <a href="#"><img src="image-5.jpg" alt="Fifth Image" /></a>
        <div>
          <h3>第五个幻灯片</h3>
          <p>这里一个幻灯片的介绍文字,您以前应该看到过很多这样的东西了吧?</p>
        </div>
      </li>
    </ul>
  </div><!-- /.slides -->
  <div></div><!-- /.progress-bar -->
</div><!-- /.slider -->
  1. div class=“slider”

    这是我们的幻灯片的主容器,整个幻灯片的所有内容都被放在该容器里面,我们将使用它*暂停动画的运行*。
  2. div class=“slides”

    我们所有的幻灯片都放在该容器中,在该容器中,存在一个无序列表,该列表的每一项目就是一张幻灯片。
  3. li

    一张幻灯片,我制作的这个幻灯片中包括一张图片,一个标题以及一段简短的介绍文字,这些都是我们最常用的格式。
  4. div class=“tooltip”

    这里面是幻灯片的标题以及介绍文字,你还可以根据需要添加一些你自己的的内容,比如让标题成为一个链接指向某一个网页。
  5. div class=“progress-bar”

    进度条,展示幻灯片动画的进度。

现在我们可以开始编辑 CSS 文件了。

 CSS 样式

我将所有的 CSS 样式都保存在名为 css3.css 的文件中。

.slider

我们使用这个类来创建幻灯片的整体结构:

.slider {
  font-size: 10px;
  background-color: #000;
  border: .5em solid #ddd;
  box-shadow: .1em .1em .5em rgba(0.0.0.0.7);
  height: 32em;
  width: 68em;
  margin: 5em auto 0;
  overflow: visible;
  position: relative;
}

.slides

从我们以前使用 JavaScript 实现的幻灯片的经验中,应该知道,我们总是需要有一个容器,它能完整的显示一个幻灯片,但同时它还需要可以隐藏掉没有激活的幻灯片,在我们的这个小项目中,.slides就是完成这个任务的。

.slides {
  overflow: hidden;
  height: 32em;
}

.slides ul & .slides li

最后我们还需要去除掉 ul 与 li 的默认样式,并且把所有的幻灯片都安排到一个初始位置,这个位置应该方便我们动画的完成,还不能让未激活的幻灯片显示出来。

.slides ul {
  margin: 0;
  padding: 0;
  list-style: none;
  position: relative;
}

.slides li {
  width: 68em;
  height: 32em;
  position: absolute;
  top: -32.5em;
}

CSS3 帧动画

在继续完成 CSS 代码之前,我们需要有一些关于当前这个小幻灯片的一些约定,这些约定也是我们需要使用 CSS3 去实现的,在本未例中,共有五张幻灯片,我准备按5秒一张的速度执行该幻灯片,那么总计将需要25秒完成整个幻灯片,但是我们需要知道 *1秒的时间能执行多少帧*。

所以,让我们做一个小小的算术题:

  1. 我们总个幻灯片使用的总幻灯片数目:*5*;
  2. 每一张幻灯片动画执行的时间:*5秒*;
  3. 整个幻灯片动画需要执行的时间:5张 x 5秒 = *25秒*;
  4. 每一秒动画执行的帖数为:155 / 25 = *4帧*。

现在我们可以来继续完成 CSS 了,我们将给所有幻灯片都设置为无限循环,因为每一张幻灯片都是在完成它自己的动画,是互不干扰的。

.slides li:first-child {
  animation: first 25s linear infinite;
}
.slides li:nth-child(2) {
  animation: second 25s linear infinite;
}
.slides li:nth-child(3) {
  animation: third 25s linear infinite;
}
.slides li:nth-child(4) {
  animation: fourth 25s linear infinite;
}
.slides li:nth-child(5) {
  animation: fifth 25s linear infinite;
}

现在我们已经为每一个幻灯片设定了一个专属于它的动画(CSS animation 的第一个参数即它所绑定的动画的名称),我们现在只需要再定义每一个名称所指定的动画的实现流程即可(注意:运行必须要先定义才能使用,所以我们应该把动画的定义放在 CSS 文件的最前面)。

/* ANIMATION */
@keyframes first {
  0%  { top:0px; }
  4%  { top:0px; }
  16% { top:0px; opacity:1; z-index:0; }
  20% { top:32.5em; opacity:0; z-index:0; }
  21% { top:-32.5em; opacity:0; z-index:-1; }
  92% { top:-32.5em; opacity:0; z-index:0; }
  96% { top:-32.5em; opacity:0; }
  100%{ top:0px; opacity:1; }

}
@keyframes second {
  0%  { top:-32.5em; opacity:0; }
  16% { top:-32.5em; opacity:0; }
  20% { top:0px; opacity:1; }
  24% { top:0px; opacity:1; }
  36% { top:0px; opacity:1; z-index:0; }
  40% { top:32.5em; opacity:0; z-index:0; }
  41% { top:-32.5em; opacity:0; z-index:-1; }
  100%{ top:-32.5em; opacity:0; z-index:-1; }
}
@keyframes third {
  0%  { top:-32.5em; opacity:0; }
  36% { top:-32.5em; opacity:0; }
  40% { top:0px; opacity:1; }
  44% { top:0px; opacity:1; }
  56% { top:0px; opacity:1; }
  60% { top:32.5em; opacity:0; z-index:0; }
  61% { top:-32.5em; opacity:0; z-index:-1; }
  100%{ top:-32.5em; opacity:0; z-index:-1; }
}
@keyframes fourth {
  0%  { top:-32.5em; opacity:0; }
  56% { top:-32.5em; opacity:0; }
  60% { top:0px; opacity:1; }
  64% { top:0px; opacity:1; }
  76% { top:0px; opacity:1; z-index:0; }
  80% { top:32.5em; opacity:0; z-index:0; }
  81% { top:-32.5em; opacity:0; z-index:-1; }
  100%{ top:-32.5em; opacity:0; z-index:-1; }
}
@keyframes fifth {
  0%  { top:-32.5em; opacity:0; }
  76% { top:-32.5em; opacity:0; }
  80% { top:0px; opacity:1; }
  84% { top:0px; opacity:1; }
  96% { top:0px; opacity:1; z-index:0; }
  100%{ top:32.5em; opacity:0; z-index:0; }
}

到现在为止,我们的幻灯片已经可以正常的工作了,那么接下面,把 .tooltip 和 .progress-bar 的问题也解决掉那就完美了。

进度条

进度条我准备放在幻灯片的最下面,它是一个从左至右慢慢被颜色填充的长方形容器,它的 CSS 样式如下:

@keyframes progressbar {
  0%, 20%, 40%, 60%, 80%, 100% { width:0%; opacity:0; }
  4%, 24%, 44%, 64%, 84% { width:0%; opacity:0.3; }
 16%, 36%, 56%, 76%, 96% { width:100%; opacity:0.7; }
 17%, 37%, 57%, 77%, 97% { width:100%; opacity:0.3; }
 18%, 38%, 58%, 78%, 98% { width:100%; opacity:0; }
}

[...]

.progress-bar {
  position: absolute;
  bottom: 0;
  width: 100%;
  height: .5em;
  background-color: #000;
  animation: progressbar 25s ease-out infinite;
}

标题与介绍

幻灯片本身已经完成啦,现在我们再来给他的标题与介绍做一些细节上面的处理。

.tooltip {
    position: absolute;
    width: 100%;
    left: -100%;
    padding: .5em 0;
    bottom: 1em;
    color: #f0f0f0;
  background-color: rgba(0,0,0,0.7);
  transition: all 0.3s ease-in-out;
}

.slider:hover .tooltip {
  left: 0;
}

.tooltip h3 {
    padding-left: .5em;
  font-size: 1.6em;
  margin: 0;
}

.tooltip p {
  padding-left: .8em;
  margin: 0;
}

到现在为止我们的幻灯片已经完成做完了,最终的效果你可以下载本示例的打包文件在自己的电脑中测试,下载链接在本文的下方。

常见问题

  1. 我下载了打包文件可以运行,但是自己按照本文的代码写的去无法运行,这是怎么回事儿?

    这个问题很好理解啊,transition, animation 这些东西到现在都还没见着哪个浏览器直接支持,而是使用了自家的指令,比如对于 Chrome浏览器我们应该使用:*-webkit-animation*,但是对于火狐我们却又得使用 *-moz-animation*,但是在本文的文章中,我并没有把这每一个都写出来,而是直接写了 *animation*,所以,如果你接照我的文章来做的话,你需要自己加各种浏览器的不同版本的指令才行,IE的话直接跳过吧……

从05年使用Bluehost的虚拟主机开始,就知道可以通过服务器来达到翻山跃岭的目的,以前都是使用虚拟主机,然后配上主机服务器提供的Shell帐户在本地创建一个代理来达实现,记得那个时候用得最多的就是DreamHost的,当然,那还是一个流行着合租虚拟主机的时代。

从09年开始就慢慢地都转到VPS上面来了,到现在我已经只使用VPS了,以前最常使用的是CentOS作为服务器系统,后来慢慢地全部转到Ubuntu上面来了,现在又正准备转到openSUSE上面去,玩什么就转什么吧,毕竟我不是一个IT行业中人了,什么东西精不精对我来说没有太大的影响了,完全是自己的兴趣了。

时代不同,山还是那座山,岭还是那道岭,有些时间走不过还是得借一些工具,以致于本身因为安全考虑的VPN产品现在成了跳出牢笼的工具,一直以来我也是那种过不过都无所谓的人,所以,虽然我的服务器都在国外,我也从来都没有做过什么不符合政策的事情来,只是现在还是感觉,多走出去看看对自己的个人成长还是有好处的,所以,我还是想着,哪天还是整整这个吧,哪知一整,整去了我半条命。

闲话多说无益,解决不了的问题还是要解决,所以,还是回到正题来吧,不过首先申明,我这不是什么教程,纯粹只是记录,有可能我今天真的解决了我应该要解决的问题,但是并不一定就能成为你们的参考。

操作平台的选择

我是准备在openSUSE上面架设的,可是因为我还有两台Ubuntu的VPS。所以我还得解决Ubuntu的问题(好像这个更好解决一些?也许是因为可参考的资料多得多吧),软件的话我也就没啥选择了的,大家都在说openvpn,就这个了,其实我自己个人是没法儿去选择什么的,因为我对这一块是完全不通,所以只能随大流,而这里面的道道也就只能硬着头皮去学了。

使用openvpn

这个安装在两种系统里面都不难,比如下面这样的:

yast2 -i openvpn

或者在 Ubuntu 下面:

apt-get install openvpn

(*题外话*:我一朋友安装这个东西安装了一天,使用的是源码安装,唉,我还是没那么有勇气去玩源码安装,还是使用这种最简单的办法吧!)

配置openVPN

就是这个东西整死我了,尤其是对于我这种什么基础知识都没有的人来说就更是难上加难了啊,从昨天晚上8点多到现在,我还是没有搞定,现在也只能再继续去摸索了。

openSUSE下面的安装与配置

我还是先使用 openSUSE 吧,因为我更喜欢这个破东西一点。

安装 openVPN

我们可以先通过*zypper se*命令来查找一下下我们的软件源里面是否已经有openvpn了,运行下面这样的命令:

cox:~ # zypper se openvpn

这将会得到一个结果集,它在终端里面是一个表格,我们可以很轻松地看到 openvpn 这七个字母在那里啊,所以,我再直接安装了:

cox:~ # yast2 -i openvpn

包构建安装工具能为我们解决很多事情,比如包的依赖关系等。

复制需要的目录

我们现在需要把 */usr/share/openvpn* 复制到 */etc/* 目录下面,yast2 安装在安装 openvpn 是会创建一个 */etc/init.d/openvpn* 的启动脚本,该脚本会自动的搜索 */etc/openvpn* 中的配置文件:

cox:~ # cp -r /usr/share/openvpn /etc/
cox:~ # cd /etc/openvpn/
cox:/etc/openvpn # ls
easy-rsa
cox:/etc/openvpn #

创建主CA与KEY文件

现在我们修改工作目录到 */etc/openvpn/easy-rsa/2.0/* 下,修改 *./vars* 文件:

# These are the default values for fields
# which will be placed in the certificate.
# Don't leave any of these fields blank.
export KEY_COUNTRY="CN"
export KEY_PROVINCE="HN"
export KEY_CITY="ChangSha"
export KEY_ORG="AntuSoftInc"
export KEY_EMAIL="cox@antusoft.com"
export KEY_EMAIL=cox@antusoft.com
export KEY_CN=antusoft
export KEY_NAME=cox
export KEY_OU=antusoft
export PKCS11_MODULE_PATH=changeme
export PKCS11_PIN=1234

保存之后执行下面这样的命令:

cox:/etc/openvpn/easy-rsa/2.0 # . ./vars
NOTE: If you run ./clean-all, I will be doing a rm -rf on /etc/openvpn/easy-rsa/2.0/keys
cox:/etc/openvpn/easy-rsa/2.0 # ./clean-all
cox:/etc/openvpn/easy-rsa/2.0 # ./build-ca
Generating a 1024 bit RSA private key
.................................................++++++
..++++++
[......]
cox:/etc/openvpn/easy-rsa/2.0 #

创建服务器证书与key文件

在 */etc/openvpn/easy-rsa/2.0* 目录中执行下面这条命令来创建服务器端的证书与 key 文件:

cox:/etc/openvpn/easy-rsa/2.0 # ./build-key-server server
Generating a 1024 bit RSA private key
[,,,]
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
cox:/etc/openvpn/easy-rsa/2.0 #

创建客户端证书与key文件

与服务器证书创建在同样的一个目录中,运行下面这样的命令:

cox:/etc/openvpn/easy-rsa/2.0 # ./build-key-server server
Generating a 1024 bit RSA private key
[......]
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
cox:/etc/openvpn/easy-rsa/2.0 #

创建Diffie-Hellman (DH) 文件

使用下面这样的命令创建Diffie-Hellman文件:

cox:/etc/openvpn/easy-rsa/2.0 # ./build-dh
Generating DH parameters, 1024 bit long safe prime, generator 2
This is going to take a long time
....................+...++*++*++*
cox:/etc/openvpn/easy-rsa/2.0 #

现在我们可以进入 *./vars* 目录,可以看到刚才生成的所有这些证书文件与密钥文件了:

cox:/etc/openvpn/easy-rsa/2.0 # cd ./keys/
cox:/etc/openvpn/easy-rsa/2.0/keys # ls
01.pem  ca.key      client.key  index.txt.attr      serial      server.csr
02.pem  client.crt  dh1024.pem  index.txt.attr.old  serial.old  server.key
ca.crt  client.csr  index.txt   index.txt.old       server.crt
cox:/etc/openvpn/easy-rsa/2.0/keys #

这些文件分别是:

  • *ca.crt*-服务端与客户端的根证书
  • *ca.key*-登陆机器专用的CA密钥文件
  • dh1024.pem*-服务器端DH文件,格式为*dh.pem
  • *server.crt*与*server.key*-服务器端证书与密钥文件(名称就是我们生成它时输入的)
  • *client.crt*与*client.key*-客户端证书与密钥文件(名称也是我们创建时指定的)

服务器端配置文件

安装了 openvpn 之后,会自动创建一个示例配置文件,它被保存在 */usr/share/docs/packages/openvpn/sample-config-files/* 目录中,我们为了简单,就不再自己全新的创建配置文件了,而是把这个示例配置文件复制到 */etc/openvpn* 目录稍作修改即可:

cox:/etc/openvpv # cp /usr/share/doc/packages/openvpn/sample-config-files/server.conf ./

编辑该文件,作如下所示的修改:

  • port 1194
  • proto udp
  • ca /etc/openvpn/easy-rsa/2.0/keys/ca.crt
  • cert /etc/openvpn/easy-rsa/2.0/keys/server.crt
  • key /etc/openvpn/easy-rsa/2.0/keys/server.key
  • dh /etc/openvpn/easy-rsa/2.0/keys/dh1024.pem
  • dev tun
  • server 10.8.0.0 255.255.255.0

修改完成之后,运行服务器端:

cox:/etc/openvpn # /etc/init.d/openvpn start
redirecting to systemctl
cox:/etc/openvpn #

客户端配置文件

与服务器端配置文件一样,客户端也有一个示例文件被保存在 server.conf 示例文件同一个目录下面,我们现在进入我现在的系统中,并且将前面生成的这些文件都保存到本地客户端电脑中的:*/home/cox/vpnconf*目录中。

需要创建一个地方:

remote 50.116.15.100 1194

上面的IP地址为无端服务器地址,如果你的和我不一样,那么就改成你的即可,商品是我们的服务器的配置文件里面定义,默认是1194我没有做任何修改,如果你做了什么修改,也需要改成你自己设置的端口号。

测试是否能成功连接上

root@Cox:/home/cox/vpnconf# openvpn ./client.conf
[...]
Fri Nov  9 11:16:37 2012 Initialization Sequence Completed
.

当出现了 *Initialization Sequence Completed*的时候,使用 *ifconfig* 查看到下面这样的信息:

tun0      Link encap:未指定  硬件地址 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          inet 地址:10.8.0.6  点对点:10.8.0.5  掩码:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  跃点数:1
          接收数据包:1 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:2 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:100
          接收字节:52 (52.0 B)  发送字节:104 (104.0 B)

再用一个更加直观的办法来检测一下,我的服务器IP地址是 50.116.15.100,我在服务器上面安装Nginx服务器,直接使用这个服务器IP地址是可以访问的,而现在我使用 http://10.8.0.1/也能访问到这个服务器了,因为对于我的桌面电脑与服务器来说,他们现在已经是点对点处在同一个网里面了。

Mac用户的客户端解决办法

不用多说了吧,就使用TunnelBlick即可,这个使用方法其实很简单,只需要把上面我们的 vpnconf目录成为一个特别的名称,比如:

AhaInsight.tblk

然后,把*client.crt*与*client.key*两个文件的名称都改为*AhaInsight*即可。

在你安装了TunnelBlick软件之后,以 .tblk 后缀结尾的目录会被视为一个TunnelBlick包文件,直接双击它即可把这个配置添加到TunnelBlick中去。

Ubuntu中OpenVPN的安装与

其实与在 OpenSUSE中是一样的,只是好像就easy-rsa/server.conf/client.conf这几个目录和文件的保存位置不一样,其它的都是一样的,自己玩吧。

通过openVPN共享服务器上网

其实我前面整那么多,发现对于我来说是没有任何用处的,我真正的需求在本文的最前面就已经说得很清楚了:

我要翻山跃岭,跃过长江大河,飘洋过海去看看外面的世界

而现在,我只是从黄花机场飞到了New York,但是却没签证,出不了机场的大门,啊,这不算去外面的世界看了,所以,我还需要做更多的事情,一些能真正让我《自由地飞翔》的事情,也让《我飞得更高》的事情,什么事?我现在还不知道,只能继续来摸索了。

我的一点点理解是这样的,要实现我所想实现的目标,那么可能需要满足下面这两个条件:

  1. 服务器端允许客户通过它访问外网
  2. 客户端要自己知道它是要通过服务器访问外网

把这样的想法转换成具体的实现,那么可能是这样的:

  1. 我打开某个网站
  2. 客户端告诉服务器哥要访问这个网站了
  3. 服务器把这些请求发送出去(这个时候服务器就像我们的TP-Link了吧)
  4. 服务器再把回复过来的东西发到客户端这里

啊,这些个都只是我的想法,不过也没有去看过相关的资料,还不知道是不是按着这样的思路下面,有时间再找找一些资料再来好好整整这个东西,至少现在VPN已经是连上了,至少,哥现在已经坐上灰机了。饿,想想不对,我只是买到机票了而已……

使用PPTP

实然之间,把脑子里面的水给掉跳了,发现原来还有PPTP这等东西,对于我们这种电脑白痴来讲,这东西应该简单得多吧,所以,我就又试了试使用PPTP,于是乎,我把VPS又重新给安装了,现在回到一个空白的系统中来。

安装PPTP

在 Ubuntu 下面的话,像下面这样的安装PPTP的服务端程序:

root@ubuntu:~# apt-get install pptpd

在SuSE中的话,这种事情还是交给 yast吧:

suse:~ # yast2 -i pptpd

哦,对了,这个是服务器端,如果要安装客户端的话,请使用:

suse:~ # yast2 -i pptp

而对于 Ubuntu,pptp客户端的名称应该是 pptp-linux

安装好了之后,又回到让人头痛的事情上面来了。

配制PPTP服务器端

PPTP我看了看说明,好像比OpenVPN简单得多啊。

创建用户及密码

用来登陆 PPTPD 服务器的用户与其登陆密码是保存在 */etc/ppp/chap-secrets* 文件中的,我们先进入到这个 */etc/ppp* 目录来,再修改这个文件:

root@ubuntu:~# cd /etc/ppp
root@ubuntu:/etc/ppp# ls
chap-secrets  ip-up      ipv6-down.d  options      pppoe_on_boot
ip-down       ip-up.d    ipv6-up      pap-secrets  resolv
ip-down.d     ipv6-down  ipv6-up.d    peers
root@ubuntu:/etc/ppp# emacs chap-secrets

看上面列出的第一个文件,就是我们需要修改的文件,打开它之后会发现,其实这个文件里面除了被注释掉的各种各样的说明之外,什么配置都没有,就当这里是一个空数据库吧,用来存储用户帐户与密码的空数据库,但是可以看到最后的一行标明了我们应该怎么写这个文件,如下所示:

#client         hostname        <password>      192.168.1.1
cox     pptpd       iamcox  *

对于上面我添加的这一行的意思是这样的:

  • cox : 用户名
  • iamcox : 登陆使用的密码明文

其它的就暂时不去管吧,如果要改也就只需要改这两样就成了。

添加DNS配置

我想使用Google的“死吧!”DNS服务,它的服务器IP地址是:*8.8.8.8*,在我的系统(openSUSE 12.2)里面需要修改 */etc/ppp/options* 文件,你的可能不一样,但是位置是一样的,有可能叫作:*pptpd-options*吧:

root@ubuntu:/etc/ppp# emacs options
[...]
ms-dns 8.8.8.8
[...]
netmask 255.255.255.0
[...]

修改PPTP的配置

PPTP的配置文件一般都是保存在 */etc* 这个目录的 *pptpd.conf* 文件中,打开并像下面这样的编辑它:

root@ubuntu:/etc/ppp# emacs /etc/pptpd.conf
localip 10.8.0.1
remoteip 10.8.1.100-199

修改完成之后启动 PPTPD:

root@ubuntu:/etc/ppp# /etc/init.d/pptpd start

配制并测试PPTP客户端

在客户端,我们需要先安装PPTP,安装方法在上面已经介绍了,这里又不再说了。

手工添加 /etc/ppp/peers/pptpd 文件

注意啦,注意啦,*/etc/ppp/peers/pptpd* 是一个文件,而不是一个目录,使用 *touch*命令也行,或者使用 *vi /etc/ppp/peers/pptpd* 或 *emacs /etc/ppp/peers/pptpd* 即可,其内容如下:

pty "pptp 50.116.15.100 -nolaunchpppd"
name cox
remotename pptpd
require-mppe-128
file /etc/ppp/options.pptp
ipparam pptpd

修改 *chap-secrets*(同服务器)

如果你本地电脑和我一样,与服务器用的是一样的话,那么这里和服务器上面是一样的。

测试连接

我们使用下面这种Debug方式来测试连接:

root@Cox:/etc/ppp# pon pptpd debug dump logfd 2 nodetach
[...]
rcvd [IPCP ConfAck id=0x2 <compress VJ 0f 01> <addr 10.8.0.100>]
local  IP address 10.8.0.1
remote IP address 10.8.0.100
Script /etc/ppp/ip-up started (pid 15997)
Script /etc/ppp/ip-up finished (pid 15997), status = 0x0

出现上面这样的信息,就证明我的客户端已经成功的连接上服务器了。现在使用 *ifconfig* 来查看一下下:

ppp0      Link encap:点对点协议
          inet 地址:192.168.1.234  点对点:192.168.0.234  掩码:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1496  跃点数:1
          接收数据包:5 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:5 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:3
          接收字节:62 (62.0 B)  发送字节:68 (68.0 B)

配置IP转发

修改 */etc/sysctl.conf*文件,即去掉下面这一行前面的注释即可:

net.ivp4.ip_forward=1

修改完成之后重新加载IP转发配置:

root@ubuntu:/var/log# sysctl -p

 设置网络地址翻译

修改 iptables :

root@ubuntu:/var/log# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

上述设置在重起后会丢失,因此需要修改 */etc/rc.local*,在 exit 0之前添加以下语句:

iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

一些小问题

  1. 修改了 */etc/pptpd.conf* 文件之后出现:*Long config file line ignored.*错误

    这个问题整死我了,一直找不到原因,后来,无意之间发现,*/etc/pptpd.conf* 文件内容最后面必须保留有至少一个空行,否则就会出现这个问题,而这个问题的结果就是, */etc/init.d/pptpd start*无法成功执行。
  2. 连上VPN之后,但是却没法打开网站

    这个一般是因为服务器没有开启 53 DNS 查询端口,使用下面命令开启即可:
    iptables -A INPUT -p udp --dport 53 -j ACCEPT
    iptables -A OUTPUT -p udp --sport 53 -j ACCEPT

一直没有找到怎么从官方下载安装包哪里有得下,所以,还是直接使用了 *apt-get* 完成安装:

sudo apt-get install flashplugin-nonfree
sudo cp /usr/lib/flashplugin-installer/libflashplayer.so /usr/lib/chromium-browser/plugins
chromium-browser --enable-plugins

另外,Flash中文乱码解决方法: 终端命令:sudo gedit /etc/fonts/conf.d/49-sansserif.conf 将倒数第4行内容替换成:文泉驿正黑。

今天一整天的时间,就花在了这个显示器的检测上面了,从昨天开始就一直在准备着把Windows系统全部下掉,转而到Linux环境下,最开始我选择的是openSUSE,因为我的服务器也准备就使用这个系统,但是却被显示器的检测浪费了一整天的时间,最开始很自然的去找驱动安装,但是后来发现,不是驱动的问题,而是我的显示器根本就不能自动地向操作系统发送其信息(可能还是与驱动有关吧)。

在openSUSE下面整了整整一天没有任何收获的情况下,我试着重新安装了Ubuntu,这个是熟悉一些吧,几分钟时间搞定这个问题,下面是我的整个解决过程:

  1. 使用 xrandr 命令列出所有当前能检测到的分辨率,比如我的计算机安装完Ubuntu之后,是下面这样的一个情况:
    cox@Cox:~$ xrandr
    Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 8192 x 8192
    VGA1 connected 1024x768+0+0 (normal left inverted right x axis y axis) 0mm x 0mm
        1024x768       60.0
        800x600        60.3     56.2
        848x480        60.0
        640x480        59.9

    从上面的信息中我们可以看到,我现在只识别到上面四种分辨率,现在使用的是1024x768的。
  2. 现在使用 xrandr 新添加显示模式(分辨率可以自己定,但是不要大于显示器的最大分辨率),我建议使用最佳分辨率,比如我的显示器的最佳分辨率是:1920x1080,那么就添加这个分辨率的即可,这里可能你会不知道怎么添加显示模式,其实很简单,还有一个名为 cvt 的工具,它会根据你所提供的分辨率来创建一个可用的显示模式:
    cox@Cox:~$ cvt 1920 1080
    # 1920x1080 59.96 Hz (CVT 2.07M9) hsync: 67.16 kHz; pclk: 173.00 MHz
    Modeline "1920x1080_60.00"  173.00  1920 2048 2248 2576  1080 1083 1088 1120 -hsync +vsync

    默认的屏幕刷新率是 60.0Hz。
  3. 将得到的显示模式使用 xrandr 命令添加:
    cox@Cox:~$ su
    root@Cox:~# xrandr --newmode "1920x1080_60.00"  173.00  1920 2048 2248 2576  1080 1083 1088 1120 -hsync +vsync
    root@Cox:~# xrandr --addmode VGA1 1920x1080_60.00
    root@Cox:~# xrandr --output VGA1 --mode 1920x1080_60.00

    上面四行命令的作用分别为:
    1. 以Root身份登陆
    2. 创建新的显示模式
    3. 添加新的显示模式
    4. 设置刚才创建的显示模式为当前使用的模式
  4. 有可能每次开机之后都又不能使用设定好的分辨库了,那我们可以将上述命令添加到X图形界面的启动命令中去,使用如下命令:
    sudo gedit /etc/gdm/Lnit/Default

    将上面后三个步骤的所有命令都复制到新打开的文件中,然后再在最后面加上下面这一行命令:
    /sbin/initctl -q emit login-session-start DISPLAY_MANAGER=gdm
  5. 如果开机之后任务栏显示不正常,则可以使用下面脚本关闭并重新启动任务栏来纠正:
    gconftool-2 --shutdown
    rm -rf ~/.gconf/apps/panel
    pkill gnome-panel

去年十一月一号考的理论,到今天为止一年堆28天,终于算是个司机了,虽然驾照还需要三天左右才寄到手里,不过也已经是确定了,本来应该是刚好一年就能拿到的,上次考科目三是11月1号,如果过的话就是刚好一年的时间,但是没过,没过的原因很简单,感觉自己开得太好了,所以就连靠边停车都没有打转向灯了。

唉,以前的事情就不用再想啦,这种算是一辈子的事情,以后就永远都是一个司机了,也应该庆祝一下……不过没有什么可庆祝的……

准备慢慢的把自己的台式机的操作系统从Windows转移到Linux下面(OpenSUSE或者也可能是Ubuntu),所以最近总是在寻找一些Windows下必做的工作软件在Linux下面的替代软件,其实说实施,除了店里面使用的百胜BS3000+这个ERP系统必须使用Windows外,其它的都已经没有什么需要在Windows下面工作的了,而这个软件我可以直接连程连接上服务器使用,或者在Linux下面安装VirtualBox虚拟机安装Windows系统也可以。

如果使用远程连接的话,我一直使用TeamViewer也可以解决问题,另外我希望使用的就是一个在Linux下面连接Windows远程桌面的软件了 RDesktop,该软件是直接源码发行的,我写这篇文章时最近版本是1.7.1,可能你下载的版本与我这里使用的不一样,但是安装和使用方法是一样的。

使用下面命令进行安装:

tar zxvf rdesktop-1.7.1.tar.gz
cd rdesktop-1.7
./configure
make
make install
make clean

如果你使用的是Ubuntu/Debian,还可以像下面这样的安装:

apt-get install rdesktop

对于CentOS还可以这样安装:

yum install rdesktop

安装完成之后,会添加一个 rdesktop 命令,使用方法为:

rdesktop [options] server[:port]

例如我的服务器地址是 *192.168.1.10*,并且要以32位色彩(本地连接的速度是很快的,所以使用了更高的颜色),那么像下面这样的:

rdesktop -f -a 32 192.168.1.10

关于 rdesktop 更多的使用方法可以运行下面这行命令:

man rdesktop

Seamlessrdp

这里还有一个很有趣的使用方法,那就是让RDesktop结合Seamlessrdp使用,Seamlessrdp可以让你在Linux直接执行Windows桌面中的应用程序,就好像直接运行在Linux的源生程序一样,其实现方法如下:

  1. 下载Seamlessrdp,并将其提取到 C:seamlessrdp 目录中。
  2. 在 Linux 按下面方式执行 rdesktop(我这里想要直接打开IE浏览器):
    rdesktop -A -s "C:seamlessrdpseamlessrdpshell.exe C:Program FilesInternet Exploreriexplore.exe" <SERVER IP>:3389 -u administrator -p <ADMINISTRATOR PASSWORD>

    说明*:将 * 替换为你服务器的IP地址,把 替换为你Windows系统的登陆密码。

关于 Seamlessrdp 的使用,还可以查看一下下Ubuntu Wiki

安装过程中经常遇到的问题

  • Could not find X Window System headers/libraries To specify paths manually, use the options –x-includes and –x-libraries”

    解决办法
    # yast2 -i

    然后 software->Software Management->serch xorg-x11-devel,然后install
  • “configure: error: no acceptable C compiler found in $PATH See config.log' for more details.”

    解决办法
    # yast2 -i gcc
  • “ERROR: Could not find OpenSSL headers/libraries. To specify a path manually, use the Cwith-openssl option”

    解决办法
    # yast2 -i openssl-devel

    或者
    # yast2 -i libopenssl-devel

一些链接

删除了运行了100多天之后滴 Ubuntu 服务器之后,Linode里面还有40美金的余额没用完(这个是9月份的时候那个Linode的活动送的,没法儿取出来,也不能转移到其它的Linode帐户里面去,只能消费完),这段时间就尝试着玩玩各种各样的Linux发行版吧,基本上都是Linode提供的,最开始就选择OpenSUSE是因为昨天看到腾讯的开放平台提供的服务器基本上都是OpenSUSE的。

OpenSUSE还只是在北京的时候看公司里面一同事玩过,后来也就只是在自己的虚拟机里面装过一两回,不过从来没有深入的去研究过,我玩Linux基本上的作用都是做网站的服务器之用,所以,最开始我就会去了解如何在某一个Linux发行版上面更好的配置安装LEMP环境。

在本文中,我使用 opensuse.antusoft.com 作为服务器的域名,IP地址还是旧服务器的 50.115.16.100,这可能与你的情况有些不一样,不过也不影响整个安装。

安装 MySQL 5

首先,我们像下面这样的安装 MySQL 5:

yast2 -i mysql mysql-client mysql-community-server

接着我们为 MySQL 创建系统启动链接(这样一来,MySQL 就会随着系统启动而启动),再启动 MySQL 服务器:

chkconfig -f --add mysql
/etc/init.d/mysql start

我们可以像下面这样的来确定是否已经运行成功:

netstat -tap | grep mysql

这可能会输出像下面这样的内容:

cox: ~ # netstat - tap | grep mysql
tcp     0    0    *:mysql     *:*    LISTEN    8924/mysqld
cox: ~ #

如果没有出现类似上面这样的信息,你可能需要打开 /etc/my.cnf 然后注释掉 skip-networking 选项:

[...]
#skip-networking
[...]

修改配置之后,重起你的 MySQL 服务器

/etc/init.d/mysql restart

(如果你遇到*You do not have a valid vim binary package installed. Please install either “vim”, “vim-enhanced” or “gvim.”*,请运行下面这行命令来安装 VIM 之后再尝试修改。

yast2 -i vim

运行下面这行命令来设置 MySQL 服务器的 root 帐户密码:

mysql_secure_installation

否则的话,任何人都可以访问你的 MySQL 数据库。进行安装设置时,脚本会询问你一些问题,你只需要回答是(Y)或者否(n),下面是我的选择:

  • Enter current password for root ( enter for none): –> 直接回车
  • Set root password? [Y/n] –> Y
  • Remove anonymouse users? [Y/n] –> Y
  • Disallow root login remotely? [Y/n] –> Y
  • Remove test database and access to it? [Y/n] –> Y
  • Reload privilege tables now? [Y/n] –> Y

安装 Nginx

我先使用 search 或者 se 搜索最新的 Nginx 软件包:

cox:~ # zypper se nginx

现在得到的最新的Nginx版本是 Nginx-1.0,我也就安装这个版本的了,像下面这样安装它:

yast2 -i nginx-1.0

与 MySQL 一样创建 Nginx 的启动链接,然后启动 Nginx

chkconfig -f --add nginx
/etc/init.d/nginx start

启动之后,我们就可以访问 http://50.116.15.100 访问,Nginx 会返回一个 403 Forbidden 错误,这证明Nginx已经安装成功,这是因为在 OpenSUSE 12中,Nginx 的默认文档根目录是: /src/www/htdocs,而这个目录下面默认又是没有索引页的。

安装 PHP5

我们可以让 PHP5通过 PHP-FPM 在Nginx下工作(PHP-FPM:FastCGI Process Manager)是一个添加了一些特别功能的PHP的FastCGI实现,像下面这样安装:

yast2 -i php5-fpm

在运行 PHP-FPM 之前,我们将 /etc/php5/fpm/php-fpm.conf.default 修改为 */etc/php5/fpm/php-fpm.conf*:

mv /etc/php5/fpm/php-fpm.conf.default /etc/php5/fpm/php-fpm.conf

然后打开 /etc/php5/fpm/php-fpm.conf :

vi /etc/php5/fpm/php-fpm.conf

然后 error_log 为 /var/log/php-fpm.log 同时去掉 pm.min_spare_servers 与 pm.max_spare_servers 两个选项前的注释并修改为下面这样的值:

[...]
error_log = /var/log/php-fpm.log
[...]
pm.min_spare_servers = 5
[...]
pm.max_spare_servers = 35
[....]

保存刚才所作的修改,然后创建系统启动链接,并系统 PHP-FPM:

chkconfig -f --add php-fpm
/etc/init.d/php-fpm start

PHP-FPM 是一个由 /etc/init.d/php-fpm 创建的守护进程,它运行一个 FastCGI 服务器并监听 9000 端口,你可以看到下面这样的信息:

cox:~ # netstat -tapn
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      9473/nginx
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      3407/sshd
tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN      9912/php-fpm.conf)
tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN      8924/mysqld
tcp        0      0 50.116.15.100:22        113.240.81.181:16158    ESTABLISHED 8184/0
tcp        0    300 50.116.15.100:22        113.240.81.181:18023    ESTABLISHED 9937/1
tcp        0      0 :::22                   :::*                    LISTEN      3407/sshd

配置 Nginx

Nginx的配置都由 /etc/nginx/nginx.conf 文件定义,我们现在打开它:

vi /etc/nginx/nginx.conf

Nginx的文档是十分简单的,你可以Google一下如何对Nginx进行配置,在我的这个网站里面,我的配置是下面这样的:

首先,我提高了 worker_processes 到5,同时让 keepalive_timeout 为 2

[...]
worker_processes 5;
[...]
    keepalive_timeout 2;
[...]

在Nginx中,虚拟主机是在 server {} 容器中定义的,我修改了默认的虚拟主机为下面这样的:

[...]
    server {
        listen       80;
        server_name  _;
        #charset koi8-r;
        #access_log  /var/log/nginx/host.access.log  main;
        location / {
            root   /srv/www/htdocs/;
            index  index.php index.html index.htm;
        }
        #error_page  404                  /404.html;
        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /srv/www/htdocs/;
        }
        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ .php$ {
        #    proxy_pass   http://127.0.0.1;
        #}
        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        location ~ .php$ {
            root               /srv/www/htdocs;
            try_files $uri =404;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /srv/www/htdocs$fastcgi_script_name;
            include            fastcgi_params;
        }
        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        location ~ /.ht {
            deny  all;
        }
    }
[...]
  • server_name _; 让服务器监听任何没有在其它虚拟主机中定义了的域名(当然你还可以指定为某一个特定的域名,比如: *opensuse.antusoft.com*)。
  • 在 location / 部分,我给 index 添加了一个 index.php ,这让Nginx知道最先以 index.php 为索引页,同时 root /srv/www/htdocs 表示该虚拟主机的根目录是 */srv/www/htdocs*。
  • 最重要的部分就是 location ~ .php$ {} 部分,修改 root 为虚拟主机的根目录,还需要确定fastcgi_param 那一行已经修改成为了 *fastcgi_param SCRIPT_FILENAME /srv/www/htdocs$fastcgi_script_name;*,否则有可能 Nginx 找不到脚本文件

保存所做的修改,然后重新启动 Nginx

/etc/init.d/nginx restart

接着我们创建一个 info.php 文件,并保存到 /srv/www/htdocs 目录中:

vi /srv/www/htdocs/info.php

它的内容是:

<?php
    phpinfo();
?>

现在我们打开 http://50.116.15.100/info.php 即可看到下面这样的画面了:

安装 PHP5 的MySQL 支持

要让PHP支持MySQL,我们只需要安装 php5-mysql 包即可,我在这里还安装了一些其它的模块,这些都是很有可能在我的应用里面会用到的,像下面这样:

yast2 -i php5-mysql php5-bcmath php5-bz2 php5-calendar php5-ctype php5-curl php5-dom php5-ftp php5-gd php5-gettext php5-gmp php5-iconv php5-imap php5-ldap php5-mbstring php5-mcrypt php5-odbc php5-openssl php5-pcntl php5-pgsql php5-posix php5-shmop php5-snmp php5-soap php5-sockets php5-sqlite php5-sysvsem php5-tokenizer php5-wddx php5-xmlrpc php5-xsl php5-zlib php5-exif php5-pear php5-sysvmsg php5-sysvshm

安装完成之后我们需要重新启动 PHP-FPM:

/etc/init.d/php-fpm restart

重新启动之后再去查看 http://50.116.15.100/info.php,向下滚动网页直到 Modules 章节,你应该就可以看到现在PHP所支持的模块了,当然,包括了MySQL。

测试环境是否满足我的需要

我一般使用 phpMyAdmin 来管理数据库,同时使用 WordPressDrupalTextPattern 等 基于PHP 语言的CMS程序,我这里就安装了 phpMyAdmin:

一些链接

从昨天开始就在做新服务器的配置,到现在全部转移完成,花了一个晚上哈,只是昨天晚上还是睡了觉,也没有必要通宵搞,最主要是这一次我使用了自己修改过后的LEMP安装SHELL脚本,对 LNMP.ORG 的脚本做了自己必要的优化,同时对 vhost.sh 也做了文件也做了一些修改。

现在的 vhost.sh 脚本在创建虚拟主机的时候,会询问该网站是属于哪一个用户的(我现在还没有做用户是否存在的判断,准备以后有时间再加上,如果不存在的话就创建一个新用户),现在还是只允许 root 用户使用,我是准备以后让我服务器上面的所有用户都可以直接使用它。比如:名为 cox 的用户使用的话会直接在 /home/cox/websites/ 目录下面创建网站目录,最终的目录结构可能是: /home/cox/websites/cox.antusoft.com/public 。

另外,我的习惯是所有内容都转移之后,关闭旧服务器,测试是否所有网站都能正常运行,如果可以的话就将旧服务器完全关闭,并删除,旧服务器也已经成功运行了102天了,Linode的VPS还是不错滴:

以前小时候时常看到老一辈人玩这个打山棋,它的棋盘简单,棋子更简单,随便一个地方都能很快速的画出来,而棋子则更简单了,没有任何特别要求,只需要下棋的双方所使用的棋子能被区分开来即可,所以,我吃了个香蕉就把香蕉皮弄成一小片儿的,他吃了个桔子就把桔子皮弄成一小片儿的然后再画个三层方框的棋盘就能下棋了。

打山棋棋盘样式
╋++++++++╋++++++++╋
++       +       ++
+ +      +      + +
+  ╋+++++╋+++++╋  +
+  ++    +    ++  +
+  + +   +   + +  +
+  +  ╋++╋++╋  +  +
+  +  +     +  +  +
╋++╋++╋     ╋++╋++╋
+  +  +     +  +  +
+  +  ╋++╋++╋  +  +
+  + +   +   + +  +
+  ++    +    ++  +
+  ╋+++++╋+++++╋  +
+ +      +      + +
++       +       ++
╋++++++++╋++++++++╋

上面的就是一个打山棋的棋盘(我是使用“+”来画出来的)的样式,粗的加号“╋”表示摆棋位,在这些位置可以摆放棋子,两人对局,其详细的走棋规则为:

  • 下棋双方先摆棋,一人一着只到将整个棋盘所有棋位都摆满;
  • 摆满后后摆棋子者先走棋;
  • 摆棋子时,逢三子(一方的)连成一线,称为打山;
  • 打山后用自己的棋子压住对方棋子,称为压包;
  • 在摆完棋子走棋前,所有压包都需要拿开;
  • 走棋过程中未发生打山,先无走棋位的一方就是36担;
  • 走棋过程中发生打山,则可以拿掉对方一粒棋子(最多也只允许拿掉一个棋子); +走棋过程中,如遇移动一子可能时形成两山(即同时出现两个一方的三个棋子连成一线),称之为过根;
  • 任意摆方两子,如果这两子中间有三个空位,则从任一个棋子数起第三个棋位(即中间位)为关键根,或者根位;
  • 被拿掉所有棋子的一方输棋。
打山棋根位
☆++++++++◇++++++++◇
++       +       ++
+ +      +      + +
+  △+++++╋+++++╋  +
+  ++    +    ++  +
+  + +   +   + +  +
+  +  ╋++╋++╋  +  +
+  +  +     +  +  +
◇++╋++╋     △++╋++╋
+  +  +     +  +  +
+  +  ◇++◇++☆  +  +
+  + +   +   + +  +
+  ++    +    ++  +
+  ╋+++++╋+++++◇  +
+ +      +      + +
++       +       ++
◇++++++++╋++++++++◇

如上棋盘所未:左下与右上角两个棋子之间总共有三个棋位,“☆”所处的位置即为根位,如果“△”处的棋子也是“◇”一方的棋子,那么,它往左上角移动一位即可同时形成两山,即过根。

今天用着电脑,突然一个轻轻的爆炸声音,电脑报销了,还好有一台备用电脑,把事情做完之后,带着电脑去维修,才买两个月时间,还在保修时间内,是电源烧坏了,换个电源就没有问题了,顺便我就想着给电脑加一块硬盘,整了一块2Tb的,后来也没加到电脑上面来,而是被我拿回来放到服务器上面去了。

服务器上面安装着TeamViewer,所以也不需要插屏幕即可操作,并了机,我用的是联想的台式服务器,以前也从来没有开过机箱,不过今天打开一看,还真不错,用了快两年了,里面居然没有什么灰尘,最开始我还以为和普通PC一样,把硬盘在光驱下面插了半天,怎么就是对不上螺丝孔,后来看了看原装的硬盘,有一个专用的硬盘盒,再看了年机箱的最下面,还有一个空着的,不错。

硬盘盒是免螺丝的,抽出来之后,将硬盘的螺丝孔对着硬盘盒上面的四个有弹性的东西就行了,装好之后直接插入到机箱上面去即可,两个Tb的硬盘用来做文件共享应该是足够了的吧我保留下来的最喜欢的电影有1.5个T左右,照片100来Gb,再加上100来Gb的软件,还可以余下来一些空间,今年一年使用应该足够了。

如果对于你来说,编程最大的乐趣来源于通过代码简单、清楚地表达你的意图,那么,选择Common Lisp作为你的编程语言可能比任何其它语言更得让你快乐,使用它,你会发现,它比其它程序语言使用了更少的代码但是却为你做得更多更快。

作者:Paul Graham

译者:阮一峰

为什么 Lisp 会如此先进?

如果我们把流行的编程语言,以这样的顺序排列:Java、Perl、Python、Ruby。你会发现,排在越后面的语言,越像Lisp。Python模仿Lisp,甚至把许多Lisp黑客认为属于设计错误的功能,也一起模仿了。至于Ruby,如果回到1975年,你声称它是一种Lisp方言,没有人会反对。编程语言现在的发展,不过刚刚赶上1958年Lisp语言的水平。

1958年,John McCarthy设计了Lisp语言。我认为,当前最新潮的编程语言,只是实现了他在1958年的设想而已。这怎么可能呢?计算机技术的发展,不是日新月异吗?1958年的技术,怎么可能超过今天的水平呢?让我告诉你原因。这是因为John McCarthy本来没打算把Lisp设计成编程语言,至少不是我们现在意义上的编程语言。他的原意只是想做一种理论演算,用更简洁的方式定义图灵机。

所以,为什么上个世纪50年代的编程语言,到现在还没有过时?简单说,因为这种语言本质上不是一种技术,而是数学。数学是不会过时的。你不应该把Lisp语言与50年代的硬件联系在一起,而是应该把它与快速排序(Quicksort)算法进行类比。这种算法是1960年提出的,至今仍然是最快的通用排序方法。

Fortran语言也是上个世纪50年代出现的,并且一直使用至今。它代表了语言设计的一种完全不同的方向。Lisp是无意中从纯理论发展为编程语言,而Fortran从一开始就是作为编程语言设计出来的。但是,今天我们把Lisp看成高级语言,而把Fortran看成一种相当低层次的语言。

1956年,Fortran刚诞生的时候,叫做Fortran I,与今天的Fortran语言差别极大。Fortran I实际上是汇编语言加上数学,在某些方面,还不如今天的汇编语言强大。比如,它不支持子程序,只有分支跳转结构(branch)。

Lisp和Fortran代表了编程语言发展的两大方向。前者的基础是数学,后者的基础是硬件架构。从那时起,这两大方向一直在互相靠拢。Lisp刚设计出来的时候,就很强大,接下来的二十年,它提高了自己的运行速度。而那些所谓的主流语言,把更快的运行速度作为设计的出发点,然后再用超过四十年的时间,一步步变得更强大。

直到今天,最高级的主流语言,也只是刚刚接近Lisp的水平。虽然已经很接近了,但还是没有Lisp那样强大。

Lisp语言诞生的时候,就包含了9种新思想。其中一些我们今天已经习以为常,另一些则刚刚在其他高级语言中出现,至今还有2种是Lisp独有的。按照被大众接受的程度,这9种思想依次是:

  1. 条件结构(即"if-then-else"结构)。现在大家都觉得这是理所当然的,但是Fortran I就没有这个结构,它只有基于底层机器指令的goto结构。
  2. 函数也是一种数据类型。在Lisp语言中,函数与整数或字符串一样,也属于数据类型的一种。它有自己的字面表示形式(literal representation),能够储存在变量中,也能当作参数传递。一种数据类型应该有的功能,它都有。
  3. 递归。Lisp是第一种支持递归函数的高级语言。
  4. 变量的动态类型。在Lisp语言中,所有变量实际上都是指针,所指向的值有类型之分,而变量本身没有。复制变量就相当于复制指针,而不是复制它们指向的数据。
  5. 垃圾回收机制。
  6. 程序由表达式(expression)组成。Lisp程序是一些表达式区块的集合,每个表达式都返回一个值。这与Fortran和大多数后来的语言都截然不同,它们的程序由表达式和语句(statement)组成。 区分表达式和语句,在Fortran I中是很自然的,因为它不支持语句嵌套。所以,如果你需要用数学式子计算一个值,那就只有用表达式返回这个值,没有其他语法结构可用,因为否则就无法处理这个值。

    后来,新的编程语言支持区块结构(block),这种限制当然也就不存在了。但是为时已晚,表达式和语句的区分已经根深蒂固。它从Fortran扩散到Algol语言,接着又扩散到它们两者的后继语言。
  7. 符号(symbol)类型。符号实际上是一种指针,指向储存在哈希表中的字符串。所以,比较两个符号是否相等,只要看它们的指针是否一样就行了,不用逐个字符地比较。
  8. 代码使用符号和常量组成的树形表示法(notation)。
  9. 无论什么时候,整个语言都是可用的。Lisp并不真正区分读取期、编译期和运行期。你可以在读取期编译或运行代码;也可以在编译期读取或运行代码;还可以在运行期读取或者编译代码。

在读取期运行代码,使得用户可以重新调整(reprogram)Lisp的语法;在编译期运行代码,则是Lisp宏的工作基础;在运行期编译代码,使得Lisp可以在Emacs这样的程序中,充当扩展语言(extension language);在运行期读取代码,使得程序之间可以用S-表达式(S-expression)通信,近来XML格式的出现使得这个概念被重新"发明"出来了。

Lisp语言刚出现的时候,它的思想与其他编程语言大相径庭。后者的设计思想主要由50年代后期的硬件决定。随着时间流逝,流行的编程语言不断更新换代,语言设计思想逐渐向Lisp靠拢。

思想1到思想5已经被广泛接受,思想6开始在主流编程语言中出现,思想7在Python语言中有所实现,不过似乎没有专用的语法。

思想8可能是最有意思的一点。它与思想9只是由于偶然原因,才成为Lisp语言的一部分,因为它们不属于John McCarthy的原始构想,是由他的学生Steve Russell自行添加的。它们从此使得Lisp看上去很古怪,但也成为了这种语言最独一无二的特点。Lisp古怪的形式,倒不是因为它的语法很古怪,而是因为它根本没有语法,程序直接以解析树(parse tree)的形式表达出来。在其他语言中,这种形式只是经过解析在后台产生,但是Lisp直接采用它作为表达形式。它由列表构成,而列表则是Lisp的基本数据结构。

用一门语言自己的数据结构来表达该语言,这被证明是非常强大的功能。思想8和思想9,意味着你可以写出一种能够自己编程的程序。这可能听起来很怪异,但是对于Lisp语言却是再普通不过。最常用的做法就是使用宏。

术语"宏"在Lisp语言中,与其他语言中的意思不一样。Lisp宏无所不包,它既可能是某样表达式的缩略形式,也可能是一种新语言的编译器。如果你想真正地理解Lisp语言,或者想拓宽你的编程视野,那么你必须学习宏。

就我所知,宏(采用Lisp语言的定义)目前仍然是Lisp独有的。一个原因是为了使用宏,你大概不得不让你的语言看上去像Lisp一样古怪。另一个可能的原因是,如果你想为自己的语言添上这种终极武器,你从此就不能声称自己发明了新语言,只能说发明了一种Lisp的新方言。

我把这件事当作笑话说出来,但是事实就是如此。如果你创造了一种新语言,其中有car、cdr、cons、quote、cond、atom、eq这样的功能,还有一种把函数写成列表的表示方法,那么在它们的基础上,你完全可以推导出Lisp语言的所有其他部分。事实上,Lisp语言就是这样定义的,John McCarthy把语言设计成这个样子,就是为了让这种推导成为可能。

就算Lisp确实代表了目前主流编程语言不断靠近的一个方向,这是否意味着你就应该用它编程呢?

如果使用一种不那么强大的语言,你又会有多少损失呢?有时不采用最尖端的技术,不也是一种明智的选择吗?这么多人使用主流编程语言,这本身不也说明那些语言有可取之处吗?

另一方面,选择哪一种编程语言,许多项目是无所谓的,反正不同的语言都能完成工作。一般来说,条件越苛刻的项目,强大的编程语言就越能发挥作用。但是,无数的项目根本没有苛刻条件的限制。大多数的编程任务,可能只要写一些很小的程序,然后用胶水语言把这些小程序连起来就行了。你可以用自己熟悉的编程语言,或者用对于特定项目来说有着最强大函数库的语言,来写这些小程序。如果你只是需要在Windows应用程序之间传递数据,使用Visual Basic照样能达到目的。

那么,Lisp的编程优势体现在哪里呢?

语言的编程能力越强大,写出来的程序就越短(当然不是指字符数量,而是指独立的语法单位)。

代码的数量很重要,因为开发一个程序耗费的时间,主要取决于程序的长度。如果同一个软件,一种语言写出来的代码比另一种语言长三倍,这意味着你开发它耗费的时间也会多三倍。而且即使你多雇佣人手,也无助于减少开发时间,因为当团队规模超过某个门槛时,再增加人手只会带来净损失。Fred Brooks在他的名著《人月神话》(The Mythical Man-Month)中,描述了这种现象,我的所见所闻印证了他的说法。

如果使用Lisp语言,能让程序变得多短?以Lisp和C的比较为例,我听到的大多数说法是C代码的长度是Lisp的7倍到10倍。但是最近,New Architect杂志上有一篇介绍ITA软件公司的文章,里面说"一行Lisp代码相当于20行C代码",因为此文都是引用ITA总裁的话,所以我想这个数字来自ITA的编程实践。 如果真是这样,那么我们可以相信这句话。ITA的软件,不仅使用Lisp语言,还同时大量使用C和C++,所以这是他们的经验谈。

根据上面的这个数字,如果你与ITA竞争,而且你使用C语言开发软件,那么ITA的开发速度将比你快20倍。如果你需要一年时间实现某个功能,它只需要不到三星期。反过来说,如果某个新功能,它开发了三个月,那么你需要五年才能做出来。

你知道吗?上面的对比,还只是考虑到最好的情况。当我们只比较代码数量的时候,言下之意就是假设使用功能较弱的语言,也能开发出同样的软件。但是事实上,程序员使用某种语言能做到的事情,是有极限的。如果你想用一种低层次的语言,解决一个很难的问题,那么你将会面临各种情况极其复杂、乃至想不清楚的窘境。

所以,当我说假定你与ITA竞争,你用五年时间做出的东西,ITA在Lisp语言的帮助下只用三个月就完成了,我指的五年还是一切顺利、没有犯错误、也没有遇到太大麻烦的五年。事实上,按照大多数公司的实际情况,计划中五年完成的项目,很可能永远都不会完成。

我承认,上面的例子太极端。ITA似乎有一批非常聪明的黑客,而C语言又是一种很低层次的语言。但是,在一个高度竞争的市场中,即使开发速度只相差两三倍,也足以使得你永远处在落后的位置。

附录:编程能力

为了解释我所说的语言编程能力不一样,请考虑下面的问题。我们需要写一个函数,它能够生成累加器,即这个函数接受一个参数n,然后返回另一个函数,后者接受参数i,然后返回n增加(increment)了i后的值。

Common Lisp的写法如下:

(defun foo (n)
    (lambda (i) (incf n i)))

Ruby的写法几乎完全相同:

def foo (n)
    lambda {|i| n += i } end

Perl 5的写法则是:

sub foo {
    my ($n) = @_;
    sub {$n += shift}
}

这比Lisp和Ruby的版本,有更多的语法元素,因为在Perl语言中,你不得不手工提取参数。

Smalltalk的写法稍微比Lisp和Ruby的长一点:

foo: n
    |s|
    s := n.
    ^[:i| s := s+i. ]

因为在Smalltalk中,局部变量(lexical variable)是有效的,但是你无法给一个参数赋值,因此不得不设置了一个新变量,接受累加后的值。

Javascript的写法也比Lisp和Ruby稍微长一点,因为Javascript依然区分语句和表达式,所以你需要明确指定return语句,来返回一个值:

function foo (n) {
    return function (i) {
        return n += i } }

(实事求是地说,Perl也保留了语句和表达式的区别,但是使用了典型的Perl方式处理,使你可以省略return。)

如果想把Lisp/Ruby/Perl/Smalltalk/Javascript的版本改成Python,你会遇到一些限制。因为Python并不完全支持局部变量,你不得不创造一种数据结构,来接受n的值。而且尽管Python确实支持函数数据类型,但是没有一种字面量的表示方式(literal representation)可以生成函数(除非函数体只有一个表达式),所以你需要创造一个命名函数,把它返回。最后的写法如下:

def foo (n):
    s = [n]
    def bar (i):
        s[0] += i
        return s[0]
    return bar

Python用户完全可以合理地质疑,为什么不能写成下面这样:

def foo (n):
    return lambda i: return n += i

或者:

def foo (n):
    lambda i: n += i

我猜想,Python有一天会支持这样的写法。(如果你不想等到Python慢慢进化到更像Lisp,你总是可以直接……)

在面向对象编程的语言中,你能够在有限程度上模拟一个闭包(即一个函数,通过它可以引用由包含这个函数的代码所定义的变量)。你定义一个类(class),里面有一个方法和一个属性,用于替换封闭作用域(enclosing scope)中的所有变量。这有点类似于让程序员自己做代码分析,本来这应该是由支持局部作用域的编译器完成的。如果有多个函数,同时指向相同的变量,那么这种方法就会失效,但是在这个简单的例子中,它已经足够了。

Python高手看来也同意,这是解决这个问题的比较好的方法,写法如下:

def foo (n):
    class acc:
        def _ _init_ _ (self, s):
            self.s = s
        def inc (self, i):
            self.s += i
            return self.s
    return acc (n).inc

或者

class foo:
    def _ _init_ _ (self, n):
        self.n = n
    def _ _call_ _ (self, i):
        self.n += i
        return self.n

我添加这一段,原因是想避免Python爱好者说我误解这种语言。但是,在我看来,这两种写法好像都比第一个版本更复杂。你实际上就是在做同样的事,只不过划出了一个独立的区域,保存累加器函数,区别只是保存在对象的一个属性中,而不是保存在列表(list)的头(head)中。使用这些特殊的内部属性名(尤其是call),看上去并不像常规的解法,更像是一种破解。

在Perl和Python的较量中,Python黑客的观点似乎是认为Python比Perl更优雅,但是这个例子表明,最终来说,编程能力决定了优雅。Perl的写法更简单(包含更少的语法元素),尽管它的语法有一点丑陋。

其他语言怎么样?前文曾经提到过Fortran、C、C++、Java和Visual Basic,看上去使用它们,根本无法解决这个问题。Ken Anderson说,Java只能写出一个近似的解法:

public interface Inttoint {
    public int call (int i);
}
public static Inttoint foo (final int n) {
    return new Inttoint () {
    int s = n;
    public int call (int i) {
    s = s + i;
    return s;
    }};
}

这种写法不符合题目要求,因为它只对整数有效。

当然,我说使用其他语言无法解决这个问题,这句话并不完全正确。所有这些语言都是图灵等价的,这意味着严格地说,你能使用它们之中的任何一种语言,写出任何一个程序。那么,怎样才能做到这一点呢?就这个小小的例子而言,你可以使用这些不那么强大的语言,写一个Lisp解释器就行了。

这样做听上去好像开玩笑,但是在大型编程项目中,却不同程度地广泛存在。因此,有人把它总结出来,起名为"格林斯潘第十定律"(Greenspun's Tenth Rule):

“任何C或Fortran程序复杂到一定程度之后,都会包含一个临时开发的、只有一半功能的、不完全符合规格的、到处都是bug的、运行速度很慢的Common Lisp实现。”

如果你想解决一个困难的问题,关键不是你使用的语言是否强大,而是好几个因素同时发挥作用:

a. 使用一种强大的语言 b. 为这个难题写一个事实上的解释器 c. 你自己变成这个难题的人肉编译器

在Python的例子中,这样的处理方法已经开始出现了,我们实际上就是自己写代码,模拟出编译器实现局部变量的功能。

这种实践不仅很普遍,而且已经制度化了。举例来说,在面向对象编程的世界中,我们大量听到"模式"(pattern)这个词,我觉得那些"模式"就是现实中的因素(c),也就是人肉编译器。 当我在自己的程序中,发现用到了模式,我觉得这就表明某个地方出错了。程序的形式,应该仅仅反映它所要解决的问题。代码中其他任何外加的形式,都是一个信号,(至少对我来说)表明我对问题的抽象还不够深,也经常提醒我,自己正在手工完成的事情,本应该写代码,通过宏的扩展自动实现。

使用telnet或SSH远程登录linux时,如果连接非正常中断,重新连接时,系统将开一个新的session,无法恢复原来的session。screen命令可以解决这个问题。

Screen工具是一个终端多路转接器,在本质上,这意味着你能够使用一个单一的终端窗口运行多终端的应用。

基本命令:

开一个会话,并命名为temp:

screen -S temp

临时退出这个会话:

ctrl-a d

再次进入名为temp的会话:

screen -r temp

检查正在运行的会话:

screen -ls

对 Lisp 早就有所耳闻了,不过一直也没有去看过相关的资料(除了那些说Lisp如何如何的文章或者某些文章里面顺便带过的文字,我对Lisp可以说是一无所知),不过理论上来讲应该不是很难吧。

现在我已经可以完全回到大学时代对编程的热爱了,因为我现在不是以它为职业,我从来都不希望自己的兴趣变成职业,当前在北京,虽然靠着自学的那点东西能混口饭吃,但是毕竟我还是选择了离开那个职业。

现在我每天忙完自己了工作,可以随便自己的兴趣写写代码,玩玩摄影,是没有任何负担的去玩,而不是带着压力的去写,我写好写坏,没有任何人来说我,只是唯一的遗憾是我现在只能自己一个人摸索,无任何旁人的指点,进步总是很慢的,但是这样得到的结果是我能更深刻地学习。

从05年接触互联网开始,我已经陆陆续续接触了Asp,PHP,Java,Python,JavaScript,Ruby这些语言,Asp是很果断的抛弃的,之后就是PHP,一直都是需要的时候就用用,不过自己的项目除了当年在民工网上班时上头指定使用PHP,我也不会在自己的项目中去使用,Java是在北京学习时系统学习过的,不过后来还是因为个人原因果断抛弃(这东西,唉),Python是07年就开始接触的,不过真正地去学习它也是后面的事情了,现在它还是我使用最多的语言(没有之一),JavaScript我也没怎么去学过,都是需要用到什么的时候就去学习什么(毕竟只是作为兴趣,所以一切都以服务我的兴趣为目的),前几个月开始接触了Go,感觉很不错,加上现在的Lisp,准备把自己的主要语言再增加两个,一个是Go,一个是Lisp。

在Windows上配置Common Lisp的开发环境步骤简单来说,就是下面四步:

  1. 下载并安装 CLisp:(http://clisp.cons.org/
  2. 下载并安装 EMacs:(http://www.gnu.org/software/emacs/
  3. 下载并安装 Quicklisp:(http://www.quicklisp.org/
  4. 进行一些必要的配置以使得所有工具能正确的工作。

第一步:下载并安装 CLISP

你可以在下面网页中下载 CLISP:

http://sourceforge.net/projects/clisp/files/clisp/2.48/

*注意*: 2.48并不是最新的CLISP版本,原因在于新版本的CLISP对于Windows的路径的处理还有一些问题,所以现在我还是推荐你使用2.48版。

请下载 clisp-2.48-win32-mingw-big.exe ,默认情况下,CLISP将安装到你的 C:Program Filesclisp-2.48 目录下,同时安装程序还会添加该路径到你的 PATH 环境变量中。

注意*:如果你和我一样,使用的是Windows 7 64位系统,则默认的安装路径为 *C:Program Files (x86)clisp-2.48

在 clisp-2.48 目录里,你会发现有一个名为 libsvm 的子目录,复制该目录下的 svm.dll 文件,并将其粘贴到 clisp-2.48/full 目录下。

完成前面的工作之后,你会发现在开始菜单中将有一个GNU CLISP 2.48 的链接,你现在已经可以运行它。

要退出GNU CLISP,最简单的办法就是按 Ctrl + D 或者在命令行中输入 (quit) 即可。

下载并安装 Emacs

最新版的 Emacs 二进制发行包可以在 http://ntemacs.sourceforge.net/ 找到,你可以选择下载最新的CVS版本或者最新的稳定发行版,你只能下载到 .7z 格式的发布包,所以,如果你的电脑上面没有可解压 .7z 格式文件的软件,你还需要安装一个来解压它http://www.7zip.org/ 。

解压 ntemacs 文件到任何一个你想它被安装的目录中(C:Program Files 同样是可以的)。在ntemacs/bin 目录中,你会看到有一个名为 runemacs.exe 的文件,在一个你感觉最方便的位置创建一个指向该文件的快捷方式(桌面,开始菜单等),之后点击该快捷方式以运行 Emacs。

下载并安装 Quicklisp

http://www.quicklisp.org/ 下载 Quicklisp (在本文发布时,它还只有Beta版本,但是它已经提供了非常好的功能,并且可以很好的工作)。下载下面这个文件:

http://beta.quicklisp.org/quicklisp.lisp

保存该文件至任何一个位置,之后打开 GNU CLISP,并运行下面这行代码:

(load "C:/path/to/quicklisp.lisp")
*注意*:你可以使用正斜线以替代Windows路径格式中的反斜线,或者使用两个反斜线,比如:(load “C:pathtoquicklisp.lisp”)

当上面的代码运行完成之后,接着运行下面这一行代码:

(quicklisp-quickstart:install :path "C:quicklisp")
注意*: 默认Quicklisp 将尝试安装到 *$HOME/quicklisp 目录中,由于在Windows中,$HOME 变量是根据你在哪个位置运行程序来定义的,所以,我们需要手工的指定安装路径。

现在,Quicklisp 安装脚本会为你下载所需要的文件并为你安装它们,现在你可以返回到http://www.quicklisp.org/ 查看Quicklisp的帮助文档以了解如何使用它。它能非常方便快速的为你搜索并安装Common Lisp库,比如:

(ql:quickload "ieee-floats")

上面代码将会首先检查所需要的库是否已经添加到你的CLISP环境中,如果没有,它会自动的下载该库并为你添加至CLISP环境中。

必要的配置以使得所有工具都能正常的工作

首先,CLISP需要一些配置,以使得它能找到正确的临时文件目录,找到 $HOME.clisprc.lisp (比如C:UsersusernameAppDataRoaming.clisprc.lisp )

添加如下代码:

;;; Load Quicklisp when CLISP launches
#-quicklisp
(let ((quicklisp-init "C:quicklispsetup.lisp"))
  (when (probe-file quicklisp-init)
    (load quicklisp-init)))

;;; Fix for CLISP on Windows.
(setf temporary-file-directory "C:<home-directory>AppDataLocalTemp")
(setf (ext:getenv "temp") temporary-file-directory)
(setf (ext:getenv "tmp") temporary-file-directory)

修改代码中的 为你的个人HOME目录,比如我的是 *C:/Users/pantao/*,上面的代码在 Windows Vista 以及 Windows 7 中能正常工作,对于 Windows XP,使用下面的路径:

C:<home-directory>AppDataTemp

下一步,重新打开 GNU CLISP,并运行下面代码:

(ql:quicklisp "quicklisp-slime-helper")

它将为你安装 SLIME (http://common-lisp.net/project/slime/),一个十分好的 Common Lisp 开发环境。

最后,你需要打开你的 Emacs 配置文件,最简单的办法是打开 Emacs然后Ctrl + X, Ctrl + F ,之后再输入 *~/.emacs*。

在该文件中,添加下面两行:

(setq inferior-lisp-program "clisp.exe")
(load "C:quicklispslime-helper.el")

现在,你应该已经可以使用 SLIME了,重新打开 Emacs,然后按 Alt + X之后键入 *slime*。