<![CDATA[lagvin.bokee.com]]> zh_cn Mon,24 Apr 2006 04:16:13 CST Thu,27 Apr 2006 12:18:57 CST http://www.bokee.com http://reg.bokee.com/account/web/img/logo.gif 博客网 http://www.bokee.com 您好,欢迎访问yunle110.bokee.com <![CDATA[日志说明]]> .html 在这里说明一下关于本博客日志上的标识说明:

 

1、标题没有前缀或者后缀

  这类一般是自己所创,但自我感觉又不是很重要的日志。但也有可能不是自己所创,而属他人,但由于找不到出处和为了简便,就没有注明“【转】”字样而已。当然,如果原创作者看到,要求著名“【转】”字样或出处的话,我当然会尊重原创作者的要求,相应的补上。

 

2、标题有前缀或者后缀“【原】”字样

  这类是自己所创,而且感觉比较重要的日志,当他人引用或者转载时需要注明“【转】”字样并注明出处。

 

3、标题有前缀后者后缀“【转】”字样

  这类是他人所创,而且有较重的知识产权味。为了尊重知识产权,我会尽量注明原创作者和出处(如果有的话,都会注明)。

]]>
Thu,27 Apr 2006 12:18:57 CST 99
<![CDATA[【转】Apache2的httpd.conf翻译]]> .html 基于 NCSA 服务的配置文件。
#
#这是Apache服务器主要配置文件。
#它包含服务器的影响服务器运行的配置指令。
#参见<URL:http://httpd.ache.org/doc-2.0/>以取得关于这些指令的详细信息
#
#不要只是简单的阅读这些指令信息而不去理解它。
#这里只是做了简单的说明,如果你没有参考在线文件,你就会被警告。
#
#这些配置指令被分为下面三个部分:
#1. 控制整个Apache服务器行为的部分(即全局环境变量)
#2. 定义主要或者默认服务参数的指令,也为所有虚拟主机提供默认的设置参数
#3. 虚拟主机的设置参数
#
#配置和日志文件名:如果你指定的文件名以“/”开始(win32下以“dirver:/”),
#服务器将使用绝对路径,如果文件名不是以“/”开始的,那么它将把ServerRoot
#的值附加在文件名的前面,例如,对“logs/foo.log",如果ServerRoot的值
#为“/usr/local/apache2”,则该文件应为“/usr/local/apache2/logs/foo.log”
#
##第一区:全局环境参数
#
#这里设置的参数将影响整个Apache服务器的行为;
#例如Apache能够处理的并发请求的数量等。
#
#ServerRoot:指出服务器保存其配置、出错和日志文件等的根目录。
#
#注意!如果你想要将它指定为NFS或其它网络上的位置,
#请一定要去阅读与LockFile有关的文档(可能在
#<URL:http://httpd.apache.org/docs-2.0/mod/mpm_common.html#lockfile>)。
#这将会使你自己也能解决很多问题。
#
#路径的结尾不要添加斜线。
#
ServerRoot "/usr/loacl/apache2"

#
#串行访问的锁文件必须保存在本地磁盘上
#
<IfModule !mpm_winnt.c>
<IfModule !mpm_neware.c>
#LockFile logs/accept.lock
</IfModule>
</IfModule>

#ScoreBoardFile:用来保存内部服务进程信息的文件。
#如果未指明(默认),记分板(scoreboard)将被保存在一个匿名的共享内存段中,
#并且它不能被第三方软件所使用。
#如果指定了,要确保不能使用两个Apache使用同一个记分板文件,
#这个记分板文件必须保存在本地磁盘上。
#
<IfModule !mpm_netware.c>
<IfModule !perchild.c>
#ScoreBoardFile logs/apache_runtime_status
<IfModule>
<IfModule>

#
#PidFile:记录服务器启动进程号的文件。
#
<IfModule !mpm_neware.c>
PidFile logs/httpd.pid
</IfModule>

#
#Timeout:接收和发送前超时秒数
#
Timeout 300

#
#KeepAlive:是否允许稳固的连接(每个连接有多个请求),
#设为"Off"则停用。
#
KeepAlive On

#
#MaxKeepAliveRequests:在稳固连接期间允许的最大请求数,
#设为0表示无限制接入。
#我们推荐你将其设为一个较大的值,以便提高性能
MaxKeepAliveRequests 100

#
#KeepAliveTimeout:在同一个连接上从同一台客户上接收请求的秒数
#
KeepAliveTimeout 15

##
##Server-Pool大小设定(针对MPM的)
##

# prefork MPM
# StartServers:启动时服务器启动的进程数
# MinSpareServers:保有的备用进程的最小数目
# MaxSpareServers:保有的备用进程的最大数目
# MaxClients:服务器允许启动的最大进程数
# MaxRequestsPerChild:一个服务进程允许的最大请求数
<IfModule prefork.c>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxClients 150
MaxRequestPerChild 0
</IfModule>

# worker MPM
# StartServers:服务器启动时的服务进程数目
# MaxClients:允许同时连接的最大用户数目
# MinSpareThreads:保有的最小工作线程数目
# MaxSpareThreads:允许保有的最大工作线程数目
# ThreadsPerChild:每个服务进程中的工作线程常数
# MaxRequestsPerChild:服务进程中允许的最大请求数目
<IfModule worker.c>
StartServers 2
MaxClients 150
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestsPerChild 0
</IfModule>

# perchild MPM
# NumServers:服务进程数量
# StartThreads:每个服务进程中的起始线程数量
# MinSpareThreads:保有的最小线程数量
# MaxSpareThreads:保有的最大线程数量
# MaxThreadsPerChild:每个服务进程允许的最大线程数
# MaxRequestsPerChild:每个服务进程允许连接的最大数量
<IfModule perchild.c>
NumServers 5
StartThreads 5
MinSpareThreads 5
MaxSpareThreads 10
MaxThreadsPerChild 20
MaxRequestsPerChild 0
</IfModule>

# WinNT MPM
# ThreadsPerChild:服务进程中工作线程常数
# MaxRequestsPerChild:服务进程允许的最大请求数
<IfModule mpm_winnt.c>
ThreadsPerChild 250
MaxRequestsPerChild 0
</IfModule>

# BeOS MPM
# StartThreads:服务器启动时启动的线程数
# MaxClients:可以启动的最大线程数(一个线程等于一个用户)
# MaxRequestsPerThread:每个线程允许的最大请求数
<IfModule beos.c>
StartThreads 10
MaxClients 50
MaxRequestsPerThread 10000
</IfModule>

# NetWare MPM
# ThreadStachSize:为每个工作线程分配的堆栈尺寸
# StartThreads:服务器启动时启动的线程数
# MinSpareThreads:用于处理实发请求的空闲线程数
# MaxSpareThreads:空闲线程的最大数量
# MaxThreads:在同一时间活动的最大线程数
# MaxRequestPerChild:一个线程服务请求的最大数量,
# 推荐将其设置为0,以实现无限制的接入
<IfModule mpm_netware.c>
ThreadStackSize 65536
StartThreads 250
MinSpareThreads 25
MaxSpareThreads 250
MaxThreads 1000
MaxRequestPerChild 0
</IfModule>

# OS/2 MPM
# StartServers:启动的服务进程数量
# MinSpareThreads:每个进程允许的最小空闲线程
# MaxSpareThreads:每个进程允许的最大空闲线程
# MaxRequestsPerChild:每个服务进程允许的最大连接数
<IfModule mpmt_os2.c>
StartServers 2
MinSpareThreads 5
MaxSpareThreads 10
MaxRequestsPerChild 0
</IfModule>

#
# Listen:允许你绑定Apache服务到指定的IP地址和端口上,以取代默认值
# 参见<VirtualHost>指令
# 使用如下命令使Apache只在指定的IP地址上监听,
# 以防止它在IP地址0.0.0.0上监听
#
# Listen 12.34.56.78:80

Listen 80

#
# 动态共享支持(DSO)
#
# 为了能够使用那些以DSO模式编译的模块中的函数,你必须有相应的“LoadModule”行,
# 因此,在这里包含了这些指令,以便能在使用它之前激活。
# 那些静态编译的模块不需要在这里列出 (即以“httpd -l”列出的模块)
#
# 示例:
# LoadModule foo_module modules/mod_foo.so
#

#
# ExtendedStatus:当调用“server-status”时,控制Apache是产生“全”状态
# 信息(ExtendedStatus On),还是产生基本信息(ExtendedStatus Off)。
# 默认为off
#
# ExtendedStatus On

### 第二区:“主”服务配置
#
# 这一区建立被 “主” 服务器用的指令值,以回应那些不被 <VirtualHost>
# 定义处理的任何请求。
# 这些数值也提供默认值给后面定义的<VirtualHost>容器。
# 如果<VirtualHost>中有定义,那么这里定义的指令值将被
# <VirtualHost>中的定义所覆盖。
#

<IfModule !mpm_winnt.c>
<IfModule !mpm_neware.c>
#
# 如果你想使httpd以另外的用户或组来运行,你必须在开始时以root方式启动
# 然后再将它切换为你想要使用的用户或组。
#
# User/Group:运行httpd的用户和组
# 在SCO (ODT3)上使用“User nouser”和“Group nogroup”
# 在HPUX上,你可能不能以nobody身份使用共享内存,建议创建一个www用户。
# 注意一些核心(kernel)在组ID大于60000时拒绝setgid(Group)或semctl(IPC_SET),
#节在这些系统上不要使用“Group #-1”。
#
User nobody
Group #-1
</IfModule>
</IfModule>

#
# ServerAdmin:你的邮件地址,当发生问题时Apache将向你发出邮件。
# 作为一个出错文档,这个地址显示在server-generated页上,
# 例如:admin@your-domain.com
#
ServerAdmin kreny@sina.com

#
# ServerName指定Apache用于识别自身的名字和端口号。
# 通常这个值是自动指定的,但是我们推荐你显式的指定它以防止启动时出错
#
# 如果你为你的主机指定了一个无效的DNS名,server-generated重定向将不能工作。
# 参见UseCanonicalName指令
#
# 如果你的主机没有注册DNS名,在这里键入它的IP地址
# 无论如何,你必须使用它的IP地址来提供服务,
# 这里使用一种容易理解的方式重定向服务
ServerName www.dalouis.com:80

#
# UseCanonicalName:决定Apache如何构造URLS和 SERVER_NAME 和 SERVER_PORT 的指令。
# 当设置为 “Off”时,Apache会使用用户端提供的主机名和端口号。
# 当设置为“On”,Apache会使用ServerName指令的值。
#
UseCanonicalName Off

#
# DocumentRoot:你的文档的根目录。默认情况下,所有的请求从这个目录进行应答。
# 但是可以使用符号链接和别名来指向到其他的位置。
#
DocumentRoot "/home/redhat/public_html"

#
# Apache可以存取的每个目录都可以配置存取权限(包括它的子目录)。
#
# 首先,我们配置一个高限制的特征。

# 这将禁止访问文件系统所在的目录,并添加你希望允许访问的目录块。
# 如下所示
<Directory />
Order Deny,Allow
Deny from all
</Directory>

#
# 注意从这里开始你一定要明确地允许哪些特别的特征能够被使用。
# - 所以,如果Apache没有象你所期待的那样工作的话,
# 请检查你是否在下面明确的指定它可用。
#

#
# 这将改变到你设置的DocumentRoot
#
<Directory "/home/redhat/public_html">

#
# Options:这个指令的值可以是“None”,“All”,或者下列选项的任意组合:
# Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
#
# 注意,“MultiViews”必须被显式的指定,“Options All”不能为你提供这个特性。
#
# 这个指令既复杂又重要,请参见
#“http://httpd.apache.org/docs-2.0/mod/core.html#optioins”以取得更多的信息。
#
Options FollowSymLinks

#
# AllowOverride控制那些被放置在.htaccess文件中的指令。
# 它可以是“All”,“None”,或者下列指令的组合:
# Options FileInfo AuthConfig Limit
#
AllowOverride None

#
# 控制谁可以获得服务。
#
Order allow,deny
Allow from all

</Directory>

#
# UserDir:指定在得到一个~user请求时将会添加到用户home目录后的目录名。
#

UserDir public_html

# 为防止在UserDir指令上的漏洞,对root用户设置
# 象“./”这样的UserDir是非常有用的。
# 如果你使用Apache 1.3或以上版本,我们强烈建议你
# 在你的服务器配置文件中包含下面的行

UserDir disabled root

#
# 下面是一个使用UserDir指令使一个站点的目录具有只读属性的示例:
#
# <Directory /home/*/public_html>
# AllowOverride FileInfo AuthConfig Limit Indexes
# Options MultiViews Indexes SymLinksIfOwnerMatch IncludeNoExec
# <Limit GET POST OPTIONS PROPFIND>
# Order allow,deny
# Allow from all
# </Limit>
# <LimitExcept GET POST OPTIONS PROPFIND>
# Order deny,allow
# Deny from all
# </LimitExcept>
# </Directory>

#
# DirectoryIndex:定义请求是一个目录时,Apache向用户提供服务的文件名
#
# index.html.var文件(一个类型映象文件)用于提供一个文档处理列表,
# 出于同样的目的,也可以使用MultiViews选项,但是它会非常慢。
#
DirectoryIndex index.php index.html index.html.var

#
# AccessFileName:在每个目录中查询为目录提供附加配置指令的文件的文件名。
# 参见AllowOverride指令。
#
AccessFileName .htaccess

#
# 下面的行防止.htaccess和.htpasswd文件被Web客户查看。
#
<Files ~ "^\.ht">
Order allow,deny
Deny from all
</Files>

#
# Typeconfig:定义在哪里查询mime.types文件。
#
TypeConfig conf/mime.types

#
# DefaultType:定义当不能确定MIME类型时服务器提供的默认MIME类型。
# 如果你的服务主要包含text或HTML文档,“text/plain”是一个好的选择;
# 如果大多是二进制文档,诸如软件或图像,你应使用
# “application/octer-stream”来防止浏览器象显示文本那样显示二进制文件。
#
DefaultType text/plain

#
# mod_mime_magic允许服务器从自己定义自己类型的文件中使用不同的线索(hints),
# 这个MIMEMagicFile指令定义hints定义所在的文件。
#
<IfModule mod_mime_magic.c>
MIMEMagicFile conf/magic
</IfModule>

#
# HostnameLookups:指定记录用户端的名字还是IP地址,例如,本指令为on时
# 记录主机名,如www.apache.org;为off时记录IP地址,204.62.129.132。
# 默认值为off,这要比设为on好得多,因为如果设为on则每个用户端请求都将会
# 至少造成对 nameserver 进行一次查询。
#
HostnameLookups Off

#
# EnableMMAP:控制是否进行内存转储(如果操作系统支持的话)。
# 默认为on,如果你的服务器安装在网络文件系统上(NFS),请关闭它。
# 在一些系统上,关闭它会提升系统性能(与文件系统类型无关);
# 具体情况请参阅http://httpd.apache.org/docs-2.0/mod/core.html#enablemmap
#
# EnableMMAP off

#
# EnableSendfile:控制是否使用sendfile kernel支持发送文件
# (如果操作系统支持的话)。默认为on,如果你的服务器安装在网络文件系统
# (NFS)上,请你关闭它。
# 参见http://httpd.apache.org/docs-2.0/mod/core.html#enablesendfile
#
# EnableSendfile off

#
# ErrorLog:错误日志文件定位。
# 如果你没有在<VirtualHost>内定义ErrorLog指令,这个虚拟主机的错误信息
# 将记录在这里。如果你在那儿定义了ErrorLog,这些错误信息将记录在你所
# 定义的文件里,而不是这儿定义的文件。
#
ErrorLog logs/error_log

#
# LogLevel:控制记录在错误日志文件中的日志信息数量。
# 可能的值包括:debug,info,notice,warn,error,crit,alert,emerg。
#
LogLevel warn

#
# 下面的指令为CustomLog指令定义格式别名。
#
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

# 你需要安装了mod_logio.c模块才能使用%I和%O。
# LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio

#
# 指定接入日志文件的定位和格式(一般日志格式)。
# 如果你没有在<VirtualHost>内定义这个指令,传输信息将记录在这里,
# 如果你定义了这个指令,则记录在你指定的位置,而不是这儿定义的位置。
#
CustomLog logs/access_log common

#
# 如果你想要记录agent和referer信息,可以使用下面的指令
#
# CustomLog logs/referer_log referer
# CustomLog logs/agent_log agent

#
# 如果你想要使用一个文件记录access,agent和referer信息,
# 你可以如下定义这个指令:
#
# CustomLog logs/access_log combined

#
# ServerTokens
# 这个指令定义包含在HTTP回应头中的信息类型。默认为“Full”,
# 这表示在回应头中将包含模块中的操作系统类型和编译信息。
# 可以设为列各值中的一个:
# Full | OS | Minor | Minimal | Major | Prod
# Full传达的信息最多,而Prod最少。
#
ServerTokens Full

#
# 随意的添加包含服务器版本和虚拟主机名字一行信息到server-generated输出页中
# (内部错误文档,FTP目录列表,mod_status和mod_info输出等等,除了CGI错误
# 或自定义的错误文档以外)。
# 设为“EMail”将包含一个指向ServerAdmin的mailto:连接。
# 可以为如下值:On | Off | EMail
#
ServerSignature On

#
# Aliases:在这时添加你需要的别名,格式如下:
# Alias 别名 真实名
#
# 注意,如果你在别名的未尾包含了“/”,那么在URL中也需要包含“/”。
# 因此,“/icons”不是这个示例中的别名。
# 如果别名中以“/”结尾,那么真实名也必须以“/”结尾,
# 如果别名中省略了结尾的“/”,那么真实名也必须省略。
#
# 我们使用别名“/icons/”来表示FancyIndexed目录列表,如果你不使用、
# FancyIndexing,你可以注释掉它。
#
# Alias /icons/ "/usr/local/apache2/icons/"

# <Directory "/usr/local/apache2/icons">
# Options Indexes MultiViews
# AllowOverride None
# Order allow,deny
## Allow from all
# </Directory>

#
# 这将改变ServerRoot/manual。这个别名提供了手册页所在的位置,
# 即使你改变了你的DocumentRoot。如果你对有无手册页并不在意的话,
# 你可以注释掉它。
#
Alias /manual "/usr/loacl/apache2/manual"

<Directory "/usr/local/apache2/manual">
Options Indexes FollowSymLinks MultiViews IncludesNoExec
AddOutputFilter Includes html
Order allow,deny
Allow from all
</Directory>

#
# ScriptAlias:指定包含服务脚本的目录。
# ScriptAliases 本质上与Aliases一样,除了这里的文档在请求时做为程序处理处理以外。
# 尾部的“/”规则与Alias一样
#
ScriptAlias /cgi-bin/ "/usr/loacl/apache2/cgi-bin/"

# 这里是添加php 4支持的指令
AddType application/x-httpd-php .php
LoadModule php4_module modules/libphp4.so

<IfModule mod_cgid.c>
#
# 添加mod_cgid.c设置,mod_cgid提供使用cgid进行通讯的UNIX套接字的
# 脚本接口路径。
#
# Scriptsock logs/cgisock
</IfModule>

#
# 将"/usr/local/apache2/cgi-bin"改为你的ScriptAliased指定的CGI目录,
# 如果你配置了的话。
#
<Directory "/usr/local/apache2/cgi-bin">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>

#
# Redirect允许你告诉客户端使用存在于服务器名字空间中的文档,
# 而不是现在的,这帮助客户定位那些改变了位置的文档。
# 例如:
# Redirect permanent /foo http://www.example.com/bar

#
# 控制server-generated目录列表显示的指令
#

#
# IndexOptions:控制server-generated目录列表显示特征。
#
IndexOptions FancyIndexing VersionSort

#
# AddIcon* 指令告诉服务器不同扩展名的图象文件如何显示,
# 只适用于FancyIndexed指令
#
AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip
AddIconByType (TXT,/icons/text.gif) text/*
AddIconByType (IMG,/icons/image2.gif) image/*
AddIconByType (SND,/icons/sound2.gif) audio/*
AddIconByType (VID,/icons/movie.gif) video/*

AddIcon /icons/binary.gif .bin .exe
AddIcon /icons/binhex.gif .hqx
AddIcon /icons/tar.gif .tar
AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv
AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip
AddIcon /icons/a.gif .ps .ai .eps
AddIcon /icons/layout.gif .html .shtml .htm .pdf
AddIcon /icons/text.gif .txt
AddIcon /icons/c.gif .c
AddIcon /icons/p.gif .pl .py
AddIcon /icons/f.gif .for
AddIcon /icons/dvi.gif .dvi
AddIcon /icons/uuencoded.gif .uu
AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl
AddIcon /icons/tex.gif .tex
AddIcon /icons/bomb.gif core

AddIcon /icons/back.gif ..
AddIcon /icons/hand.right.gif README
AddIcon /icons/folder.gif ^^DIRECTORY^^
AddIcon /icons/blank.gif ^^BLANKICON^^

#
# DefaultIcon 为那些没有显式定义图标的文件提供处理
#
DefaultIcon /icons/unknown.gif

#
# AddDescription允许你在server-generated索引后放置一个简短的说明。
# 只对FancyIndexed指令有效。
# 格式:AddDescription "说明" 文件名
#
# AddDescription "GZIP compressed document" .gz
# AddDescription "tar archive" .tar
# AddDescription "GZIP compressed tar archive" .tgz

#
# ReadmeName指定服务器默认查找的README文件的名字,并添加到目录列表中
#
# HeaderName指定目录列表前缀文件的文件名
ReadmeName README.html
HeaderName HEADER.html

#
# IndexIgnore指定目录索引忽略并且不包含在列表中的文件名集合,
# 支持shell类型的通配符。
#
IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t

#
# AddEncoding允许你在信息传送中使用(Mosaic/X 2.1 )解压缩信息,
# 注意:不是所有的浏览器都支持这个选项。
# 尽管名字相似,但是下列的指令与上面的FancyIndexing定制指令不同。
#
AddEncoding x-compress Z
AddEncoding x-gzip gz tgz

#
# DefaultLanguage和AddLanguage允许你指定文档的语言。
# 这使你可以让用户用容易理解的语言浏览文档。
#
# 指定默认的语言,这意味着所有没有指定语言的包都将使用该语言。
# 多数情况下,你也许并不想设置它,除非你确信这样做是正确的。
# 通常,不使用确定的语言比使用错误的语言要好。
#
# DefaultLanguage nl
#
# 注意1:作为语言关键字的词缀毫无疑问是不能一样的--采用波兰
# 文的文档(网络标准语言代码是pl)将希望使用“AddLanguage pl .po”
# 来避免与perl脚本的一般词缀产生二义性。
#
# 注意2: 下面的例子举例说明在一些范例中语言的二字符缩写与它的国家
# 的二字符缩写不相同,例如 “Danmark/dk” 和 “Danmark/da” 的比较.
#
# 注意3: 在 “ltz” 的情况下我们使用三字符词缀,违犯了 RFC的规定,
# 运行中将修复它并使用RFC1766标准取得参考数据。
#
# Danish (da) - Dutch (nl) - English (en) - Estonian (et)
# French (fr) - German (de) - Greek-Modern (el)
# Italian (it) - Norwegian (no) - Norwegian Nynorsk (nn) - Korean (ko)
# Portugese (pt) - Luxembourgeois* (ltz)
# Spanish (es) - Swedish (sv) - Catalan (ca) - Czech(cz)
# Polish (pl) - Brazilian Portuguese (pt-br) - Japanese (ja)
# Russian (ru) - Croatian (hr)
#
AddLanguage da .dk
AddLanguage nl .nl
AddLanguage en .en
AddLanguage et .et
AddLanguage fr .fr
AddLanguage de .de
AddLanguage he .he
AddLanguage el .el
AddLanguage it .it
AddLanguage ja .ja
AddLanguage pl .po
AddLanguage ko .ko
AddLanguage pt .pt
AddLanguage nn .nn
AddLanguage no .no
AddLanguage pt-br .pt-br
AddLanguage ltz .ltz
AddLanguage ca .ca
AddLanguage es .es
AddLanguage sv .sv
AddLanguage cz .cz
AddLanguage ru .ru
AddLanguage tw .tw
AddLanguage zh-tw .tw
AddLanguage hr .hr

# LanguagePriority允许你在会话过程中优先使用一些语言。
#
# 以优先次序递减的方式列出它们。我们或多或少地采用按字母排列顺序的方式
# 排列它们。也许你想要改变这个顺序。
LanguagePriority en da nl et fr de el it ja ko no pl pt pt-br ltz ca es sv tw

#
# ForceLanguagePriority 允许你为MULTIPLE CHOICES(Prefer)[在通讯的情况下]
# 或NOT ACCEPTABLE(Fallback)[没有可接受的语言匹配的情况]提供一个结果页。
#
ForceLanguagePriority Prefer Fallback

#
# 为发送出的所有页指定默认的字符集,这总是一个好主意,并且为你的
# web站点的国际化打开了大门,这不正是你曾经想要的吗。同样地,指定
# 默认字符集有一些小的损害,如一个使用iso-8859-1(latin1)标准命令
# 的页面,除非以别的方式指定例如你仅仅以显式方式声明它。
# 也有一些与那些总是鼓励你使用默认字符集的javascropt和URL语法有关
# 的浏览器安全原因。
#
#AddDefaultCharset ISO-8859-1
AddDefaultCharse GB2312

#
# 一般以文件扩展名的方式使用字符集。也许你想要避免与语言扩展发生
# 碰撞,除非你在每次改变后都做了很好的测试。
# 参见http://www.iana.org/assignments/character-sets以取得字符集
# 的名字列表和它们各自的RFCs。
#
AddCharset ISO-8859-1 .iso8859-1 .latin1
AddCharset ISO-8859-2 .iso8859-2 .latin2 .cen
AddCharset ISO-8859-3 .iso8859-3 .latin3
AddCharset ISO-8859-4 .iso8859-4 .latin4
AddCharset ISO-8859-5 .iso8859-5 .latin5 .cyr .iso-ru
AddCharset ISO-8859-6 .iso8859-6 .latin6 .arb
AddCharset ISO-8859-7 .iso8859-7 .latin7 .grk
AddCharset ISO-8859-8 .iso8859-8 .latin8 .heb
AddCharset ISO-8859-9 .iso8859-9 .latin9 .trk
AddCharset ISO-2022-JP .iso2022-jp .jis
AddCharset ISO-2022-KR .iso2022-kr .kis
AddCharset ISO-2022-CN .iso2022-cn .cis
AddCharset Big5 .Big5 .big5
# 对于俄语,使用了多个字符集(如何使用主要依靠客户端):
AddCharset WINDOWS-1251 .cp-1251 .win-1251
AddCharset CP866 .cp866
AddCharset KOI8-r .koi8-r .koi8-ru
AddCharset KOI8-ru .koi8-uk .ua
AddCharset ISO-10646-UCS-2 .ucs2
AddCharset ISO-10646-UCS-4 .ucs4
AddCharset UTF-8 .utf8


# 下面的字符集没有映射到一个特定的标准(iso)上,但是它们在浏览器
# 中被广泛的支持。注意那些大写字母。
# (它不应该,但是它是为兼容一些浏览器而做)
#
# 参见http://www.iana.org/assianments/character-sets以取得
# 它们的列表。但是浏览器支持较少。
#
AddCharset GB2312 .gb2312 .gb
AddCharset utf-7 .utf7
AddCharset utf-8 .utf8
AddCharset big5 .big5 .b5
AddCharset EUC-TW .euc-tw
AddCharset EUC-JP .euc-jp
AddCharset EUC-KR .euc-kr
AddCharset shift_jis .sjis

#
# AddType允许你为指定的文件类型添加或覆盖mime.types文件中配置的MIME
#
AddType application/x-tar .tgz
AddType image/x-icon .ico

#
# AddHandler允许你映射确定的文件扩展名到“handlers”:
# 与文件类型无关的行为。这既能编译到服务器中也可以添加到Action指令
# 中(看下面)。
# 为了在ScriptAliased指令指定的以外使用CGI脚本:
#(要使它可用,你还需要在Options中添加“ExecCGI”。
#
# AddHandler cgi-script .cgi

#
# 对于那些包含他们自己的HTTP头的文件
#
# AddHandler send-as-is asis

#
# 对于server-parsed imagemap文件:
#
# AddHandler imap-file map

#
# agemap 文件:
#
#AddHandler imap- 文件映像

#
# 对于类型映像:(转移资源)
#(这是默认的设定以允许Apache的“It Worked”页能多种语言分发)。
#
AddHandler type-map var

#
# 过滤器允许你在将它发送到客户端前进行处理。
#
# 为了在服务器端分析包含(SSI)的.shtml文档:
# (要执行这个指令,你还需要在Options指令中添加“Includes”。)
#
# AddType text/html .shtml
# AddOutputFilter INCLUDES .shtml

#
# Action让你定义当调用匹配的媒体文件时将要执行的脚本。这将减少
# 那些经常使用的CGI脚本的URL路径名的重复输入。
# 格式:Action media/type /cgi-script/location
# 格式:Action handler-name /cgi-script/location
#

#
# 可配置的错误应答有三种风格:
# 1)plain text 2)local redirects 3) external redirects
#
# 一些示例:
# ErrorDocument 500 "The server made a boo boo."
# ErrorDocument 404 /missing.html
# ErrorDocument 404 "/cgi-bin/missing_handler.pl"
# ErrorDocument 402 http://www.example.com/subscription_info.html
#

#
# 综合应用这些指令,我们可以创建一个国际化的出错应答。
#
# 我们使用Alias来重定向任意/error/HTTP_<error>.html.var应答到
# 我们的多语言错误消息集合。使用正确的文本替代它。
#
# 通过加入下面的行,你就能够改变这些消息的显示,而不必改变
# HTTP_<error>.html.var文件。
#
# Alias /error/include/ "/your/include/path/"
#
# 以将/usr/local/apache2/error/include/下的文件拷贝到/your/inclue/path/下
# 开始,你可以创建你自己的文件集合,甚至是其于每个虚拟主机的。
# 不管你的ServerSignature如何设置,默认的包含文件将显示你的
# Aapche版本号和你的ServerAdmin邮件地址
#
# 国际化的错误文档需要mod_alias,mod_include和mod_negotiation三个
# 模块。要激活它们,取消下面30行的注释符号

# Alias /error/ "/usr/local/apache2/error/"
#
# <Directory "/usr/local/apache2/error">
# AllowOverride None
# Options IncludesNoExec
# AddOutputFilter Includes html
# AddHandler type-map var
# Order allow,deny
# Allow from all
# LanguagePriority en de es fr it nl sv
# ForceLanguagePriority Prefer Fallback
# </Directory>
#
# ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var
# ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var
  ErrorDocument 403 /error.php
# ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var
# ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var
# ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var
# ErrorDocument 410 /error/HTTP_GONE.html.var
# ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var
# ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var
# ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var
# ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var
# ErrorDocument 415 /error/HTTP_SERVICE_UNAVAILABLE.html.var
# ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var
# ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var
# ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var
# ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var
# ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var

#
# 下面的命令更改标准的HTTP应答行为以处理己知的浏览器问题。
#
BrowserMatch "Mozilla/2" nokeepalive
BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
BrowserMatch "RealPlayer 4\.0" force-response-1.0
BrowserMatch "Java/1\.0" force-response-1.0
BrowserMatch "JDK/1\.0" force-response-1.0

#
# 下面命令关闭对那些没有尾部“/”的目录的非GET请求的重定向,
# 这些命令修复了微软的采用DAV方法不能正确处理重定向的WEB文件夹的问题。
# Apple下的DAV文件系统和Gnome下的VFS对DAV的支持也是采用这样的方法
# 进行处理的。
#
BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
BrowserMatch "^WebDrive" redirect-carefully
BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
BrowserMatch "^gnome-vfs" redirect-carefully

#
# 允许你使用URL:http://servername/server-status来通过mod_status生
# 成并报告服务器状态信息。改变.example.com为你自己的域名。
#
# <Location /server-status>
# SetHandler server-status
# Order deny,allow
# Deny from all
# Allow from .example.com
# </Location>

#
# 允许使用URL:http://servername/server-info来远程报告服务器配置信息
# (需要mod_info.c支持)。改变“.example.com”为你自己的域名。
#
# <Location /server-info>
# SetHandler server-info
# Order deny,allow
# Deny from all
# Allow from .example.com
# </Location>

#
# 代理服务器命令,去掉下面的行使代理服务可用。
#
# <IfModule mod_proxy.c>
# ProxyRequests On
# <Proxy *>
# Order deny,allow
# Deny from all
# Allow from .example.com
# </Proxy>

#
# 安装或关闭HTTP/1.1“通道”头处理。
# (“Full”添加服务器版本信息,“Block”移掉所有输出“通道”头信息。
# 可以设为下面各选项之一:Off | On | Full | Block
#
# ProxyVia On

# 最好为代理服务安装高速缓冲,去掉下面几行的注释符号:
# (没有CacheRoot则不缓冲)
#
# CacheRoot "/usr/local/apache2/proxy"
# CacheSize 5
# CacheGcInterval 4
# CacheMaxExpire 24
# CacheLastModifiedFactor 01
# CacheDefaultExpire 1
# NoCache a-domain.com another-domain.edu joes.garage-sale.com

# </IfModule>
# 代理命令结束。

#
# 附加的特定模块配置。
#
<IfModule mod_ssl.c>
Include conf/ssl.conf
</IfModule>

## 第三区:虚拟主机
#
# VirtualHost:你可以通过设置虚拟主机容器以实现在你的主机上保有多个
# 域名/主机名。大多数配置信息只使用基于名字的虚拟主机,因此服务器
# 不必担心IP地址的问题,下面的命令以*号代替虚拟主机名。
#
# 在你试着配置你的虚拟主机以前,请参见
# URL:http://httpd.apache.org/docs-2.0/vhosts/>以取得更多的信息。
#
# 你可以使用命令行选项“-S”来检验你的虚拟主机配置。

#
# 使用基于名字的虚拟主机。
#
# NameVirtualHost *

#
# 虚拟主机示例:
# 几乎所有的Apache命令都可以在虚拟主机容器中使用。
# 第一个虚拟主机区是用于向服务名未知的请求进行应答的配置。
#
# <VirtualHost *>
# ServerAdmin webmaster@dummy-host.example.com
# DocumentRoot /www/docs/dummy-host.example.com
# ServerName dummy-host.example.com
# ErrorLog logs/dummy-host.example.com-error_log
# CustomLog logs/dummy-host.example.com-access_log commom
# </virtualHost>

]]>
Wed,27 Feb 2008 16:18:43 CST 0
<![CDATA[Slackware下安装配置CVSweb]]> .html CVSweb就是CVS的WEB界面,可以大大提高程序员定位修改的效率:
使用的样例可以看:http://www.freebsd.org/cgi/cvsweb.cgi

 

1、系统环境

操 作 系 统 :slackware 12.0
CVSweb版本 :3.0.6
Apache版本 :2.2.4(slackware 12.0默认)
说      明    :以下说明中,"#"表示超级用户运行的命令,"$"表示用户"user"运行的命令,其它表示命令内容。

 

2、假定

2.1、Apache配置(slackware 12.0默认):
配置文件   :/etc/httpd/httpd.conf
DocumentRoot  : /srv/httpd/htdocs/
cgi directory : /srv/httpd/cgi/

2.2、安装用户为pub,其根目录为/home/pub/

2.3、安装CVSweb之前,要确保CVS已经安装好(安装方法可参考:http://lagvin.bokee.com/viewdiary.15620466.html)

 

3、安装

3.1、下载CVSweb(当前最新版本是3.0.6)
$cd ~
$wget http://people.freebsd.org/~scop/cvsweb/cvsweb-3.0.6.tar.gz

3.2、解压
$tar zxvf cvsweb-3.0.6.tar.gz

3.3、把配置文件cvsweb.conf复制到apache的配置文件所在目录
$cd cvsweb-3.0.6
#cp cvsweb.conf /etc/httpd/

3.4、把cvsweb.cgi复制到apache的cgi-bin目录的cvsweb子目录下
#cd /srv/httpd/cgi-bin/
#mkdir cvsweb
#cp /home/pub/cvsweb-3.0.6/cvsweb.cgi ./cvsweb/

3.5、在DocumentRoot下创建cvsweb的目录
#mkdir /srv/httpd/htdocs/cvsweb

3.6、放置好cvsweb的icons和css目录
#cp -fr /home/pub/cvsweb-3.0.6/icons/ /srv/httpd/htdocs/cvsweb/
#cp -fr /home/pub/cvsweb-3.0.6/css/ /srv/httpd/htdocs/cvsweb/

 

4、配置

 

4.1、cvsweb.cgi的配置

4.1.1、设置CVS资源库
#vi /etc/httpd/cvsweb.conf

查找@CVSrepositories,找到如下段落:
#
# 'symbolic_name' => ['Name to display',  '/path/to/cvsroot']
#
@CVSrepositories = (
        'local'   => ['Local Repository', '/var/cvs'],
#       'freebsd' => ['FreeBSD',          '/var/ncvs'],
#       'openbsd' => ['OpenBSD',          '/var/ncvs'],
#       'netbsd'  => ['NetBSD',           '/var/ncvs'],
#       'ruby'    => ['Ruby',             '/var/anoncvs/ruby'],
);

其中'symbolic_name'用来标识一个CVS repository,'Name to display'部分用来显示库名,
'/path/to/cvsroot'就是实际CVS库的路径
(注意:这里说的路径,是指CVSROOT目录所在的目录。例如,我的目录是/home/cvsroot/projects/CVSROOT,则填:/home/cvsroot/projects)

 

4.1.2、设置icons和css路径
查找$iconsdir,找到以下段落:
my $iconsdir = '/icons';

# format:          TEXT       ICON-URL                  width height
%ICONS = (
     back    => [('[BACK]',   "$iconsdir/back.gif",      20,   22)],
     dir     => [('[DIR]',    "$iconsdir/dir.gif",       20,   22)],
     file    => [('[TXT]',    "$iconsdir/text.gif",      20,   22)],
     binfile => [('[BIN]',    "$iconsdir/binary.gif",    20,   22)],
     graph   => [('[GRAPH]',  "$iconsdir/minigraph.png", 16,   16)],
);
undef $iconsdir;

# An URL where to find the CSS.
#
$cssurl = '/css/cvsweb.css';

修改$iconsdir和$cssurl值为:
my $iconsdir = '/cvsweb/icons';
$cssurl = '/cvsweb/css/cvsweb.css';

 

4.1.3、个性化设置
cvsweb.conf还有许多其它个性化设置,常见的有这些变量:
$logo 图标设置
$defaulttitle 标题设置
$address 管理员email地址设置
$long_intro 介绍文字
$short_instruction 说明文字

 

4.2、cvsweb.cgi的配置

4.2.1、指定配置文件cvsweb.cgi路径
#vi /srv/httpd/cgi-bin/cvsweb/cvsweb.cgi
查找$config,找到段落:
##### Start of Configuration Area ########

# == EDIT this ==
# Locations to search for user configuration, in order:
for (catfile($mydir, 'cvsweb.conf'), '/usr/local/etc/cvsweb/cvsweb.conf') {
  if (-r $_) {
    $config = $_;
    last;
  }
}

##### End of Configuration Area   ########

更改'/usr/local/etc/cvsweb/cvsweb.conf'部分为你的存放cvsweb.conf的实际路径,比如我的是'/etc/httpd/cvsweb.conf',则改成
for (catfile($mydir, 'cvsweb.conf'), '/etc/httpd/cvsweb.conf') {
  if (-r $_) {
    $config = $_;
    last;
  }
}

4.2.2、中文页面支持
#vi /srv/httpd/cgi-bin/cvsweb/cvsweb.cgi
查找sub html_header($;$),找到如下函数段落:
sub html_header($;$)
{
  my ($title, $moddate) = @_;
  $title  = htmlquote($title);
  my $l   = $logo || '';
  my $css = $CSS || '';
  http_header('text/html', $moddate);
  print <<EOH;
$HTML_DOCTYPE
<html>
<head>
<title>$title</title>
$HTML_META$css</head>
<body>
$l <h1>$title</h1>
EOH
}

在<head> <title>之间插入一行:<meta http-equiv="Content-Type" content="text/html; charset=gb2312">

4.3、Apache配置

配置Aapche支持cgi
#vi /etc/httpd/httpd.conf

4.3.1、确保对cgi使用脚本别名:
ScriptAlias /cgi-bin/ "/srv/httpd/cgi-bin/"

4.3.2、确保Apache能对.cgi后缀做解释
AddHandler cgi-script .cgi

4.3.3、修改cgi-bin目录的访问权限为:
<Directory "/srv/httpd/cgi-bin">
    AllowOverride None
    Options Indexes ExecCGI -MultiViews SymLinksIfOwnerMatch
    Order allow,deny
    Allow from all
</Directory>

4.3.4、增加访问控制
#vi /etc/httpd/httpd.conf
添加内容:
#
# Control cvsweb accessing permission
#
<Directory "/srv/httpd/cgi-bin/cvsweb/">
    AuthName "CVS Authorization"
    AuthType Basic
    AuthUserFile /etc/httpd/cvsweb.passwd
    require valid-user
</Directory>

4.4、更改/srv/httpd/cgi-bin/cvsweb/cvsweb.cgi的访问权限
#chmod 755 /srv/httpd/cgi-bin/cvsweb/cvsweb.cgi

4.5、生成访问的帐号密码
#htpasswd -c cvsweb.paaswd <user>
如果是添加多个用户和密码到文件cvsweb.passwd,则不用-c参数

 

5、错误处理:

5.1、如果发生类似
Permission denied: exec of '/srv/httpd/cgi-bin/cvsweb/cvsweb.cgi' failed
Premature end of script headers: cvsweb.cgi
的错误,则大多数是因为Apache CGI的配置和cgi权限的问题,请重看4.3

 

5.2、安装cvsweb.cgi前,必须已安装File::Temp、IPC::Run、URI等3个perl模块,如果报错:
Can't locate IPC/Run.pm in @INC (@INC contains: /usr/lib/perl5/5.8.8/i486-linux-thread-multi /usr/lib/perl5/5.8.8 /usr/lib/perl5/site_perl/5.8.8/i486-linux-thread-multi /usr/lib/perl5/site_perl/5.8.8 /usr/lib/perl5/site_perl) at /srv/httpd/cgi-bin/cvsweb/cvsweb.cgi line 100.
则说明IPC::Run没有安装,下载个安装则可:
$wget http://search.cpan.org/CPAN/authors/id/R/RS/RSOD/IPC-Run-0.80.tar.gz
$tar zxvf IPC-Run-0.80.tar.gz
$cd IPC-Run-0.80/
#cp -fr lib/IPC/ /usr/lib/perl5/5.8.8/i486-linux-thread-multi/

有其他错误的话,请查看apache的error.log来分析解决:
#vi /var/log/httpd/error.log

]]>
Wed,16 Jan 2008 16:14:00 CST 0
<![CDATA[在MySQL中管理分级数据]]> .html Managing Hierarchical Data in MySQL

Introduction

Most users at one time or another have dealt with hierarchical data in a SQL database and no doubt learned that the management of hierarchical data is not what a relational database is intended for. The tables of a relational database are not hierarchical (like XML), but are simply a flat list. Hierarchical data has a parent-child relationship that is not naturally represented in a relational database table.

For our purposes, hierarchical data is a collection of data where each item has a single parent and zero or more children (with the exception of the root item, which has no parent). Hierarchical data can be found in a variety of database applications, including forum and mailing list threads, business organization charts, content management categories, and product categories. For our purposes we will use the following product category hierarchy from an fictional electronics store:

 

 

These categories form a hierarchy in much the same way as the other examples cited above. In this article we will examine two models for dealing with hierarchical data in MySQL, starting with the traditional adjacency list model.

 

The Adjacency List Model

Typically the example categories shown above will be stored in a table like the following (I'm including full CREATE and INSERT statements so you can follow along):

CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL);
INSERT INTO category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);
SELECT * FROM category ORDER BY category_id;
 ------------- ---------------------- -------- 
| category_id | name                 | parent |
 ------------- ---------------------- -------- 
|           1 | ELECTRONICS          |   NULL |
|           2 | TELEVISIONS          |      1 |
|           3 | TUBE                 |      2 |
|           4 | LCD                  |      2 |
|           5 | PLASMA               |      2 |
|           6 | PORTABLE ELECTRONICS |      1 |
|           7 | MP3 PLAYERS          |      6 |
|           8 | FLASH                |      7 |
|           9 | CD PLAYERS           |      6 |
|          10 | 2 WAY RADIOS         |      6 |
 ------------- ---------------------- -------- 
10 rows in set (0.00 sec)

In the adjacency list model, each item in the table contains a pointer to its parent. The topmost element, in this case electronics, has a NULL value for its parent. The adjacency list model has the advantage of being quite simple, it is easy to see that FLASH is a child of mp3 players, which is a child of portable electronics, which is a child of electronics. While the adjacency list model can be dealt with fairly easily in client-side code, working with the model can be more problematic in pure SQL.

 

Retrieving a Full Tree

The first common task when dealing with hierarchical data is the display of the entire tree, usually with some form of indentation. The most common way of doing this is in pure SQL is through the use of a self-join:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';
 ------------- ---------------------- -------------- ------- 
| lev1        | lev2                 | lev3         | lev4  |
 ------------- ---------------------- -------------- ------- 
| ELECTRONICS | TELEVISIONS          | TUBE         | NULL  |
| ELECTRONICS | TELEVISIONS          | LCD          | NULL  |
| ELECTRONICS | TELEVISIONS          | PLASMA       | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS  | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS   | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL  |
 ------------- ---------------------- -------------- ------- 
6 rows in set (0.00 sec)
 

Finding all the Leaf Nodes

We can find all the leaf nodes in our tree (those with no children) by using a LEFT JOIN query:

SELECT t1.name FROM
category AS t1 LEFT JOIN category as t2
ON t1.category_id = t2.parent
WHERE t2.category_id IS NULL;
 -------------- 
| name         |
 -------------- 
| TUBE         |
| LCD          |
| PLASMA       |
| FLASH        |
| CD PLAYERS   |
| 2 WAY RADIOS |
 -------------- 
 

Retrieving a Single Path

The self-join also allows us to see the full path through our hierarchies:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS' AND t4.name = 'FLASH';
 ------------- ---------------------- ------------- ------- 
| lev1        | lev2                 | lev3        | lev4  |
 ------------- ---------------------- ------------- ------- 
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH |
 ------------- ---------------------- ------------- ------- 
1 row in set (0.01 sec)

The main limitation of such an approach is that you need one self-join for every level in the hierarchy, and performance will naturally degrade with each level added as the joining grows in complexity.

 

Limitations of the Adjacency List Model

Working with the adjacency list model in pure SQL can be difficult at best. Before being able to see the full path of a category we have to know the level at which it resides. In addition, special care must be taken when deleting nodes because of the potential for orphaning an entire sub-tree in the process (delete the portable electronics category and all of its children are orphaned). Some of these limitations can be addressed through the use of client-side code or stored procedures. With a procedural language we can start at the bottom of the tree and iterate upwards to return the full tree or a single path. We can also use procedural programming to delete nodes without orphaning entire sub-trees by promoting one child element and re-ordering the remaining children to point to the new parent.

 

The Nested Set Model

What I would like to focus on in this article is a different approach, commonly referred to as the Nested Set Model. In the Nested Set Model, we can look at our hierarchy in a new way, not as nodes and lines, but as nested containers. Try picturing our electronics categories this way:

 

Notice how our hierarchy is still maintained, as parent categories envelop their children.We represent this form of hierarchy in a table through the use of left and right values to represent the nesting of our nodes:

CREATE TABLE nested_category (
 category_id INT AUTO_INCREMENT PRIMARY KEY,
 name VARCHAR(20) NOT NULL,
 lft INT NOT NULL,
 rgt INT NOT NULL
);
INSERT INTO nested_category
VALUES(1,'ELECTRONICS',1,20),(2,'TELEVISIONS',2,9),(3,'TUBE',3,4),
(4,'LCD',5,6),(5,'PLASMA',7,8),(6,'PORTABLE ELECTRONICS',10,19),
(7,'MP3 PLAYERS',11,14),(8,'FLASH',12,13),
(9,'CD PLAYERS',15,16),(10,'2 WAY RADIOS',17,18);
SELECT * FROM nested_category ORDER BY category_id;
 ------------- ---------------------- ----- ----- 
| category_id | name                 | lft | rgt |
 ------------- ---------------------- ----- ----- 
|           1 | ELECTRONICS          |   1 |  20 |
|           2 | TELEVISIONS          |   2 |   9 |
|           3 | TUBE                 |   3 |   4 |
|           4 | LCD                  |   5 |   6 |
|           5 | PLASMA               |   7 |   8 |
|           6 | PORTABLE ELECTRONICS |  10 |  19 |
|           7 | MP3 PLAYERS          |  11 |  14 |
|           8 | FLASH                |  12 |  13 |
|           9 | CD PLAYERS           |  15 |  16 |
|          10 | 2 WAY RADIOS         |  17 |  18 |
 ------------- ---------------------- ----- ----- 

We use lft and rgt because left and right are reserved words in MySQL, see http://dev.mysql.com/doc/mysql/en/reserved-words.html for the full list of reserved words.

So how do we determine left and right values? We start numbering at the leftmost side of the outer node and continue to the right:

 

 

This design can be applied to a typical tree as well:

 

 

When working with a tree, we work from left to right, one layer at a time, descending to each node's children before assigning a right-hand number and moving on to the right. This approach is called the modified preorder tree traversal algorithm.

 

Retrieving a Full Tree

We can retrieve the full tree through the use of a self-join that links parents with nodes on the basis that a node's lft value will always appear between its parent's lft and rgt values:

SELECT node.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND parent.name = 'ELECTRONICS'
ORDER BY node.lft;
 ---------------------- 
| name                 |
 ---------------------- 
| ELECTRONICS          |
| TELEVISIONS          |
| TUBE                 |
| LCD                  |
| PLASMA               |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS          |
| FLASH                |
| CD PLAYERS           |
| 2 WAY RADIOS         |
 ---------------------- 

Unlike our previous examples with the adjacency list model, this query will work regardless of the depth of the tree. We do not concern ourselves with the rgt value of the node in our BETWEEN clause because the rgt value will always fall within the same parent as the lft values.

 

Finding all the Leaf Nodes

Finding all leaf nodes in the nested set model even simpler than the LEFT JOIN method used in the adjacency list model. If you look at the nested_category table, you may notice that the lft and rgt values for leaf nodes are consecutive numbers. To find the leaf nodes, we look for nodes where rgt = lft 1:

SELECT name
FROM nested_category
WHERE rgt = lft   1;
 -------------- 
| name         |
 -------------- 
| TUBE         |
| LCD          |
| PLASMA       |
| FLASH        |
| CD PLAYERS   |
| 2 WAY RADIOS |
 -------------- 
 

Retrieving a Single Path

With the nested set model, we can retrieve a single path without having multiple self-joins:

SELECT parent.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name = 'FLASH'
ORDER BY parent.lft;
 ---------------------- 
| name                 |
 ---------------------- 
| ELECTRONICS          |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS          |
| FLASH                |
 ---------------------- 
 

Finding the Depth of the Nodes

We have already looked at how to show the entire tree, but what if we want to also show the depth of each node in the tree, to better identify how each node fits in the hierarchy? This can be done by adding a COUNT function and a GROUP BY clause to our existing query for showing the entire tree:

SELECT node.name, (COUNT(parent.name) - 1) AS depth
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
 ---------------------- ------- 
| name                 | depth |
 ---------------------- ------- 
| ELECTRONICS          |     0 |
| TELEVISIONS          |     1 |
| TUBE                 |     2 |
| LCD                  |     2 |
| PLASMA               |     2 |
| PORTABLE ELECTRONICS |     1 |
| MP3 PLAYERS          |     2 |
| FLASH                |     3 |
| CD PLAYERS           |     2 |
| 2 WAY RADIOS         |     2 |
 ---------------------- ------- 

We can use the depth value to indent our category names with the CONCAT and REPEAT string functions:

SELECT CONCAT( REPEAT(' ', COUNT(parent.name) - 1), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
 ----------------------- 
| name                  |
 ----------------------- 
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  PORTABLE ELECTRONICS |
|   MP3 PLAYERS         |
|    FLASH              |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
 ----------------------- 

Of course, in a client-side application you will be more likely to use the depth value directly to display your hierarchy. Web developers could loop through the tree, adding <li></li> and <ul></ul> tags as the depth number increases and decreases.

 

Depth of a Sub-Tree

When we need depth information for a sub-tree, we cannot limit either the node or parent tables in our self-join because it will corrupt our results. Instead, we add a third self-join, along with a sub-query to determine the depth that will be the new starting point for our sub-tree:

SELECT node.name, (COUNT(parent.name) - (sub_tree.depth   1)) AS depth
FROM nested_category AS node,
	nested_category AS parent,
	nested_category AS sub_parent,
	(
		SELECT node.name, (COUNT(parent.name) - 1) AS depth
		FROM nested_category AS node,
		nested_category AS parent
		WHERE node.lft BETWEEN parent.lft AND parent.rgt
		AND node.name = 'PORTABLE ELECTRONICS'
		GROUP BY node.name
		ORDER BY node.lft
	)AS sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
	AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
	AND sub_parent.name = sub_tree.name
GROUP BY node.name
ORDER BY node.lft;
 ---------------------- ------- 
| name                 | depth |
 ---------------------- ------- 
| PORTABLE ELECTRONICS |     0 |
| MP3 PLAYERS          |     1 |
| FLASH                |     2 |
| CD PLAYERS           |     1 |
| 2 WAY RADIOS         |     1 |
 ---------------------- ------- 

This function can be used with any node name, including the root node. The depth values are always relative to the named node.

 

Find the Immediate Subordinates of a Node

Imagine you are showing a category of electronics products on a retailer web site. When a user clicks on a category, you would want to show the products of that category, as well as list its immediate sub-categories, but not the entire tree of categories beneath it. For this, we need to show the node and its immediate sub-nodes, but no further down the tree. For example, when showing the PORTABLE ELECTRONICS category, we will want to show MP3 PLAYERS, CD PLAYERS, and 2 WAY RADIOS, but not FLASH.

This can be easily accomplished by adding a HAVING clause to our previous query:

SELECT node.name, (COUNT(parent.name) - (sub_tree.depth   1)) AS depth
FROM nested_category AS node,
	nested_category AS parent,
	nested_category AS sub_parent,
	(
		SELECT node.name, (COUNT(parent.name) - 1) AS depth
		FROM nested_category AS node,
		nested_category AS parent
		WHERE node.lft BETWEEN parent.lft AND parent.rgt
		AND node.name = 'PORTABLE ELECTRONICS'
		GROUP BY node.name
		ORDER BY node.lft
	)AS sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
	AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
	AND sub_parent.name = sub_tree.name
GROUP BY node.name
HAVING depth <= 1
ORDER BY node.lft;
 ---------------------- ------- 
| name                 | depth |
 ---------------------- ------- 
| PORTABLE ELECTRONICS |     0 |
| MP3 PLAYERS          |     1 |
| CD PLAYERS           |     1 |
| 2 WAY RADIOS         |     1 |
 ---------------------- ------- 

If you do not wish to show the parent node, change the HAVING depth <= 1 line to HAVING depth = 1.

 

Aggregate Functions in a Nested Set

Let's add a table of products that we can use to demonstrate aggregate functions with:

CREATE TABLE product(
product_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(40),
category_id INT NOT NULL
);
INSERT INTO product(name, category_id) VALUES('20" TV',3),('36" TV',3),
('Super-LCD 42"',4),('Ultra-Plasma 62"',5),('Value Plasma 38"',5),
('Power-MP3 5gb',7),('Super-Player 1gb',8),('Porta CD',9),('CD To go!',9),
('Family Talk 360',10);
SELECT * FROM product;
 ------------ ------------------- ------------- 
| product_id | name              | category_id |
 ------------ ------------------- ------------- 
|          1 | 20" TV            |           3 |
|          2 | 36" TV            |           3 |
|          3 | Super-LCD 42"     |           4 |
|          4 | Ultra-Plasma 62"  |           5 |
|          5 | Value Plasma 38"  |           5 |
|          6 | Power-MP3 128mb   |           7 |
|          7 | Super-Shuffle 1gb |           8 |
|          8 | Porta CD          |           9 |
|          9 | CD To go!         |           9 |
|         10 | Family Talk 360   |          10 |
 ------------ ------------------- ------------- 

Now let's produce a query that can retrieve our category tree, along with a product count for each category:

SELECT parent.name, COUNT(product.name)
FROM nested_category AS node ,
nested_category AS parent,
product
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.category_id = product.category_id
GROUP BY parent.name
ORDER BY node.lft;
 ---------------------- --------------------- 
| name                 | COUNT(product.name) |
 ---------------------- --------------------- 
| ELECTRONICS          |                  10 |
| TELEVISIONS          |                   5 |
| TUBE                 |                   2 |
| LCD                  |                   1 |
| PLASMA               |                   2 |
| PORTABLE ELECTRONICS |                   5 |
| MP3 PLAYERS          |                   2 |
| FLASH                |                   1 |
| CD PLAYERS           |                   2 |
| 2 WAY RADIOS         |                   1 |
 ---------------------- --------------------- 

This is our typical whole tree query with a COUNT and GROUP BY added, along with a reference to the product table and a join between the node and product table in the WHERE clause. As you can see, there is a count for each category and the count of subcategories is reflected in the parent categories.

 

Adding New Nodes

Now that we have learned how to query our tree, we should take a look at how to update our tree by adding a new node. Let's look at our nested set diagram again:

 

 

If we wanted to add a new node between the TELEVISIONS and PORTABLE ELECTRONICS nodes, the new node would have lft and rgt values of 10 and 11, and all nodes to its right would have their lft and rgt values increased by two. We would then add the new node with the appropriate lft and rgt values. While this can be done with a stored procedure in MySQL 5, I will assume for the moment that most readers are using 4.1, as it is the latest stable version, and I will isolate my queries with a LOCK TABLES statement instead:

LOCK TABLE nested_category WRITE;
SELECT @myRight := rgt FROM nested_category
WHERE name = 'TELEVISIONS';
UPDATE nested_category SET rgt = rgt   2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft   2 WHERE lft > @myRight;
INSERT INTO nested_category(name, lft, rgt) VALUES('GAME CONSOLES', @myRight   1, @myRight   2);
UNLOCK TABLES;
We can then check our nesting with our indented tree query:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
 ----------------------- 
| name                  |
 ----------------------- 
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  GAME CONSOLES        |
|  PORTABLE ELECTRONICS |
|   MP3 PLAYERS         |
|    FLASH              |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
 ----------------------- 

If we instead want to add a node as a child of a node that has no existing children, we need to modify our procedure slightly. Let's add a new FRS node below the 2 WAY RADIOS node:

LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft FROM nested_category
WHERE name = '2 WAY RADIOS';
UPDATE nested_category SET rgt = rgt   2 WHERE rgt > @myLeft;
UPDATE nested_category SET lft = lft   2 WHERE lft > @myLeft;
INSERT INTO nested_category(name, lft, rgt) VALUES('FRS', @myLeft   1, @myLeft   2);
UNLOCK TABLES;

In this example we expand everything to the right of the left-hand number of our proud new parent node, then place the node to the right of the left-hand value. As you can see, our new node is now properly nested:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
 ----------------------- 
| name                  |
 ----------------------- 
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  GAME CONSOLES        |
|  PORTABLE ELECTRONICS |
|   MP3 PLAYERS         |
|    FLASH              |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
|    FRS                |
 ----------------------- 
 

Deleting Nodes

The last basic task involved in working with nested sets is the removal of nodes. The course of action you take when deleting a node depends on the node's position in the hierarchy; deleting leaf nodes is easier than deleting nodes with children because we have to handle the orphaned nodes.

When deleting a leaf node, the process if just the opposite of adding a new node, we delete the node and its width from every node to its right:

LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft   1
FROM nested_category
WHERE name = 'GAME CONSOLES';
DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
UNLOCK TABLES;

And once again, we execute our indented tree query to confirm that our node has been deleted without corrupting the hierarchy:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
 ----------------------- 
| name                  |
 ----------------------- 
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  PORTABLE ELECTRONICS |
|   MP3 PLAYERS         |
|    FLASH              |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
|    FRS                |
 ----------------------- 

This approach works equally well to delete a node and all its children:

LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft   1
FROM nested_category
WHERE name = 'MP3 PLAYERS';
DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
UNLOCK TABLES;

And once again, we query to see that we have successfully deleted an entire sub-tree:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
 ----------------------- 
| name                  |
 ----------------------- 
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  PORTABLE ELECTRONICS |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
|    FRS                |
 ----------------------- 

The other scenario we have to deal with is the deletion of a parent node but not the children. In some cases you may wish to just change the name to a placeholder until a replacement is presented, such as when a supervisor is fired. In other cases, the child nodes should all be moved up to the level of the deleted parent:

LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft   1
FROM nested_category
WHERE name = 'PORTABLE ELECTRONICS';
DELETE FROM nested_category WHERE lft = @myLeft;
UPDATE nested_category SET rgt = rgt - 1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - 2 WHERE lft > @myRight;
UNLOCK TABLES;

In this case we subtract two from all elements to the right of the node (since without children it would have a width of two), and one from the nodes that are its children (to close the gap created by the loss of the parent's left value). Once again, we can confirm our elements have been promoted:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
 --------------- 
| name          |
 --------------- 
| ELECTRONICS   |
|  TELEVISIONS  |
|   TUBE        |
|   LCD         |
|   PLASMA      |
|  CD PLAYERS   |
|  2 WAY RADIOS |
|   FRS         |
 --------------- 

Other scenarios when deleting nodes would include promoting one of the children to the parent position and moving the child nodes under a sibling of the parent node, but for the sake of space these scenarios will not be covered in this article.

 

Final Thoughts

While I hope the information within this article will be of use to you, the concept of nested sets in SQL has been around for over a decade, and there is a lot of additional information available in books and on the Internet. In my opinion the most comprehensive source of information on managing hierarchical information is a book called Joe Celko's Trees and Hierarchies in SQL for Smarties, written by a very respected author in the field of advanced SQL, Joe Celko. Joe Celko is often credited with the nested sets model and is by far the most prolific author on the subject. I have found Celko's book to be an invaluable resource in my own studies and highly recommend it. The book covers advanced topics which I have not covered in this article, and provides additional methods for managing hierarchical data in addition to the Adjacency List and Nested Set models.

In the References / Resources section that follows I have listed some web resources that may be of use in your research of managing hierarchal data, including a pair of PHP related resources that include pre-built PHP libraries for handling nested sets in MySQL. Those of you who currently use the adjacency list model and would like to experiment with the nested set model will find sample code for converting between the two in the Storing Hierarchical Data in a Database resource listed below.

 

 

]]>
Fri,11 Jan 2008 00:12:03 CST 0
<![CDATA[对关系型数据库设计范式的理解]]> .html   所谓范式,是关系型数据库关系模式规范化的标准,从规范化的宽松到严格,分别

为不同的范式,通常使用的有第一范式、第二范式、第三范式及BC范式等。范式是建立

在函数依赖基础上的。

 

函数依赖

 

定义:设有关系模式R(U),X和Y是属性集U的子集,函数依赖是形为X→Y的一个命题,

对任意R中两个元组t和s,都有t[X]=s[X]蕴涵t[Y]=s[Y],那么FD X→Y在关系模式R(U)中成

立。X→Y读作‘X函数决定Y’,或‘Y函数依赖于X’。

  通俗的讲,如果一个表中某一个字段Y的值是由另外一个字段或一组字段X的值来确

定的,就称为Y函数依赖于X。

  函数依赖应该是通过理解数据项和企业的规则来决定的,根据表的内容得出的函数

依赖可能是不正确的。

 

第一范式(1NF)

 

定义:果关系模式R的每个关系r的属性都是不可分的数据项,那么就称R是第一范式的模

式。
  简单的说,每一个属性都是原子项,不可分割。
  1NF是关系模式应具备的最起码的条件,如果数据库设计不能满足第一范式,就不称

为关系型数据库。关系数据库设计研究的关系规范化是在1NF之上进行的。

 

第二范式(2NF)

 

定义:如果关系模式R是1NF,且每个非主属性完全函数依赖于候选键,那么就称R是第

二范式。
简单的说,第二范式要满足以下的条件:首先要满足第一范式,其次每个非主属性要完

全函数依赖与候选键,或者是主键。也就是说,每个非主属性是由整个主键函数决定的

,而不能由主键的一部分来决定。
举个例子:
  有股票日行情表的主键是股票代码和交易日期组成。非主属性中有收盘价和成交量

等,都是由主键,即股票代码和交易日期函数决定的,单独的股票代码或者交易日期都

不能函数决定这些非主属性。如果这个表中有非主属性股票简称,则股票简称是可以由

股票代码来函数决定的,这样股票简称这个非主属性就不是完全函数依赖于候选键,这

样的设计就不满足第二范式。

 

第三范式(3NF)

 

定义:如果关系模式R是2NF,且关系模式R(U,F)中的所有非主属性对任何候选关键

字都不存在传递依赖,则称关系R是属于第三范式。
简单的说,第三范式要满足以下的条件:首先要满足第二范式,其次非主属性之间不存

在函数依赖。由于满足了第二范式,表示每个非主属性都函数依赖于主键。如果非主属

性之间存在了函数依赖,就会存在传递依赖,这样就不满足第三范式。
举个例子:
在股票基本情况表中,主键是股票代码,有非主属性所属一级行业和所属二级行业。根

据业务规则,所属二级行业能够函数决定所属一级行业,这就表示存在这样一种关系:

股票代码函数决定所属二级行业,所属二级行业函数决定所属一级行业,这就形成了传

递依赖,这样的设计就不符合第三范式。
不过在实际运用中,为查询和使用的方便,有时也会违反第三范式。如上例,如果没有

所属一级行业的属性,需要查询所属一级行业的相关股票,需要查询时使用函数来从二

级行业中函数生成所属一级行业,使用性能上会受影响。所以通常会加上所属一级行业

的属性。

 

BC范式(BCNF)

 

BC范式是第三范式的增强版,不过也有人说是直接从1NF发展过来的,即每个属性,包

括主属性或非主属性,都完全依赖于候选键,并且不存在传递依赖情况。

 

]]>
Wed,02 Jan 2008 15:22:27 CST 0
<![CDATA[网站架构概述]]> .html 一:硬架构

 

1:机房的选择:

 

    在选择机房的时候,根据网站用户的地域分布,可以选择网通或电信机房,但更多时候,可能双线机房才是合适的。越大的城市,机房价格越贵,从成本的角 度看可以在一些中小城市托管服务器,比如说北京的公司可以考虑把服务器托管在天津,廊坊等地,不是特别远,但是价格会便宜很多。

 

2:带宽的大小:

 

    通常老板花钱请我们架构网站的时候,会给我们提出一些目标,诸如网站每天要能承受100万PV的访问量等等。这时我们要预算一下大概需要多大的带宽,计算带宽大小主要涉及两个指标(峰值流量和页面大小),我们不妨在计算前先做出必要的假设:

第一:假设峰值流量是平均流量的5倍。
第二:假设每次访问平均的页面大小是100K字节左右。

    如果100万PV的访问量在一天内平均分布的话,折合到每秒大约12次访问,如果按平均每次访问页面的大小是100K字节左右计算的话,这12次访 问总计大约就是1200K字节,字节的单位是Byte,而带宽的单位是bit,它们之间的关系是1Byte = 8bit,所以1200K Byte大致就相当于9600K bit,也就是9Mbps的样子,实际情况中,我们的网站必须能在峰值流量时保持正常访问,所以按照假设的峰值流量算,真实带宽的需求应该在45Mbps 左右。

    当然,这个结论是建立在前面提到的两点假设的基础上,如果你的实际情况和这两点假设有出入,那么结果也会有差别。

 

3:服务器的划分:

 

    先看我们都需要哪些服务器:图片服务器,页面服务器,数据库服务器,应用服务器,日志服务器等等。

对于访问量大点的网站而言,分离单独的图片服务器和页面服务器相当必要,我们可以用lighttpd来跑图片服务器,用apache来跑页面服务 器,当然也可以选择别的,甚至,我们可以扩展成很多台图片服务器和很多台页面服务器,并设置相关域名,如img.domain.com和 www.domain.com,页面里的图片路径都使用绝对路径,如<img src="http://img.domain.com/abc.gif" />,然后设置DNS轮循,达到最初级的负载均衡。当然,服务器多了就不可避免的涉及一个同步的问题,这个可以使用rsync软件来搞定。

    数据库服务器是重中之重,因为网站的瓶颈问题十有八九是出在数据库身上。现在一般的中小网站多使用MySQL数据库,不过它的集群功能似乎还没有达 到stable的阶段,所以这里不做评价。一般而言,使用MySQL数据库的时候,我们应该搞一个主从(一主多从)结构,主数据库服务器使用innodb 表结构,从数据服务器使用myisam表结构,充分发挥它们各自的优势,而且这样的主从结构分离了读写操作,降低了读操作的压力,甚至我们还可以设定一个 专门的从服务器做备份服务器,方便备份。不然如果你只有一台主服务器,在大数据量的情况下,mysqldump基本就没戏了,直接拷贝数据文件的话,还得 先停止数据库服务再拷贝,否则备份文件会出错。但对于很多网站而言,即使数据库服务仅停止了一秒也是不可接受的。如果你有了一台从数据库服务器,在备份数 据的时候,可以先停止服务(slave stop)再备份,再启动服务(slave start)后从服务器会自动从主服务器同步数据,一切都没有影响。但是主从结构也是有致命缺点的,那就是主从结构只是降低了读操作的压力,却不能降低写 操作的压力。为了适应更大的规模,可能只剩下最后这招了:横向/纵向分割数据库。所谓横向分割数据库,就是把不同的表保存到不同的数据库服务器上,比如说 用户表保存在A数据库服务器上,文章表保存在B数据库服务器上,当然这样的分割是有代价的,最基本的就是你没法进行LEFT JOIN之类的操作了。所谓纵向分割数据库,一般是指按照用户标识(user_id)等来划分数据存储的服务器,比如说:我们有5台数据库服务器,那么 “user_id % 5 + 1”等于1的就保存到1号服务器,等于2的就保存到2好服务器,以此类推,纵向分隔的原则有很多种,可以视情况选择。不过和横向分割数据库一样,纵向分割 数据库也是有代价的,最基本的就是我们在进行如COUNT, SUM等汇总操作的时候会麻烦很多。综上所述,数据库服务器的解决方案一般视情况往往是一个混合的方案,以其发挥各种方案的优势,有时候还需要借助 memcached之类的第三方软件,以便适应更大访问量的要求。

    如果有专门的应用服务器来跑PHP脚本是最合适不过的了,那样我们的页面服务器只保存静态页面就可以了,可以给应用服务器设置一些诸如 app.domain.com之类的域名来和页面服务器加以区别。对于应用服务器,我还是更倾向于使用prefork模式的apache,配上必要的 xcache之类的PHP缓存软件,加载模块要越少越好,除了mod_rewrite等必要的模块,不必要的东西统统舍弃,尽量减少httpd进程的内存 消耗,而那些图片服务器,页面服务器等静态内容就可以使用lighttpd或者tux来搞,充分发挥各种服务器的特点。

    如果条件允许,独立的日志服务器也是必要的,一般小网站的做法都是把页面服务器和日志服务器合二为一了,在凌晨访问量不大的时候cron运行前一天 的日志计算,不过如果你使用awstats之类的日志分析软件,对于百万级访问量而言,即使按天归档,也会消耗很多时间和服务器资源去计算,所以分离单独 的日志服务器还是有好处的,这样不会影响正式服务器的工作状态。

 

二:软架构

 

1:框架的选择:

 

    现在的PHP框架有很多选择,比如:CakePHP,Symfony,Zend Framework等等,至于应该使用哪一个并没有唯一的答案,要根据Team里团队成员对各个框架的了解程度而定。很多时候,即使没有使用框架,一样能 写出好的程序来,比如Flickr据说就是用Pear+Smarty这样的类库写出来的,所以,是否用框架,用什么框架,一般不是最重要的,重要的是我们 的编程思想里要有框架的意识。

 

2:逻辑的分层:

 

    网站规模到了一定的程度之后,代码里各种逻辑纠缠在一起,会给维护和扩展带来巨大的障碍,这时我们的解决方式其实很简单,那就是重构,将逻辑进行分层。通常,自上而下可以分为表现层,应用层,领域层,持久层。

    所谓表现层,并不仅仅就指模板,它的范围要更广一些,所有和表现相关的逻辑都应该被纳入表现层的范畴。比如说某处的字体要显示为红色,某处的开头要 空两格,这些都属于表现层。很多时候,我们容易犯的错误就是把本属于表现层的逻辑放到了其他层面去完成,这里说一个很常见的例子:我们在列表页显示文章标 题的时候,都会设定一个最大字数,一旦标题长度超过了这个限制,就截断,并在后面显示“..”,这就是最典型的表现层逻辑,但是实际情况,有很多程序员都 是在非表现层代码里完成数据的获取和截断,然后赋值给表现层模板,这样的代码最直接的缺点就是同样一段数据,在这个页面我可能想显示前10个字,再另一个 页面我可能想显示前15个字,而一旦我们在程序里固化了这个字数,也就丧失了可移植性。正确的做法是应该做一个视图助手之类的程序来专门处理此类逻辑,比 如说:Smarty里的truncate就属于这样的视图助手(不过它那个实现不适合中文)。

    所谓应用层,它的主要作用是定义用户可以做什么,并把操作结果反馈给表现层。至于如何做,通常不是它的职责范围(而是领域层的职责范围),它会通过 委派把如何做的工作交给领域层去处理。在使用MVC架构的网站中,我们可以看到类似下面这样的URL: domain.com/articles/view/123,其内部编码实现,一般就是一个Articles控制器类,里面有一个view方法,这就是一 个典型的应用层操作,因为它定义了用户可以做一个查看的动作。在MVC架构中,有一个准则是这么说的:Rich Model Is Good。言外之意,就是Controller要保持“瘦”一些比较好,进而说明应用层要尽量简单,不要包括涉及领域内容的逻辑。

    所谓领域层,最直接的解释就是包含领域逻辑的层。它是一个软件的灵魂所在。先来看看什么叫领域逻辑,简单的说,具有明确的领域概念的逻辑就是领域逻 辑,比如我们在ATM机上取钱,过程大致是这样的:插入银联卡,输入密码,输入取款金额,确定,拿钱,然后ATM吐出一个交易凭条。在这个过程中,银联卡 在ATM机器里完成钱从帐户上划拨的过程就是一个领域逻辑,因为取钱在银行中是一个明确的领域概念,而ATM机吐出一个交易凭条则不是领域逻辑,而仅是一 个应用逻辑,因为吐出交易凭条并不是银行中一个明确的领域概念,只是一种技术手段,对应的,我们取钱后不吐交易凭条,而发送一条提醒短信也是可能的,但并 不是一定如此,如果在实际情况中,我们要求取款后必须吐出交易凭条,也就是说吐出交易凭条已经和取款紧密结合,那么你也可以把吐出交易凭条看作是领域逻辑 的一部分,一切都以问题的具体情况而定。在Eric那本经典的领域驱动设计中,把领域层分为了五种基本元素:实体,值对象,服务,工厂,仓储。具体可以参 阅书中的介绍。领域层最常犯的错误就是把本应属于领域层的逻辑泄露到了其他层次,比如说在一个CMS系统,对热门文章的定义是这样的:每天被浏览的次数多 于1000次,被评论的次数多于100次,这样的文章就是热门文章。对于一个CMS来说,热门文章这个词无疑是一个重要的领域概念,那么我们如何实现这个 逻辑的设计的?你可能会给出类似下面的代码:“SELECT ... FROM ... WHERE 浏览 > 1000 AND 评论 > 100”,没错,这是最简单的实现方式,但是这里需要注意的是“每天被浏览的次数多于1000次,被评论的次数多于100次”这个重要的领域逻辑被隐藏到 了SQL语句中,SQL语句显然不属于领域层的范畴,也就是说,我们的领域逻辑泄露了。

    所谓持久层,就是指把我们的领域模型保存到数据库中。因为我们的程序代码是面向对象风格的,而数据库一般是关系型的数据库,所以我们需要把领域模型 碾平,才能保存到数据库中,但是在PHP里,直到目前还没有非常好的ORM出现,所以这方面的解决方案不是特别多,参考Martin的企业应用架构模式一 书,大致可以使用的方法有行数据入口(Row Data Gateway)或者表数据入口(Table Data Gateway),或者把领域层和持久层合二为一变成活动记录(Active Record)的方式。

 

转自:http://www.chukun.com/show-185-1.html

]]>
Mon,24 Dec 2007 12:04:20 CST 0
<![CDATA[c与c static函数的区别]]> .html static关键字是C, C++中都存在的关键字, 它主要有三种使用方式, 其中前两种只指在C语言中使用, 第三种在C++中使用(C,C++中具体细微操作不尽相同, 本文以C++为准).
(1)局部静态变量
(2)外部静态变量/函数
(3)静态数据成员/成员函数

 

下面就这三种使用方式及注意事项分别说明

 

一、局部静态变量

 

在C/C++中, 局部变量按照存储形式可分为三种auto, static, register
(<C语言程序设计(第二版)>谭浩强, 第174-175页)
与auto类型(普通)局部变量相比, static局部变量有三点不同


1. 存储空间分配不同
auto类型分配在栈上, 属于动态存储类别, 占动态存储区空间, 函数调用结束后自动释放, 而static分配在静态存储区, 在程序整个运行期间都不释放. 两者之间的作用域相同, 但生存期不同.


2. static局部变量在所处模块在初次运行时进行初始化工作, 且只操作一次


3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符, 而auto类型的初值是不确定的. (对于C++中的class对象例外, class的对象实例如果不初始化, 则会自动调用默认构造函数, 不管是否是static类型)
特点: static局部变量的”记忆性”与生存期的”全局性”
所谓”记忆性”是指在两次函数调用时, 在第二次调用进入时, 能保持第一次调用退出时的值.

 

示例程序一
#include <iostream>
using namespace std;
void staticLocalVar()
{
static int a = 0; // 运行期时初始化一次, 下次再调用时, 不进行初始化工作
cout<<"a="<<a<<endl;
++a;
}
int main()
{
staticLocalVar(); // 第一次调用, 输出a=0
staticLocalVar(); // 第二次调用, 记忆了第一次退出时的值, 输出a=1
return 0;
}

应用:
利用”记忆性”, 记录函数调用的次数(示例程序一)
利用生存期的”全局性”, 改善”return a pointer / reference to a local object”的问题. Local object的问题在于退出函数, 生存期即结束,. 利用static的作用, 延长变量的生存期.

 

示例程序二:
// IP address to string format
// Used in Ethernet Frame and IP Header analysis
const char * IpToStr(UINT32 IpAddr)
{
static char strBuff[16]; // static局部变量, 用于返回地址有效
const unsigned char *pChIP = (const unsigned char *)&IpAddr;
sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
return strBuff;
}

注意事项:
1. “记忆性”, 程序运行很重要的一点就是可重复性, 而static变量的”记忆性”破坏了这种可重复性, 造成不同时刻至运行的结果可能不同.
2. “生存期”全局性和唯一性. 普通的local变量的存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样, 而static具有全局唯一性的特点, 每次调用时, 都指向同一块内存, 这就造成一个很重要的问题 ----不可重入性!!!
这样在多线程程序设计或递归程序设计中, 要特别注意这个问题.
(不可重入性的例子可以参见<effective C++ (2nd)>(影印版)第103-105页)
下面针对示例程序二, 分析在多线程情况下的不安全性.(为方便描述, 标上行号)
① const char * IpToStr(UINT32 IpAddr)
② {
③ static char strBuff[16]; // static局部变量, 用于返回地址有效
④ const unsigned char *pChIP = (const unsigned char *)&IpAddr;
⑤ sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
⑥ return strBuff;
⑦ }
假设现在有两个线程A,B运行期间都需要调用IpToStr()函数, 将32位的IP地址转换成点分10进制的字符串形式. 现A先获得执行机会, 执行IpToStr(), 传入的参数是0x0B090A0A, 顺序执行完应该返回的指针存储区内容是:”10.10.9.11”, 现执行到⑥时, 失去执行权, 调度到B线程执行, B线程传入的参数是0xA8A8A8C0, 执行至⑦, 静态存储区的内容是192.168.168.168. 当再调度到A执行时, 从⑥继续执行, 由于strBuff的全局唯一性, 内容已经被B线程冲掉, 此时返回的将是192.168.168.168字符串, 不再是10.10.9.11字符串.

 

二、外部静态变量/函数

 

在C中 static有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

 

示例程序三:

//file1.cpp

static int varA;
int varB;
extern void funA()
{
……
}

static void funB()
{
……
}

//file2.cpp

extern int varB; // 使用file1.cpp中定义的全局变量
extern int varA; // 错误! varA是static类型, 无法在其他文件中使用
extern vod funA(); // 使用file1.cpp中定义的函数
extern void funB(); // 错误! 无法使用file1.cpp文件中static函数

 

三、静态数据成员/成员函数(C++特有)

 

C+ +重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数. 这是与普通成员函数的最大区别, 也是其应用所在, 比如在对某一个类的对象进行计数时, 计数生成多少个类的实例, 就可以用到静态数据成员. 在这里面, static既不是限定作用域的, 也不是扩展生存期的作用, 而是指示变量/函数在此类中的唯一性. 这也是”属于一个类而不是属于此类的任何特定对象的变量和函数”的含义. 因为它是对整个类来说是唯一的, 因此不可能属于某一个实例对象的. (针对静态数据成员而言, 成员函数不管是否是static, 在内存中只有一个副本, 普通成员函数调用时, 需要传入this指针, static成员函数调用时, 没有this指针. )

 

请看示例程序四(<effective c++ (2nd)>(影印版)第59页)
class EnemyTarget {
public:
EnemyTarget() { ++numTargets; }
EnemyTarget(const EnemyTarget&) { ++numTargets; }
~EnemyTarget() { --numTargets; }
static size_t numberOfTargets() { return numTargets; }
bool destroy(); // returns success of attempt to destroy EnemyTarget object
private:
static size_t numTargets; // object counter
};
// class statics must be defined outside the class;
// initialization is to 0 by default
size_t EnemyTarget::numTargets;

在这个例子中, 静态数据成员numTargets就是用来计数产生的对象个数的.


另外, 在设计类的多线程操作时, 由于POSIX库下的线程函数pthread_create()要求是全局的, 普通成员函数无法直接做为线程函数, 可以考虑用Static成员函数做线程函数.

 

转自: http://blog.csdn.net/lipps/archive/2007/05/18/1615419.aspx

]]>
Tue,04 Dec 2007 19:33:26 CST 0
<![CDATA[Linux管理、使用点滴]]> .html 1、添加用户并决定是否将之加入root用户组

   在一般情况下,一般用户通过执行“su -”命令、输入正确的root密码,可以登录为root用户来对系统进行管理员级别的配置。但是,为了更进一步加强系统的安全性,有必要建立一个管理员的 组,只允许这个组的用户来执行“su -”命令登录为root用户,而让其他组的用户即使执行“su -”、输入了正确的root密码,也无法登录为root用户。在UNIX下,这个组的名称通常为“wheel”。

[root@sample ~]# useradd test           ← 新增用户test

[root@sample ~]# usermod -G wheel test  ← 将一般用户 test 加在管理员组wheel组中

[root@sample ~]# vi /etc/pam.d/su       ← 打开这个配置文件

 

#auth required /lib/security/$ISA/pam_wheel.so use_uid       ← 找到此行,去掉行首的“#”
auth required /lib/security/$ISA/pam_wheel.so use_uid        ← 变为此状态(大约在第6行的位置)

 

[root@sample ~]# echo "SU_WHEEL_ONLY yes" >> /etc/login.defs ← 添加语句到行末


  以上操作完成后,可以再建立一个新用户,然后用这个新建的用户测试会发现,没有加入到wheel组的用户,执行“su -”命令,即使输入了正确的root密码,也无法登录为root用户。

]]>
Tue,30 Oct 2007 20:28:41 CST 0
<![CDATA[Bourne Shell及shell编程]]> .html 介绍:Bourne Shell 基础及其他很多有用的特性,shell编程及组织。 

主要内容: 
.shell基础 基本介绍,环境,选项,特殊字符 
.shell变量 用户定义变量,环境变量,位置变量(shell 参数) 
.shell script编程 
条件测试,循环及重复控制 
.shell定制 

1.shell基础知识 
作者:Stephen Bourne 在Bell实验室开发 
建议:man sh 查看相关UNIX上的改进或特性 

(1)shell提示符及其环境 
/etc/passwd文件 
提示符:$ 
/etc/profile $HOME/.profile 
(2)shell执行选项 
-n 测试shell script语法结构,只读取shell script但不执行 
-x 进入跟踪方式,显示所执行的每一条命令,用于调度 
-a Tag all variables for export 
-c "string" 从strings中读取命令 
-e 非交互方式 
-f 关闭shell文件名产生功能 
-h locate and remember functions as defind 
-i 交互方式 
-k 从环境变量中读取命令的参数 
-r 限制方式 
-s 从标准输入读取命令 
-t 执行命令后退出(shell exits) 
-u 在替换中如使用未定义变量为错误 
-v verbose,显示shell输入行 

这些选项可以联合使用,但有些显然相互冲突,如-e和-i. 

(3)受限制shell(Restircted Shell) 
sh -r 或 /bin/rsh 

不能执行如下操作:cd, 更改PATH,指定全路径名,输出重定向,因此可以提供一个较 
好的控制和安全机制。通常rsh用于应用型用户及拨号用户,这些用户通常是看不到提 
示符的。通常受限制用户的主目录是不可写的。 

不足:如果用户可以调用sh,则rsh的限制将不在起作用,事实上如果用户在vi及more 
程序中调用shell,而这时rsh的限制将不再起作用。 

(4)用set改变 shell选项 
用户可以在$提示符下用set命令来设置或取消shell的选项。使用-设置选项,+取消相应 
选项,大多数UNIX系统允许a,e,f,h,k,n,u,v和x的开关设置/取消。 

set -xv 
启动跟踪方式;显示所有的命令及替换,同样显示输入。 
set -tu 
关闭在替换时对未定义变量的检查。 

使用echo $-显示所有已设置的shell选项。 

(5)用户启动文件 .profile 
PATH=$PATH:/usr/loacl/bin; export PATH 

(6)shell环境变量 
CDPATH      用于cd命令的查找路径 
HOME        /etc/passwd文件中列出的用户主目录 
IFS         Internal Field Separator,默认为空格,tab及换行符 
MAIL        /var/mail/$USERNAME mail等程序使用 
PATH 
PS1,PS2    默认提示符($)及换行提示符(>) 
TERM        终端类型,常用的有vt100,ansi,vt200,xterm等 

示例:$PS1="test:";export PS1 
test: PS1="\$";export PS1 
$echo $MAIL 
/var/mail/username 
(7)保留字符及其含义 
$ shell变量名的开始,如$var 
| 管道,将标准输出转到下一个命令的标准输入 
# 注释开始 
& 在后台执行一个进程 
? 匹配一个字符 
* 匹配0到多个字符(与DOS不同,可在文件名中间使用,并且含.) 
$- 使用set及执行时传递给shell的标志位 
$! 最后一个子进程的进程号 
$# 传递给shell script的参数个数 
$* 传递给shell script的参数 
$@ 所有参数,个别的用双引号括起来 
$? 上一个命令的返回代码  (返回 0 则表示成立;返回 1 ,则表示不成立)
$0 当前shell的名字 
$n (n:1-) 位置参数 
$$ 进程标识号(Process Identifier Number, PID) 
>file 输出重定向 
`command` 命令替换,如 filename=`basename /usr/local/bin/tcsh` 
>>fiile 输出重定向,append 

转义符及单引号: 
$echo "$HOME $PATH" 
/home/hbwork /opt/kde/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin: 
$echo '$HOME $PATH' 
$HOME $PATH 

$echo \$HOME $PATH 
$HOME /opt/kde/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/hbwork/bin 

其他: 
$dir=ls 
$$dir 
$alias dir ls 
$dir 

ls > filelist 
ls >> filelist 
wc -l < filelist 
wc -l filelist 
sleep 5; echo 5 seconds reaches; ls -l 
ps ax |egrep inetd 
find / -name core -exec rm {} \; & 
filename=`date "+%Y%m%d"`.log 

2. shell变量 
变量:代表某些值的符号,如$HOME,cd命令查找$HOME,在计算机语言中可以使用变量可以 
进行多种运算和控制。 

Bourne Shell有如下四种变量: 
.用户自定义变量 
.位置变量即 shell script之参数 
.预定义变量(特殊变量) 
.环境变量(参考shell定制部分) 
(1)用户自定义变量(数据的存储) 
$ COUNT=1 
$ NAME="He Binwu" 

技巧:因为大部分UNIX命令使用小写字符,因此在shell编程中通常使用全大写变量, 
当然这并不是强制性的,但使用大写字符可以在编程中方便地识别变量。 

变量的调用:在变量前加$ 
$ echo $HOME 
/home/hbwork 
$ WEEK=Satur 
$ echo Today is $WEEKday 
Today is 
$echo Today is ${WEEK}day   //必须给变量加上大括号{}
Today is Saturday 

Shell变量赋值从右从左进行赋值(Linux Shell/bash从左向右赋值!) 
$ X=$Y Y=y 
$ echo $X 

$ Z=z Y=$Z 
$ echo $Y 



使用unset命令删除变量的赋值 
$ Z=hello 
$ echo $Z 
hello 
$ unset Z 
$ echo $Z 



有条件的命令替换 
在Bourne Shell中可以使变量替换在特定条件下执行,即有条件的环境变量替换。 
这种变量替换总是用大括号括起来的。 

.设置变量的默认值 
在变量未赋值之前其值为空。Bourne Shell允许对变量设置默认值,其格式如下: 
${variable:-defaultvalue}    //注意这里的符号"-"
例: 
$ echo Hello $UNAME 
Hello 
$ echo Hello ${UNAME:-there} 
Hello there 
$ echo $UNAME #变量值并未发生变化 

$ UNAME=hbwork 
$ echo Hello ${UNAME:-there} 
Hello hbwork 

.另一种情况:改变变量的值,格式如下: 
${variable:=value} 

例: 
$ echo Hello $UNAME 
Hello 
$ echo Hello ${UNAME:=there} 
Hello there 
$ echo $UNAME #变量值并未发生变化 
there 

.变量替换中使用命令替换 
$USERDIR=${$MYDIR:-`pwd`} 

.在变量已赋值时进行替换 ${variable:+value} 
.带有错误检查的有条件变量替换 
${variable:?value} 

例: 
$ UNAME= 
$ echo ${UNAME:?"UNAME has not been set"} 
UNAME: UNAME has not been set 
$ echo ${UNAME:?} 
UNAME: parameter null or not set 

(2)位置变量(Shell参数) 
在shell script中位置参数可用$1..$9表示,$0表示内容通常为当前执行程序的文件名。 
.防止变量值被替换 readonly variable 
.使用export命令输出变量,使得变量对子shell可用,当shell执行一下程序时,shell 
将为其设置一个新的环境让其执行,这称之了subshell. 在Bourne Shell中变量通常 
被认为是本地变量,也就是说在对其赋值之外的shell环境之外是不认识此变量的。使 
用export对subshell可用。 

例:对变量PS1的export操作,shell的提示符将发生变化。 
$ PS1=`hostname`$ 
peony$sh 
$ echo $PS1 
$ <-输出结果 
$ exit 
peony$export PS1 
peony$sh 
peony$ echo $PS1 
peony$ <-输出结果 
peony$ 


3.Shell Script编程 
目的:使用UNIX所提供的最常用工具来完成所需复杂任务的强大功能。 

(1)最简单的Shell 编程 
$ls -R / |grep myname |more 

每天数据的备份: 
$ cd /usr/yourname; ls * |cpio -o > /dev/rmt/0h 

书写程序的目的是一次编程,多次使用(执行)! 

$ cat > backup.sh 
cd /home/hbwork 
ls * | cpio -o > /dev/rmt/0h 
^D 

执行: 
$ sh backup.sh 

或: 
$ chmod +x backup.sh 
$ ./backup.sh 

技巧:在shell script中加入必要的注释,以便以后阅读及维护。 

(2)shell是一个(编程)语言 
同传统的编程语言一样,shell提供了很多特性,这些特性可以使你的shell script 
编程更为有用,如:数据变量、参数传递、判断、流程控制、数据输入和输出,子 
程序及以中断处理等。 

. 在shell编程中使用数据变量可以将程序变量更为通用,如在上面backup.sh中: 
cd $WORKDIR 
ls * | cpio -o > /dev/rmt/0h 

. Shell编程中的注释以#开头 
. 对shell变量进行数字运算,使用expr命令 
expr integer operator integer 
其中operator为+ - * / %, 但对*的使用要用转义符\,如: 
$expr 4 \* 5 
20 
$int=`expr 5 + 7` 
$echo $int 
12 

(3)Shell编程的参数传递, 可通过命令行参数以及交互式输入变量(read) 

restoreall.sh 对backup.sh程序的备份磁带进行恢复 
$cat > restoreall.sh 
cd $WORKDIR 
cpio -i < /dev/rmt/0h 
^D 
restore1.sh:只能恢复一个文件 
#restore1 --program to restore a single file 
cd $WORKDIR 
cpio -i $i < /dev/rmt/0h 

$restore1 file1 

恢复多个文件restoreany : 
#restoreany --program to restore a single file 
cd $WORKDIR 
cpio -i $* < /dev/rmt/0h 

$ restoreany file1 file2 file3 


(4)条件判断 
. if-then语句,格式如下: 
if command_1 
then 
command_2 
command_3 
fi 
command_4 

在if-then语句中使用了命令返回码$?,即当command_1执行成功时才执行command_2和 
command_3,而command_4总是执行. 

示例程序unload: 在备份成功时删除原始文件,带有错误检查 

cd $1 
#备份时未考虑不成功的情况! 
ls -a | cpio -o > /dev/rmt/0h 
rm -rf * 

改进如下: 

#当使用了管道命令时,管理命令的返回代码为最后一个命令的返回代码 
if ls -a | cpio -o > /dev/rmt/0h 
then 
rm -rf * 
fi 

. if-then-else语句 
if command_1 
then 
command_2 
else 
command_3 
fi 

技巧: 由于shell对命令中的多余的空格不作任何处理,一个好的程序员会用这一特性 
对自己的程序采用统一的缩进格式,以增强自己程序的可读性. 

. 使用test命令进行进行条件测试 
格式: test conditions 

test在以下四种情况下使用: a. 字符比较 b.两个整数值的比较 
c. 文件操作,如文件是否存在及文件的状态等 
d. 逻辑操作,可以进行and/or,与其他条件联合使用 

a. 测试字符数据: shell变量通常民政部下均作为字符变量 
str1 = str2 二者相长,相同 
str1 != str2 不同 
-n string string不为空(长度不为零) 
-z string string为空 
string string不为空 

例: 
$ str1=abcd #在含有空格时必须用引号括起来 
$ test $str1=abcd 
$ echo $? 

$ str1="abcd " 
$ test $str1=abcd 
$ echo $? 

Note: 在test处理含有空格的变量时最好用引号将变量括起来,否则会出现错误的结果, 
因为shell在处理命令行时将会去掉多余的空格,而用引号括起来则可以防止shell去掉这些空格. 
例: 
$ str1=" " 
$ test $str1 
$ echo $? 

$ test "$str1" 
$ echo $? 

$ test -n $str1 
test: argument expected 
$ test -n "$str1" 
$ echo $? 



b. 整数测试: test与expr相同,可以将字符型变量转换为整数进行操作,expr进行整数的算术运算,而test则进行逻辑运算. 

表达式        说明 
--------------------------------------- 
int1 -eq int2 相等? 
int1 -ne int2 不等? 
int1 -gt int2 int1 > int2 ? 
int1 -ge int2 int1 >= int2 ? 
int1 -lt int2 int1 < int2 ? 
int1 -le int2 int1 <= int2 ? 

例: 
$ int1=1234 
$ int2=01234 
$ test $int1 -eq $int2 
$ echo $? 


c. 文件测试:检查文件状态如存在及读写权限等 

-r filename 用户对文件filename有读权限? 
-w filename 用户对文件filename有写权限? 
-x filename 用户对文件filename有可执行权限? 
-f filename 文件filename为普通文件? 
-d filename 文件filename为目录? 
-c filename 文件filename为字符设备文件? 
-b filename 文件filename为块设备文件? 
-s filename 文件filename大小不为零? 
-t fnumb 与文件描述符fnumb(默认值为1)相关的设备是一个终端设备? 

d. 测试条件之否定,使用! 
例: 
$ cat /dev/null > empty 
$ test -r empty 
$ echo $? 

$ test -s empty 

$ test ! -s empty 
$ echo $? 

e. 测试条件之逻辑运算 
-a And 
-o Or 

例: $ test -r empty -a -s empty 
$ echo $? 

f. 进行test测试的标准方法 
因为test命令在 shell编程中占有很重要的地位,为了使shell能同其他编程语言一样 
便于阅读和组织, Bourne Shell在使用test测试时使用了另一种方法:用方括号将整个test测试括起来

$ int1=4 
$ [ $int1 -gt 2 ] 
$ echo $? 


例: 重写unload程序,使用test测试 
#!/bin/sh 
#unload - program to backup and remove files 
#syntax: unload directory 

#check arguments 
if [ $# -ne 1 ] 
then 
echo "usage: $0 directory" 
exit 1 
fi 

#check for valid directory name 
if [ ! -d "$1" ] 
then 
echo "$1 is not a directory" 
exit 2 
fi 

cd $1 

ls -a | cpio -o > /dev/rmt/0h 

if [ $? -eq 0 ] 
then 
rm -rf * 
else 
echo "A problem has occured in creating backup" 
echo "The directory will not be ereased" 
echo "Please check the backup device" 
exit 3 
fi 
# end of unload 

在如上示例中出现了exit, exit有两个作用:一是停止程序中其他命令的执行,二是设置程序的退出状态 

g. if嵌套及elif结构 
if command 
then 
command 
else 
if command 
then 
command 
else 
if command 
then 
command 
fi 
fi 
fi 

改进:使用elif结构 
if command 
then 
command 
elif command 
then 
command 
elif command 
then 
command 
fi 

elif结构同if结构类似,但结构更清淅,其执行结果完全相同. 

 

h.交互式从键盘读入数据 
使用read语句,其格式如下: 

read var1 var2 ... varn 

read将不作变量替换,但会删除多余的空格,直到遇到第一个换行符(回车), 
并将输入值依次赋值给相应的变量。 

例: 
$ read var1 var2 var3 
Hello my friends 
$ echo $var1 $var2 $var3 
Hello my friends 
$ echo $var1 
Hello 
$ read var1 var2 var3 
Hello my dear friends 
$ echo $var3 
dear friends <-输入多余变量时,输入值余下的内容赋给最后一个变量 
$ read var1 var2 var3 
Hello friends 
$ echo $var3 
<- var3为空 


在shell script中可使用read语句进行交互操作: 

... 
#echo -n message 输出结果后不换行 
echo -n "Do you want to continue: Y or N" 
read ANSWER 

if [ $ANSWER=N -o $ANSWER=n ] 
then 
exit 
fi 

i. case结构:结构较elif-then结构更清楚 

比较if-then语句: 

if [ variable1 = value1 ] 
then 
command 
command 
elif [ variable1 = value2 ] 
then 
command 
command 
elif [ variable1 = value3 ] 
then 
command 
command 
fi 

相应的case结构: 

case value in 
pattern1) 
command 
command;; 
pattern2) 
command 
command;; 
... 
patternn) 
command; 
esac 

* case语句只执行第一个匹配模式 

例:使用case语句建立一个菜单选择shell script 

#Display a menu 
echo _ 
echo "1 Restore" 
echo "2 Backup" 
echo "3 Unload" 
echo 

#Read and excute the user's selection 
echo -n "Enter Choice:" 
read CHOICE 

case "$CHOICE" in 
1) echo "Restore";; 
2) echo "Backup";; 
3) echo "Unload";; 
*) echo "Sorry $CHOICE is not a valid choice 
exit 1 
esac 

在上例中,*指默认匹配动作。此外,case模式中也可以使用逻辑操作,如下所示: 

pattern1 | pattern2 ) command 
command ;; 

这样可以将上面示例程序中允许用户输入数字或每一个大写字母。 

case "$CHOICE" in 
1|R) echo "Restore";; 
2|B) echo "Backup";; 
3|U) echo "Unload";; 
*) echo "Sorry $CHOICE is not a valid choice 
exit 1 
esac 

(5)循环控制 
<1> while循环: 
格式: 
while command 
do 
command 
command 
command 
... 
done 

例: 计算1到5的平方 
#!/bin/sh 

#Filename: square.sh 
int=1 

while [ $int -le 5 ] 
do 
sq=`expr $int \* $int` 
echo $sq 
int=`expr $int + 1` 
done 
echo "Job completed" 

$ sh square.sh 



16 
25 
Job completed 

<2> until循环结构: 
格式: 
until command 
do 
command 
command 
.... 
command 
done 

示例:使用until结构计算1-5的平方 
#!/bin/sh 

int=1 

until [ $int -gt 5 ] 
do 
sq=`expr $int \* $int` 
echo $sq 
int=`expr $int + 1` 
done 
echo "Job completed" 

<3> 使用shift对不定长的参数进行处理 
在以上的示例中我们总是假设命令行参数唯一或其个数固定,或者使用$*将整个命令 
行参数传递给shell script进行处理。对于参数个数不固定并且希望对每个命令参数 
进行单独处理时则需要shift命令。使用shift可以将命令行位置参数依次移动位置, 
即$2->$1, $3->$2. 在移位之前的第一个位置参数$1在移位后将不在存在。 

示例如下: 

#!/bin/sh 

#Filename: shifter 

until [ $# -eq 0 ] 
do 
echo "Argument is $1 and `expr $# - 1` argument(s) remain" 
shift 
done 


$ shifter 1 2 3 4 
Argument is 1 and 3 argument(s) remain 
Argument is 2 and 2 argument(s) remain 
Argument is 3 and 1 argument(s) remain 
Argument is 4 and 0 argument(s) remain 


使用shift时,每进行一次移位,$#减1,使用这一特性可以用until循环对每个参 
数进行处理,如下示例中是一个求整数和的shell script: 

#!/bin/sh 
# sumints - a program to sum a series of integers 


if [ $# -eq 0 ] 
then 
echo "Usage: sumints integer list" 
exit 1 
fi 

sum=0 

until [ $# -eq 0 ] 
do 
sum=`expr $sum + $1` 
shift 
done 
echo $sum 


$ sh sumints 324 34 34 12 34 
438 


使用shift的另一个原因是Bourne Shell的位置参数变量为$1~$9, 因此通过位置变量 
只能访问前9个参数。但这并不等于在命令行上最多只能输入9个参数。此时如果想访问 
前9个参数之后的参数,就必须使用shift. 

另外shift后可加整数进行一次多个移位,如: 

shift 3 


<4>. for循环 
格式: 
for var in arg1 arg2 ... argn 
do 
command 
.... 
command 
done 

示例: 
$ for letter in a b c d e; do echo $letter;done 






对当前目录下的所有文件操作: 
$ for i in * 
do 
if [ -f $i ] 
then 
echo "$i is a file" 
elif [ -d $i ] 
echo "$i is a directory" 
fi 
done 

求命令行上所有整数之和: 
#!/bin/sh 

sum=0 

for INT in $* 
do 
sum=`expr $sum + $INT` 
done 

echo $sum 


<6> 从循环中退出: break和continue命令 
break 立即退出循环 
continue 忽略本循环中的其他命令,继续下一下循环 

在shell编程中有时我们要用到进行无限循环的技巧,也就是说这种循环一直执行碰 
到break或continue命令。这种无限循环通常是使用true或false命令开始的。UNIX 
系统中的true总是返加0值,而false则返回非零值。如下所示: 

#一直执行到程序执行了break或用户强行中断时才结束循环 
while true 
do 
command 
.... 
command 
done 

上面所示的循环也可以使用until false, 如下: 

until false 
do 
command 
.... 
command 
done 

在如下shell script中同时使用了continue,break以及case语句中的正规表达式用法: 

#!/bin/sh 
# Interactive program to restore, backup, or unload 
# a directory 

echo "Welcome to the menu driven Archive program" 

while true 
do 
# Display a Menu 
echo 
echo "Make a Choice from the Menu below" 
echo _ 
echo "1 Restore Archive" 
echo "2 Backup directory" 
echo "3 Unload directory" 
echo "4 Quit" 
echo 

# Read the user's selection 
echo -n "Enter Choice: " 

read CHOICE 

case $CHOICE in 
[1-3] ) echo 
# Read and validate the name of the directory 

echo -n "What directory do you want? " 
read WORKDIR 

if [ ! -d "$WORKDIR" ] 
then 
echo "Sorry, $WORKDIR is not a directory" 
continue 
fi 

# Make the directory the current working directory 
cd $WORKDIR;; 

4) :;; # :为空语句,不执行任何动作 
*) echo "Sorry, $CHOICE is not a valid choice" 
continue 
esac 

case "$CHOICE" in 
1) echo "Restoring..." 
cpio -i 
2) echo "Archiving..." 
ls | cpio -o >/dev/rmt/0h;; 

3) echo "Unloading..." 
ls | cpio -o >/dev/rmt/0h;; 

4) echo "Quitting" 
break;; 
esac 

#Check for cpio errors 

if [ $? -ne 0 ] 
then 
echo "A problem has occurred during the process" 
if [ $CHOICE = 3 ] 
then 
echo "The directory will not be erased" 
fi 

echo "Please check the device and try again" 
continue 
else 
if [ $CHOICE = 3 ] 
then 
rm * 
fi 
fi 
done 


(6)结构化编程:定义函数 
同其他高级语言一样,shell也提供了函数功能。函数通常也称之为子过程(subroutine), 
其定义格式如下: 

funcname() 

command 
... 
command; #分号 


定义函数之后,可以在shell中对此函数进行调用,使用函数定义可以将一个复杂的程序分 
为多个可管理的程序段,如下所示: 

# start program 

setup () 
{ command list ; } 

do_data () 
{ command list ; } 

cleanup () 
{ command list ; } 

errors () 
{ command list ; } 

setup 
do_data 
cleanup 
# end program 

技巧: 
. 在对函数命名时最好能使用有含义的名字,即函数名能够比较准确的描述函数所完成 
的任务。 
. 为了程序的维护方便,请尽可能使用注释 


使用函数的另一个好处就是可以在一个程序中的不同地方执行相同的命令序列(函数), 
如下所示: 

iscontinue() 

while true 
do 
echo -n "Continue?(Y/N)" 
read ANSWER 

case $ANSWER in 
[Yy]) return 0;; 
[Nn]) return 1;; 
*) echo "Answer Y or N";; 
esac 
done 


这样可以在shell编程中调用iscontinue确定是否继续执行: 

if iscontinue 
then 
continue 
else 
break 
fi 


** shell函数与shell程序非常相似,但二者有一个非常重要的差别:shell程序是由子shell 
执行的,而shell函数则是作为当前shell的一部分被执行的,因此在当前shell中可以改 
变函数的定义。此外在任意shell(包括交互式的shell)中均可定义函数,如: 

$ dir 
dir: not found 
$ dir () { ls -l ;} 
$ dir 
total 5875 
-rw-r--r-- 1 hbwork 100 Nov 10 23:16 doc 
-rw-r--r-- 1 hbwork 2973806 Nov 10 23:47 ns40docs.zip 
-rw-r--r-- 1 hbwork 1715011 Nov 10 23:30 ns840usr.pdf 
-rw-r--r-- 1 hbwork 1273409 Sep 23 1998 radsol21b6.tar.Z 
-rw-r--r-- 1 hbwork 7526 Nov 10 23:47 wget-log 
-rw-r--r-- 1 hbwork 1748 Nov 13 21:51 wget-log.1 
$ unset dir 
$ dir () { 
> echo "Permission Link Owner Group File_SZ LastAccess FileName" 
> echo "-----------------------------------------------------------" 
> ls -l $*; 
> } 

$ dir 
Permission Link Owner Group File_SZ LastAccess FileName 
----------------------------------------------------------- 
total 5875 
-rw-r--r-- 1 hbwork 100 Nov 10 23:16 doc 
-rw-r--r-- 1 hbwork 2973806 Nov 10 23:47 ns40docs.zip 
-rw-r--r-- 1 hbwork 1715011 Nov 10 23:30 ns840usr.pdf 
-rw-r--r-- 1 hbwork 1273409 Sep 23 1998 radsol21b6.tar.Z 
-rw-r--r-- 1 hbwork 7526 Nov 10 23:47 wget-log 
-rw-r--r-- 1 hbwork 1748 Nov 13 21:51 wget-log.1 

通常情况下,shell script是在子shell中执行的,困此在此子shell中对变量所作的 
修改对父shell不起作用。点(.) 命令使用shell在不创建子shell而由当前shell读取 
并执行一个shell script, 可以通过这种方式来定义函数及变量。此外点(.)命令最 
常用的功能就是通过读取.profile来重新配置初始化login变量。如下所示: 

$ . .profile 
(csh相对于.命令的是source命令). 

(7)使用And/Or结构进行有条件的命令执行 
<1> And , 仅当第一个命令成功时才有执行后一个命令,如同在逻辑与表达式中如果前面的 
结果为真时才有必要继续运算,否则结果肯定为假。 

格式如下: 

command1 && command2 

例:rm $TEMPDIR/* && echo "File successfully removed" 

等价于 

if rm $TEMPDIR/* 
then 
echo "File successfully removed" 
fi 

<2>Or, 与AND相反,仅当前一个命令执行出错时才执行后一条命令 

例: rm $TEMPDIR/* || echo "File not removed" 

等价与: 

if rm $TEMPDIR/* 
then 
command 
else 
echo "File not removed" 
fi 

<3> 混合命令条件执行 
command1 && command2 && command3 
当command1, command2成功时才执行command3 

command1 && command2 || comamnd3 
仅当command1成功,command2失败时才执行command3 

当然可以根据自己的需要进行多种条件命令的组合,在此不多讲述。 


(8) 使用getopts命令读取unix格式选项 
UNIX格式选项指如下格式的命令行参数: 
command -options parameters 

使用格式: 
getopts option_string variable 

具体使用方法请参考getopts的在线文档(man getopts). 

示例如下: 

#newdate 
if [ $# -lt 1 ] 
then 
date 
else 
while getopts mdyDHMSTjJwahr OPTION 
do 
case $OPTION 
in 
m) date '+%m ';; # Month of Year 
d) date '+%d ';; # Day of Month 
y) date '+%y ';; # Year 
D) date '+%D ';; # MM/DD/YY 
H) date '+%H ';; # Hour 
M) date '+%M ';; # Minute 
S) date '+%S ';; # Second 
T) date '+%T ';; # HH:MM:SS 
j) date '+%j ';; # day of year 
J) date '+%y%j ';;# 5 digit Julian date 
w) date '+%w ';; # Day of the Week 
a) date '+%a ';; # Day abbreviation 
h) date '+%h ';; # Month abbreviation 
r) date '+%r ';; # AM-PM time 
\?) echo "Invalid option $OPTION";; 
esac 
done 
fi 

$ newdate -J 
94031 
$ newdate -a -h -d 
Mon 
Jan 
31 
$ newdate -ahd 
Mon 
Jan 
31 



示例程序:复制程序 

# Syntax: duplicate [-c integer] [-v] filename 
# where integer is the number of duplicate copies 
# and -v is the verbose option 

COPIES=1 
VERBOSE=N 


while getopts vc: OPTION 
do 
case $OPTION 
in 
c) COPIES=$OPTARG;; 
v) VERBOSE=Y;; 
\?) echo "Illegal Option" 
exit 1;; 
esac 
done 

if [ $OPTIND -gt $# ] 
then 
echo "No file name specified" 
exit 2 
fi 


shift `expr $OPTIND -1` 

FILE=$1 
COPY=0 

while [ $COPIES -gt $COPY ] 
do 
COPY=`expr $COPY + 1` 
cp $FILE ${FILE}${COPY} 
if [ VERBOSE = Y ] 
then 
echo ${FILE}${COPY} 
fi 
done 


$ duplicate -v fileA 
fileA1 
$ duplicate -c 3 -v fileB 
fileB1 
fileB2 
fileB3 


4. Shell的定制 
通常使用shell的定制来控制用户自己的环境,比如改变shell的外观(提示符)以及增强 
自己的命令。 

(1)通常环境变量来定制shell 
通常改变环境变量可以定制shell的工作环境。shell在处理信息时会参考这些环境变量 
,改变环境变量的值在一定程度上改变shell的操作方式,比如改变命令行提示符。 

.使用IFS增加命令行分隔符 
默认状态下shell的分隔符为空格、制表符及换行符,但可以通过改变IFS的值加入自己 
的分隔符。如下所示: 


$ IFS=":" 
$ echo:Hello:my:Friend 
Hello my Friend 

(2)加入自己的命令及函数 
如下程序: 
#Directory and Prompt change program 
#Syntax: chdir directory 

if [ ! -d "$1" ] 
then 
echo "$1 is not a directory" 
exit 1 
fi 

cd $1 
PS1=`pwd`$ 
export PS1 

$ chdir /usr/home/teresa 


但此程序在执行时系统提示符并不会改变,因为此程序是在子shell中执行的。因此其变量 
对当前shell并无影响,要想对当前shell起作用,最好是将此作为函数写在自己的.profile中 
或建立自己的个人函数文件.persfuncs 

#Personal function file persfuncs 

chdir() 

#Directory and Prompt change program 
#Syntax: chdir directory 
if [ ! -d "$1" ] 
then 
echo "$1 is not a directory" 
exit 1 
fi 

cd $1 
PS1=`pwd`$ 
export PS1; 


再执行: 
$ . .persfuncs 
$ chdir temp 
/home/hbbwork/temp$ 

也可在自己的.profile文件中用 . .persfuncs调用.persfuncs. 

说明:在bash/tcsh中已经使用别名,相对而言别名比此方法更为方便。 


5. 有关shell的专门讨论 
(1)shell程序的调试 
切记:程序员(人)总是会犯错误的,而计算机是不会错的。 
使用-x进行跟踪执行,执行并显示每一条指令。 

(2)命令组 
用小括号将一组命令括起来,则这些命令会由子shell来完成;而{command_list;}则在当 
前shell中执行。这两者的主要区别在于其对shell变量的影响,子shell执行的命令不会 
影响当前shell中的变量。 

$ NUMBER=2 
$ (A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER) 

$ echo $NUMBER 

$ { A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER; } 

$ echo $NUMBER 



总结: 
在本章中讲述了Bourne Shell的基本知识,使用shell变量,shell script基础。这些概念 
对于理解学习Korn Shell, csh以及其他script编程都是非常有用的。 

很多OS都有不少语言及一些script功能,但很少有象UNIX SHELL这样灵活强大的script脚 
本语言能力。 

对于系统管理员或程序员来说,熟练地使用shell script将对日常工作(系统维护及管理) 
非常有用,如果你想作一个合格的系统管理员,强烈建议你进一步深入的了解和使用 
shell. 

另外,对于系统管理员来说,PERL也是一个必不可少的script编程语言,尤其是对于处理 
文本格式的各种文件,PERL具有shell, awk, sed, grep等的功能,但使用起来更为灵活, 
功能也更强大。大家可以参考“Perl By Examples"来学习和使用PERL。 

来源:http://www.wanglimin.com/Tutorial/HP-UNIX/DOCS/shell01.htm

]]>
Tue,30 Oct 2007 18:00:04 CST 0
<![CDATA[【转】配置Apache服务器]]>